Hi! I've recently come into to the possesion of a couple of Belkin Omniguard 3200 UPS's. Given the horrible apache/java based software they come with, I decided to see if I cannot get them to work with NUT. To cut a long story short, they work out to be SEC -based, and extensively modifying the nut-1.4 driver code to come up with the attached two files. I've marked them experimental, as they seem to work on the two UPS I have, but the driver is generic ... and have only tested the features that the omniguard support ... I cannot fully test all features. That said, the driver seems stable and has been working here for a few days now :). The only 'feature' that is missing is alarms, but it seems that alarms are an incomplete feature with nut .... I hope this driver can be re-instated into the 2.X series of nut. Hopefully it will help others out there who have a SEC-based UPS and have been stuck with the nut ... - Julius. -------------- next part -------------- /* -*- Mode: C -*- * sec.h -- header for sec protocol module * * Copyright (C) 2000 John Marley * The author can be contacted at: John.Marley@alcatel.com.au * * Author : John Marley * Created On : Thu Mar 29 11:07:01 2001 * Last Modified By: John Marley * Last Modified On: Fri May 4 16:26:00 2001 * Update Count : 53 * Status : Unknown, Use with caution! * $Locker: $ * $Log: sec.h,v $ * Revision 1.2 2001/05/08 03:21:09 marleyj * Added supported() macro. * * Revision 1.1 2001/05/02 04:54:33 marleyj * Initial revision * */ #ifndef _SEC_H_ #define _SEC_H_ #define SEC_MSG_STARTCHAR '^' #define SEC_POLLCMD 'P' #define SEC_SETCMD 'S' #define SEC_DATAMSG 'D' #define SEC_UPSMSG '*' #define SEC_ACK '1' #define SEC_NAK '0' /* commands */ #define SEC_CMD_AVAILP1 "AP1" /* Part1 of available variables */ #define SEC_CMD_AVAILP2 "AP2" /* Part1 of available variables */ #define SEC_CMD_AUTORESTART "ATR" /* Enable/disable auto restart */ #define SEC_CMD_MFR "MAN" /* UPS Manufacturer */ #define SEC_CMD_MOD "MOD" /* UPS Model */ #define SEC_CMD_NOMINAL "NOM" /* Nominal Values */ #define SEC_CMD_SHUTDOWN "PSD" /* Shutdown after delay/cancel */ #define SEC_CMD_REBOOT "RWD" /* Reboot with duration/cancel */ #define SEC_CMD_SHUTTYPE "SDA" /* Shutdown Type */ #define SEC_CMD_BATTSTAT "ST1" /* Battery Status */ #define SEC_CMD_INPUTSTAT "ST2" /* Input Status */ #define SEC_CMD_OUTPUTSTAT "ST3" /* Output Status */ #define SEC_CMD_BYPASSSTAT "ST4" /* Bypass Status */ #define SEC_CMD_ALARMSTAT "ST5" /* UPS Alarms */ #define SEC_CMD_STARTDELAY "STD" /* Startup after delay */ #define SEC_CMD_TESTRESULT "STR" /* Test Results */ #define SEC_CMD_TEST "TST" /* UPS Test/abort */ #define SEC_CMD_BAUDRATE "UBR" /* UPS Baud Rate */ #define SEC_CMD_UPSID "UID" /* UPS Identifier */ #define SEC_CMD_VERSION "VER" /* UPS Software Version */ /* variables */ #define SEC_UNUSED ( 0) #define SEC_ALARM_PWRWAIT ( 1) #define SEC_ALARM_BADBYPASS ( 2) #define SEC_ALARM_CHARGFAIL ( 3) #define SEC_ALARM_FANFAIL ( 4) #define SEC_ALARM_FUSEFAIL ( 5) #define SEC_ALARM_GENERAL ( 6) #define SEC_ALARM_BADINPUT ( 7) #define SEC_ALARM_BADOUTPUT ( 8) #define SEC_ALARM_OUTPUTOFF ( 9) #define SEC_ALARM_OVERLOAD (10) #define SEC_ALARM_IMMSHUT (11) #define SEC_ALARM_PENDSHUT (12) #define SEC_ALARM_SYSOFF (13) #define SEC_ALARM_TEMP (14) #define SEC_ALARM_UPSSHUT (15) #define SEC_AUDIBLE_ALARM (16) #define SEC_AUTORESTART (17) #define SEC_BATTERY_CHARGE (18) #define SEC_BATTERY_COND (19) #define SEC_BATTERY_CURRENT (20) #define SEC_BATTERY_INSTALLED (21) #define SEC_BATTERY_STATUS (22) #define SEC_BATTERY_TEMPERATURE (23) #define SEC_BATTERY_VOLTAGE (24) #define SEC_BATTERY_CURRENT_1 (25) #define SEC_BATTERY_CURRENT_2 (26) #define SEC_BATTERY_CURRENT_3 (27) #define SEC_BYPASS_FREQUENCY (28) #define SEC_BYPASS_NUM_LINES (29) #define SEC_BYPASS_POWER_1 (30) #define SEC_BYPASS_POWER_2 (31) #define SEC_BYPASS_POWER_3 (32) #define SEC_BYPASS_VOLTAGE_1 (33) #define SEC_BYPASS_VOLTAGE_2 (34) #define SEC_BYPASS_VOLTAGE_3 (35) #define SEC_ESTIMATED_CHARGE (36) #define SEC_ESTIMATED_MINUTES (37) #define SEC_HIGH_VOLT_XFER_POINT (38) #define SEC_UPS_IDENTIFICATION (39) #define SEC_INPUT_CURRENT_1 (40) #define SEC_INPUT_CURRENT_2 (41) #define SEC_INPUT_CURRENT_3 (42) #define SEC_INPUT_FREQUENCY_1 (43) #define SEC_INPUT_FREQUENCY_2 (44) #define SEC_INPUT_FREQUENCY_3 (45) #define SEC_INPUT_LINE_BADS (46) #define SEC_INPUT_NUM_LINES (47) #define SEC_INPUT_POWER_1 (48) #define SEC_INPUT_POWER_2 (49) #define SEC_INPUT_POWER_3 (50) #define SEC_INPUT_VOLTAGE_1 (51) #define SEC_INPUT_VOLTAGE_2 (52) #define SEC_INPUT_VOLTAGE_3 (53) #define SEC_LOW_VOLT_XFER_POINT (54) #define SEC_MANUFACTURER (55) #define SEC_MODEL (56) #define SEC_NOM_BATTERY_LIFE (57) #define SEC_NOM_INPUT_FREQUENCY (58) #define SEC_NOM_INPUT_VOLTAGE (59) #define SEC_NOM_LOW_BATTERY_TIME (60) #define SEC_NOM_OUTPUT_FREQUENCY (61) #define SEC_NOM_OUTPUT_POWER (62) #define SEC_NOM_OUTPUT_VOLTAGE (63) #define SEC_NOM_VA_RATING (64) #define SEC_OUTPUT_CURRENT_1 (65) #define SEC_OUTPUT_CURRENT_2 (66) #define SEC_OUTPUT_CURRENT_3 (67) #define SEC_OUTPUT_FREQUENCY (68) #define SEC_OUTPUT_LOAD_1 (69) #define SEC_OUTPUT_LOAD_2 (70) #define SEC_OUTPUT_LOAD_3 (71) #define SEC_OUTPUT_NUM_LINES (72) #define SEC_OUTPUT_POWER_1 (73) #define SEC_OUTPUT_POWER_2 (74) #define SEC_OUTPUT_POWER_3 (75) #define SEC_OUTPUT_SOURCE (76) #define SEC_OUTPUT_VOLTAGE_1 (77) #define SEC_OUTPUT_VOLTAGE_2 (78) #define SEC_OUTPUT_VOLTAGE_3 (79) #define SEC_REBOOT_WITH_DURATION (80) #define SEC_SECONDS_ON_BATTERY (81) #define SEC_SHUTDOWN_TYPE (82) #define SEC_SHUTDOWN_AFTER_DELAY (83) #define SEC_SOFTWARE_VERSION (84) #define SEC_STARTUP_AFTER_DELAY (85) #define SEC_TEST_RESULTS_DETAIL (86) #define SEC_TEST_RESULTS_SUMMARY (87) #define SEC_TEST_TYPE (88) #define SEC_BAUD_RATE (89) /* Some baud rates for setup_serial() */ #define SEC_NUMBAUDS 5 struct baud_rate_t { int rate; int name; }; /* * Exhaustive information for each possible variable */ #define SEC_NUMVARS 90 #define SEC_MAX_VARSIZE 65 struct sec_varlist_t { char *infotag; /* variable names from new-names.txt */ char *name; /* Human readable text (also in shared-tables.h) */ float unit; /* Variable should be scaled by this */ char *cmd; /* Command to send to get/set variable */ int field; /* Which returned field variable corresponsd to */ int size; /* string length/integer max/enum count */ int flags; /* Flags for addinfo() */ }; /* * List of possible enumerations */ struct sec_enumdata_t { int type; /* corresponding variable */ int index; /* offset from first enum */ const char *value; /* enumerated value */ }; /* a type for the supported variables */ #define SEC_QUERYLIST_LEN 17 #define SEC_MAXFIELDS 16 struct sec_querylist_t { char *command; /* sec command */ int varnum[SEC_MAXFIELDS]; /* sec variable number for each field */ } sec_querylist[SEC_QUERYLIST_LEN]; #define sqv(a,b) sec_querylist[a].varnum[b] #endif /* _SEC_H_ */ -------------- next part -------------- /* -*- Mode: C -*- * sec.c -- new style SEC UPS Protocol driver * * Copyright (C) 2001 John Marley * The author can be contacted at: John.Marley@alcatel.com.au * * 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 * * Author : John Marley * Created On : Wed Mar 28 14:48:26 2001 * Last Modified By: Jules Taplin * Last Modified On: Sunday August 11, 2002 19:30 GMT * Update Count : 198 * Status : Unknown, Use with caution! * $Locker: $ * $Log: sec.c,v $ * * Revision 1.6 11 August 2002 Jules Taplin. jules@netsitepro.co.uk * increased size of buffer that holds data returned from UPS during * probing of baud rate in function setup_serial(). * * Move function call nolongertimeout() in sec_upsrecv outside of loop * so the timeout is reset only when a valid message start character * from the UPS is received. * * Revision 1.5 10 August 2002 Eric Lawson. elawson@inficad.com * Fix setup_serial so that any valid response from the UPS is accepted * as having found the correct serial port baud rate. During serial port * baud rate probing, some UPS units get confused by commands sent at * the wrong baud rate and always reports a command failed the first time * the command is sent at the correct baud rate. * * Revision 1.4 17 July 2002 Eric Lawson. elawson@inficad.com * added shutdown feature * * Revison 1.3 15 July 2002 Eric Lawson. elawson@inficad.com * * Use actual sec protocol command instead of bogus command when * probing for a ups on the serial port in the setup_serial function. * * Zero msglen before calling sec_cmd the 2nd time in upsdrv_initinfo * function. This fixes driver's use of sec protocol parameters * numbered 47 and up. * * Revision 1.2 2001/05/08 03:05:21 marleyj * Added synthetic variables SEC_STATUS, INFO_ACFREQ, INFO_UTILITY, * SEC_CURRENT, INFO_LOADPCT, INFO_LOADPWR and INFO_OUTVOLT. * * Revision 1.1 2001/05/02 04:54:19 marleyj * Initial revision */ #define SEC_DRIVER_VERSION "$Revision: 1.4 $" #include "main.h" #include "serial.h" #define SEC_LOG_DEBUG (1) #define SEC_LOG_ERROR (1) #define SEC_LOG_INFO (2) #define SEC_LOG_LOWLEVEL (3) #define SEC_FLAG_NORMAL (0 ) #define SEC_FLAG_ORIGINAL (1<<0) #define SEC_FLAG_ENUM (1<<2) #define SEC_FLAG_STRING (1<<3) #define SEC_FLAG_NUMBER (1<<4) #define SEC_FLAG_RD (1<<5) #define SEC_FLAG_WR (1<<6) #define SEC_FLAG_RW (SEC_FLAG_RD|SEC_FLAG_WR) /* extra */ /* ------ */ #define SEC_STR_STATUS "ups.status" #define SEC_STR_INPUT_FREQUENCY "input.frequency" #define SEC_STR_INPUT_VOLTAGE "input.voltage" #define SEC_STR_OUTPUT_CURRENT "output.current" #define SEC_STR_OUTPUT_LOAD "output.load" #define SEC_STR_OUTPUT_POWER "output.power" #define SEC_STR_OUTPUT_VOLTAGE "output.voltage" #define SEC_SIZE (256) #include "sec.h" /* Some baud rates for setup_serial() */ #define SEC_NUMBAUDS 5 static struct baud_rate_t baud_rates[SEC_NUMBAUDS] = { { B1200, 1200 }, { B2400, 2400 }, { B4800, 4800 }, { B9600, 9600 }, { B19200, 19200 }, }; /* Exhaustive information for each possible variable */ static const struct sec_varlist_t sec_varlist[SEC_NUMVARS] = { { SEC_UNUSED, "", 0.0, "", 0, 0, 0 }, /* infotag name unit cmd field size flags */ { "ups.alarm", "Alarm Awaiting Power", 1.0, SEC_CMD_ALARMSTAT, 13, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Bypass Bad", 1.0, SEC_CMD_ALARMSTAT, 5, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Charger Failure", 1.0, SEC_CMD_ALARMSTAT, 8, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Fan Failure", 1.0, SEC_CMD_ALARMSTAT, 10, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Fuse Failure", 1.0, SEC_CMD_ALARMSTAT, 11, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm General Fault", 1.0, SEC_CMD_ALARMSTAT, 12, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Input Bad", 1.0, SEC_CMD_ALARMSTAT, 2, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Output Bad", 1.0, SEC_CMD_ALARMSTAT, 3, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Output Off", 1.0, SEC_CMD_ALARMSTAT, 6, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Overload", 1.0, SEC_CMD_ALARMSTAT, 4, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Shutdown Imminent", 1.0, SEC_CMD_ALARMSTAT, 15, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Shutdown Pending", 1.0, SEC_CMD_ALARMSTAT, 14, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm System Off", 1.0, SEC_CMD_ALARMSTAT, 9, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm Temperature", 1.0, SEC_CMD_ALARMSTAT, 1, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.alarm", "Alarm UPS Shutdown", 1.0, SEC_CMD_ALARMSTAT, 7, 2, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.beeper.status", "Audible Alarm", 1.0, SEC_CMD_NOMINAL, 8, 4, SEC_FLAG_NORMAL |SEC_FLAG_ENUM |SEC_FLAG_RW }, { "ups.autorestart", "Auto Restart", 1.0, SEC_CMD_AUTORESTART, 1, 2, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RW }, { "battery.charge.state", "Battery Charge", 1.0, SEC_CMD_BATTSTAT, 3, 4, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RD }, { "battery.condition", "Battery Condition", 1.0, SEC_CMD_BATTSTAT, 1, 3, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RD }, { "battery.current", "Battery Current", 0.1, SEC_CMD_BATTSTAT, 8, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "battery.date", "Battery Installed", 1.0, SEC_CMD_NOMINAL, 11, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RW }, { "battery.status", "Battery Status", 1.0, SEC_CMD_BATTSTAT, 2, 3, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RD }, { "battery.temperature", "Battery Temperature", 1.0, SEC_CMD_BATTSTAT, 9, 99, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "battery.voltage", "Battery Voltage", 0.1, SEC_CMD_BATTSTAT, 7, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.current", "Bypass Current 1", 0.1, SEC_CMD_BYPASSSTAT, 4, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.current", "Bypass Current 2", 0.1, SEC_CMD_BYPASSSTAT, 7, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.current", "Bypass Current 3", 0.1, SEC_CMD_BYPASSSTAT, 10, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.frequency", "Bypass Frequency", 0.1, SEC_CMD_BYPASSSTAT, 1, 999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.lines", "Bypass Num Lines", 1.0, SEC_CMD_BYPASSSTAT, 2, 9, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.power", "Bypass Power 1", 1.0, SEC_CMD_BYPASSSTAT, 5, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.power", "Bypass Power 2", 1.0, SEC_CMD_BYPASSSTAT, 8, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.power", "Bypass Power 3", 1.0, SEC_CMD_BYPASSSTAT, 11, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.voltage", "Bypass Voltage 1", 0.1, SEC_CMD_BYPASSSTAT, 3, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.voltage", "Bypass Voltage 2", 0.1, SEC_CMD_BYPASSSTAT, 6, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "bypass.voltage", "Bypass Voltage 3", 0.1, SEC_CMD_BYPASSSTAT, 9, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "battery.charge", "Estimated Charge", 1.0, SEC_CMD_BATTSTAT, 6, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "battery.runtime", "Estimated Minutes", 60.0, SEC_CMD_BATTSTAT, 5, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.transfer.high", "High Volt Xfer Pt", 1.0, SEC_CMD_NOMINAL, 10, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "ups.id", "Identification", 1.0, SEC_CMD_UPSID, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RW }, { "input.current", "Input Current 1", 0.1, SEC_CMD_INPUTSTAT, 5, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.current", "Input Current 2", 0.1, SEC_CMD_INPUTSTAT, 9, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.current", "Input Current 3", 0.1, SEC_CMD_INPUTSTAT, 13, 9999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.frequency", "Input Frequency 1", 0.1, SEC_CMD_INPUTSTAT, 3, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.frequency", "Input Frequency 2", 0.1, SEC_CMD_INPUTSTAT, 7, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.frequency", "Input Frequency 3", 0.1, SEC_CMD_INPUTSTAT, 11, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.line_bads", "Input Line Bads", 1.0, SEC_CMD_INPUTSTAT, 1, 999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.lines", "Input Num Lines", 1.0, SEC_CMD_INPUTSTAT, 2, 9, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.power", "Input Power 1", 1.0, SEC_CMD_INPUTSTAT, 6, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.power", "Input Power 2", 1.0, SEC_CMD_INPUTSTAT, 10, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.power", "Input Power 3", 1.0, SEC_CMD_INPUTSTAT, 14, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.voltage", "Input Voltage 1", 0.1, SEC_CMD_INPUTSTAT, 4, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.voltage", "Input Voltage 2", 0.1, SEC_CMD_INPUTSTAT, 8, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.voltage", "Input Voltage 3", 0.1, SEC_CMD_INPUTSTAT, 12, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "input.transfer.low", "Low Volt Xfer Pt", 1.0, SEC_CMD_NOMINAL, 9, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "ups.mfr", "Manufacturer", 1.0, SEC_CMD_MFR, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RD }, { "ups.model", "Model", 1.0, SEC_CMD_MOD, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RD }, { "battery.life.nominal", "Nominal Battery Life", 1.0, SEC_CMD_NOMINAL, 12, 16, SEC_FLAG_ORIGINAL|SEC_FLAG_STRING|SEC_FLAG_RW }, { "input.frequency.nominal", "Nominal Input Frequency", 0.1, SEC_CMD_NOMINAL, 2, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "input.voltage.nominal", "Nominal Input Voltage", 1.0, SEC_CMD_NOMINAL, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "battery.runtime.low", "Nominal Low Battery Time",60.0, SEC_CMD_NOMINAL, 7, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RW }, { "output.frequency.nominal","Nominal Output Frequency", 0.1, SEC_CMD_NOMINAL, 4, 999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "ups.power.nominal", "Nominal Output Power", 1.0, SEC_CMD_NOMINAL, 6, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RW }, { "output.voltage.nominal", "Nominal Output Voltage", 1.0, SEC_CMD_NOMINAL, 3, 16, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "ups.power.nominal", "Nominal VA Rating", 1.0, SEC_CMD_NOMINAL, 5, 16, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RW }, { "output.current", "Output Current 1", 0.1, SEC_CMD_OUTPUTSTAT, 5, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.current", "Output Current 2", 0.1, SEC_CMD_OUTPUTSTAT, 9, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.current", "Output Current 3", 0.1, SEC_CMD_OUTPUTSTAT, 13, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.frequency", "Output Frequency", 0.1, SEC_CMD_OUTPUTSTAT, 2, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "ups.load", "Output Load 1", 1.0, SEC_CMD_OUTPUTSTAT, 7, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "ups.load", "Output Load 2", 1.0, SEC_CMD_OUTPUTSTAT, 11, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "ups.load", "Output Load 3", 1.0, SEC_CMD_OUTPUTSTAT, 15, 999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.lines", "Output Num Lines", 1.0, SEC_CMD_OUTPUTSTAT, 3, 9, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.power", "Output Power 1", 1.0, SEC_CMD_OUTPUTSTAT, 6, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.power", "Output Power 2", 1.0, SEC_CMD_OUTPUTSTAT, 10, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.power", "Output Power 3", 1.0, SEC_CMD_OUTPUTSTAT, 14, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.source", "Output Source", 1.0, SEC_CMD_OUTPUTSTAT, 1, 6, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RD }, { "output.voltage", "Output Voltage 1", 0.1, SEC_CMD_OUTPUTSTAT, 4, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.voltage", "Output Voltage 2", 0.1, SEC_CMD_OUTPUTSTAT, 8, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "output.voltage", "Output Voltage 3", 0.1, SEC_CMD_OUTPUTSTAT, 12, 9999, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "ups.delay.reboot", "Reboot With Duration", 1.0, SEC_CMD_REBOOT, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_WR }, { "battery.ontime", "Seconds on Battery", 1.0, SEC_CMD_BATTSTAT, 4, 99999, SEC_FLAG_ORIGINAL|SEC_FLAG_NUMBER|SEC_FLAG_RD }, { "ups.shutdown.type", "Shutdown Type", 1.0, SEC_CMD_SHUTTYPE, 1, 2, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RW }, { "ups.delay.shutdown", "Shutdown After Delay", 1.0, SEC_CMD_SHUTDOWN, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_WR }, { "ups.firmware", "Software Version", 1.0, SEC_CMD_VERSION, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RD }, { "ups.delay.start", "Startup After Delay", 1.0, SEC_CMD_STARTDELAY, 1, 16, SEC_FLAG_NORMAL |SEC_FLAG_NUMBER|SEC_FLAG_WR }, { "ups.test.result", "Test Results Detail", 1.0, SEC_CMD_TESTRESULT, 2, 16, SEC_FLAG_NORMAL |SEC_FLAG_STRING|SEC_FLAG_RD }, { "ups.test.result.summary", "Test Results Summary", 1.0, SEC_CMD_TESTRESULT, 1, 6, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RD }, { "ups.test.type", "Test Type", 1.0, SEC_CMD_TEST, 1, 5, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_WR }, { "ups.baudrate", "Baud Rate", 1.0, SEC_CMD_BAUDRATE, 1, 19200, SEC_FLAG_ORIGINAL|SEC_FLAG_ENUM |SEC_FLAG_RW }, }; /* List of possible enumerations */ static const struct sec_enumdata_t sec_enumdata[] = { { SEC_ALARM_PWRWAIT, 0, "Not Awaiting Power" }, { SEC_ALARM_PWRWAIT, 1, "Awaiting Power" }, { SEC_ALARM_BADBYPASS, 0, "Bypass OK" }, { SEC_ALARM_BADBYPASS, 1, "Bypass Fault" }, { SEC_ALARM_CHARGFAIL, 0, "Charger OK" }, { SEC_ALARM_CHARGFAIL, 1, "Charger Fault" }, { SEC_ALARM_FANFAIL, 0, "Fan OK" }, { SEC_ALARM_FANFAIL, 1, "Fan Fault" }, { SEC_ALARM_FUSEFAIL, 0, "Fuse OK" }, { SEC_ALARM_FUSEFAIL, 1, "Fuse Fault" }, { SEC_ALARM_GENERAL, 0, "General system OK" }, { SEC_ALARM_GENERAL, 1, "General system Fault" }, { SEC_ALARM_BADINPUT, 0, "Input OK" }, { SEC_ALARM_BADINPUT, 1, "Input Fault" }, { SEC_ALARM_BADOUTPUT, 0, "Output OK" }, { SEC_ALARM_BADOUTPUT, 1, "Output Fault" }, { SEC_ALARM_OUTPUTOFF, 0, "Output On" }, { SEC_ALARM_OUTPUTOFF, 1, "Output off" }, { SEC_ALARM_OVERLOAD, 0, "UPS not overloaded" }, { SEC_ALARM_OVERLOAD, 1, "UPS Overloaded" }, { SEC_ALARM_IMMSHUT, 0, "No Shutdown Imminent" }, { SEC_ALARM_IMMSHUT, 1, "Shutdown Imminent" }, { SEC_ALARM_PENDSHUT, 0, "No Shutdown Pending" }, { SEC_ALARM_PENDSHUT, 1, "Shutdown Pending" }, { SEC_ALARM_SYSOFF, 0, "System On" }, { SEC_ALARM_SYSOFF, 1, "System off" }, { SEC_ALARM_TEMP, 0, "Temperature OK" }, { SEC_ALARM_TEMP, 1, "Over Temperature" }, { SEC_ALARM_UPSSHUT, 0, "UPS not Shutdown" }, { SEC_ALARM_UPSSHUT, 1, "UPS Shutdown" }, { SEC_AUDIBLE_ALARM, 0, "Disabled" }, { SEC_AUDIBLE_ALARM, 1, "Enabled" }, { SEC_AUDIBLE_ALARM, 2, "Muted" }, { SEC_AUDIBLE_ALARM, 3, "Low Battery" }, { SEC_AUTORESTART, 1, "Automatic Restart" }, { SEC_AUTORESTART, 2, "Manual Restart" }, { SEC_BATTERY_CHARGE, 0, "Floating" }, { SEC_BATTERY_CHARGE, 1, "Charging" }, { SEC_BATTERY_CHARGE, 2, "Resting" }, { SEC_BATTERY_CHARGE, 3, "Discharging" }, { SEC_BATTERY_COND, 0, "Good" }, { SEC_BATTERY_COND, 1, "Weak" }, { SEC_BATTERY_COND, 2, "Replace" }, { SEC_BATTERY_STATUS, 0, "Battery OK" }, { SEC_BATTERY_STATUS, 1, "Battery Low" }, { SEC_BATTERY_STATUS, 2, "Battery Depleted" }, { SEC_OUTPUT_SOURCE, 0, "Normal" }, { SEC_OUTPUT_SOURCE, 1, "On Battery" }, { SEC_OUTPUT_SOURCE, 2, "On Bypass" }, { SEC_OUTPUT_SOURCE, 3, "Reducing" }, { SEC_OUTPUT_SOURCE, 4, "Boosting" }, { SEC_OUTPUT_SOURCE, 5, "Other" }, { SEC_SHUTDOWN_TYPE, 1, "UPS output only" }, { SEC_SHUTDOWN_TYPE, 2, "Entire UPS" }, { SEC_TEST_RESULTS_SUMMARY, 0, "No test performed" }, { SEC_TEST_RESULTS_SUMMARY, 1, "Test Passed" }, { SEC_TEST_RESULTS_SUMMARY, 2, "Test in progress" }, { SEC_TEST_RESULTS_SUMMARY, 3, "General Test failed" }, { SEC_TEST_RESULTS_SUMMARY, 4, "Battery Test failed" }, { SEC_TEST_RESULTS_SUMMARY, 5, "Deep Battery Test failed" }, { SEC_TEST_TYPE, 0, "No test" }, { SEC_TEST_TYPE, 1, "General Systems Test" }, { SEC_TEST_TYPE, 2, "Quick Battery Test" }, { SEC_TEST_TYPE, 3, "Deep Battery Calibration" }, { SEC_BAUD_RATE, 0, "1200 Baud" }, { SEC_BAUD_RATE, 1, "2400 Baud" }, { SEC_BAUD_RATE, 2, "4800 Baud" }, { SEC_BAUD_RATE, 3, "9600 Baud" }, { SEC_BAUD_RATE, 4, "19200 Baud" }, { SEC_UNUSED, 0 , 0 } }; #define sec_support(i) (_sec_meta[(i)%SEC_NUMVARS].supported) #define sec_edi(i) (_sec_meta[(i)%SEC_NUMVARS].edi) #define sec_value(i) (_sec_meta[(i)%SEC_NUMVARS].value) struct _sec_meta { int supported; int edi; /* enumeratated data index */ char value[SEC_MAX_VARSIZE]; }; static struct _sec_meta _sec_meta[SEC_NUMVARS] = { { 0, 0 } }; /* * sec_recv(fd, buf, len) * * Read a SEC format message (^<cmdchar><len><data>) from the UPS * and store in <data> in buf. * * Return -2 if failed to read valid response * Return -1 if command failed (^0) * Return 0 if command succeeded (^1) * Return len if data returned * */ static int sec_recv(int fd, char *buf, int len) { char *p; int i, l, r; errno = EINVAL; upsdebugx(SEC_LOG_LOWLEVEL, "sec_recv..."); if (len < 4) { /* min length */ upsdebugx(SEC_LOG_LOWLEVEL, " invalid buffer length %d", len); return -2; } /* look for the startchar */ *buf = '\0'; for (i=0; i<30 && (*buf != SEC_MSG_STARTCHAR); i++) { /* up to 30 characters, OR 3 seconds */ if (ser_get_char(fd, buf, 0, 100000)<0) /* failed to read */ *buf = '\0'; } if (*buf != SEC_MSG_STARTCHAR) { upsdebugx(SEC_LOG_LOWLEVEL, " FAILED to find start char %c",SEC_MSG_STARTCHAR); return -2; } /* we found a msg, which one? */ upsdebugx(SEC_LOG_LOWLEVEL, " found start char..."); if (ser_get_char(fd, buf, 0, 100000)<0) { /* failed to read next char */ upsdebugx(SEC_LOG_LOWLEVEL, " FAILED to receive type char"); return -2; } r = -2; switch (*buf) { case SEC_DATAMSG: upsdebugx(SEC_LOG_LOWLEVEL, " got a %c!", SEC_DATAMSG); /* data being returned - get the length */ if ((r=ser_get_buf_len(fd, buf, 3, 1, 0)) < 3) { upsdebugx(SEC_LOG_LOWLEVEL, " OOPS only %d of 3 chars read (%c)",r,*buf); return -2; } *(buf+3) = '\0'; p = buf; l = strtol(buf, &p, 10); if ((p == buf) || (*p != '\0')) { /* invalid number */ upsdebugx(SEC_LOG_LOWLEVEL, " OOPS invalid length: (%s)", buf); return -2; } if (l >= len) { upsdebugx(SEC_LOG_LOWLEVEL, " OOPS not enough buffer space"); return -2; } upsdebugx(SEC_LOG_LOWLEVEL, " UPS returning %d bytes of data...", l); if ((r=ser_get_buf_len(fd, buf, l, 0, 500000)) != l) { upsdebugx(SEC_LOG_LOWLEVEL, " OOPS less than %d chars read (%d)", l, r); r = -2; } else upsdebugx(SEC_LOG_LOWLEVEL, " OK, read %d bytes of data",l); break; case '0': upsdebugx(SEC_LOG_LOWLEVEL, "UPS returned: command failed"); r = -1; break; case '1': upsdebugx(SEC_LOG_LOWLEVEL, "UPS returned: command succeeded!"); r = 0; break; default: r = -2; break; } return r; } /* * sec_send(fd, mode, command, buffer, length) * * Sends mode command to the UPS with the given data. * * Returns -1 if command fails * 0 if command succeeds, but returns no data * length of returned data if data is returned */ static int sec_send(int fd, const char mode, const char *command, char *buf, int len) { char msg[SEC_SIZE]; int ret; errno = EINVAL; /* create the message string */ if (len > 0) { snprintf(msg, SEC_SIZE, "%c%c%03d%s%s", SEC_MSG_STARTCHAR, mode, len+3, command, buf); } else { snprintf(msg, SEC_SIZE, "%c%c003%s", SEC_MSG_STARTCHAR, mode, command); } upsdebugx(SEC_LOG_LOWLEVEL, "PC-->UPS: \"%s\"", msg); ret = ser_send_buf(fd, msg, strlen(msg)); upsdebugx(SEC_LOG_LOWLEVEL, " send returned: %d",ret); return ret; } /* set up the serial connection */ static int sec_setup_serial(const char *port) { char tmp[SEC_SIZE]; int i, ret, fd; /* The SEC protocol alows for different baud rates. My daddy told me "Never assume ...", so we try the different rates to see which works. */ fd = ser_open(port); for (i=0; i<SEC_NUMBAUDS; i++) { upsdebugx(SEC_LOG_LOWLEVEL, "Trying to connect at %d baud",baud_rates[i].name); ser_set_speed(fd, port, baud_rates[i].rate); ser_flush_in(fd, "", 0); /* drain input */ upsdebugx(SEC_LOG_LOWLEVEL, " sending probing command..."); sec_send(fd, SEC_POLLCMD, SEC_CMD_MFR, tmp, 0); upsdebugx(SEC_LOG_LOWLEVEL, " reading reply..."); ret = sec_recv(fd, tmp, SEC_SIZE); if (ret >= -1) /* valid reply */ break; upsdebugx(SEC_LOG_LOWLEVEL, " no connection."); } if (i == 5) { printf("Can't talk to UPS on port %s!\n",port); printf("Check the cabling and portname and try again\n"); exit (1); } return fd; } /* * addquery(cmd, field, varnum) * * Records that sec variable <varnum> is supported by this UPS and should be * queried as part of the regular update. We need to record which command * (cmd) to send, and which <field> corresponds to the variable. */ static void sec_addquery(char *cmd, int field, int varnum) { int q; for (q=0; q<SEC_QUERYLIST_LEN; q++) { if (sec_querylist[q].command == NULL) { /* command has not been recorded yet */ sec_querylist[q].command = cmd; upsdebugx(SEC_LOG_LOWLEVEL, " Query %d is %s",q,cmd); } if (sec_querylist[q].command == cmd) { sec_querylist[q].varnum[field-1] = varnum; upsdebugx(SEC_LOG_LOWLEVEL, " Querying varnum %d",varnum); break; } } } /* * sec_setinfo(varnum, value) * * Update variable number <varnum> to value <value> in the info array. */ static void sec_setinfo(int varnum, char *value) { char *s, buf[SEC_SIZE]; int i, e; double d; if (dstate_getinfo(sec_varlist[varnum].infotag) == 0) /* nothing to do */ return; s = value; if (sec_varlist[varnum].flags & SEC_FLAG_ENUM) { i = strtol(value, &s, 10); if ((s == value) || (*s != '\0')) { /* some problem */ upsdebugx(SEC_LOG_ERROR, "Error parsing enum for %s (%s)", sec_varlist[varnum].name, value); return; } for (e=sec_edi(varnum); sec_enumdata[e].type == varnum; e++) { if (sec_enumdata[e].index == i) break; } if (sec_enumdata[e].type == varnum) { /* out of bounds otherwise */ snprintf(buf, SEC_SIZE, "\"%s\" (ENUM)", sec_enumdata[e].value); dstate_setinfo(sec_varlist[varnum].infotag, "%s", sec_enumdata[e].value); } else snprintf(buf, SEC_SIZE, "\"%d\" (Invalid ENUM)", i); } else if (sec_varlist[varnum].flags & SEC_FLAG_STRING) { snprintf(buf, SEC_SIZE, "\"%s\" (STRING)", s); dstate_setinfo(sec_varlist[varnum].infotag, "%s", s); } else if (sec_varlist[varnum].flags & SEC_FLAG_NUMBER) { if (sec_varlist[varnum].unit == 1.0) { /* integer */ i = strtol(value, &s, 10); if ((s == value) || (*s != '\0')) { /* some problem */ upsdebugx(SEC_LOG_ERROR, "Error parsing number for %s (%s)", sec_varlist[varnum].name, value); return; } snprintf(buf, SEC_SIZE, "\"%d\" (Integer)", i); dstate_setinfo(sec_varlist[varnum].infotag, "%d", i); } else { d = strtod(value, &s); if ((s == value) || (*s != '\0')) { /* some problem */ upsdebugx(SEC_LOG_ERROR, "Error parsing number for %s (%s)", sec_varlist[varnum].name, value); return; } d = d * sec_varlist[varnum].unit; snprintf(buf, SEC_SIZE, "\"%.1f\" (Float)", d); dstate_setinfo(sec_varlist[varnum].infotag, "%.1f", d); } } upsdebugx(SEC_LOG_INFO, "Updating variable %d (%s), new value is %s", varnum, sec_varlist[varnum].name, buf); } /* * sec_update_pseudovars * * There are a number of non-SEC variables that are functions of the real * variables. We update them here. * * OFF - UPS is off - SEC_ALARM_OUTPUTOFF is "Output off" * OL - UPS is online - SEC_OUTPUT_SOURCE is "Normal" * OB - UPS is on battery - SEC_OUTPUT_SOURCE is "On Battery" * BY - UPS is on bypass - SEC_OUTPUT_SOURCE is "On Bypass" * * OVER - UPS is overloaded - SEC_ALARM_OVERLOAD is "UPS Overloaded" * LB - UPS battery is low - SEC_BATTERY_STATUS is "Battery Low" * RB - UPS replace battery - SEC_BATTERY_COND is "Replace" * * TRIM - UPS is trimming - SEC_OUTPUT_SOURCE is "Reducing" * BOOST - UPS is boosting - SEC_OUTPUT_SOURCE is "Boosting" * * FSD - UPS is shutdown - SEC_ALARM_SYSTEM_OFF is "System off" */ static void sec_update_pseudovars( void ) { const char *v, *s; int n; float fsum; s = 0; if (sec_support(SEC_ALARM_OUTPUTOFF)) { v = sec_value(SEC_ALARM_OUTPUTOFF); if (strcmp("Output off", v) == 0) s = "OFF"; } if (sec_support(SEC_OUTPUT_SOURCE)) { v = sec_value(SEC_OUTPUT_SOURCE); /* enum value */ if (strcmp("0", v) == 0) /* Normal */ s = "OL"; if (strcmp("1", v) == 0) /* On Battery */ s = "OB"; if (strcmp("2", v) == 0) /* On Bypass */ s = "BYPASS"; if (strcmp("3", v) == 0) /* Reducing */ s = "TRIM"; if (strcmp("4", v) == 0) /* Boosting */ s = "BOOST"; } if (sec_support(SEC_ALARM_OVERLOAD)) { v = sec_value(SEC_ALARM_OVERLOAD); if (strcmp("1", v) == 0) /* Alarm Active */ s = "OVER"; } if (sec_support(SEC_BATTERY_STATUS)) { v = sec_value(SEC_BATTERY_STATUS); if (strcmp("1", v) == 0) /* Low */ s = "LB"; } if (sec_support(SEC_BATTERY_COND)) { v = sec_value(SEC_BATTERY_COND); if (strcmp("2", v) == 0) /* replace */ s = "RB"; } if (sec_support(SEC_ALARM_SYSOFF)) { v = sec_value(SEC_ALARM_SYSOFF); if (strcmp("1", v) == 0) /* Alarm Active */ s = "OFF"; } if (s) { upsdebugx(SEC_LOG_INFO, "Synthesizing status (%s)", s); status_init(); status_set(s); status_commit(); } upsdebugx(SEC_LOG_INFO, "Synthesizing averages..."); /* Average stats */ fsum = 0.0; n = 0; if (sec_support(SEC_INPUT_FREQUENCY_1) && ((v=dstate_getinfo(sec_varlist[SEC_INPUT_FREQUENCY_1].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_INPUT_FREQUENCY_2) && ((v=dstate_getinfo(sec_varlist[SEC_INPUT_FREQUENCY_2].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_INPUT_FREQUENCY_3) && ((v=dstate_getinfo(sec_varlist[SEC_INPUT_FREQUENCY_3].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (n) dstate_setinfo(SEC_STR_INPUT_FREQUENCY, "%.1f", fsum / n); fsum = 0.0; n = 0; if (sec_support(SEC_INPUT_VOLTAGE_1) && ((v=dstate_getinfo(sec_varlist[SEC_INPUT_VOLTAGE_1].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_INPUT_VOLTAGE_2) && ((v=dstate_getinfo(sec_varlist[SEC_INPUT_VOLTAGE_2].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_INPUT_VOLTAGE_3) && ((v=dstate_getinfo(sec_varlist[SEC_INPUT_VOLTAGE_3].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (n) dstate_setinfo(SEC_STR_INPUT_VOLTAGE, "%.1f", fsum / n); fsum = 0.0; n = 0; if (sec_support(SEC_OUTPUT_CURRENT_1) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_CURRENT_1].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_CURRENT_2) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_CURRENT_2].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_CURRENT_3) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_CURRENT_3].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (n) dstate_setinfo(SEC_STR_OUTPUT_CURRENT, "%.1f", fsum / n); fsum = 0.0; n = 0; if (sec_support(SEC_OUTPUT_LOAD_1) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_LOAD_1].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_LOAD_2) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_LOAD_2].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_LOAD_3) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_LOAD_3].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (n) dstate_setinfo(SEC_STR_OUTPUT_LOAD, "%.1f", fsum / n); fsum = 0.0; n = 0; if (sec_support(SEC_OUTPUT_POWER_1) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_POWER_1].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_POWER_2) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_POWER_2].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_POWER_3) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_POWER_3].infotag)) != 0)) { fsum += strtod(v,0); n++; } if (n) dstate_setinfo(SEC_STR_OUTPUT_POWER, "%.1f", fsum / n); fsum = 0.0; n = 0; if (sec_support(SEC_OUTPUT_VOLTAGE_1) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_VOLTAGE_1].infotag))!=0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_VOLTAGE_2) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_VOLTAGE_2].infotag))!=0)) { fsum += strtod(v,0); n++; } if (sec_support(SEC_OUTPUT_VOLTAGE_3) && ((v=dstate_getinfo(sec_varlist[SEC_OUTPUT_VOLTAGE_3].infotag))!=0)) { fsum += strtod(v,0); n++; } if (n) dstate_setinfo(SEC_STR_OUTPUT_VOLTAGE, "%.1f", fsum / n); } static int sec_setvar(const char *name, const char *val) { int i, e; char *p, buf[SEC_SIZE]; for (i=1; i<SEC_NUMVARS; i++) { if (strcasecmp(name, sec_varlist[i].infotag) == 0) { /* found entry */ p = buf; for (e=1; e<sec_varlist[i].field; e++) *(p++) = ','; *p = '\0'; if (sec_varlist[i].flags&SEC_FLAG_ENUM) { /* enumerated */ for (e = sec_edi(i); sec_enumdata[e].type == i; e++) { if (strcasecmp(val, sec_enumdata[e].value) == 0) { /* a match */ snprintf(p, SEC_SIZE-1-(p-buf), "%d", sec_enumdata[e].index); break; } } if (sec_enumdata[e].type != i) return STAT_SET_UNKNOWN; } else /* SEC_FLAG_STRING|SEC_FLAG_NUMBER */ strncpy(p, val, SEC_SIZE-1-(p-buf)); *(buf+SEC_SIZE-1) = '\0'; /* set the command .. in buf */ if (sec_support(i)) { if (sec_varlist[i].flags & SEC_FLAG_WR) { /* can be stored, will return on next poll */ upsdebugx(SEC_LOG_INFO, "setting: %s:%s -> %s", name, val, buf); sec_send(upsfd, SEC_SETCMD, sec_varlist[i].cmd, buf, strlen(buf)); if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_SET_UNKNOWN; } else { strncpy(sec_value(i), val, SEC_MAX_VARSIZE-1); *(sec_value(i)+SEC_MAX_VARSIZE-1) = '\0'; } } return STAT_SET_HANDLED; } } return STAT_SET_UNKNOWN; } static int sec_getnum(const char *value) { char *p; int i; i = 0; if (value) { p = (char*)value; i = strtol(value, &p, 10); /* convert */ if ((p==value) || (*p)) /* invalid number */ i = 0; /* default */ } return i; } static int sec_instcmd(const char *cmd, const char *extra) { char *p, buf[SEC_SIZE]; int i; if (strcasecmp(cmd, "load.off") == 0) { /* send SDA:0, PSD:1 */ sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_TYPE].cmd, "0", 1); /* shutdown type: UPS output */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_AFTER_DELAY].cmd, "0", 1); /* shutdown now */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; return STAT_INSTCMD_HANDLED; } if (strcasecmp(cmd, "load.on") == 0) { /* send SDA:0, STD:1 */ sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_TYPE].cmd, "0", 1); /* shutdown type: UPS output */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_STARTUP_AFTER_DELAY].cmd, "0", 1); /* startup now */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; return STAT_INSTCMD_HANDLED; } if (strcasecmp(cmd, "shutdown.return") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_TYPE].cmd, "1", 1); /* shutdown type: UPS system */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_AUTORESTART].cmd, "1", 1); /* automatic restart */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; snprintf(buf, SEC_SIZE, "%d", sec_getnum(sec_value(SEC_SHUTDOWN_AFTER_DELAY))); *(buf+SEC_SIZE-1) = '\0'; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_AFTER_DELAY].cmd, buf, strlen(buf)); /* shutdown ... */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; return STAT_INSTCMD_HANDLED; } if (strcasecmp(cmd, "shutdown.reboot") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_TYPE].cmd, "1", 1); /* shutdown type: UPS system */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; snprintf(buf, SEC_SIZE, "%d", sec_getnum(sec_value(SEC_REBOOT_WITH_DURATION))); sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_REBOOT_WITH_DURATION].cmd, buf, strlen(buf)); /* reboot now */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "shutdown.stayoff") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_TYPE].cmd, "1", 1); /* shutdown type: UPS system */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_AUTORESTART].cmd, "0", 1); /* manual restart */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; snprintf(buf, SEC_SIZE, "%d", sec_getnum(sec_value(SEC_SHUTDOWN_AFTER_DELAY))); sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_AFTER_DELAY].cmd, buf, strlen(buf)); /* shutdown now */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "shutdown.stop") == 0) { /* does this matter which reboot/shutdown */ sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_REBOOT_WITH_DURATION].cmd, "-1", 2); /* abort reboot */ i = sec_recv(upsfd, buf, SEC_SIZE); /* not succeeded? */ sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_SHUTDOWN_AFTER_DELAY].cmd, "-1", 2); /* abort shutdown */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "test.panel.start") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_TEST_TYPE].cmd, "0", 1); /* general test */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "test.panel.stop") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_TEST_TYPE].cmd, "-1", 2); /* abort */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "test.battery.start") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_TEST_TYPE].cmd, "1", 1); /* battery test */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "test.battery.start.deep") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_TEST_TYPE].cmd, "2", 1); /* deep battery test */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "test.battery.stop") == 0) { sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_TEST_TYPE].cmd, "-1", 2); /* abort */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "beeper.on") == 0) { p = buf; for (i=0; i<sec_varlist[SEC_AUDIBLE_ALARM].field; i++) *(p++) = ','; *(p++) = '1'; *(p++) = '\0'; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_AUDIBLE_ALARM].cmd , buf, p-buf); /* AUDIBLE_ALARM_STATE: Enabled */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; return STAT_INSTCMD_UNKNOWN; } if (strcasecmp(cmd, "beeper.off") == 0) { p = buf; for (i=0; i<sec_varlist[SEC_AUDIBLE_ALARM].field; i++) *(p++) = ','; *(p++) = '0'; /* Disabled */ *(p++) = '\0'; sec_send(upsfd, SEC_SETCMD, sec_varlist[SEC_AUDIBLE_ALARM].cmd , buf, p-buf); /* AUDIBLE_ALARM_STATE: Disabled */ if (sec_recv(upsfd, buf, SEC_SIZE) != 0) /* not succeeded */ return STAT_INSTCMD_UNKNOWN; return STAT_INSTCMD_UNKNOWN; } return STAT_INSTCMD_UNKNOWN; } /*------------------------------------------------------------------------- * * Here are the mandatory functions called from the shared main.c */ void upsdrv_initinfo(void) { int l, e, f, v, err; char *p, *q, buf[SEC_SIZE]; /* find out which variables/commands this UPS supports */ sec_send(upsfd, SEC_POLLCMD, SEC_CMD_AVAILP1, 0, 0); l = sec_recv(upsfd, buf, SEC_SIZE); p = buf + l; if (l) *p++ = ','; sec_send(upsfd, SEC_POLLCMD, SEC_CMD_AVAILP2, 0, 0); l = sec_recv(upsfd, p, SEC_SIZE-l); *(p+l) = '\0'; if (strlen(buf) == 0) fatalx("No available variables found!"); upsdebugx(SEC_LOG_INFO, "List of available vars: %s", buf); /* scan list adding variables to info array */ p = buf; while (*p) { q = p; v = strtol(p, &q, 10); /* variable number of supported variable */ /* check for errors */ err = (p == q); err |= (*q && (*q != ',')); err |= !((v > 0) && (v < SEC_NUMVARS)); if (err) { /* an error occured */ upsdebugx(SEC_LOG_ERROR, "Invalid number received: %p (%02x)", p, *p); for (p=q; *p && *p != ','; p++); /* next list element */ } else { /* don't bother adding a write-only variable */ if (sec_varlist[v].infotag) { sec_support(v) = 1; if ((sec_varlist[v].flags&SEC_FLAG_RW) != SEC_FLAG_WR) sec_addquery(sec_varlist[v].cmd, sec_varlist[v].field, v); if ((sec_varlist[v].flags & SEC_FLAG_ORIGINAL) == 0) { upsdebugx(SEC_LOG_INFO, "Adding variable %d (%s)",v,sec_varlist[v].name); f = 0; if ((sec_varlist[v].flags&SEC_FLAG_WR)) f |= ST_FLAG_RW; if (sec_varlist[v].flags & (SEC_FLAG_STRING|SEC_FLAG_NUMBER)) f |= ST_FLAG_STRING; if (sec_varlist[v].flags & SEC_FLAG_NUMBER) dstate_setinfo(sec_varlist[v].infotag, "0"); else dstate_setinfo(sec_varlist[v].infotag, "(nil)"); dstate_setflags(sec_varlist[v].infotag, f); dstate_setaux(sec_varlist[v].infotag, sec_varlist[v].size); /* find entries in enumdata for current variable */ if (sec_varlist[v].flags & SEC_FLAG_ENUM) { e = 0; while ((sec_enumdata[e].type != SEC_UNUSED) && (sec_enumdata[e].type != v)) e++; if (sec_enumdata[e].type == SEC_UNUSED) upsdebugx(SEC_LOG_ERROR, "Could not find ENUMs for (%d) %s",v,sec_varlist[v].name); else sec_edi(v) = e; /* add entries for enumerated variable */ while (sec_enumdata[e].type == v) { upsdebugx(SEC_LOG_INFO, " adding enumval \"%s\" (%d)",sec_enumdata[e].value,e); dstate_addenum(sec_varlist[sec_enumdata[e].type].infotag, sec_enumdata[e].value); e++; } } } } } for (p = q; *p == ','; p++); /* skip over ',' */ } /* variables */ dstate_setinfo("driver.version.internal", "%s", SEC_DRIVER_VERSION); /* -- commands -- */ if (sec_support(SEC_SHUTDOWN_TYPE)) { if (sec_support(SEC_REBOOT_WITH_DURATION)) { dstate_setinfo("ups.delay.reboot", "60"); dstate_addcmd("shutdown.reboot"); /* RWDN ... see also ups.delay.reboot */ dstate_addcmd("shutdown.stop"); /* RWD-1 */ if (sec_support(SEC_AUTORESTART)) dstate_addcmd("shutdown.return"); /* RWDN ... see also ups.delay.reboot */ } if (sec_support(SEC_SHUTDOWN_AFTER_DELAY)) { dstate_setinfo("ups.delay.shutdown", "20"); if (sec_support(SEC_AUTORESTART)) dstate_addcmd("shutdown.stayoff"); /* SDA1 PSD0 */ dstate_addcmd("load.off"); /* SDA0 PSD0 ... see also ups.delay.shutdown */ dstate_addcmd("shutdown.stop"); /* PSD-1 */ } if (sec_support(SEC_STARTUP_AFTER_DELAY)) { dstate_setinfo("ups.delay.start", "0"); dstate_addcmd("load.on"); /* SDA0 STD0 ... see also ups.delay.start */ } } if (sec_support(SEC_TEST_TYPE)) { dstate_addcmd("test.panel.start"); /* TST0 */ dstate_addcmd("test.panel.stop"); /* TST-1 */ dstate_addcmd("test.battery.start"); /* TST1 */ /* dstate_addcmd("test.battery.start.deep"); */ /* TST2 */ dstate_addcmd("test.battery.stop"); /* TST-1 */ } if (sec_support(SEC_AUDIBLE_ALARM)) { dstate_addcmd("beeper.on"); dstate_addcmd("beeper.off"); } upsh.instcmd = sec_instcmd; upsh.setvar = sec_setvar; } /* * upsdrv_updateinfo() * * For a SEC protocol UPS, we only need to query those variables that are * supported. */ void upsdrv_updateinfo(void) { char buf[128],*r,*n; int ret, q, f; upsdebugx(SEC_LOG_INFO, "--------Updating--------------------------"); /* loop over each query (a single SEC poll command)*/ for (q=0; q<SEC_QUERYLIST_LEN; q++) { if (sec_querylist[q].command == NULL) break; upsdebugx(SEC_LOG_INFO, "Polling %s...", sec_querylist[q].command); sec_send(upsfd, SEC_POLLCMD, sec_querylist[q].command, 0, 0); ret = sec_recv(upsfd, buf, 128); if (ret <=0) { upslog(LOG_WARNING, "Warning sending poll cmd \"%s\" to UPS (%d)", sec_querylist[q].command, ret); continue; } r = buf; *(r+ret) = '\0'; for (f=0; f<SEC_MAXFIELDS; f++) { n = strchr(r, ','); if (n != NULL) *n = '\0'; /* is field/variable supported? */ if (sqv(q,f) > 0) { /* only update the value if it's changed */ if (strcmp(sec_value(sqv(q,f)), r) != 0) { snprintf(sec_value(sqv(q,f)), SEC_MAX_VARSIZE, "%s", r); sec_setinfo(sqv(q,f), r); } } if (n == NULL) break; r = n+1; } } /* update the non-sec variables */ sec_update_pseudovars(); dstate_dataok(); } void upsdrv_shutdown(void) { char buf[16]; /* supposedly the serial port stuff is already set * since a serial port was found a moment ago, I won't bother seeing if * the UPS is found here, except for final shutdown command * so cancel any running shutdowns, set auto restart when AC returns, * set to shutdown entire UPS and actually shutdown the UPS. * This UPS momentarily shuts off power to computer even when commercial * power is available. */ sec_send(upsfd, SEC_SETCMD, SEC_CMD_SHUTDOWN, "-1", 2); /* cancel shutdown command */ sec_recv(upsfd, buf, 16); sec_send(upsfd, SEC_SETCMD, SEC_CMD_AUTORESTART, "1", 1); sec_recv(upsfd, buf, 16); sec_send(upsfd, SEC_SETCMD, SEC_CMD_SHUTTYPE, "2", 1); sec_recv(upsfd, buf, 16); sec_send(upsfd, SEC_SETCMD, SEC_CMD_SHUTDOWN, "5", 1); sec_recv(upsfd, buf, 16); } void upsdrv_help(void) { } void upsdrv_makevartable(void) { /* allow '-x xyzzy' */ /* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */ /* allow '-x foo=<some value>' */ /* addvar(VAR_VALUE, "foo", "Override foo setting"); */ } void upsdrv_banner(void) { printf("Network UPS Tools (%s) - SEC protocol UPS driver %s\n", UPS_VERSION, SEC_DRIVER_VERSION); experimental_driver = 1; } void upsdrv_initups(void) { /* probe ups type */ upsfd = sec_setup_serial(device_path); /* to get variables and flags from the command line, use this: * * first populate with upsdrv_buildvartable above, then... * * set flag foo : /bin/driver -x foo * set variable 'cable' to '1234' : /bin/driver -x cable=1234 * * to test flag foo in your code: * * if (testvar("foo")) * do_something(); * * to show the value of cable: * * if ((cable == getval("cable"))) * printf("cable is set to %s\n", cable); * else * printf("cable is not set!\n"); * * don't use NULL pointers - test the return result first! */ /* the upsh handlers can't be done here, as they get initialized * shortly after upsdrv_initups returns to main. */ /* don't try to detect the UPS here */ } void upsdrv_cleanup(void) { ser_close(upsfd, device_path); }