[Nut-upsdev] [PATCH] tripplite driver updates

Nicholas Kain njk at kain.us
Fri Jul 11 15:24:48 UTC 2008


* Arjen de Korte <nut+devel at de-korte.org> [080711 07:02]:

Thanks for the review! (and sorry for the duplicate -- it's been a while
since I've used this mail client)

> 1) Flushing buffers *after* a serial command, is (usually) pointless. If
> you flush buffers (which is a good idea), you should do it right *before*
> sending a command. Otherwise, junk characters after flushing the buffer
> will still garble up the communication.
> 
> 2) Get rid of the 'goto' statements. See also the 'docs/developers.txt'
> document about the use of goto.

Flushing at the start of the function is a good idea.  I'll make that
change, and it will nicely make the exception-emulation goto use
unnecessary (which neatly sidesteps the whole goto religion issue!).

> 3) Never, *ever* use a while loop in a driver that is not guaranteed to
> exit at some point. The driver will now retry W/L/V/X forever if the
> commands fail. It may be a bit harsh to give up on the first failure (like
> it did before), but the number of retries must be limited.

Sadly, the original code wouldn't fail here (unless the W command hidden
in the preceeding ups_sync() call failed) -- it would just carry on
and silently return bad data.

It actually turns out to be a missed cleanup -- I had the mistaken
assumption that NUT would call ups_sync(), but that's just a remnant
from the original tripplite code.  ups_sync() now is more generalized
and its error handling logic is now used for all W/L/V/X commands.

> 4) The new code in hex2d() seems to be over complicated. You're right
> about your comments on the use of strncpy(), so we prefer to use
> snprintf() now in most cases. Something like
> 
>         char buf[32];
>         snprintf(buf, sizeof(buf), "%.*s", len, start);
>         return strtol(buf, NULL, 16);
> 
> will do the job just fine and is much easier to follow.

Yup, that's nicer.  What I have right now in the patch is an open-coded
strlcpy(): great semantics but not portable.

> 5) Don't call dstate_datastale() on the first failure, unless you already
> know retrying is not going to fix the problem. Otherwise, it is generally
> a good idea to mask out the first two or three failures before
> complaining. When in doubt, don't call neither dstate_dataok() nor
> dstate_datastale() so that the server will report the last state the
> driver was in.

The only reason I had the function call dstate_datastale() on the first
entry is that the documentation in new-developers.txt seemed to indicate
to me that all returns from upsdrv_updateinfo() should either call
dstate_datastale() or dstate_dataok().

I prefer your suggested approach greatly (it's what I originally did
before I scanned new-drivers.txt to refresh my memory), so I'll
change back to the old code.

Updated changelog and inlined patch (compiles and detects UPS fine) follows:

- Update comment for tripplite documentation URL; appears to be dead now.
- Move #defines to tripplite.h
- Remove a useless static float definition and replace with an inline
  constant; saves .text space, easier to read, and optimizes better.
- Make hex2d() use snprintf instead of strncpy.  strncpy's edge case is
  ugly.  The old code could never overflow, but was harder to read.
- send_cmd() now flushes both input and output buffers on entry.
  This change guards against corrupted serial port i/o between send_cmd()
  calls.
- Squash a harmless unsigned/signed char warning in send_cmd().
- ups_sync() is removed -- detection success is now non-failure of
  upsdrv_initinfo().
- upsdrv_initinfo(): W/L/V/X commands now behave identically to the old W
  command in ups_sync(); three tries for each, and failure means the driver
  prints an error and exits.
- upsdrv_shutdown() is significantly more robust.  All serial i/o is checked
  for failure, errors are reported, and where possible, data returned is
  checked for range validity.
- Wait time for ser_get calls is now 1.0s rather than 3.0s.
- Update copyright date.


--- nut-2.2.2/drivers/tripplite.c	2007-05-26 10:24:26.000000000 -0400
+++ nut-2.2.2-nk/drivers/tripplite.c	2008-07-11 10:22:24.000000000 -0400
@@ -6,7 +6,7 @@
 
    Copyright (C) 1999  Russell Kroll <rkroll at exploits.org>
    Copyright (C) 2001  Rickard E. (Rik) Faith <faith at alephnull.com>
