/* 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 Copyright (C) 2004-2006 Niels Baggesen Copyright (C) 2006 Niklas Edmundsson 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 $Id: upscode2.c 2350 2010-02-16 08:28:21Z adkorte-guest $ */ /* * 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 ) * Powerware 9120 (Gorm J. Siiger ) * Fiskars PowerServer 10 (Per Larsson ) */ #include "main.h" #include "serial.h" #include "timehead.h" #include "nut_stdint.h" #include #define DRIVER_NAME "UPScode II UPS driver" #define DRIVER_VERSION "0.87" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "H K Lygre, \n" \ "Niels Baggesen \n" \ "Niklas Edmundsson ", 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.peakcurrent" }, { "MOIP2", t_value, "output.L2.peakcurrent" }, { "MOIP3", t_value, "output.L3.peakcurrent" }, { "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(); 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.peakcurrent", "output.L1.peakcurrent"); 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) { if (upsc_commandlist()) { if (!can_upsd || !can_uppc) { fatalx(LOG_EMERG, "Shutdown called, but UPS does not support it"); } } else { upslogx(LOG_EMERG, "Can't determine if shutdown is supported, attempting anyway"); } upslogx(LOG_EMERG, "Emergency shutdown"); upscsend("UPSD"); /* Set shutdown delay */ upscsend("1"); /* 1 second (lowest possible. 0 returns current.*/ upslogx(LOG_EMERG, "Shutting down..."); upscsend("UPPC"); /* Powercycle UPS */ upscsend("IJHLDMGCIU"); /* security code */ } static int instcmd (const char *auxcmd, const char *data) { cmd_t *cp = commands; 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 : "\"\""); while (cp->cmd) { if (strcmp(cp->cmd, auxcmd) == 0) { upscsend(cp->upsc); if (cp->upsp) upscsend(cp->upsp); else if (data) upscsend(data); return STAT_INSTCMD_HANDLED; } cp++; } upslogx(LOG_INFO, "instcmd: unknown command %s", auxcmd); return STAT_INSTCMD_UNKNOWN; } static int setvar (const char *var, const char *data) { cmd_t *cp = variables; upsdebugx(1, "Setvar: %s %s", var, data); while (cp->cmd) { if (strcmp(cp->cmd, var) == 0) { upsc_getvalue(cp->upsc, data, cp->upsp, cp->cmd, NULL); return STAT_SET_HANDLED; } cp++; } 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; cp = commands; while (cp->cmd) { 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; } cp++; } cp = variables; while (cp->cmd) { if (cp->upsc && strcmp(cp->upsc, buf) == 0) { upsdebugx(1, "setvar: %s %s", cp->cmd, cp->upsc); cp->enabled = 1; break; } cp++; } if (strcmp(buf, "UPCL") == 0) break; } cp = variables; while (cp->cmd) { if (cp->enabled) { upsc_getvalue(cp->upsc, "0", cp->upsp, cp->cmd, NULL); dstate_setflags(cp->cmd, ST_FLAG_RW | ST_FLAG_STRING); dstate_setaux(cp->cmd, 7); } cp++; } 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 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 */