[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