-   Copyright (C) 2004  Nicholas J. Kain <nicholas at kain.us>
+   Copyright (C) 2004,2008  Nicholas J. Kain <nicholas at kain.us>
 
    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
@@ -26,8 +26,8 @@
 /* REFERENCE 1
 
    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/.
+   opensrc_server/upscmd.cpp, formerly (but not longer) available from
+   Tripp Lite at http://www.tripplite.com/linux/.
 */
 
 /* REFERENCE 2
@@ -111,30 +111,6 @@
 #include <math.h>
 #include <ctype.h>
 
-#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.4          /* 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;
@@ -143,9 +119,9 @@ static unsigned int bootdelay = DEFAULT_
 static int hex2d(char *start, unsigned int len)
 {
 	char buf[32];
-	buf[31] = '\0';
+	unsigned int i;
 
-	strncpy(buf, start, (len < (sizeof buf) ? len : (sizeof buf - 1)));
+	snprintf(buf, sizeof buf, "%.*s", len, start);
 	return strtol(buf, NULL, 16);
 }
 
@@ -159,10 +135,11 @@ static int hex2d(char *start, unsigned i
  * return: # of chars in buf, excluding terminating \0 */
 static int send_cmd(const char *str, char *buf, size_t len)
 {
-	char c;
-	int ret;
+	unsigned char c;
+	int ret = 0;
 	size_t i = 0;
 
+	ser_flush_io(upsfd);
 	ser_send(upsfd, str);
 
 	if (!len || !buf)
@@ -171,31 +148,29 @@ static int send_cmd(const char *str, cha
 	for (;;) {
 		ret = ser_get_char(upsfd, &c, SER_WAIT_SEC, SER_WAIT_USEC);
 		if (ret == -1)
-			return -1;
+			return ret;
 		if (c == ENDCHAR)
 			break;
 	}
 	do {
 		ret = ser_get_char(upsfd, &c, SER_WAIT_SEC, SER_WAIT_USEC);
 		if (ret == -1)
-			return -1;
+			return ret;
 
 		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 void ups_sync(void)
+static void get_letter_cmd(char *str, char *buf, size_t len)
 {
-	char buf[256];
 	int tries, ret;
 
 	for (tries = 0; tries < MAXTRIES; ++tries) {
-		ret = send_cmd(":W\r", buf, sizeof buf);
+		ret = send_cmd(str, buf, len);
 		if ((ret > 0) && isdigit((unsigned char)buf[0]))
 			return;
 	}
@@ -301,14 +276,10 @@ void upsdrv_initinfo(void)
 	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);
+	get_letter_cmd(":W\r", w_value, sizeof w_value);
+	get_letter_cmd(":L\r", l_value, sizeof l_value);
+	get_letter_cmd(":V\r", v_value, sizeof v_value);
+	get_letter_cmd(":X\r", x_value, sizeof x_value);
 
 	dstate_setinfo("ups.mfr", "%s", "Tripp Lite");
 
@@ -364,58 +335,140 @@ void upsdrv_shutdown(void)
 
 void upsdrv_updateinfo(void)
 {
+	static int numfails;
 	char buf[256];
-	int bp;
-	float bv;
+	int bp, volt, temp, load, vmax, vmin, stest, len;
+	int bcond, lstate, tstate, mode;
+	float bv, freq;
+
+	len = send_cmd(":D\r", buf, sizeof buf);
+	if (len != 21) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("Data command failed: [%d] bytes != 21 bytes.", len);
+			dstate_datastale();
+		}
+		return;
+	}
+
+	volt = hex2d(buf + 2, 2);
+	temp = (int)(hex2d(buf + 6, 2)*0.3636 - 21.0);
+	load = hex2d(buf + 12, 2);
+	freq = hex2d(buf + 18, 3) / 10.0;
+	bcond = buf[0];
+	lstate = buf[1];
+	tstate = buf[4];
+	mode = buf[5];
+	if (volt > INVOLT_MAX || volt < INVOLT_MIN ||
+			temp > TEMP_MAX || temp < TEMP_MIN ||
+			load > LOAD_MAX || load < LOAD_MIN ||
+			freq > FREQ_MAX || freq < FREQ_MIN) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("Data out of bounds: [%0d,%3d,%3d,%02.2f]",
+					volt, temp, load, freq);
+			dstate_datastale();
+		}
+		return;
+	}
+
+	send_cmd(":B\r", buf, sizeof buf);
+	bv = (float)hex2d(buf, 2) / 10.0;
+	if (bv > 50.0 || bv < 0.0) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("Battery voltage out of bounds: [%02.1f]", bv);
+			dstate_datastale();
+		}
+		return;
+	}
 
