[Nut-upsdev] new(er) SEC driver.

Julius P. Malkiewicz julius at sonartech.com.au
Mon Dec 12 23:13:51 UTC 2005


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 at 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 at 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 at 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 at 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 at inficad.com
 * added shutdown feature
 *
 * Revison 1.3   15 July 2002 Eric Lawson.  elawson at 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);
}


More information about the Nut-upsdev mailing list