[Nut-upsdev] Bestfortress driver, network serial patch for nut-2.0
Stuart D. Gathman
stuart at bmsi.com
Fri Aug 14 19:13:06 UTC 2009
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);
More information about the Nut-upsdev
mailing list