nut-debian/drivers/upscode2.c
2013-11-24 16:00:12 +01:00

1422 lines
36 KiB
C

/* upscode2.c - model specific routines for UPSes using the UPScode II
command set. This includes PowerWare, Fiskars,
Compaq (PowerWare OEM?), some IBM (PowerWare OEM?)
Copyright (C) 2002 H?vard Lygre <hklygre@online.no>
Copyright (C) 2004-2006 Niels Baggesen <niels@baggesen.net>
Copyright (C) 2006 Niklas Edmundsson <nikke@acc.umu.se>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Currently testing against
* Fiskars PowerRite Max
* Fiskars PowerServer 10
* Fiskars PowerServer 30
* Powerware Profile (9150)
* Powerware 9305
*
* Also tested against
* Compaq T1500h (Per J?nsson <per.jonsson@bth.se>)
* Powerware 9120 (Gorm J. Siiger <gjs@sonnit.dk>)
* Fiskars PowerServer 10 (Per Larsson <tucker@algonet.se>)
*/
#include "main.h"
#include "serial.h"
#include "timehead.h"
#include "nut_stdint.h"
#include <math.h>
#define DRIVER_NAME "UPScode II UPS driver"
#define DRIVER_VERSION "0.88"
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"H K Lygre, <hklygre@online.no>\n" \
"Niels Baggesen <niels@baggesen.net>\n" \
"Niklas Edmundsson <nikke@acc.umu.se>",
DRV_EXPERIMENTAL,
{ NULL }
};
#define ENDCHAR '\n'
/* default values */
#define OUT_PACE_USEC 200
#define INP_TIMO_SEC 2
#define FULL_UPDATE_TIMER 60
#define UPSC_BUFLEN 256 /* Size of response buffers from UPS */
/* Status messages from UPS */
#define UPSC_STAT_ONLINE (1UL << 0)
#define UPSC_STAT_ONBATT (1UL << 1)
#define UPSC_STAT_LOBATT (1UL << 2)
#define UPSC_STAT_REPLACEBATT (1UL << 3)
#define UPSC_STAT_BOOST (1UL << 4)
#define UPSC_STAT_TRIM (1UL << 5)
#define UPSC_STAT_OVERLOAD (1UL << 6)
#define UPSC_STAT_CALIBRATION (1UL << 7)
#define UPSC_STAT_OFF (1UL << 8)
#define UPSC_STAT_BYPASS (1UL << 9)
#define UPSC_STAT_NOTINIT (1UL << 31)
typedef enum {
t_ignore, /* Ignore this response */
t_value, /* Sets a NUT variable */
t_final, /* Marks the end of UPS data for this command */
t_string, /* Set a NUT string variable */
t_finstr, /* Set a NUT string variable, and marks end of data */
t_setval, /* Sets a variable in the driver and possibly in NUT */
t_setrecip, /* Sets a driver variable to 1/value and possibly NUT */
t_setpct,
t_setrecpct,
t_status, /* Sets a status bit */
t_alarm, /* Sets an alarm message */
t_list /* The value must be matched in the list */
} type_t;
typedef struct simple_s {
const char *code;
type_t type;
const char *desc;
int status;
float *aux;
struct simple_s *stats;
} simple_t;
typedef struct {
const char *cmd;
const char *upsc;
const char *upsp;
int enabled;
} cmd_t;
static int
can_upda = 0,
can_upbs = 0,
can_upid = 0,
can_uppm = 0,
can_updt = 0,
can_uptm = 0,
can_upsd = 0,
can_uppc = 0;
static char has_uppm_p[100];
static int
input_timeout_sec = INP_TIMO_SEC,
output_pace_usec = OUT_PACE_USEC,
full_update_timer = FULL_UPDATE_TIMER,
use_crlf = 0,
use_pre_lf = 0,
buffer_empty = 0;
static uint32_t
status = UPSC_STAT_NOTINIT;
static time_t
last_full = 0;
static float
batt_volt_low = 0,
batt_volt_nom = 0,
batt_volt_high = 0,
batt_volt = 0,
batt_cap_nom = -1,
batt_charge = -1,
batt_current = -1,
batt_disch_curr_max = 0,
batt_runtime = -1,
batt_runtime_max = -1,
kilo_to_unity = 1000.0,
outpwr_factor = 1000.0,
nom_out_current = -1,
max_out_current = -1;
/* To get average battery current */
#define NUM_BATTHIST 60
static float batthist[NUM_BATTHIST];
static int numbatthist=0, lastbatthist=0;
static int
inited_phaseinfo = 0,
num_inphases = 1,
num_outphases = 1;
/* Status codes for the STAT and STMF status responses */
static simple_t att[] = {
{ "00", t_ignore },
{ "AC", t_alarm, "Aux contact failure", 0 },
{ "BA", t_alarm, "Batteries disconnected", 0 },
{ "BC", t_alarm, "Backfeed contact failure", 0 },
{ "BD", t_alarm, "Abnormal battery discharge", 0 },
{ "BF", t_alarm, "Battery fuse failure", 0 },
{ "BL", t_status, "Battery low limit", UPSC_STAT_LOBATT },
{ "BO", t_alarm, "Battery over voltage", 0 },
{ "BP", t_alarm, "Bypass fuse failure", 0 },
{ "BR", t_alarm, "Abnormal battery recharge", 0 },
{ "BT", t_alarm, "Battery over temperature", 0 },
{ "BX", t_alarm, "Bypass unavailable", 0 },
{ "BY", t_alarm, "Battery failure", 0 },
{ "CE", t_alarm, "Configuration error", 0 },
{ "CM", t_alarm, "Battery converter failure", 0 },
{ "CT", t_alarm, "Cabinet over temperature", 0 },
{ "DO", t_alarm, "DC over voltage", 0 },
{ "DU", t_alarm, "DC under voltage", 0 },
{ "EP", t_alarm, "Emergency power off", 0 },
{ "FF", t_alarm, "Fan failure", 0 },
{ "FH", t_alarm, "Line frequency high", 0 },
{ "FL", t_alarm, "Line frequency low", 0 },
{ "FT", t_alarm, "Filter over temperature", 0 },
{ "GF", t_alarm, "Ground failure", 0 },
{ "HT", t_alarm, "Charger over temperature", 0 },
{ "IB", t_alarm, "Internal data bus failure", 0 },
{ "IF", t_alarm, "Inverter fuse failure", 0 },
{ "IM", t_alarm, "Inverter failure", 0 },
{ "IO", t_alarm, "Inverter over voltage", 0 },
{ "IP", t_alarm, "Internal power supply failure", 0 },
{ "IT", t_alarm, "Inverter over temperature", 0 },
{ "IU", t_alarm, "Inverter under voltage", 0 },
{ "IV", t_alarm, "Inverter off", 0 },
{ "LR", t_alarm, "Loss of redundancy", 0 },
{ "NF", t_alarm, "Neutral fault", 0 },
{ "OD", t_status, "UPS not supplying load", UPSC_STAT_OFF },
{ "OF", t_alarm, "Oscillator failure", 0 },
{ "OL", t_status, "Overload", UPSC_STAT_OVERLOAD },
{ "OR", t_alarm, "Redundancy overload", 0 },
{ "OV", t_alarm, "Abnormal output voltage", 0 },
{ "OW", t_alarm, "Output failure", 0 },
{ "PB", t_alarm, "Parallel bus failure", 0 },
{ "PE", t_alarm, "Phase rotation error", 0 },
{ "RE", t_alarm, "Rectifier off", 0 },
{ "RF", t_alarm, "Rectifier fuse failure", 0 },
{ "RM", t_alarm, "Rectifier failure", 0 },
{ "RT", t_alarm, "Rectifier over temperature", 0 },
{ "SM", t_alarm, "Static switch failure", 0 },
{ "ST", t_alarm, "Static switch over temperature", 0 },
{ "TT", t_alarm, "Trafo over temperature", 0 },
{ "UD", t_alarm, "UPS disabled", 0 },
{ "UO", t_alarm, "Utility over voltage", 0 },
{ "US", t_alarm, "Unsynchronized", 0 },
{ "UU", t_alarm, "Utility under voltage", 0 },
{ "VE", t_alarm, "internal voltage error", 0 },
{ NULL }
};
/* Status code for the STLR response */
static simple_t stlr[] = {
{ "NO", t_ignore },
{ "SD", t_status, NULL, UPSC_STAT_TRIM },
{ "SU", t_status, NULL, UPSC_STAT_BOOST },
{ "DU", t_status, NULL, UPSC_STAT_BOOST },
{ NULL }
};
/* Status code for the STEA and STEM responses */
static simple_t env[] = {
{ "HH", t_ignore, "Humidity high", 0 },
{ "HL", t_ignore, "Humidity low", 0 },
{ "TH", t_ignore, "Temperature high", 0 },
{ "TL", t_ignore, "Temperature low", 0 },
{ "01", t_ignore, "Environment alarm 1", 0 },
{ "02", t_ignore, "Environment alarm 2", 0 },
{ "03", t_ignore, "Environment alarm 3", 0 },
{ "04", t_ignore, "Environment alarm 4", 0 },
{ "05", t_ignore, "Environment alarm 5", 0 },
{ "06", t_ignore, "Environment alarm 6", 0 },
{ "07", t_ignore, "Environment alarm 7", 0 },
{ "08", t_ignore, "Environment alarm 8", 0 },
{ "09", t_ignore, "Environment alarm 9", 0 },
{ NULL }
};
/* Responses for UPSS and UPDS */
static simple_t simple[] = {
{ "STAT", t_list, NULL, 0, 0, att },
{ "STBO", t_status, NULL, UPSC_STAT_ONBATT },
{ "STBL", t_status, NULL, UPSC_STAT_LOBATT },
{ "STBM", t_ignore },
{ "STBP", t_status, NULL, UPSC_STAT_BYPASS },
{ "STEA", t_list, NULL, 0, 0, env },
{ "STEM", t_list, NULL, 0, 0, env },
{ "STLR", t_list, NULL, 0, 0, stlr },
{ "STMF", t_list, NULL, 0, 0, att },
{ "STOK", t_ignore },
{ "STUF", t_status, NULL, UPSC_STAT_ONBATT },
{ "BTIME", t_setval, NULL, 0, &batt_runtime },
{ "METE1", t_value, "ambient.temperature" },
{ "MERH1", t_value, "ambient.humidity" },
{ "MIFFF", t_value, "input.frequency" },
{ "MIIL1", t_value, "input.current" },
{ "MIIL2", t_value, "input.L2.current" },
{ "MIIL3", t_value, "input.L3.current" },
{ "MIPL1", t_value, "input.realpower" },
{ "MIPL2", t_value, "input.L2.realpower" },
{ "MIPL3", t_value, "input.L3.realpower" },
{ "MISL1", t_value, "input.power" },
{ "MISL2", t_value, "input.L2.power" },
{ "MISL3", t_value, "input.L3.power" },
{ "MIUL1", t_value, "input.voltage" },
{ "MIUL2", t_value, "input.L2-N.voltage" },
{ "MIUL3", t_value, "input.L3-N.voltage" },
{ "MIU12", t_value, "input.L1-L2.voltage" },
{ "MIU23", t_value, "input.L2-L3.voltage" },
{ "MIU31", t_value, "input.L3-L1.voltage" },
{ "MBCH1", t_setval, NULL, 0, &batt_charge }, /* battery.charge */
{ "MBIII", t_setval, "battery.current", 0, &batt_current },
{ "MBINE", t_ignore, /* "battery.current.negative" */ },
{ "MBIPO", t_ignore, /* "battery.current.positive" */ },
{ "MBUNE", t_ignore, /* "battery.voltage.negative" */ },
{ "MBUPO", t_ignore, /* "battery.voltage.positive" */},
{ "MBUUU", t_setval, "battery.voltage", 0, &batt_volt },
{ "MLUNE", t_ignore, /* "dc.voltage.negative" */ },
{ "MLUPO", t_ignore, /* "dc.voltage.positive" */ },
{ "MLUUU", t_ignore, /* "dc.voltage" */ },
{ "MOFFF", t_final, "output.frequency" },
{ "MOIL1", t_value, "output.current" },
{ "MOIL2", t_value, "output.L2.current" },
{ "MOIL3", t_value, "output.L3.current" },
{ "MOIP1", t_value, "output.current.peak" },
{ "MOIP2", t_value, "output.L2.current.peak" },
{ "MOIP3", t_value, "output.L3.current.peak" },
{ "MOPL1", t_value, "output.realpower", 0, &kilo_to_unity },
{ "MOPL2", t_value, "output.L2.realpower", 0, &kilo_to_unity },
{ "MOPL3", t_value, "output.L3.realpower", 0, &kilo_to_unity },
{ "MOSL1", t_value, "output.power" },
{ "MOSL2", t_value, "output.L2.power" },
{ "MOSL3", t_value, "output.L3.power" },
{ "MOUL1", t_value, "output.voltage" },
{ "MOUL2", t_value, "output.L2-N.voltage" },
{ "MOUL3", t_value, "output.L3-N.voltage" },
{ "MOU12", t_value, "output.L1-L2.voltage" },
{ "MOU23", t_value, "output.L2-L3.voltage" },
{ "MOU31", t_value, "output.L3-L1.voltage" },
{ "MPUL1", t_value, "input.bypass.L1-N.voltage" },
{ "MPUL2", t_value, "input.bypass.L2-N.voltage" },
{ "MPUL3", t_value, "input.bypass.L3-N.voltage" },
{ "MUTE1", t_value, "ups.temperature" },
{ NULL }
};
/* Responses for UPDV */
static simple_t nominal[] = {
{ "NIUHH", t_value, "input.voltage.maximum" },
{ "NIULL", t_value, "input.voltage.minimum" },
{ "NIUNN", t_value, "input.voltage.nominal" },
{ "NIIHH", t_value, "input.current.maximum" },
{ "NIILL", t_value, "input.current.minimum" },
{ "NIINN", t_value, "input.current.nominal" },
{ "NIPHH", t_value, "input.realpower.maximum" },
{ "NIPNN", t_value, "input.realpower.nominal" },
{ "NISHH", t_value, "input.power.maximum" },
{ "NISNN", t_value, "input.power.nominal" },
{ "NBAHN", t_setval, "battery.capacity.nominal", 0, &batt_cap_nom },
{ "NBIHH", t_ignore, "battery charge current maximum" },
{ "NBILL", t_setval, NULL, 0, &batt_disch_curr_max},
{ "NBINN", t_value, "battery.current.nominal" },
{ "NBTHH", t_setval, NULL, 0, &batt_runtime_max},
{ "NBUHH", t_setval, "battery.voltage.maximum", 0, &batt_volt_high },
{ "NBULL", t_setval, "battery.voltage.minimum", 0, &batt_volt_low },
{ "NBUNN", t_setval, "battery.voltage.nominal", 0, &batt_volt_nom },
{ "NOFHH", t_value, "output.frequency.maximum" },
{ "NOFLL", t_final, "output.frequency.minimum" },
{ "NOIHH", t_setval, "output.current.maximum", 0, &max_out_current },
{ "NOINN", t_setval, "output.current.nominal", 0, &nom_out_current },
{ "NOPNN", t_value, "output.realpower.nominal", 0, &outpwr_factor },
{ "NOSNN", t_value, "ups.power.nominal", 0, &outpwr_factor },
{ "NOUHH", t_value, "output.voltage.maximum" },
{ "NOULL", t_value, "output.voltage.minimum" },
{ "NOUNN", t_value, "output.voltage.nominal" },
{ "NUTEH", t_value, "ups.temperature.maximum" },
{ NULL }
};
/* Status responses for UPBS command */
static simple_t battery[] = {
{ "MBTE1", t_value, "battery.1.temperature" },
{ "MBIN1", t_ignore, NULL /* aging index */ },
{ "BDAT1", t_string, "battery.1.date" },
{ "MBTE2", t_value, "battery.2.temperature.2" },
{ "MBIN2", t_ignore, NULL },
{ "BDAT2", t_string, "battery.2.date" },
{ "MBTE3", t_value, "battery.3.temperature" },
{ "MBIN3", t_ignore, NULL },
{ "BDAT3", t_string, "battery.3.date" },
{ "MBTE4", t_value, "battery.4.temperature" },
{ "MBIN4", t_ignore, NULL },
{ "BDAT4", t_string, "battery.4.date" },
{ "MBTE5", t_value, "battery.5.temperature" },
{ "MBIN5", t_ignore, NULL },
{ "BDAT5", t_string, "battery.5.date" },
{ "MBTE6", t_value, "battery.6.temperature" },
{ "MBIN6", t_ignore, NULL },
{ "BDAT6", t_string, "battery.6.date" },
{ "MBTE7", t_value, "battery.7.temperature" },
{ "MBIN7", t_ignore, NULL },
{ "BDAT7", t_string, "battery.7.date" },
{ "MBTE8", t_value, "battery.8.temperature" },
{ "MBIN8", t_ignore, NULL },
{ "BDAT8", t_finstr, "battery.8.date" },
{ NULL }
};
static cmd_t commands[] = {
{ "load.off", NULL, NULL },
{ "load.on", NULL, NULL },
{ "shutdown.return", "UPPF", "IJHLDMGCIU" },
{ "shutdown.stayoff", "UPPD", "LGGNLMDPGV" },
{ "shutdown.stop", "UPPU", NULL },
{ "shutdown.reboot", "UPPC", "IJHLDMGCIU" },
{ "shutdown.reboot.graceful", NULL, NULL },
{ "test.panel.start", "UPIS", NULL },
{ "test.panel.stop", NULL, NULL },
{ "test.failure.start", NULL, NULL },
{ "test.failure.stop", NULL, NULL },
{ "test.battery.start", "UPBT", "1" },
{ "test.battery.stop", NULL, NULL },
{ "calibrate.start", NULL, NULL },
{ "calibrate.stop", NULL, NULL },
{ "bypass.start", NULL, NULL },
{ "bypass.stop", NULL, NULL },
{ "reset.input.minmax", NULL, NULL },
{ "reset.watchdog", NULL, NULL },
{ "beeper.enable", NULL, NULL },
{ "beeper.disable", NULL, NULL },
{ "beeper.on", NULL, NULL },
{ "beeper.off", NULL, NULL },
{ NULL }
};
static cmd_t variables[] = {
{ "ups.delay.reboot", "UPCD", "ACCD" },
{ "ups.delay.shutdown", "UPSD", "ACSD" },
{ NULL }
};
static int instcmd (const char *auxcmd, const char *data);
static int setvar (const char *var, const char *data);
static void upsc_setstatus(unsigned int status);
static void upsc_flush_input(void);
static void upsc_getbaseinfo(void);
static int upsc_commandlist(void);
static int upsc_getparams(const char *cmd, const simple_t *table);
static int upsc_getvalue(const char *cmd, const char *param,
const char *resp, const char *var, char *ret);
static int upscsend(const char *cmd);
static int upscrecv(char *buf);
static int upsc_simple(const simple_t *sp, const char *var, const char *val);
static void check_uppm(void);
static float batt_charge_pct(void);
void upsdrv_help(void)
{
}
void upsdrv_initups(void)
{
struct termios tio;
int baud = B1200;
char *str;
if ((str = getval("baudrate")) != NULL) {
int temp = atoi(str);
switch (temp) {
case 300:
baud = B300; break;
case 600:
baud = B600; break;
case 1200:
baud = B1200; break;
case 2400:
baud = B2400; break;
case 4800:
baud = B4800; break;
case 9600:
baud = B9600; break;
case 19200:
baud = B19200; break;
case 38400:
baud = B38400; break;
default:
fatalx(EXIT_FAILURE, "Unrecognized baudrate: %s", str);
}
upsdebugx(1, "baud_rate = %d", temp);
}
upsfd = ser_open(device_path);
ser_set_speed(upsfd, device_path, baud);
if (tcgetattr(upsfd, &tio) != 0)
fatal_with_errno(EXIT_FAILURE, "tcgetattr(%s)", device_path);
tio.c_lflag = ICANON;
tio.c_iflag |= IGNCR; /* Ignore CR */
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;
tcsetattr(upsfd, TCSANOW, &tio);
if ((str = getval("input_timeout")) != NULL) {
int temp = atoi(str);
if (temp <= 0)
fatalx(EXIT_FAILURE, "Bad input_timeout parameter: %s", str);
input_timeout_sec = temp;
}
upsdebugx(1, "input_timeout = %d Sec", input_timeout_sec);
if ((str = getval("output_pace")) != NULL) {
int temp = atoi(str);
if (temp <= 0)
fatalx(EXIT_FAILURE, "Bad output_pace parameter: %s", str);
output_pace_usec = temp;
}
upsdebugx(1, "output_pace = %d uSec", output_pace_usec);
if ((str = getval("full_update_timer")) != NULL) {
int temp = atoi(str);
if (temp <= 0)
fatalx(EXIT_FAILURE, "Bad full_update_timer parameter: %s", str);
full_update_timer = temp;
}
upsdebugx(1, "full_update_timer = %d Sec", full_update_timer);
use_crlf = testvar("use_crlf");
upsdebugx(1, "use_crlf = %d", use_crlf);
use_pre_lf = testvar("use_pre_lf");
upsdebugx(1, "use_pre_lf = %d", use_pre_lf);
}
void upsdrv_initinfo(void)
{
if (!upsc_commandlist()) {
upslogx(LOG_ERR, "No contact with UPS, delaying init.");
status = UPSC_STAT_NOTINIT;
return;
} else {
status = 0;
}
upsc_getbaseinfo();
if (can_upda) {
upsc_flush_input();
upscsend("UPDA");
}
if (can_upid) {
upsc_getvalue("UPID", NULL, "ACID", "ups.id", NULL);
}
if (can_uppm) {
check_uppm();
}
/* make sure we have some sensible defaults */
setvar("ups.delay.shutdown", "10");
setvar("ups.delay.reboot", "60");
upsh.instcmd = instcmd;
upsh.setvar = setvar;
}
/* Change a variable name in a table */
static void change_name(simple_t *sp,
const char *oldname, const char *newname)
{
while(sp->code) {
if (sp->desc && !strcmp(sp->desc, oldname)) {
sp->desc = strdup(newname);
if (dstate_getinfo(oldname)) {
dstate_setinfo(newname, "%s",
dstate_getinfo(oldname));
}
dstate_delinfo(oldname);
upsdebugx(1, "Changing name: %s => %s", oldname, newname);
break;
}
sp++;
}
}
static float calc_upsload(void) {
float load=-1, nom_out_power=-1, nom_out_realpower=-1, maxcurr, tmp;
const char *s;
/* Some UPSen (Fiskars 9000 for example) only reports current, and
* only the max current */
if (nom_out_current > 0) {
maxcurr = nom_out_current;
}
else {
maxcurr = max_out_current;
}
if (maxcurr > 0) {
if ((s=dstate_getinfo("output.L1.current")) ||
(s=dstate_getinfo("output.current"))) {
if (sscanf(s, "%f", &tmp) == 1) {
load = tmp/maxcurr;
}
}
if ((s=dstate_getinfo("output.L2.current"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp=tmp/maxcurr;
if (tmp>load) {
load = tmp;
}
}
}
if ((s=dstate_getinfo("output.L3.current"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp=tmp/maxcurr;
if (tmp>load) {
load = tmp;
}
}
}
}
/* This is aggregated (all phases) */
if ((s=dstate_getinfo("ups.power.nominal"))) {
if (sscanf(s, "%f", &nom_out_power) != 1) {
nom_out_power = -1;
}
}
if (nom_out_power > 0) {
if ((s=dstate_getinfo("output.L1.power"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp /= (nom_out_power/num_outphases);
if (tmp>load) {
load = tmp;
}
dstate_setinfo("output.L1.power.percent",
"%.1f", tmp*100);
}
}
if ((s=dstate_getinfo("output.L2.power"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp /= (nom_out_power/num_outphases);
if (tmp>load) {
load = tmp;
}
dstate_setinfo("output.L2.power.percent",
"%.1f", tmp*100);
}
}
if ((s=dstate_getinfo("output.L3.power"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp /= (nom_out_power/num_outphases);
if (tmp>load) {
load = tmp;
}
dstate_setinfo("output.L3.power.percent",
"%.1f", tmp*100);
}
}
}
/* This is aggregated (all phases) */
if ((s=dstate_getinfo("output.realpower.nominal"))) {
if (sscanf(s, "%f", &nom_out_realpower) != 1) {
nom_out_realpower = -1;
}
}
if (nom_out_realpower >= 0) {
if ((s=dstate_getinfo("output.L1.realpower"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp /= (nom_out_realpower/num_outphases);
if (tmp>load) {
load = tmp;
}
dstate_setinfo("output.L1.realpower.percent",
"%.1f", tmp*100);
}
}
if ((s=dstate_getinfo("output.L2.realpower"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp /= (nom_out_realpower/num_outphases);
if (tmp>load) {
load = tmp;
}
dstate_setinfo("output.L2.realpower.percent",
"%.1f", tmp*100);
}
}
if ((s=dstate_getinfo("output.L3.realpower"))) {
if (sscanf(s, "%f", &tmp) == 1) {
tmp /= (nom_out_realpower/num_outphases);
if (tmp>load) {
load = tmp;
}
dstate_setinfo("output.L3.realpower.percent",
"%.1f", tmp*100);
}
}
}
return load;
}
void upsdrv_updateinfo(void)
{
time_t now;
int ok;
float load;
if (status & UPSC_STAT_NOTINIT) {
upsdrv_initinfo();
}
if (status & UPSC_STAT_NOTINIT) {
return;
}
status = 0;
ok = upsc_getparams("UPDS", simple);
time(&now);
if (ok && now - last_full > full_update_timer) {
last_full = now;
ok = upsc_getparams("UPDV", nominal);
if (ok && can_upbs)
ok = upsc_getparams("UPBS", battery);
}
if (!ok) {
dstate_datastale();
last_full = 0;
return;
}
if (!inited_phaseinfo) {
if (dstate_getinfo("input.L3-L1.voltage") ||
dstate_getinfo("input.L3-N.voltage")) {
num_inphases = 3;
change_name(simple,
"input.current", "input.L1.current");
change_name(simple,
"input.realpower", "input.L1.realpower");
change_name(simple,
"input.power", "input.L1.power");
change_name(simple,
"input.voltage", "input.L1-N.voltage");
}
if (dstate_getinfo("output.L3-L1.voltage") ||
dstate_getinfo("output.L3-N.voltage")) {
const char *s;
num_outphases = 3;
if ((s=dstate_getinfo("ups.model")) &&
(!strncmp(s, "UPS9075", 7) ||
!strncmp(s, "UPS9100", 7) ||
!strncmp(s, "UPS9150", 7) ||
!strncmp(s, "UPS9200", 7) ||
!strncmp(s, "UPS9250", 7) ||
!strncmp(s, "UPS9300", 7) ||
!strncmp(s, "UPS9400", 7) ||
!strncmp(s, "UPS9500", 7) ||
!strncmp(s, "UPS9600", 7)) ) {
/* Insert kludges for Fiskars UPS9000 here */
upslogx(LOG_INFO, "Fiskars UPS9000 detected, protocol kludges activated");
batt_volt_nom = 384;
dstate_setinfo("battery.voltage.nominal", "%.0f", batt_volt_nom);
}
else {
outpwr_factor *= 3;
}
change_name(simple,
"output.current", "output.L1.current");
change_name(simple,
"output.current.peak", "output.L1.current.peak");
change_name(simple,
"output.realpower", "output.L1.realpower");
change_name(simple,
"output.power", "output.L1.power");
change_name(simple,
"output.voltage", "output.L1-N.voltage");
}
dstate_setinfo("input.phases", "%d", num_inphases);
dstate_setinfo("output.phases", "%d", num_outphases);
inited_phaseinfo=1;
}
load = calc_upsload();
if (load >= 0) {
upsdebugx(2, "ups.load: %.1f", load*100);
dstate_setinfo("ups.load", "%.1f", load*100);
}
else {
upsdebugx(2, "ups.load: No value");
}
/* TODO/FIXME: Set UPS date/time on startup and daily if needed */
if (can_updt) {
char dtbuf[UPSC_BUFLEN];
if (upsc_getvalue("UPDT", "0", "ACDT", NULL, dtbuf)) {
dstate_setinfo("ups.date", "%s", dtbuf);
}
}
if (can_uptm) {
char tmbuf[UPSC_BUFLEN];
if (upsc_getvalue("UPTM", "0", "ACTM", NULL, tmbuf)) {
dstate_setinfo("ups.time", "%s", tmbuf);
}
}
if (batt_charge < 0) {
if (batt_current < 0) {
/* Reset battery current history if discharging */
numbatthist = lastbatthist = 0;
}
batt_charge = batt_charge_pct();
}
if (batt_charge >= 0) {
dstate_setinfo("battery.charge", "%.1f", batt_charge);
}
else {
dstate_delinfo("battery.charge");
}
/* 9999 == unknown value */
if (batt_runtime >= 0 && batt_runtime < 9999) {
dstate_setinfo("battery.runtime", "%.0f", batt_runtime*60);
}
else if (load > 0 && batt_disch_curr_max != 0) {
float est_battcurr = load * abs(batt_disch_curr_max);
/* Peukert equation */
float runtime = (batt_cap_nom*3600)/pow(est_battcurr, 1.35);
upsdebugx(2, "Calculated runtime: %.0f seconds", runtime);
if (batt_runtime_max > 0 && runtime > batt_runtime_max*60) {
runtime = batt_runtime_max*60;
}
dstate_setinfo("battery.runtime", "%.0f", runtime);
}
else if (batt_runtime_max > 0) {
/* Show max possible runtime as reported by UPS */
dstate_setinfo("battery.runtime", "%.0f", batt_runtime_max*60);
}
else {
dstate_delinfo("battery.runtime");
}
/* Some UPSen only provides this when on battery, so reset between
* each iteration to make sure we use the right value */
batt_charge = -1;
batt_runtime = -1;
if (!(status & UPSC_STAT_ONBATT))
status |= UPSC_STAT_ONLINE;
upsc_setstatus(status);
dstate_dataok();
ser_comm_good();
}
void upsdrv_shutdown(void)
{
upslogx(LOG_EMERG, "Shutting down...");
/* send shutdown command twice, just to be sure */
instcmd("shutdown.reboot", NULL);
sleep(1);
instcmd("shutdown.reboot", NULL);
sleep(1);
}
static int instcmd (const char *auxcmd, const char *data)
{
cmd_t *cp;
if (!strcasecmp(auxcmd, "beeper.off")) {
/* compatibility mode for old command */
upslogx(LOG_WARNING,
"The 'beeper.off' command has been renamed to 'beeper.disable'");
return instcmd("beeper.disable", NULL);
}
if (!strcasecmp(auxcmd, "beeper.on")) {
/* compatibility mode for old command */
upslogx(LOG_WARNING,
"The 'beeper.on' command has been renamed to 'beeper.enable'");
return instcmd("beeper.enable", NULL);
}
upsdebugx(1, "Instcmd: %s %s", auxcmd, data ? data : "\"\"");
for (cp = commands; cp->cmd; cp++) {
if (strcasecmp(cp->cmd, auxcmd)) {
continue;
}
upscsend(cp->upsc);
if (cp->upsp) {
upscsend(cp->upsp);
} else if (data) {
upscsend(data);
}
return STAT_INSTCMD_HANDLED;
}
upslogx(LOG_INFO, "instcmd: unknown command %s", auxcmd);
return STAT_INSTCMD_UNKNOWN;
}
static int setvar (const char *var, const char *data)
{
cmd_t *cp;
upsdebugx(1, "Setvar: %s %s", var, data);
for (cp = variables; cp->cmd; cp++) {
if (strcasecmp(cp->cmd, var)) {
continue;
}
upsc_getvalue(cp->upsc, data, cp->upsp, cp->cmd, NULL);
return STAT_SET_HANDLED;
}
upslogx(LOG_INFO, "Setvar: unsettable variable %s", var);
return STAT_SET_UNKNOWN;
}
/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
addvar(VAR_VALUE, "manufacturer", "manufacturer [unknown]");
addvar(VAR_VALUE, "baudrate", "Serial interface baudrate [1200]");
addvar(VAR_VALUE, "input_timeout", "Command response timeout [2]");
addvar(VAR_VALUE, "output_pace", "Output character delay in usecs [200]");
addvar(VAR_VALUE, "full_update", "Delay between full value downloads [60]");
addvar(VAR_FLAG, "use_crlf", "Use CR-LF to terminate commands to UPS");
addvar(VAR_FLAG, "use_pre_lf", "Use LF to introduce commands to UPS");
}
void upsdrv_cleanup(void)
{
ser_close(upsfd, device_path);
}
/*
* Generate status string from bitfield
*/
static void upsc_setstatus(unsigned int status)
{
/*
* I'll look for all available statuses, even though they might not be
* supported in the UPScode II protocol.
*/
status_init();
if (status & UPSC_STAT_ONLINE)
status_set("OL");
if (status & UPSC_STAT_ONBATT)
status_set("OB");
if (status & UPSC_STAT_LOBATT)
status_set("LB");
if (status & UPSC_STAT_REPLACEBATT)
status_set("RB");
if (status & UPSC_STAT_BOOST)
status_set("BOOST");
if (status & UPSC_STAT_TRIM)
status_set("TRIM");
if (status & UPSC_STAT_OVERLOAD)
status_set("OVER");
if (status & UPSC_STAT_CALIBRATION)
status_set("CAL");
if (status & UPSC_STAT_OFF)
status_set("OFF");
if (status & UPSC_STAT_BYPASS)
status_set("BYPASS");
status_commit();
}
/* Add \r to end of command and send to UPS */
/* returns < 0 on errors, 0 on timeout and > 0 on success. */
static int upscsend(const char *cmd)
{
int res;
res = ser_send_pace(upsfd, output_pace_usec, "%s%s%s",
use_pre_lf ? "\n" : "",
cmd,
use_crlf ? "\r\n" : "\r");
if (res < 0) {
upsdebug_with_errno(3, "upscsend");
} else if (res == 0) {
upsdebugx(3, "upscsend: Timeout");
} else {
upsdebugx(3, "upscsend: '%s'", cmd);
}
return res;
}
/* Return a string read from UPS */
/* returns < 0 on errors, 0 on timeout and > 0 on success. */
static int upscrecv(char *buf)
{
int res;
/* NOTE: the serial port is set to use Canonical Mode Input Processing,
which means ser_get_buf() either returns one line terminated with
ENDCHAR, an error or times out. */
while (1) {
res = ser_get_buf(upsfd, buf, UPSC_BUFLEN, input_timeout_sec, 0);
if (res != 1) {
break;
}
/* Only one character, must be ENDCHAR */
upsdebugx(3, "upscrecv: Empty line");
}
if (res < 0) {
upsdebug_with_errno(3, "upscrecv");
} else if (res == 0) {
upsdebugx(3, "upscrecv: Timeout");
} else {
upsdebugx(3, "upscrecv: %u bytes:\t'%s'", res-1, rtrim(buf, ENDCHAR));
}
return res;
}
static void upsc_flush_input(void)
{
/*
char buf[UPSC_BUFLEN];
do {
upscrecv(buf);
if (strlen(buf) > 0)
upsdebugx(1, "Skipping input: %s", buf);
} while (strlen(buf) > 0);
*/
ser_flush_in(upsfd, "", nut_debug_level);
}
/* check which commands this ups supports.
* Returns TRUE if command list was recieved, FALSE otherwise */
static int upsc_commandlist(void)
{
char buf[UPSC_BUFLEN];
cmd_t *cp;
upsc_flush_input();
upscsend("UPCL");
while (1) {
upscrecv(buf);
if (strlen(buf) == 0) {
upslogx(LOG_ERR, "Missing UPCL after UPCL");
return 0;
}
upsdebugx(2, "Supports command: %s", buf);
if (strcmp(buf, "UPBS") == 0)
can_upbs = 1;
else if (strcmp(buf, "UPPM") == 0)
can_uppm = 1;
else if (strcmp(buf, "UPID") == 0)
can_upid = 1;
else if (strcmp(buf, "UPDA") == 0)
can_upda = 1;
else if (strcmp(buf, "UPDT") == 0)
can_updt = 1;
else if (strcmp(buf, "UPTM") == 0)
can_uptm = 1;
else if (strcmp(buf, "UPSD") == 0)
can_upsd = 1;
else if (strcmp(buf, "UPPC") == 0)
can_uppc = 1;
for (cp = commands; cp->cmd; cp++) {
if (cp->upsc && strcmp(cp->upsc, buf) == 0) {
upsdebugx(1, "instcmd: %s %s", cp->cmd, cp->upsc);
dstate_addcmd(cp->cmd);
cp->enabled = 1;
break;
}
}
for (cp = variables; cp->cmd; cp++) {
if (cp->upsc && strcmp(cp->upsc, buf) == 0) {
upsdebugx(1, "setvar: %s %s", cp->cmd, cp->upsc);
cp->enabled = 1;
break;
}
}
if (strcmp(buf, "UPCL") == 0)
break;
}
for (cp = variables; cp->cmd; cp++) {
if (cp->enabled) {
upsc_getvalue(cp->upsc, "0000", cp->upsp, cp->cmd, NULL);
dstate_setflags(cp->cmd, ST_FLAG_RW | ST_FLAG_STRING);
dstate_setaux(cp->cmd, 7);
}
}
return 1;
}
/* get limits and parameters */
static int upsc_getparams(const char *cmd, const simple_t *table)
{
char var[UPSC_BUFLEN];
char val[UPSC_BUFLEN];
int first = 1;
upsc_flush_input();
upscsend(cmd);
buffer_empty = 0;
while (!buffer_empty) {
upscrecv(var);
if (strlen(var) == 0) {
if (first)
upscrecv(var);
if (strlen(var) == 0) {
ser_comm_fail("Empty string from UPS for %s!",
cmd);
break;
}
}
first = 0;
upscrecv(val);
if (strlen(val) == 0) {
ser_comm_fail("Empty value from UPS for %s %s!", cmd, var);
break;
}
upsdebugx(2, "Parameter %s %s", var, val);
if (!upsc_simple(table, var, val))
upslogx(LOG_ERR, "Unknown response to %s: %s %s",
cmd, var, val);
}
return buffer_empty;
}
static void check_uppm(void)
{
int i, last = 0;
char var[UPSC_BUFLEN];
char val[UPSC_BUFLEN];
upsc_flush_input();
upscsend("UPPM");
upscsend("0");
upscrecv(var);
if (strcmp(var, "ACPM"))
upslogx(LOG_ERR, "Bad response to UPPM: %s", var);
while (1) {
int val, stat;
upscrecv(var);
if (strlen(var) == 0)
break;
upsdebugx(2, "UPPM available: %s", var);
stat = sscanf(var, "P%2d", &val);
if (stat != 1) {
upslogx(LOG_ERR, "Bad response to UPPM: %s", var);
return;
}
has_uppm_p[val] = 1;
if (val > last)
last = val;
}
for (i = 0; i <= last; i++) {
if (!has_uppm_p[i])
continue;
upscsend("UPPM");
snprintf(var, sizeof(var), "P%.2d", i);
upscsend(var);
upscrecv(val);
if (strcmp(val, "ACPM")) {
upslogx(LOG_ERR, "Bad response to UPPM %s: %s", var, val);
continue;
}
upscrecv(var);
upsdebugx(1, "UPPM value: %s", var);
}
}
static int upsc_getvalue(const char *cmd, const char *param,
const char *resp, const char *nutvar, char *ret)
{
char var[UPSC_BUFLEN];
char val[UPSC_BUFLEN];
upsdebugx(2, "Request value: %s %s", cmd, param ? param : "\"\"");
upscsend(cmd);
if (param)
upscsend(param);
upscrecv(var);
upscrecv(val);
upsdebugx(2, "Got value: %s %s", var, val);
if (strcmp(var, resp)) {
upslogx(LOG_ERR, "Bad response to %s %s: %s %s",
cmd, param ? param : "\"\"", var, val);
return 0;
}
else {
if (nutvar)
dstate_setinfo(nutvar, "%s", val);
if (ret)
strcpy(ret, val);
}
return 1;
}
static void upsc_getbaseinfo(void)
{
char *buf;
dstate_setinfo("ups.mfr", "%s",
((buf = getval("manufacturer")) != NULL) ? buf : "unknown");
if (!upsc_getvalue("UPTP", NULL, "NNAME", "ups.model", NULL))
upsc_getvalue("UPTP", NULL, "NNAME", "ups.model", NULL);
upsc_getvalue("UPSN", "0", "ACSN", "ups.serial", NULL);
}
static int upsc_simple(const simple_t *sp, const char *var, const char *val)
{
int stat;
float fval;
while (sp->code) {
if (strcmp(sp->code, var) == 0) {
switch (sp->type) {
case t_setval:
stat = sscanf(val, "%f", &fval);
if (stat != 1)
upslogx(LOG_ERR, "Bad float: %s %s", var, val);
if (sp->desc)
dstate_setinfo(sp->desc, "%.2f", fval);
*sp->aux = fval;
break;
case t_setrecip:
stat = sscanf(val, "%f", &fval);
if (stat != 1)
upslogx(LOG_ERR, "Bad float: %s %s", var, val);
if (sp->desc)
dstate_setinfo(sp->desc, "%s", val);
*sp->aux = 1/fval;
break;
case t_setpct:
stat = sscanf(val, "%f", &fval);
if (stat != 1)
upslogx(LOG_ERR, "Bad float: %s %s", var, val);
*sp->aux = fval*100;
if (sp->desc)
dstate_setinfo(sp->desc, "%s", val);
break;
case t_setrecpct:
stat = sscanf(val, "%f", &fval);
if (stat != 1)
upslogx(LOG_ERR, "Bad float: %s %s", var, val);
*sp->aux = 1/fval*100;
if (sp->desc)
dstate_setinfo(sp->desc, "%s", val);
break;
case t_final:
buffer_empty = 1;
case t_value:
if (!sp->desc) {
break;
}
if (sscanf(val, "%f", &fval) == 1) {
if (sp->aux != NULL) {
fval *= *(sp->aux);
}
dstate_setinfo(sp->desc, "%.2f", fval);
}
else {
upslogx(LOG_ERR, "Bad float in %s: %s", var, val);
dstate_setinfo(sp->desc, "%s", val);
}
break;
case t_finstr:
buffer_empty = 1;
case t_string:
if (!sp->desc) {
break;
}
dstate_setinfo(sp->desc, "%s", val);
break;
case t_status:
if (strcmp(val, "00") == 0)
;
else if (strcmp(val, "11") == 0)
status |= sp->status;
else
upslogx(LOG_ERR, "Unknown status value: '%s' '%s'", var, val);
break;
case t_alarm:
if (strcmp(val, "00") == 0)
;
else if (strcmp(val, "11") == 0)
status |= sp->status;
else
upslogx(LOG_ERR, "Unknown alarm value: '%s' '%s'", var, val);
break;
case t_ignore:
upsdebugx(3, "Ignored value: %s %s", var, val);
break;
case t_list:
if (!upsc_simple(sp->stats, val, "11"))
upslogx(LOG_ERR, "Unknown value: %s %s",
var, val);
break;
default:
upslogx(LOG_ERR, "Unknown type for %s", var);
break;
}
return 1;
}
sp++;
}
return 0;
}
static float batt_charge_pct(void)
{
float chg=-1;
/* This is only a rough estimate of charge status while charging.
* When on battery something like Peukert's equation should be used */
if (batt_current >= 0) {
float maxcurr = 10; /* Assume 10A max as default */
float avgcurr = 0;
int i;
batthist[lastbatthist] = batt_current;
lastbatthist = (lastbatthist+1) % NUM_BATTHIST;
if (numbatthist < NUM_BATTHIST) {
numbatthist++;
}
for(i=0; i<numbatthist; i++) {
avgcurr += batthist[i];
}
avgcurr /= numbatthist;
if (batt_cap_nom > 0) {
/* Estimate max charge current based on battery size */
maxcurr = batt_cap_nom * 0.3;
}
chg = maxcurr - avgcurr;
chg *= (100/maxcurr);
}
/* Old method, assumes battery high/low-voltages provided by UPS are
* applicable to battery charge, but they usually aren't */
else if (batt_volt_low > 0 && batt_volt_high > 0 && batt_volt > 0) {
if (batt_volt > batt_volt_high) {
chg=100;
}
else if (batt_volt < batt_volt_low) {
chg=0;
}
else {
chg = (batt_volt - batt_volt_low) /
(batt_volt_high - batt_volt_low);
chg*=100;
}
}
else {
return -1;
}
if (chg < 0) {
chg = 0;
}
else if (chg > 100) {
chg = 100;
}
return chg;
}
/*
vim:noet:ts=8:sw=8:cindent
*/