drivers/gendb | 3 drivers/tripplite.c | 762 +++++++++++++++++++++++++++++----------------------- man/tripplite.8 | 31 +- 3 files changed, 462 insertions(+), 334 deletions(-) This patch modernizes the tripplite driver to work with newer NUT versions. tripplite does not function at all (as of 2.0.0) without this patch. It also improves and expands functionality, cleans code, and fixes bugs. Detailed changes: Modernized API so that the driver works properly with NUT >= 2.0. The old driver would not work at all because of a change in the behavior of ser_get_line(): the UPS innately echos back our commands, requiring a special function that discards the spurious echo. Communication with the UPS does not sleep() and should be more efficient. Additionally, the code for communicating with the UPS is much cleaner and is centralized into a single function. The UPS relay is no longer disabled on a shutdown event; disabling the relay prevents the UPS from powering back up when mains power returns (this is tested). Added boundary checks to some functions that may not have been safe. Unnecessary mallocs removed in favor of stack allocation, reducing memory footprint and removing some unnecessary global variables. Driver now exclusively uses snprintf() rather than sprintf(). Driver no longer redundantly attempts to configure the serial port using ioctls. Driver now calls ser_comm_fail() and ser_comm_good() at appropriate times. UPS probes and identification are now performed in upsdrv_initinfo() rather than upsdrv_initups(). Driver does not call status_set() with non-standard values on failure. Battery charge percentage is now a continuous rather than discrete function; additionally, it will be easier to handle multiple battery capacities with the new code. Shutdown delays are all now user configurable. Added command handlers for load.off, load.on, shutdown.reboot, shutdown.reboot.graceful, shutdown.return, and shutdown.stayoff. Added variables for ups.delay.start, ups.delay.reboot, and ups.delay.shutdown. Greatly fixed whitespace and indentation. Tabs are now used rather than spaces and the coding style is more normal and consistent. Added a nice comment that documents the various UPS commands, including some that are not used in the driver (but are used in the Tripp-Lite PowerChute driver). Manpage is updated to indicate that driver supports only serial SmartPro UPSes -- there are now USB UPSes within the SmartPro line. Manpage is also updated to document the new switches. Author information updated to add my name to the driver and man page. --- drivers/gendb.orig Sat Aug 21 16:39:30 2004 +++ drivers/gendb Sat Aug 21 16:40:04 2004 @@ -28,6 +28,9 @@ isbmex) EXTRA="-lm" ;; + tripplite) + EXTRA="-lm" + ;; *) EXTRA="" esac --- drivers/tripplite.c.orig Fri Jul 30 06:50:26 2004 +++ drivers/tripplite.c Sun Aug 22 13:27:51 2004 @@ -1,10 +1,12 @@ /* tripplite.c - model specific routines for Tripp Lite SmartUPS models - (tested with "SMART 700" [on back -- "SmartPro UPS" on front]) + (tested with: + "SMART 700" [on back -- "SmartPro UPS" on front], "SMART700SER") tripplite.c was derived from Russell Kroll's bestups.c by Rik Faith. Copyright (C) 1999 Russell Kroll Copyright (C) 2001 Rickard E. (Rik) Faith + Copyright (C) 2004 Nicholas J. Kain 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 @@ -25,26 +27,7 @@ A few magic numbers were derived from the GPL'd file opensrc_server/upscmd.cpp, available from Tripp Lite at - http://www.tripplite.com/linux/. The copyright notice that applies - to this file is as follows: - - The PowerAlert server, the PowerAlert client, and all supporting - documents and files, are Copyright (C) 1998, 1999 by Tripp Lite, - unless expressly stated therein. - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. + http://www.tripplite.com/linux/. */ /* REFERENCE 2 @@ -55,7 +38,7 @@ These pages confirm the information in the Tripp Lite source code referenced above and add more details. - + The first page tells how to derive the VA rating from w_value and l_value. It's a confusing explanation because shifts are used to mask out bits. Here is an example starting with the formula on the @@ -74,38 +57,217 @@ = 700 */ -#define DRV_VERSION "0.03" +/* Known UPS Commands: + * + * :N%02X -- delay the UPS for provided time (hex seconds) + * :H%06X -- reboot the UPS. UPS will restart after provided time (hex s) + * :A -- begins a self-test + * :C -- fetches result of a self-test + * :K1 -- turns on power receptacles + * :K0 -- turns off power receptacles + * :G -- unconfirmed: shuts down UPS until power returns + * :Q1 -- enable "Remote Reboot" + * :Q0 -- disable "Remote Reboot" + * :W -- returns 'W' data + * :L -- returns 'L' data + * :V -- returns 'V' data (firmware revision) + * :X -- returns 'X' data (firmware revision) + * :D -- returns general status data + * :B -- returns battery voltage (hexadecimal decivolts) + * :I -- returns minimum input voltage (hexadecimal hertz) + * :M -- returns maximum input voltage (hexadecimal hertz) + * :P -- returns power rating + * :Z -- unknown + * :U -- unknown + * :O -- unknown + * :E -- unknown + * :Y -- returns mains frequency (':D' is preferred) + * :T -- returns ups temperature (':D' is preferred) + * :R -- returns input voltage (':D' is preferred) + * :F -- returns load percentage (':D' is preferred) + * :S -- enables remote reboot/remote power on + */ + +/* Returned value from ':D' looks like: + * + * 0123456789abcdef01234 + * ABCCDEFFGGGGHHIIIIJJJ + * A 0=LB 1=OK + * B 0=OVER 1=OK + * CC INFO_UTILITY + * D 0=normal 1=TRIM 2=BOOST 3="EXTRA BOOST" + * E 0=OFF 1=OB 2=OL 3=OB (1 and 3 are the same?) + * FF f(INFO_UPSTEMP) + * GG ? INFO_BATTPCT (00 when OB, values don't match table we use) + * HH ? (always 00) + * II INFO_LOADPCT + * JJJJ ? (e.g., 5B82 5B82 5982 037B 0082) + * KKK INFO_ACFREQ * 10 + */ + +#define DRV_VERSION "0.7" #include "main.h" #include "serial.h" +#include #include -#define ENDCHAR '\n' /* replies end with CR LF -- use LF to end */ -#define IGNCHARS "\r" /* ignore CR */ -#define UPSDELAY 1 -#define MAXTRIES 3 +#define ENDCHAR '\n' /* replies end with CR LF -- use LF to end */ +#define IGNCHAR '\r' /* ignore CR */ +#define MAXTRIES 3 +#define SER_WAIT_SEC 3 /* allow 3.0 sec for ser_get calls */ +#define SER_WAIT_USEC 0 +#define DEFAULT_OFFDELAY 64 /* seconds (max 0xFF) */ +#define DEFAULT_STARTDELAY 60 /* seconds (max 0xFFFFFF) */ +#define DEFAULT_BOOTDELAY 64 /* seconds (max 0xFF) */ +#define MAX_VOLT 13.0 /* Max battery voltage (100%) */ +#define MIN_VOLT 11.0 /* Min battery voltage (10%) */ + +/* We calculate battery charge (q) as a function of voltage (V). + * It seems that this function probably varies by firmware revision or + * UPS model - the Windows monitoring software gives different q for a + * given V than the old open source Tripp Lite monitoring software. + * + * The discharge curve should be the same for any given battery chemistry, + * so it should only be necessary to specify the minimum and maximum + * voltages likely to be seen in operation. + */ + +/* Interval notation for Q% = 10% <= [minV, maxV] <= 100% */ +static float V_interval[2] = {MIN_VOLT, MAX_VOLT}; + +/* Time in seconds to delay before shutting down. */ +static unsigned int offdelay = DEFAULT_OFFDELAY; +static unsigned int startdelay = DEFAULT_STARTDELAY; +static unsigned int bootdelay = DEFAULT_BOOTDELAY; + +static int hex2d(char *start, unsigned int len) +{ + char buf[32]; + buf[32] = '\0'; + + strncpy(buf, start, (len < (sizeof buf) ? len : (sizeof buf - 1))); + return strtol(buf, NULL, 16); +} + +/* The UPS that I'm using (SMART700SER) has the bizarre characteristic + * of innately echoing back commands. Therefore, we cannot use + * ser_get_line and must manually discard our echoed command. + * + * All UPS commands are challenge-response, so this function makes things + * very clean. + * + * return: # of chars in buf, excluding terminating \0 */ +static int send_cmd(const char *str, char *buf, size_t len) +{ + char c; + int ret; + size_t i = 0; + + ser_send(upsfd, str); + + if (!len || !buf) + return -1; + + for (;;) { + ret = ser_get_char(upsfd, &c, SER_WAIT_SEC, SER_WAIT_USEC); + if (ret == -1) + return -1; + if (c == ENDCHAR) + break; + } + do { + ret = ser_get_char(upsfd, &c, SER_WAIT_SEC, SER_WAIT_USEC); + if (ret == -1) + return -1; + + if (c == IGNCHAR || c == ENDCHAR) + continue; + buf[i++] = c; + } while (c != ENDCHAR && i < len); + buf[i] = '\0'; + ser_flush_in(upsfd, NULL, 0); + return i; +} -static char *w_value, *l_value, *v_value, *x_value; -static int poll_failures = 0; +static void ups_sync(void) +{ + char buf[256]; + int tries, ret; -static int hex2d(char *start, int len) + for (tries = 0; tries < MAXTRIES; ++tries) { + ret = send_cmd(":W\r", buf, sizeof buf); + if ((ret > 0) && isdigit((unsigned char)buf[0])) + return; + } + fatalx("\nFailed to find UPS - giving up..."); +} + +static int do_reboot_now(void) { - char buf[256]; + char buf[256], cmd[16]; - strncpy(buf, start, len); - buf[len] = '\0'; - return strtol(buf, NULL, 16); + snprintf(cmd, sizeof cmd, ":H%06X\r", startdelay); + return send_cmd(cmd, buf, sizeof buf); +} + +static int do_reboot(void) +{ + char buf[256], cmd[16]; + + snprintf(cmd, sizeof cmd, ":N%02X\r", bootdelay); + send_cmd(cmd, buf, sizeof buf); + do_reboot_now(); +} + +static int soft_shutdown(void) +{ + char buf[256], cmd[16]; + + snprintf(cmd, sizeof cmd, ":N%02X\r", offdelay); + send_cmd(cmd, buf, sizeof buf); + return send_cmd(":G\r", buf, sizeof buf); +} + +static int hard_shutdown(void) +{ + char buf[256], cmd[16]; + + snprintf(cmd, sizeof cmd, ":N%02X\r", offdelay); + send_cmd(cmd, buf, sizeof buf); + return send_cmd(":K0\r", buf, sizeof buf); } static int instcmd(const char *cmdname, const char *extra) { - char buf[256]; + char buf[256]; if (!strcasecmp(cmdname, "test.battery.start")) { - ser_send(upsfd, "%s", ":A\r"); /* Start self test */ - sleep(UPSDELAY); - ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - + send_cmd(":A\r", buf, sizeof buf); + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "load.off")) { + send_cmd(":K0\r", buf, sizeof buf); + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "load.on")) { + send_cmd(":K1\r", buf, sizeof buf); + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "shutdown.reboot")) { + do_reboot_now(); + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "shutdown.reboot.graceful")) { + do_reboot(); + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "shutdown.return")) { + soft_shutdown(); + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "shutdown.stayoff")) { + hard_shutdown(); return STAT_INSTCMD_HANDLED; } @@ -113,298 +275,225 @@ return STAT_INSTCMD_UNKNOWN; } +static int setvar(const char *varname, const char *val) +{ + if (!strcasecmp(varname, "ups.delay.shutdown")) { + offdelay = atoi(val); + dstate_setinfo("ups.delay.shutdown", val); + return STAT_SET_HANDLED; + } + if (!strcasecmp(varname, "ups.delay.start")) { + startdelay = atoi(val); + dstate_setinfo("ups.delay.start", val); + return STAT_SET_HANDLED; + } + if (!strcasecmp(varname, "ups.delay.reboot")) { + bootdelay = atoi(val); + dstate_setinfo("ups.delay.reboot", val); + return STAT_SET_HANDLED; + } + return STAT_SET_UNKNOWN; +} + void upsdrv_initinfo(void) { - const char *model; - int va; - long w, l; - - dstate_addcmd("test.battery.start"); /* Turns off automatically */ - - dstate_setinfo("ups.mfr", "%s", "Tripp Lite"); - - w = hex2d(w_value, 2); - l = hex2d(l_value, 2); - - if (w & 0x40) model = "Unison %d"; - else model = "Smart %d"; - - if (w & 0x80) { /* New VA rating formula */ - va = ((w & 0x3f) * 32 + (l >> 3)) * 5; - } else { /* Old VA rating formula */ - va = l / 2; - } - - dstate_setinfo("ups.model", model, va); - dstate_setinfo("ups.firmware", "%c%c", - 'A'+v_value[0]-'0', 'A'+v_value[1]-'0'); - -/* we need more details on what these really are */ -#if 0 - setinfo(INFO_FIRMREV1, "%s", v_value); - setinfo(INFO_FIRMREV2, "%s", x_value); - setinfo(INFO_REG1, "%s", w_value); - setinfo(INFO_REG2, "%s", l_value); -#endif - - upsh.instcmd = instcmd; /* Set here instead of upsdrv_initups */ - - printf("Detected %s %s on %s\n", - dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"), device_path); - - dstate_setinfo("driver.version.internal", "%s", DRV_VERSION); -} - -static void setup_serial(void) -{ - struct termios tio; - - if (tcgetattr(upsfd, &tio) == -1) fatal("tcgetattr"); - - tio.c_iflag = IXON | IXOFF; - tio.c_oflag = 0; - tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL); - tio.c_lflag = 0; - tio.c_cc[VMIN] = 1; - tio.c_cc[VTIME] = 0; - -#ifdef HAVE_CFSETISPEED - cfsetispeed(&tio, B2400); - cfsetospeed(&tio, B2400); -#else -#error This system lacks cfsetispeed() and has no other means to set the speed -#endif + const char *model; + char buf[16], w_value[16], l_value[16], v_value[16], x_value[16]; + int va; + long w, l; + + + /* Detect the UPS or die. */ + ups_sync(); + + send_cmd(":W\r", w_value, sizeof w_value); + send_cmd(":L\r", l_value, sizeof l_value); + send_cmd(":V\r", v_value, sizeof v_value); + send_cmd(":X\r", x_value, sizeof x_value); + + dstate_setinfo("ups.mfr", "%s", "Tripp Lite"); + + w = hex2d(w_value, 2); + l = hex2d(l_value, 2); + + model = "Smart %d"; + if (w & 0x40) + model = "Unison %d"; + + va = ((w & 0x3f) * 32 + (l >> 3)) * 5; /* New formula */ + if (!(w & 0x80)) + va = l / 2; /* Old formula */ + + dstate_setinfo("ups.model", model, va); + dstate_setinfo("ups.firmware", "%c%c", + 'A'+v_value[0]-'0', 'A'+v_value[1]-'0'); + + snprintf(buf, sizeof buf, "%d", offdelay); + dstate_setinfo("ups.delay.shutdown", buf); + dstate_setflags("ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING); + dstate_setaux("ups.delay.shutdown", 3); + snprintf(buf, sizeof buf, "%d", startdelay); + dstate_setinfo("ups.delay.start", buf); + dstate_setflags("ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING); + dstate_setaux("ups.delay.start", 8); + snprintf(buf, sizeof buf, "%d", bootdelay); + dstate_setinfo("ups.delay.reboot", buf); + dstate_setflags("ups.delay.reboot", ST_FLAG_RW | ST_FLAG_STRING); + dstate_setaux("ups.delay.reboot", 3); + + dstate_addcmd("test.battery.start"); /* Turns off automatically */ + dstate_addcmd("load.off"); + dstate_addcmd("load.on"); + dstate_addcmd("shutdown.reboot"); + dstate_addcmd("shutdown.reboot.graceful"); + dstate_addcmd("shutdown.return"); + dstate_addcmd("shutdown.stayoff"); + + upsh.instcmd = instcmd; + upsh.setvar = setvar; + + printf("Detected %s %s on %s\n", + dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"), device_path); - if (tcsetattr(upsfd, TCSANOW, &tio) == -1) fatal("tcsetattr"); + dstate_setinfo("driver.version.internal", "%s", DRV_VERSION); } -static void ups_sync(void) +void upsdrv_shutdown(void) { - char buf[256]; - int tries = 0; - int ret; - - for (;;) { - tries++; - if (tries > MAXTRIES) fatalx("\nFailed - giving up..."); - - ser_send(upsfd, "%s", ":W\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - - if ((ret > 0) && isdigit((unsigned char) buf[0])) break; - } -} - -void upsdrv_shutdown(void) /* FIXME: Not yet tested */ -{ - char buf[256]; - int ret; - - ser_send(upsfd, "%s", ":N40\r"); /* Delay 64 seconds */ - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - - ser_send(upsfd, "%s", ":G\r"); /* Inverter off */ - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - -#if 1 - /* FIXME: This may prevent UPS from powering up when utility comes - * back. */ - ser_send(upsfd, "%s", ":K0\r"); /* Relay off */ - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); -#endif -} - -static void ups_ident(void) -{ - char buf[256]; - int ret; - - ser_send(upsfd, "%s", ":W\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - if (w_value) free(w_value); - w_value = xstrdup(buf); - - ser_send(upsfd, "%s", ":L\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - if (l_value) free(l_value); - l_value = xstrdup(buf); - - ser_send(upsfd, "%s", ":V\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - if (v_value) free(v_value); - v_value = xstrdup(buf); - - ser_send(upsfd, "%s", ":X\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - if (x_value) free(x_value); - x_value = xstrdup(buf); -} - -static void pollfail(const char *why) -{ - poll_failures++; - - /* ignore the first few since these UPSes tend to drop characters */ - if (poll_failures == 3) upslogx(LOG_ERR, why); - - return; + soft_shutdown(); } void upsdrv_updateinfo(void) { - char buf[256], temp[32]; - int ret; - int bp; - float bv; - - ser_send(upsfd, "%s", ":D\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - - if (strlen(buf) < 21) { - pollfail("Poll failed: short read from UPS"); - dstate_datastale(); - return; - } - - if (strlen(buf) > 21) { - pollfail("Poll failed: oversized read from UPS"); - dstate_datastale(); - return; - } - - /* only say this if it got high enough to log a failure note */ - if (poll_failures >= 3) - upslogx(LOG_NOTICE, "UPS poll succeeded"); - - poll_failures = 0; - -/* this looks more like a candidate for upsdebugx, not a variable */ -#if 0 - setinfo(INFO_REG3, "%s", buf); - /* 0123456789abcdef01234 - ABCCDEFFGGGGHHIIIIJJJ - A 0=LB 1=OK - B 0=OVER 1=OK - CC INFO_UTILITY - D 0=normal 1=TRIM 2=BOOST 3="EXTRA BOOST" - E 0=OFF 1=OB 2=OL 3=OB (1 and 3 are the same?) - FF f(INFO_UPSTEMP) - GG ? INFO_BATTPCT (00 when OB, values don't match table we use) - HH ? (always 00) - II INFO_LOADPCT - JJJJ ? (e.g., 5B82 5B82 5982 037B 0082) - KKK INFO_ACFREQ * 10 - */ -#endif - - dstate_setinfo("input.voltage", "%03d", hex2d(buf + 2, 2)); - dstate_setinfo("ups.temperature", "%03d", (int)(hex2d(buf + 6, 2)*0.3636 - 21.0)); - dstate_setinfo("ups.load", "%03d", hex2d(buf + 12, 2)); - dstate_setinfo("input.frequency", "%02.2f", hex2d(buf + 18, 3) / 10.0); - - status_init(); - - /* Battery Voltage Condition */ - switch (buf[0]) { - case '0': status_set("LB"); break; /* Low Battery */ - case '1': break; /* Normal */ - default: sprintf(temp, "BAT-%c?", buf[0]); - status_set(temp); - break; /* Unknown */ - } - - /* Load State */ - switch (buf[1]) { - case '0': status_set("OVER"); break; /* Overload */ - case '1': break; /* Normal */ - default: sprintf(temp, "LOAD-%c?", buf[1]); - status_set(temp); - break; /* Unknown */ - } - - /* Tap State */ - switch (buf[4]) { - case '0': break; /* Normal */ - case '1': status_set("TRIM"); break; /* Reducing */ - case '2': status_set("BOOST"); break; /* Boost */ - case '3': status_set("BOOST"); break; /* Extra Boost */ - default: sprintf(temp, "TAP-%c?", buf[4]); - status_set(temp); - break; /* Unknown */ - } - - /* Mode */ - switch (buf[5]) { - case '0': status_set("OFF"); break; /* Off */ - case '1': status_set("OB"); break; /* On Battery */ - case '2': status_set("OL"); break; /* On Line */ - case '3': status_set("OB"); break; /* On Battery */ - default: sprintf(temp, "MODE-%c?", buf[5]); - status_set(temp); - break; /* Unknown */ - } - - status_commit(); - - ser_send(upsfd, "%s", ":B\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - bv = hex2d(buf, 2) / 10; - if (bv <= 11.0) bp = 10; - else if (bv <= 11.2) bp = 20; - else if (bv <= 11.4) bp = 30; - else if (bv <= 11.6) bp = 40; - else if (bv <= 11.8) bp = 50; - else if (bv <= 12.0) bp = 60; - else if (bv <= 12.2) bp = 70; - else if (bv <= 12.5) bp = 80; - else if (bv <= 13.2) bp = 90; - else bp = 100; - - dstate_setinfo("battery.voltage", "%.1f", bv); - dstate_setinfo("battery.charge", "%03d", bp); - - ser_send(upsfd, "%s", ":M\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - dstate_setinfo("input.voltage.maximum", "%d", hex2d(buf, 2)); - - ser_send(upsfd, "%s", ":I\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - dstate_setinfo("input.voltage.minimum", "%d", hex2d(buf, 2)); - - ser_send(upsfd, "%s", ":C\r"); - sleep(UPSDELAY); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 3, 0); - switch (atoi(buf)) { - case 0: dstate_setinfo("ups.test.result", "%s", "OK"); break; - case 1: dstate_setinfo("ups.test.result", "%s", "Battery Bad (Replace)"); break; - case 2: dstate_setinfo("ups.test.result", "%s", "In Progress"); break; - case 3: dstate_setinfo("ups.test.result", "%s", "Bad Inverter"); break; - default: dstate_setinfo("ups.test.result", "Unknown (%s)", buf); break; - } + char buf[256]; + int bp; + float bv; + + send_cmd(":D\r", buf, sizeof buf); + + if (strlen(buf) < 21) { + ser_comm_fail("Failed to get data: short read from UPS"); + dstate_datastale(); + return; + } - dstate_dataok(); + if (strlen(buf) > 21) { + ser_comm_fail("Failed to get data: oversized read from UPS"); + dstate_datastale(); + return; + } + + dstate_setinfo("input.voltage", "%03d", hex2d(buf + 2, 2)); + dstate_setinfo("ups.temperature", "%03d", + (int)(hex2d(buf + 6, 2)*0.3636 - 21.0)); + dstate_setinfo("ups.load", "%03d", hex2d(buf + 12, 2)); + dstate_setinfo("input.frequency", "%02.2f", hex2d(buf + 18, 3) / 10.0); + + status_init(); + + /* Battery Voltage Condition */ + switch (buf[0]) { + case '0': /* Low Battery */ + status_set("LB"); + break; + case '1': /* Normal */ + break; + default: /* Unknown */ + upslogx(LOG_ERR, "Unknown battery state: %c", buf[0]); + break; + } + + /* Load State */ + switch (buf[1]) { + case '0': /* Overload */ + status_set("OVER"); + break; + case '1': /* Normal */ + break; + default: /* Unknown */ + upslogx(LOG_ERR, "Unknown load state: %c", buf[1]); + break; + } + + /* Tap State */ + switch (buf[4]) { + case '0': /* Normal */ + break; + case '1': /* Reducing */ + status_set("TRIM"); + break; + case '2': /* Boost */ + case '3': /* Extra Boost */ + status_set("BOOST"); + break; + default: /* Unknown */ + upslogx(LOG_ERR, "Unknown tap state: %c", buf[4]); + break; + } + + /* Mode */ + switch (buf[5]) { + case '0': /* Off */ + status_set("OFF"); + break; + case '1': /* On Battery */ + status_set("OB"); + break; + case '2': /* On Line */ + status_set("OL"); + break; + case '3': /* On Battery */ + status_set("OB"); + break; + default: /* Unknown */ + upslogx(LOG_ERR, "Unknown mode state: %c", buf[4]); + break; + } + + status_commit(); + send_cmd(":B\r", buf, sizeof buf); + bv = hex2d(buf, 2) / 10; + + /* dq ~= sqrt(dV) is a reasonable approximation + * Results fit well against the discrete function used in the Tripp Lite + * source, but give a continuous result. */ + if (bv >= V_interval[1]) + bp = 100; + else if (bv <= V_interval[0]) + bp = 10; + else + bp = (int)sqrtf(bv - V_interval[0]) / (V_interval[1] - V_interval[0]); + + dstate_setinfo("battery.voltage", "%.1f", bv); + dstate_setinfo("battery.charge", "%.3d", bp); + + send_cmd(":M\r", buf, sizeof buf); + dstate_setinfo("input.voltage.maximum", "%d", hex2d(buf, 2)); + + send_cmd(":I\r", buf, sizeof buf); + dstate_setinfo("input.voltage.minimum", "%d", hex2d(buf, 2)); + + send_cmd(":C\r", buf, sizeof buf); + switch (atoi(buf)) { + case 0: + dstate_setinfo("ups.test.result", "%s", "OK"); + break; + case 1: + dstate_setinfo("ups.test.result", "%s", "Battery Bad (Replace)"); + break; + case 2: + dstate_setinfo("ups.test.result", "%s", "In Progress"); + break; + case 3: + dstate_setinfo("ups.test.result", "%s", "Bad Inverter"); + break; + default: + dstate_setinfo("ups.test.result", "Unknown (%s)", buf); + break; + } + + dstate_dataok(); + ser_comm_good(); } void upsdrv_help(void) @@ -413,25 +502,40 @@ void upsdrv_makevartable(void) { + char msg[256]; + + snprintf(msg, sizeof msg, "Set shutdown delay, in seconds (default=%d).", + DEFAULT_OFFDELAY); + addvar(VAR_VALUE, "offdelay", msg); + snprintf(msg, sizeof msg, "Set start delay, in seconds (default=%d).", + DEFAULT_STARTDELAY); + addvar(VAR_VALUE, "startdelay", msg); + snprintf(msg, sizeof msg, "Set reboot delay, in seconds (default=%d).", + DEFAULT_BOOTDELAY); + addvar(VAR_VALUE, "rebootdelay", msg); } void upsdrv_banner(void) { - printf("Network UPS Tools - Tripp-Lite SmartUPS driver %s (%s)\n", - DRV_VERSION, UPS_VERSION); + printf("Network UPS Tools - Tripp-Lite SmartUPS driver %s (%s)\n", + DRV_VERSION, UPS_VERSION); } void upsdrv_initups(void) { - upsfd = ser_open(device_path); - ser_set_speed(upsfd, device_path, B2400); - setup_serial(); - - ups_sync(); - ups_ident(); + upsfd = ser_open(device_path); + ser_set_speed(upsfd, device_path, B2400); + + if (getval("offdelay")) + offdelay = atoi(getval("offdelay")); + if (getval("startdelay")) + startdelay = atoi(getval("startdelay")); + if (getval("rebootdelay")) + bootdelay = atoi(getval("rebootdelay")); } void upsdrv_cleanup(void) { - ser_close(upsfd, device_path); + ser_close(upsfd, device_path); } + --- man/tripplite.8.orig Sat Aug 21 17:32:16 2004 +++ man/tripplite.8 Sun Aug 22 01:45:22 2004 @@ -1,4 +1,4 @@ -.TH TRIPPLITE 8 "Tue Oct 22 2002" "" "Network UPS Tools (NUT)" +.TH TRIPPLITE 8 "Sat Aug 21 2004" "" "Network UPS Tools (NUT)" .SH NAME tripplite \- Driver for Tripp-Lite SmartPro UPS equipment .SH NOTE @@ -7,15 +7,36 @@ \fBnutupsdrv\fR(8). .SH SUPPORTED HARDWARE -This driver should work on the SmartPro line, including the SMART 700. +This driver should work on the SmartPro line, including the SMART700 +and SMART700SER. It only supports SmartPro models that communicate +using the serial port. .SH EXTRA ARGUMENTS +This driver supports the following optional settings in the +\fBups.conf\fR(5): -This driver does not support any extra settings in the -\fBups.conf\fR(5). +.IP "offdelay=\fInum\fR" +Time to wait before the UPS is turned off after the kill power command is +sent. +The default value is 64 (in seconds). -.SH AUTHOR +.IP "rebootdelay=\fInum\fR" +Set the timer before the UPS is cycled after the reboot command is sent. +The default value is 64 (in seconds). + +.IP "startdelay=\fInum\fR" +Set the time that the UPS waits before it turns itself back on after a +reboot command. +The default value is 60 (in seconds). + +.SH KNOWN ISSUES AND BUGS +Battery charge information may not be correct for all UPSes. It is tuned +to be correct for a SMART700SER. Other models may not provide correct +information. Information from the manufacturer would be helpful. + +.SH AUTHORS Rickard E. (Rik) Faith +Nicholas Kain .SH SEE ALSO