Stuart D. Gathman
2009-Aug-14 19:13 UTC
[Nut-upsdev] Bestfortress driver, network serial patch for nut-2.0
Best fortress support was understandably dropped, but we still use them, and someone else may want the driver I ported to nut-2.0. We also often attach the serial cable from a UPS to a network terminal server, since servers these days don't come with very many serial ports. (They call them "legacy" ports.) I submitted a patch to support these for nut-1.x, and it was rejected - for very rational reasons. But I am trying again. It allows you to specify "port = trmsrv1:4015" for instance if the UPS is connected to a serial port on trmsrv1 listening on 4015. Baud rate, etc, are configured on the terminal server in this case, and set_set_speed is ignored. (In theory, it could log on to the terminal server in admin mode and auto configure, but that would be highly device dependent. Maybe doable with an expect script and admin port.) -- Stuart D. Gathman <stuart at bmsi.com> Business Management Systems Inc. Phone: 703 591-0911 Fax: 703 591-6154 "Confutatis maledictis, flammis acribus addictis" - background song for a Microsoft sponsored "Where do you want to go from here?" commercial. -------------- next part -------------- /* bestfortress.c - model specific routines for (very) old Best Power Fortress Copyright (C) 2002 Russell Kroll <rkroll at exploits.org> (skeleton) (C) 2002 Holger Dietze <holger.dietze at advis.de> 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 */ /* anything commented is optional anything else is mandatory */ #include "main.h" #include "serial.h" #define UPSDELAY 50000 /* 50 ms delay required for reliable operation */ #define SER_WAIT_SEC 2 /* allow 2.0 sec for ser_get calls */ #define SER_WAIT_USEC 0 #define ENDCHAR '\r' #define IGNCHARS " \n" #if defined(__sgi) && ! defined(__GNUC__) #define inline __inline #endif static int instcmd (const char *cmdname, const char *extra); static int upsdrv_setvar (const char *varname, const char *val); /* rated VA load if known */ static int maxload = 0; void upsdrv_initinfo(void) { dstate_setinfo("ups.mfr", "Best Power"); dstate_setinfo("ups.model", "Fortress"); dstate_setinfo("battery.voltage.nominal", "24"); /*dstate_setinfo ("alarm.overload", "0");*/ /* Flag */ /*dstate_setinfo ("alarm.temp", "0");*/ /* Flag */ if (maxload) dstate_setinfo("ups.load", "0"); dstate_setinfo("output.voltamps", "0"); dstate_setinfo("ups.delay.shutdown", "10"); /* write only */ /* tunable via front panel: (european voltage level) parameter factory default range INFO_LOWXFER 196 V p7=nnn 160-210 INFO_HIGHXFER 254 V p8=nnn 215-274 INFO_LOBATTIME 2 min p2=n 1-5 comm mode p6=0 dumb DONT USE (will lose access to parameter setting!) p6=1 B1200 p6=2 B2400 P6=3 B4800 p6=4 B9600 maybe cycle through speeds to autodetect? echo off e0 echo on e1 */ dstate_setinfo("input.transfer.low", "%s", ""); dstate_setflags("input.transfer.low", ST_FLAG_STRING | ST_FLAG_RW); dstate_setaux("input.transfer.low", 3); dstate_setinfo("input.transfer.high", "%s", ""); dstate_setflags("input.transfer.high", ST_FLAG_STRING | ST_FLAG_RW); dstate_setaux("input.transfer.high", 3); dstate_setinfo("battery.runtime.low", "%s", ""); dstate_setflags("battery.runtime.low", ST_FLAG_STRING | ST_FLAG_RW); dstate_setaux("battery.runtime.low", 3); upsh.instcmd = instcmd; upsh.setvar = upsdrv_setvar; dstate_addcmd("shutdown.return"); dstate_addcmd("load.off"); } /* convert hex digit to int */ static inline int fromhex (char c) { return (c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : 0; } /* do checksumming on UPS response */ static int checksum (char * s) { int i; int sum; for (i = 40, sum = 0; s[0] && s[1] && i > 0; i--, s += 2) { sum += (fromhex (s[0]) << 4) + fromhex (s[1]); } return sum; } /* set info to integer value */ static inline int setinfo_int (const char *key, const char * s, size_t len) { char buf[10]; int val; if (len > sizeof(buf)) len = sizeof(buf)-1; strncpy (buf, s, len); buf[len] = 0; val = atoi(buf); dstate_setinfo (key, "%d", val); return val; } /* set info to integer value (for runtime remaining) value is expressed in minutes, but desired in seconds */ static inline void setinfo_int_minutes (const char *key, const char * s, size_t len) { char buf[10]; if (len > sizeof(buf)) len = sizeof(buf)-1; strncpy (buf, s, len); buf[len] = 0; dstate_setinfo (key, "%d", 60*atoi (buf)); } /* set info to float value */ static inline void setinfo_float (const char *key, char * fmt, const char * s, size_t len, double factor) { char buf[10]; if (len > sizeof(buf)) len = sizeof(buf)-1; strncpy (buf, s, len); buf[len] = 0; dstate_setinfo (key, fmt, factor * (double)atoi (buf)); } static int upssend(const char *fmt,...) { int ret; char buf[1024], *p; va_list ap; unsigned int sent = 0; va_start(ap, fmt); ret = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if ((ret < 1) || (ret >= (int) sizeof(buf))) upslogx(LOG_WARNING, "ser_send_pace: vsnprintf needed more " "than %d bytes", sizeof(buf)); int d_usec = UPSDELAY; for (p = buf; *p; p++) { if (write(upsfd, p, 1) != 1) return -1; if (d_usec) usleep(d_usec); sent++; } return sent; } static int upsrecv(char *buf,size_t bufsize,char ec,const char *ic) { return ser_get_line(upsfd, buf, bufsize - 1, ec, ic, SER_WAIT_SEC, SER_WAIT_USEC); } static int upsflushin(int f,int verbose,const char *ignset) { return ser_flush_in(upsfd, ignset, verbose); } /* read out UPS and store info */ void upsdrv_updateinfo(void) { char temp[256]; char *p; int loadva; int len; int retry; int checksum_ok, is_online=1, is_off, low_batt, trimming, boosting; for (retry = 0; retry < 5; ++retry) { upsflushin (0, 0, "\r "); upssend ("f\r"); do { if (upsrecv (temp+2, sizeof temp - 2, ENDCHAR, IGNCHARS) <= 0) { upsflushin (0, 0, "\r "); upssend ("f\r"); } } while (temp[2] == 0); /*syslog (LOG_DAEMON | LOG_NOTICE,"ups: got '%s'\n", p);*/ /* status example: 000000000001000000000000012201210000001200014500000280600000990025000000000301BE 000000000001000000000000012401230000001200014800000280600000990025000000000301B7 |Vi||Vo| |Io||Psou| |Vb||f| |tr||Ti| CS 000000000001000000000000023802370000000200004700000267500000990030000000000301BD 1 1 2 2 3 3 4 4 5 5 6 6 7 7 78 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 90 */ /* last bytes are a checksum: interpret response as hex string, sum of all bytes must be zero */ checksum_ok = (checksum (temp+2) & 0xff) == 0; /* setinfo (INFO_, ""); */ /* I can't figure out why this is missing the first two chars. But the first two chars are not used, so just set them to zero when missing. */ len = strlen(temp+2); temp[0] = '0'; temp[1] = '0'; p = temp+2; if (len == 78) p = temp; else if (len != 80) checksum_ok = 0; if (checksum_ok) break; sleep(SER_WAIT_SEC); } if (!checksum_ok) { dstate_datastale(); return; } //upslogx(LOG_INFO, "updateinfo: %s", p); setinfo_int ("input.voltage", p+24,4); setinfo_int ("output.voltage", p+28,4); setinfo_float ("battery.voltage", "%.1f", p+50,4, 0.1); setinfo_float ("output.current", "%.1f", p+36,4, 0.1); loadva = setinfo_int ("output.voltamps", p+40,6); if (maxload) dstate_setinfo ("ups.load", "%d", loadva * 100 / maxload); setinfo_float ("input.frequency", "%.1f", p+54,3, 0.1); setinfo_int_minutes ("battery.runtime", p+58,4); setinfo_int ("ups.temperature", p+62,4); is_online = p[17] == '0'; low_batt = fromhex(p[21]) & 8 || fromhex(p[20]) & 1; is_off = p[11] == '0'; trimming = p[33] == '1'; boosting = 0; /* FIXME, don't know which bit gets set (brownouts are very rare here and I can't simulate one) */ status_init(); if (low_batt) status_set("LB "); else if (trimming) status_set("TRIM"); else if (boosting) status_set("BOOST"); else status_set(is_online ? (is_off ? "OFF " : "OL ") : "OB "); /* setinfo(INFO_STATUS, "%s%s", * (util < lownorm) ? "BOOST ", "", * (util > highnorm) ? "TRIM ", "", * ((flags & TIOCM_CD) == 0) ? "" : "LB ", * ((flags & TIOCM_CTS) == TIOCM_CTS) ? "OB" : "OL"); */ status_commit(); dstate_dataok(); } /* Parameter setting */ /* all UPS tunable parameters are set with command 'p%d=%s' */ int setparam (int parameter, int dlen, const char * data) { char reply[80]; upssend ("p%d=%*s\r", parameter, dlen, data); if (upsrecv (reply, sizeof(reply), ENDCHAR, "") < 0) return 0; return strncmp (reply, "OK", 2) == 0; } /* ups_setsuper: set super-user access (allows setting variables) */ static void ups_setsuper (int super) { setparam (999, super ? 4 : 0, super ? "2639" : ""); } /* sets whether UPS will reapply power after it has shut down and line * power returns. */ static void autorestart (int restart) { ups_setsuper (1); setparam (1, 1, restart ? "1" : "0"); ups_setsuper (0); } /* set UPS parameters */ static int upsdrv_setvar (const char *var, const char * data) { int parameter; int len = strlen(data); upsdebugx(1, "Setvar: %s %s", var, data); if (strcmp("input.transfer.low", var) == 0) { parameter = 7; } else if (strcmp("input.transfer.high", var) == 0) { parameter = 8; } else if (strcmp("battery.runtime.low", var) == 0) { parameter = 2; } else { upslogx(LOG_INFO, "Setvar: unsettable variable %s", var); return STAT_SET_UNKNOWN; } ups_setsuper (1); if (setparam (parameter, len, data)) { dstate_setinfo (var, "%*s", len, data); } ups_setsuper (0); return STAT_SET_HANDLED; } void upsdrv_shutdown(void) { const char *grace; grace = dstate_getinfo("ups.delay.shutdown"); if (!grace) grace = "1"; /* apparently, OFF0 does not work */ printf ("shutdown in %s seconds\n", grace); /* make power return when utility power returns */ autorestart (1); upssend ("OFF%s\r", grace); /* I'm nearly dead, Jim */ /* OFF will powercycle when line power is available again */ } static int instcmd (const char *cmdname, const char *extra) { const char *p; if (!strcasecmp(cmdname, "load.off")) { printf ("powering off\n"); autorestart (0); upssend ("OFF1\r"); return STAT_INSTCMD_HANDLED; } else if (!strcasecmp(cmdname, "shutdown.return")) { p = dstate_getinfo ("ups.delay.shutdown"); if (!p) p = "1"; printf ("shutdown in %s seconds\n", p); autorestart (1); upssend ("OFF%s\r", p); return STAT_INSTCMD_HANDLED; } upslogx(LOG_INFO, "instcmd: unknown command %s", cmdname); return STAT_INSTCMD_UNKNOWN; } void upsdrv_help(void) { } /* list flags and values that you want to receive via -x */ 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"); */ addvar (VAR_VALUE, "baudrate", "serial line speed"); addvar (VAR_VALUE, "max_load", "rated VA load VA"); } void upsdrv_banner(void) { printf("Network UPS Tools - Best Fortress UPS driver 0.01 (%s)\n\n", UPS_VERSION); } struct { char * val; speed_t speed; } speed_table[] = { {"1200", B1200}, {"2400", B2400}, {"4800", B4800}, {"9600", B9600}, {NULL, B1200}, }; void upsdrv_initups(void) { speed_t speed = B1200; char * speed_val = getval("baudrate"); char * max_load = getval("max_load"); if (max_load) maxload = atoi(max_load); if (speed_val) { int i; for (i=0; speed_table[i].val; i++) { if (strcmp (speed_val, speed_table[i].val) == 0) break; } speed = speed_table[i].speed; } upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, speed); /* TODO: probe ups type */ /* the upsh handlers can't be done here, as they get initialized * shortly after upsdrv_initups returns to main. */ } void upsdrv_cleanup(void) { } -------------- next part -------------- --- ./drivers/serial.c.net 2006-11-07 21:08:45.000000000 -0500 +++ ./drivers/serial.c 2009-08-13 17:47:37.000000000 -0400 @@ -27,6 +27,9 @@ #include <ctype.h> #include <sys/file.h> #include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> #include <unistd.h> #ifdef HAVE_UU_LOCK @@ -132,8 +135,37 @@ int ser_open(const char *port) { int fd; + char *path =0; + char *p =0; - fd = open(port, O_RDWR | O_NOCTTY | O_EXCL | O_NONBLOCK); + path = alloca(strlen(port) + 1); + if (path != 0) { + strcpy(path,port); + p = strchr(path,':'); + } + if (p != 0 && p != path) { /* open network port */ + int netport = 0; + struct sockaddr_in saddr; + struct hostent *blob = 0; + *p++ = 0; + netport = atoi(p); + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) + ser_open_error("socket"); + blob = gethostbyname(path); + if (blob == 0) + ser_open_error(path); + memcpy(&saddr.sin_addr,blob->h_addr,sizeof saddr.sin_addr); + saddr.sin_port = htons(netport); + saddr.sin_family = AF_INET; + if (connect(fd,(struct sockaddr *)&saddr,sizeof saddr) + || fcntl(fd,F_SETFL,O_NONBLOCK)) { + close(fd); + fd = -1; + } + } + else + fd = open(port, O_RDWR | O_NOCTTY | O_EXCL | O_NONBLOCK); if (fd < 0) ser_open_error(port); @@ -147,6 +179,7 @@ { struct termios tio; + if (!isatty(fd)) return 0; if (tcgetattr(fd, &tio) != 0) fatal_with_errno("tcgetattr(%s)", port);
On Aug 14, 2009, at 3:13 PM, Stuart D. Gathman wrote:> Best fortress support was understandably dropped, but we still use > them, > and someone else may want the driver I ported to nut-2.0.Stuart, Thanks for re-submitting the bestfortress driver. What specific version of NUT were you using to test this? If it is 2.0.5, have you had a chance to look at any of the later versions of NUT? I don't think there are any show-stoppers, but I will try to look over the code this weekend. -- Charles Lepple clepple at gmail
On Aug 14, 2009, at 3:13 PM, Stuart D. Gathman wrote:> We also often attach the serial cable from a UPS to a network terminal > server, since servers these days don't come with very many serial > ports. > (They call them "legacy" ports.) I submitted a patch to support > these for > nut-1.x, and it was rejected - for very rational reasons. But I am > trying > again. It allows you to specify "port = trmsrv1:4015" for instance if > the UPS is connected to a serial port on trmsrv1 listening on 4015. > Baud > rate, etc, are configured on the terminal server in this case, and > set_set_speed is ignored. (In theory, it could log on to the terminal > server in admin mode and auto configure, but that would be highly > device > dependent. Maybe doable with an expect script and admin port.)This looks like some useful functionality. Anticipating some of the other developers' comments, we might want to add IPv6 support here, and there could be some code reuse with the existing client-side TCP setup. Also, I think there are a few error-checking string functions that we could swap in (xstrdup() for alloca/strcpy). Not to be too pedantic, but we should probably free the string at the end of ser_open() as well. As with the other patch, I will try to take a look at it this weekend. Hopefully the changes between 2.0.x and 2.4.1 should be minimal. -- Charles Lepple clepple at gmail