-	send_cmd(":D\r", buf, sizeof buf);
+	send_cmd(":M\r", buf, sizeof buf);
+	vmax = hex2d(buf, 2);
+	if (vmax > INVOLT_MAX || vmax < INVOLT_MIN) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("InVoltMax out of bounds: [%d]", vmax);
+			dstate_datastale();
+		}
+		return;
+	}
 
-	if (strlen(buf) < 21) {
-		ser_comm_fail("Failed to get data: short read from UPS");
-		dstate_datastale();
+	send_cmd(":I\r", buf, sizeof buf);
+	vmin = hex2d(buf, 2);
+	if (vmin > INVOLT_MAX || vmin < INVOLT_MIN) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("InVoltMin out of bounds: [%d]", vmin);
+			dstate_datastale();
+		}
 		return;
 	}
 
-	if (strlen(buf) > 21) {
-		ser_comm_fail("Failed to get data: oversized read from UPS");
-		dstate_datastale();
+	send_cmd(":C\r", buf, sizeof buf);
+	errno = 0;
+	stest = strtol(buf, 0, 10);
+	if (errno == ERANGE) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("Self test is out of range: [%d]", stest);
+			dstate_datastale();
+		}
+		return;
+	}
+	if (errno == EINVAL) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("Self test returned non-numeric data.");
+			dstate_datastale();
+		}
+		return;
+	}
+	if (stest > 3 || stest < 0) {
+		++numfails;
+		if (numfails > MAXTRIES) {
+			ser_comm_fail("Self test out of bounds: [%d]", stest);
+			dstate_datastale();
+		}
 		return;
 	}
 
-	dstate_setinfo("input.voltage", "%0d", hex2d(buf + 2, 2));
-	dstate_setinfo("ups.temperature", "%3d",
-			(int)(hex2d(buf + 6, 2)*0.3636 - 21.0));
-	dstate_setinfo("ups.load", "%3d", hex2d(buf + 12, 2));
-	dstate_setinfo("input.frequency", "%02.2f", hex2d(buf + 18, 3) / 10.0);
+	/* We've successfully gathered all the data for an update. */
+	numfails = 0;
+
+	dstate_setinfo("input.voltage", "%0d", volt);
+	dstate_setinfo("ups.temperature", "%3d", temp);
+	dstate_setinfo("ups.load", "%3d", load);
+	dstate_setinfo("input.frequency", "%02.2f", freq);
 
 	status_init();
 
 	/* Battery Voltage Condition */
-	switch (buf[0]) {
+	switch (bcond) {
 		case '0': /* Low Battery */
 			status_set("LB");
 			break;
 		case '1': /* Normal */
 			break;
 		default: /* Unknown */
-			upslogx(LOG_ERR, "Unknown battery state: %c", buf[0]);
+			upslogx(LOG_ERR, "Unknown battery state: %c", bcond);
 			break;
 	}
 
 	/* Load State */
-	switch (buf[1]) {
+	switch (lstate) {
 		case '0': /* Overload */
 			status_set("OVER");
 			break;
 		case '1': /* Normal */
 			break;
 		default: /* Unknown */
-			upslogx(LOG_ERR, "Unknown load state: %c", buf[1]);
+			upslogx(LOG_ERR, "Unknown load state: %c", lstate);
 			break;
 	}
 
 	/* Tap State */
-	switch (buf[4]) {
+	switch (tstate) {
 		case '0': /* Normal */
 			break;
 		case '1': /* Reducing */
@@ -426,12 +479,12 @@ void upsdrv_updateinfo(void)
 			status_set("BOOST");
 			break;
 		default: /* Unknown */
-			upslogx(LOG_ERR, "Unknown tap state: %c", buf[4]);
+			upslogx(LOG_ERR, "Unknown tap state: %c", tstate);
 			break;
 	}
 
 	/* Mode */
-	switch (buf[5]) {
+	switch (mode) {
 		case '0': /* Off */
 			status_set("OFF");
 			break;
@@ -445,36 +498,28 @@ void upsdrv_updateinfo(void)
 			status_set("OB");
 			break;
 		default: /* Unknown */
-			upslogx(LOG_ERR, "Unknown mode state: %c", buf[4]);
+			upslogx(LOG_ERR, "Unknown mode state: %c", mode);
 			break;
 	}
 
 	status_commit();
-	send_cmd(":B\r", buf, sizeof buf);
-	bv = (float)hex2d(buf, 2) / 10.0;
 
 	/* 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])
+	if (bv >= MAX_VOLT)
 		bp = 100;
-	else if (bv <= V_interval[0])
+	else if (bv <= MIN_VOLT)
 		bp = 10;
 	else
-		bp = (int)(100*sqrt((bv - V_interval[0])
-							/ (V_interval[1] - V_interval[0])));
+		bp = (int)(100*sqrt((bv - MIN_VOLT) / (MAX_VOLT - MIN_VOLT)));
 
 	dstate_setinfo("battery.voltage", "%.1f", bv);
 	dstate_setinfo("battery.charge",  "%3d", bp);
+	dstate_setinfo("input.voltage.maximum", "%d", vmax);
+	dstate_setinfo("input.voltage.minimum", "%d", vmin);
 
-	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)) {
+	switch (stest) {
 		case 0:
 			dstate_setinfo("ups.test.result", "%s", "OK");
 			break;
--- nut-2.2.2/drivers/tripplite.h	2005-09-12 08:38:36.000000000 -0400
+++ nut-2.2.2-nk/drivers/tripplite.h	2008-07-09 14:53:00.000000000 -0400
@@ -6,7 +6,7 @@
 
    Copyright (C) 1999  Russell Kroll <rkroll at exploits.org>
    Copyright (C) 2001  Rickard E. (Rik) Faith <faith at alephnull.com>
-   Copyright (C) 2004  Nicholas J. Kain <nicholas at kain.us>
+   Copyright (C) 2004,2008  Nicholas J. Kain <nicholas at kain.us>
 
    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
@@ -23,4 +23,38 @@
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
 
-#define DRV_VERSION "0.8"
+#define DRV_VERSION "0.9"
+
+#define ENDCHAR '\n'           /* replies end with CR LF -- use LF to end */
+#define IGNCHAR '\r'           /* ignore CR */
+#define MAXTRIES 3             /* max number of times we try to detect ups */
+#define SER_WAIT_SEC  1        /* allow 1.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) */
+
+/* Sometimes the UPS seems to return bad data, so we range check it. */
+#define INVOLT_MAX 270
+#define INVOLT_MIN 0
+#define FREQ_MAX 80.0
+#define FREQ_MIN 30.0
+#define TEMP_MAX 100
+#define TEMP_MIN 0
+#define LOAD_MAX 100
+#define LOAD_MIN 0
+
+/* 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%  */
+#define MAX_VOLT 13.4          /* Max battery voltage (100%) */
+#define MIN_VOLT 11.0          /* Min battery voltage (10%) */
+

-- 
Nicholas J. Kain <nicholas at kain dot us>
http://www.kain.us/~niklata/



More information about the Nut-upsdev mailing list