[Nut-upsdev] [PATCH/RFC 1/1] APC smart driver update and new features.

Michal Soltys soltys at ziu.info
Tue Jan 25 12:38:01 UTC 2011


Summary:

- new driver options: ignorelb, minbatt, mintime, wugrace, advorder
- expanded: sdtype
- few more types in compatibility table
- minor fixes and adjustments

Long:

New driver options:

-x ignorelb

In my experience with apc units, I've found that LB signal as asserted by ups
itself, is often very oversensitive and/or premature. Of course it all depends
on the model, battery quality, calibration (if applicable), load. Still, I've
found it much better to rely on battery charge level and eventually - runtime
left. Which brings us to:

-x minbatt = [% level]
-x mintime = [minutes left]

Both options are equivalent of apcupsd's options BATTERYLEVEL and MINUTES.
Whenever respective polled variables fall behind the values set by the user, the
LB is asserted. If the values are not provided, or are invalid, or if one of
them doesn't exist (often 'runtime left' on older units) - it's ignored and
doesn't constitute to LB state. "User's" LB state is remembered in
APC_STATE_ULB - distinct from APC_STATE_LB. ups_status_set() considers both
when setting LB (keeping APC* in separate bit saves us from rechecking ups)

-x wugrace = [delay in 6 minute units]

Command @nnn has always had an ability to provide additional wakeup delay in 6
minute units (particulary useful on old models, that have no eeprom settable
delays). If undefined or invalid, defaults to 0.

-x advorder = [string of numbers corresponding to shutdown methods or "no"]

If defined, user has direct control over which shutdown methods are tried and
in which order. First one that succeedes terminates the enumeration. The option
is carefully checked for validity. If it's provided and invalid - the default
is "0123", which corresponds to: S, @nnn, K, Z. If not defined, or if it's set
to "no" - the driver defaults to old 'sdtype' control. All the necessary
information is provided by help function. "advorder" methods are numbered
in the following way:

0: S - soft hibernate
1: @nnn - hard hibernate
2: K - delayed poweroff
3: Z - instant poweroff
4: CS - "CS 350 hack" soft hibernate
5: @nn - "nn hack" hard hibernate

"sdtype" methods are the same as they have always been - so people
content with their configuration doesn't have to alter anything during
the upgrade. Two types have been added as #5 and #6 corresponding to
hard hibernates:

5: @nn (hackish one)
6: @nnn

Other changes in apcsmart.{c,h}:

- greately updated help text - explaining differences between shutdown
  commands as well as differences in behaviour between older and newer
  apc models
- 6 shutdown metolds total - S, CS (hack), @nnn, @nn (hack), K, Z.
  Handled by sdcmd_X() family of functions called by either
  upsdrv_shutdown_advanced() or upsdrv_shutdown_simple(). sdok() verifies
  if the command succeeded
- the order in which old firmware is being probed has been swapped. For
  example, I have an older unit that responds to all: 'a', 'b' and 'V'
  commands, but it returns random garbage (or whatever old units used to return)
  in 'a' and 'b'. firmware_table_lookup() now uses 'V' before falling back to
  'b'.
- added few old units I have around to compatibility table (will add few
  more a bit later); the other firmware/command pairs already present
  seem to be a bit overoptimistic with reference to supported
  commands/variables, but I have no way to actually test it - so I left
  them as they were
- in protocol_verify(), variables responsible for battery.charge and
  battery.runtime (so those responsbile for manual LB state) are always
  checked regardless if they are returned by 'a' and/or are present in
  compatibility table; this is marked by APC_CRUCIAL flag
- asterisk '*' have been removed from ignored alerts. Actually - '*' is
  ups' answer to successfully executed shutdown command (regardless of the
  type). Modern units simply answer with "OK" or "NO", older - with '*'
  (usually, sometimes "OK" as well) or nothing. All shutdown command are
  handled by separate functions, and verified by sdok() now.
- in upsdrv_updateinfo(), update_status() (which does ULB check based on
  pooled variables) is done after pooling now; guarded with stale check
- added OVER / not OVER handling to '=' and '?' alerts
- discussable / not necessary: added shutdown.return.grace to cover
  APC's command '@', and ups.firmware.old to cover 'V' (in header file,
  it was defined and commented out as ups.model.code)
- cosmetic: printfs have been replaced by upsdebugx - following docs'
  recommendations. Obviously irrelevant in production scenario, but
  consistent and tidied output during debugging / testing one (e.g. driver
  called directly with a bunch of -D, etc.)
- cosmetic: inc. driver version, added author, some tabs in apcsmart.h,

The changes have been successfully tested with (so far):

Smart-UPS 2200 RM [JS0502033395] (newer rackmount unit)

Smart-UPS 2000I, firmware 9II (rather old 2-parts white unit, with
electronics in the "top" block and batteries in the "bottom" one)

Signed-off-by: Michal Soltys <soltys at ziu.info>
---
 drivers/apcsmart.c |  544 ++++++++++++++++++++++++++++++++++++++++++----------
 drivers/apcsmart.h |   54 ++++--
 2 files changed, 477 insertions(+), 121 deletions(-)

diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c
index aad21f8..f64117b 100644
--- a/drivers/apcsmart.c
+++ b/drivers/apcsmart.c
@@ -24,7 +24,7 @@
 #include "apcsmart.h"
 
 #define DRIVER_NAME	"APC Smart protocol driver"
-#define DRIVER_VERSION	"2.03"
+#define DRIVER_VERSION	"2.1"
 
 static upsdrv_info_t table_info = {
 	"APC command table",
@@ -39,7 +39,8 @@ upsdrv_info_t upsdrv_info = {
 	DRIVER_NAME,
 	DRIVER_VERSION,
 	"Russell Kroll <rkroll at exploits.org>\n" \
-	"Nigel Metheringham <Nigel.Metheringham at Intechnology.co.uk>",
+	"Nigel Metheringham <Nigel.Metheringham at Intechnology.co.uk>\n"
+	"Michal Soltys <soltys at ziu.info>",
 	DRV_STABLE,
 	{ &table_info, NULL }
 };
@@ -73,7 +74,7 @@ static apc_vartab_t *vartab_lookup_name(const char *var)
 /* FUTURE: change to use function pointers */
 
 /* convert APC formatting to NUT formatting */
-static const char *convert_data(apc_vartab_t *cmd_entry, char *upsval)
+static const char *convert_data(apc_vartab_t *cmd_entry, const char *upsval)
 {
 	static	char tmp[128];
 	int	tval;
@@ -137,7 +138,7 @@ static void ups_status_set(void)
 		status_set("OB");		/* on battery */
 	if (ups_status & APC_STAT_OVER)
 		status_set("OVER");		/* overload */
-	if (ups_status & APC_STAT_LB)
+	if (ups_status & (APC_STAT_LB | APC_STAT_ULB))
 		status_set("LB");		/* low battery */
 	if (ups_status & APC_STAT_RB)
 		status_set("RB");		/* replace batt */
@@ -164,8 +165,13 @@ static void alert_handler(char ch)
 			break;
 
 		case '%':		/* set LB */
-			upsdebugx(4, "alert_handler: LB");
-			ups_status |= APC_STAT_LB;
+			if (testvar("ignorelb")) {
+				upsdebugx(4, "alert_handler: LB (ignored)");
+				ups_status &= ~APC_STAT_LB;
+			} else {
+				upsdebugx(4, "alert_handler: LB");
+				ups_status |= APC_STAT_LB;
+			}
 			break;
 
 		case '+':		/* clear LB */
@@ -178,6 +184,16 @@ static void alert_handler(char ch)
 			ups_status |= APC_STAT_RB;
 			break;
 
+		case '?':		/* set RB */
+			upsdebugx(4, "alert_handler: OVER");
+			ups_status |= APC_STAT_OVER;
+			break;
+
+		case '=':		/* set RB */
+			upsdebugx(4, "alert_handler: not OVER");
+			ups_status &= ~APC_STAT_OVER;
+			break;
+
 		default:
 			upsdebugx(4, "alert_handler got 0x%02x (unhandled)", ch);
 			break;
@@ -210,6 +226,11 @@ static int poll_data(apc_vartab_t *vt)
 	if ((vt->flags & APC_PRESENT) == 0)
 		return 1;
 
+#if 0
+	if (!(vt->flags & APC_PRESENT) || (vt->flags & APC_IGNORE))
+		return 1;
+#endif
+
 	upsdebugx(4, "poll_data: %s", vt->name);
 
 	ret = ser_send_char(upsfd, vt->cmd);
@@ -333,8 +354,8 @@ static void do_capabilities(void)
 	endtemp = &temp[0] + strlen(temp);
 
 	if (temp[0] != '#') {
-		printf("Unrecognized capability start char %c\n", temp[0]);
-		printf("Please report this error [%s]\n", temp);
+		upsdebugx(1, "Unrecognized capability start char %c", temp[0]);
+		upsdebugx(1, "Please report this error [%s]\n", temp);
 		upslogx(LOG_ERR, "ERROR: unknown capability start char %c!", 
 			temp[0]);
 
@@ -403,6 +424,43 @@ static void do_capabilities(void)
 	}
 }
 
+static void update_ulb_state(void)
+{
+	const char *strmin, *strval;
+	const apc_vartab_t *vt;
+	int m, x, ulb_state = ups_status & ~APC_STAT_ULB;
+
+	/* battery.charge */
+	vt = vartab_lookup_name("battery.charge");
+	strval = dstate_getinfo("battery.charge");
+	strmin = getval("minbatt");
+	if (strmin && strval && (vt->flags & APC_PRESENT)) {
+		errno = 0;
+		m = strtol(strmin, NULL, 10);
+		x = strtol(strval, NULL, 10);
+		upsdebugx(4, "curr charge: %d, min charge: %d", x, m);
+		if (!errno && m >= 0 && m <= 100 && x < m) {
+			ulb_state |= APC_STAT_ULB;
+		}
+	}
+
+	/* battery.runtime */
+	vt = vartab_lookup_name("battery.runtime");
+	strval = dstate_getinfo("battery.runtime");
+	strmin = getval("mintime");
+	if (strmin && strval && (vt->flags & APC_PRESENT)) {
+		errno = 0;
+		m = strtol(strmin, NULL, 10) * 60;	/* seconds */
+		x = strtol(strval, NULL, 10);
+		upsdebugx(4, "curr time: %d, min time: %d", x, m);
+		if (!errno && m >= 0 && x < m) {
+			ulb_state |= APC_STAT_ULB;
+		}
+	}
+
+	ups_status = ulb_state;
+}
+
 static int update_status(void)
 {
 	int	ret;
@@ -421,6 +479,7 @@ static int update_status(void)
 	}
 
 	ret = read_buf(buf, sizeof(buf));
+	upsdebugx(4, "update_status: ret = %d", ret);
 
 	if ((ret < 1) || (!strcmp(buf, "NA"))) {
 		dstate_datastale();
@@ -428,9 +487,13 @@ static int update_status(void)
 	}
 
 	ups_status = strtol(buf, 0, 16) & 0xff;
+	if ((ups_status & APC_STAT_LB) && testvar("ignorelb")) {
+		upsdebugx(4, "update_status: LB (ignored)");
+		ups_status &= ~APC_STAT_LB;
+	}
+	update_ulb_state();
 	ups_status_set();
 
-	status_commit();
 	dstate_dataok();
 
 	return 1;
@@ -479,6 +542,21 @@ static void protocol_verify(unsigned char cmd)
 			upsdebugx(3, "UPS supports variable [%s]",
 				apc_vartab[i].name);
 
+			/* be extra careful about the presence of certain
+			 * variables */
+			if (apc_vartab[i].flags & APC_CRUCIAL)
+				query_ups(apc_vartab[i].name, 1);
+
+			/*
+			 * bail out if the variable is ignored (can be asserted
+			 * by query_ups()
+			*/
+			if (apc_vartab[i].flags & APC_IGNORE) {
+				upsdebugx(3, "Your UPS doesn't actually support variable [%s]."
+					     " Please report this error.", apc_vartab[i].name);
+				return;
+			}
+
 			/* load initial data */
 			apc_vartab[i].flags |= APC_PRESENT;
 			poll_data(&apc_vartab[i]);
@@ -531,9 +609,9 @@ static int firmware_table_lookup(void)
 	unsigned int	i, j;
 	char	buf[SMALLBUF];
 
-	upsdebugx(1, "Attempting firmware lookup");
+	upsdebugx(1, "Attempting firmware lookup using command 'V'");
 
-	ret = ser_send_char(upsfd, 'b');
+	ret = ser_send_char(upsfd, 'V');
 
 	if (ret != 1) {
 		upslog_with_errno(LOG_ERR, "getbaseinfo: ser_send_char failed");
@@ -543,12 +621,14 @@ static int firmware_table_lookup(void)
 	ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS, 
 		SER_WAIT_SEC, SER_WAIT_USEC);
 
-	/* see if this is an older version like an APC600 which doesn't
-	 * response to 'a' or 'b' queries
+	/*
+	 * Some UPSes support both 'V' and 'b'. Compatibility tab is predominantly for 'V' codes,
+	 * so we attemp 'b' as a last resort (furthermore, very old units may return something else
+	 * through 'b' - so it would break detection).
 	 */
 	if ((ret < 1) || (!strcmp(buf, "NA"))) {
-		upsdebugx(1, "Attempting to contact older Smart-UPS version");
-		ret = ser_send_char(upsfd, 'V');
+		upsdebugx(1, "Attempting firmware lookup using command 'b'");
+		ret = ser_send_char(upsfd, 'b');
 
 		if (ret != 1) {
 			upslog_with_errno(LOG_ERR, "getbaseinfo: ser_send_char failed");
@@ -563,9 +643,10 @@ static int firmware_table_lookup(void)
 			return 0;
 		}
 
-		upsdebugx(2, "Firmware: [%s]", buf);
 	}
 
+	upsdebugx(2, "Firmware: [%s]", buf);
+
 	/* this will be reworked if we get a lot of these things */
 	if (!strcmp(buf, "451.2.I")) {
 		quirk_capability_overflow = 1;
@@ -602,6 +683,10 @@ static void getbaseinfo(void)
 	int	ret = 0;
 	char 	*alrts, *cmds, temp[512];
 
+	/*
+	 *  try firmware lookup first; we could start with 'a', but older models
+	 *  can return other things in 'a' than command set
+	 */
 	if (firmware_table_lookup() == 1)
 		return;
 
@@ -622,7 +707,6 @@ static void getbaseinfo(void)
 		SER_WAIT_SEC, SER_WAIT_USEC);
 
 	if ((ret < 1) || (!strcmp(temp, "NA"))) {
-
 		/* We have an old dumb UPS - go to specific code for old stuff */
 		oldapcsetup();
 		return;
@@ -774,101 +858,271 @@ static int smartmode(void)
 	return 0;	/* failure */
 }
 
-/* power down the attached load immediately */
-void upsdrv_shutdown(void)
+/*
+ * all shutdown commands should respond with 'OK' or '*'
+ */
+static int sdok(void)
 {
-	char	temp[32];
-	int	ret, tval, sdtype = 0;
+	char temp[16];
 
-	if (!smartmode())
-		printf("Detection failed.  Trying a shutdown command anyway.\n");
+	ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, SER_WAIT_SEC, SER_WAIT_USEC);
+	upsdebugx(4, "sdok: got \"%s\"", temp);
 
-	/* check the line status */
+	if (!strcmp(temp, "*") || !strcmp(temp, "OK")) {
+		upsdebugx(4, "Last issued shutdown command succeeded");
+		return 1;
+	}
 
-	ret = ser_send_char(upsfd, APC_STATUS);
+	upsdebugx(1, "Last issued shutdown command failed");
+	return 0;
+}
 
-	if (ret == 1) {
-		ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, 
-			IGNCHARS, SER_WAIT_SEC, SER_WAIT_USEC);
+/* soft hibernate: S - working only when OB, otherwise ignored */
+static int sdcmd_S(int dummy)
+{
+	ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
 
-		if (ret < 1) {
-			printf("Status read failed!  Assuming on battery state\n");
-			tval = APC_STAT_LB | APC_STAT_OB;
-		} else {
-			tval = strtol(temp, 0, 16);
-		}
+	upsdebugx(1, "Issuing soft hibernate");
+	ser_send_char(upsfd, APC_CMD_SOFTDOWN);
 
-	} else {
-		printf("Status request failed; assuming on battery state\n");
-		tval = APC_STAT_LB | APC_STAT_OB;
+	return sdok();
+}
+
+/* soft hibernate, hack version for CS 350 */
+static int sdcmd_CS(int tval)
+{
+	upsdebugx(1, "Using CS 350 'force OB' shutdown method");
+	if (tval & APC_STAT_OL) {
+		upsdebugx(1, "On-line - forcing OB temporarily");
+		ser_send_char(upsfd, 'U');
+		usleep(UPSDELAY);
 	}
+	return sdcmd_S(tval);
+}
 
-	if (testvar("sdtype"))
-		sdtype = atoi(getval("sdtype"));
+/*
+ * hard hibernate: @nnn / @nn
+ * note: works differently for older and new models, see help function for
+ * detailed info
+ */
+static int sdcmd_ATn(int cnt)
+{
+	int n = 0, mmax, ret;
+	const char *strval;
+	char timer[4] = {0, 0, 0, 0};
 
-	switch (sdtype) {
+	mmax = cnt == 2 ? 99 : 999;
 
-	case 4:		/* special hack for CS 350 and similar models */
-		printf("Using CS 350 'force OB' shutdown method\n");
+	if ((strval = getval("wugrace"))) {
+		errno = 0;
+		n = strtol(strval, NULL, 10);
+		if (errno || n < 0 || n > mmax)
+			n = 0;
+	}
 
-		if (tval & APC_STAT_OL) {
-			printf("On line - forcing OB temporarily\n");
-			ser_send_char(upsfd, 'U');
-		}
+	snprintf(timer, sizeof(timer), "%.*d", cnt, n);
 
-		ser_send_char(upsfd, 'S');
-		break;
+	ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
+	upsdebugx(1, "Issuing hard hibernate with %d min. additional wakeup delay", n*6);
 
-	case 3:		/* shutdown with grace period */
-		printf("Sending delayed power off command to UPS\n");
+	ser_send_char(upsfd, APC_CMD_GRACEDOWN);
+	usleep(CMDLONGDELAY);
+	ser_send_pace(upsfd, UPSDELAY, timer);
 
-		ser_send_char(upsfd, APC_CMD_SHUTDOWN);
-		usleep(CMDLONGDELAY);
-		ser_send_char(upsfd, APC_CMD_SHUTDOWN);
+	ret = sdok();
+	if (ret || cnt == 3)
+		return ret;
 
-		break;
+	/*
+	 * "tricky" part - we tried @nn variation and it (unsurprisingly)
+	 * failed; we have to abort the sequence with something bogus to have
+	 * the clean state; newer upses will respond with 'NO', older will be
+	 * silent (YMMV);
+	 */
+	ser_send_char(upsfd, APC_CMD_GRACEDOWN);
+	usleep(UPSDELAY);
+	ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
 
-	case 2:		/* instant shutdown */
-		printf("Sending power off command to UPS\n");
+	return 0;
+}
 
-		ser_send_char(upsfd, APC_CMD_OFF);
-		usleep(CMDLONGDELAY);
-		ser_send_char(upsfd, APC_CMD_OFF);
+/* shutdown: K - delayed poweroff */
+static int sdcmd_K(int dummy)
+{
+	ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
+	upsdebugx(1, "Issuing delayed poweroff");
 
-		break;
+	ser_send_char(upsfd, APC_CMD_SHUTDOWN);
+	usleep(CMDLONGDELAY);
+	ser_send_char(upsfd, APC_CMD_SHUTDOWN);
 
-	case 1:
+	return sdok();
+}
 
-		/* Send a combined set of shutdown commands which can work better */
-		/* if the UPS gets power during shutdown process */
-		/* Specifically it sends both the soft shutdown 'S' */
-		/* and the powerdown after grace period - '@000' commands */
-		printf("UPS - currently %s - sending shutdown/powerdown\n",
-			(tval & APC_STAT_OL) ? "on-line" : "on battery");
+/* shutdown: Z - immediate poweroff */
+static int sdcmd_Z(int dummy)
+{
+	ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
+	upsdebugx(1, "Issuing immediate poweroff");
+
+	ser_send_char(upsfd, APC_CMD_OFF);
+	usleep(CMDLONGDELAY);
+	ser_send_char(upsfd, APC_CMD_OFF);
+
+	return sdok();
+}
 
-		ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
-		ser_send_pace(upsfd, UPSDELAY, "S at 000");
+static int (*sdlist[])(int) = {
+	sdcmd_S,
+	sdcmd_ATn,	/* for @nnn version */
+	sdcmd_K,
+	sdcmd_Z,
+	sdcmd_CS,
+	sdcmd_ATn,	/* for @nn version */
+};
+
+#define SDIDX_S		0
+#define SDIDX_AT3N	1
+#define SDIDX_K		2
+#define SDIDX_Z		3
+#define SDIDX_CS	4
+#define SDIDX_AT2N	5
+
+#define SDCNT 		6
+
+static void upsdrv_shutdown_simple(int status)
+{
+	unsigned int sdtype = 0;
+	char *strval;
+
+	if ((strval = getval("sdtype"))) {
+		errno = 0;
+		sdtype = strtol(strval, NULL, 10);
+		if (errno || sdtype < 0 || sdtype > 6)
+			sdtype = 0;
+	}
+
+	switch (sdtype) {
+
+	case 6:		/* hard hibernate */
+		sdcmd_ATn(3);
+		break;
+	case 5:		/* "hack nn" hard hibernate */
+		sdcmd_ATn(2);
+		break;
+	case 4:		/* special hack for CS 350 and similar models */
+		sdcmd_CS(status);
 		break;
 
-	default:
+	case 3:		/* delayed poweroff */
+		sdcmd_K(0);
+		break;
 
-		/* @000 - shutdown after 'p' grace period             */
-		/*      - returns after 000 minutes (i.e. right away) */
+	case 2:		/* instant poweroff */
+		sdcmd_Z(0);
+		break;
+	case 1:
+		/*
+		 * Send a combined set of shutdown commands which can work
+		 * better if the UPS gets power during shutdown process
+		 * Specifically it sends both the soft shutdown 'S' and the
+		 * hard hibernate '@nnn' commands
+		 */
+		upsdebugx(1, "UPS - currently %s - sending soft/hard hibernate commands",
+			(status & APC_STAT_OL) ? "on-line" : "on battery");
+
+		/* S works only when OB */
+		if ((status & APC_STAT_OB) && sdcmd_S(0))
+			break;
+		sdcmd_ATn(3);
+		break;
 
-		/* S    - shutdown after 'p' grace period, only on battery */
-		/*        returns after 'e' charge % plus 'r' seconds      */
+	default:
+		/*
+		 * Send @nnn or S, depending on OB / OL status
+		 */
+		if (status & APC_STAT_OL)		/* on line */
+			sdcmd_ATn(3);
+		else
+			sdcmd_S(0);
+	}
+}
 
-		ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
+static void upsdrv_shutdown_advanced(int status)
+{
+	const char *strval;
+	const char deforder[] = {48 + SDIDX_S,
+				 48 + SDIDX_AT3N,
+				 48 + SDIDX_K,
+				 48 + SDIDX_Z,
+				  0};
+	size_t i;
+	int n;
+
+	strval = getval("advorder");
+
+	/* sanitize advorder */
+
+	if (!strval || !strlen(strval) || strlen(strval) > SDCNT)
+		strval = deforder;
+	for (i = 0; i < strlen(strval); i++) {
+		if (strval[i] - 48 < 0 || strval[i] - 48 >= SDCNT) {
+			strval = deforder;
+			break;
+		}
+	}
+
+	/*
+	 * try each method in the list with a little bit of handling in certain
+	 * cases
+	 */
 
-		if (tval & APC_STAT_OL) {		/* on line */
-			printf("On line, sending shutdown+return command...\n");
-			ser_send_pace(upsfd, UPSDELAY, "@000");
+	for (i = 0; i < strlen(strval); i++) {
+		if (strval[i] - 48 == SDIDX_CS) {
+			n = status;
+		} else if (strval[i] - 48 == SDIDX_AT3N) {
+			n = 3;
+		} else if (strval[i] - 48 == SDIDX_AT2N) {
+			n = 2;
 		}
-		else {
-			printf("On battery, sending normal shutdown command...\n");
-			ser_send_char(upsfd, APC_CMD_SOFTDOWN);
+		if (sdlist[strval[i] - 48](n))
+			break;	/* finish if command succeeded */
+	}
+}
+
+/* power down the attached load immediately */
+void upsdrv_shutdown(void)
+{
+	char	temp[32];
+	int	ret, status;
+
+	if (!smartmode())
+		upsdebugx(1, "SM detection failed. Trying a shutdown command anyway");
+
+	/* check the line status */
+
+	ret = ser_send_char(upsfd, APC_STATUS);
+
+	if (ret == 1) {
+		ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR,
+			IGNCHARS, SER_WAIT_SEC, SER_WAIT_USEC);
+
+		if (ret < 1) {
+			upsdebugx(1, "Status read failed ! Assuming on battery state");
+			status = APC_STAT_LB | APC_STAT_OB;
+		} else {
+			status = strtol(temp, 0, 16);
 		}
+
+	} else {
+		upsdebugx(1, "Status request failed; assuming on battery state");
+		status = APC_STAT_LB | APC_STAT_OB;
 	}
+
+	if (testvar("advorder") && strcasecmp(getval("advorder"), "no"))
+		upsdrv_shutdown_advanced(status);
+	else
+		upsdrv_shutdown_simple(status);
 }
 
 /* 940-0095B support: set DTR, lower RTS */
@@ -1222,7 +1476,12 @@ static void setuphandlers(void)
 void upsdrv_makevartable(void)
 {
 	addvar(VAR_VALUE, "cable", "Specify alternate cable (940-0095B)");
-	addvar(VAR_VALUE, "sdtype", "Specify shutdown type (1-3)");
+	addvar(VAR_VALUE, "sdtype", "Specify simple shutdown method (0-6)");
+	addvar(VAR_VALUE, "wugrace", "Hard hibernate's wakeup grace");
+	addvar(VAR_FLAG,  "ignorelb", "Ignore internal LB signal");
+	addvar(VAR_VALUE, "minbatt", "Minimum allowed battery charge");
+	addvar(VAR_VALUE, "mintime", "Minimum allowed runtime left");
+	addvar(VAR_VALUE, "advorder", "Enable advanced shutdown control");
 }
 
 void upsdrv_initups(void)
@@ -1234,9 +1493,8 @@ void upsdrv_initups(void)
 
 	cable = getval("cable");
 
-	if (cable)
-		if (!strcasecmp(cable, ALT_CABLE_1))
-			init_serial_0095B();
+	if (cable && !strcasecmp(cable, ALT_CABLE_1))
+		init_serial_0095B();
 
 	/* make sure we wake up if the UPS sends alert chars to us */
 	extrafd = upsfd;
@@ -1244,18 +1502,97 @@ void upsdrv_initups(void)
 
 void upsdrv_help(void)
 {
-	printf("\nShutdown types:\n");
-	printf("  0: soft shutdown or powerdown, depending on battery status\n");
-	printf("  1: soft shutdown followed by powerdown\n");
-	printf("  2: instant power off\n");
-	printf("  3: power off with grace period\n");
-	printf("  4: 'force OB' hack method for CS 350\n");
-	printf("Modes 0-1 will make the UPS come back when power returns\n");
-	printf("Modes 2-3 will make the UPS stay turned off when power returns\n");
+	printf(
+	"\n\nAdditional explanation of driver options:\n\n"
+	"  sdtype:\n"
+	"    see \"Simple shutdown control\" below for details\n"
+	"  advorder:\n"
+	"    see \"Advanced shutdown control\" below for details\n"
+	"  wugrace:\n"
+	"    Additional grace period used with 'hard hibernate' shutdown command.\n"
+	"    The value is in 6 minute units and its acceptable range is 0 - 999.\n"
+	"    If the value is invalid or out of range, it's assumed to be 0.\n"
+	"    \"nn hack\" version of the command expects 0 - 99 range.\n"
+	"  ignorelb:\n"
+	"    This option allows disabling LB signal asserted by UPS itself.\n"
+	"    User can control LB condition in a much more fine grained way,\n"
+	"    through 'minbatt' and 'mintime' options.\n"
+	"    Rationale:\n"
+	"    LB status asserted by UPS is sometimes invalid and/or doesn't really\n"
+	"    constitute a low battery condition. To list a few situations:\n"
+	"    - modern units enable user to set \"low battery warning\" eeprom\n"
+	"      variable to over 20 minutes - at the same time, the lowest settable\n"
+	"      value is only 2 minutes\n"
+	"    - runtime as calculated by UPS firmware often leaves a lot to be \n"
+	"      desired\n"
+	"    - some units can be extremely sensitive and assert LB prematurely\n"
+	"  minbatt:\n"
+	"    Minimal battery charge (if defined), after which LB signal is asserted\n"
+	"    Acceptable values: 0 - 100.\n"
+	"    If invalid or out of range, it *will not* constitute to LB condition.\n"
+	"  mintime:\n"
+	"    Minimal runtime left, after which LB signal is asserted. Not all UPSes\n"
+	"    provide it (old models can't even be calibrated). Obviously,\n"
+	"    the option is sensitive to calibration, battery quality and firmware.\n"
+	"    If invalid or out of range, it *will not* constitute to LB condition.\n"
+	"\n\nShutdown types:\n\n"
+	"  soft hibernate:\n"
+	"    Works only when the ups is in OB state. The power is cut off after the\n"
+	"    eeprom defined grace period. The ups will wake up when the power\n"
+	"    returns, after the eeprom defined delay AND if the eeprom defined min.\n"
+	"    battery charge level is met. The delay is counted from the power's\n"
+	"    return.\n\n"
+	"    On older models (usually w/o programmable eeprom), the ups will power up\n"
+	"    immediately after the power returns. On such models, it's safer to use\n"
+	"    'hard hibernate'. YMMV, depending on the ups model and firmware\n"
+	"    revision.\n\n"
+	"  hard hibernate:\n"
+	"    Works regardless if the ups is in OB or OL states.  The power is cut off\n"
+	"    after the eeprom defined grace period. The ups will wake up when the\n"
+	"    power returns, after the eeprom defined delay + 6*n AND if the eeprom\n"
+	"    defined min. battery charge level is met. The delay is counted from the\n"
+	"    power's return. Value 'n' is in 6 minute units, and can be provided by\n"
+	"    the user.\n\n"
+	"    On older models (usually w/o programmable eeprom), the ups will power up\n"
+	"    after 6*n minutes, often regardless it the power returned on not. YMMV,\n"
+	"    depending on the ups model and firmware revision.\n\n"
+	"  delayed poweroff:\n"
+	"    The ups will powerdown after the eeprom defined grace period. The ups\n"
+	"    stays offline until the user's intervention.\n\n"
+	"  instant poweroff:\n"
+	"    The ups will powerdown immediately. The ups stays offline until the\n"
+	"    user's intervention.\n\n"
+	"  CS 350 hack:\n"
+	"    The same as 'soft hibernate', but first the ups is forced to go into OB\n"
+	"    mode.\n\n"
+	"Simple shutdown control:\n\n"
+	"    0: soft hibernate or hard hibernate, depending on battery status\n"
+	"    1: soft hibernate followed by hard hibernate, if the former fails\n"
+	"    2: instant poweroff\n"
+	"    3: delayed poweroff\n"
+	"    4: \"force OB\" hack method for CS 350\n"
+	"    5: \"hack nn\" hard hibernate only\n"
+	"    6: hard hibernate only\n"
+	"  User should provide requested method in 'sdtype'. The default is 0.\n\n"
+	"Advanced shutdown control:\n\n"
+	"    0: soft hibernate\n"
+	"    1: hard hibernate\n"
+	"    2: delayed poweroff\n"
+	"    3: instant poweroff\n"
+	"    4: \"force OB\" hack method for CS 350\n"
+	"    5: \"nn hack\" hard hibernate\n\n"
+	"  User should set the 'advorder' option and provide the list of the methods.\n"
+	"  The methods are tried in order, until one of them succeedes.\n"
+	"  If the list is too long or contains invalid characters, it will fallback to\n"
+	"  the default - 0123. You can also use \"no\" to explicitly ignore it and use\n"
+	"  \"sdtype\". Advanced shutdown control takes precedence over simple\n"
+	"  one, if both are defined.\n\n"
+	);
 }
 
 void upsdrv_initinfo(void)
 {
+	const char *pmod, *pser;
 	if (!smartmode()) {
 		fatalx(EXIT_FAILURE, 
 			"Unable to detect an APC Smart protocol UPS on port %s\n"
@@ -1268,8 +1605,12 @@ void upsdrv_initinfo(void)
 
 	getbaseinfo();
 
-	printf("Detected %s [%s] on %s\n", dstate_getinfo("ups.model"),
-		dstate_getinfo("ups.serial"), device_path);
+	if (!(pmod = dstate_getinfo("ups.model")))
+		pmod = "\"unknown model\"";
+	if (!(pser = dstate_getinfo("ups.serial")))
+		pser = "unknown serial";
+
+	upsdebugx(1, "Detected %s [%s] on %s", pmod, pser, device_path);
 
 	setuphandlers();
 }
@@ -1290,9 +1631,6 @@ void upsdrv_updateinfo(void)
 
 	ser_comm_good();
 
-	if (!update_status())
-		return;
-
 	time(&now);
 
 	/* refresh all variables hourly */
@@ -1300,10 +1638,16 @@ void upsdrv_updateinfo(void)
 	if (difftime(now, last_full) > 3600) {
 		last_full = now;
 		update_info_all();
-		return;
+	} else {
+		update_info_normal();
 	}
 
-	update_info_normal();
+	/*
+	 * this test seems necessary - if we stop / kill the driver while it's
+	 * pooling above, update_status will get garbage (and send to all)
+	 */
+	if (!dstate_is_stale())
+		update_status();
 }
 
 void upsdrv_cleanup(void)
diff --git a/drivers/apcsmart.h b/drivers/apcsmart.h
index 41ebbe1..daf074d 100644
--- a/drivers/apcsmart.h
+++ b/drivers/apcsmart.h
@@ -28,15 +28,17 @@
 /* Basic UPS reply line structure */
 #define ENDCHAR 10		/* APC ends responses with LF */
 
-/* these two are only used during startup */
-#define IGNCHARS "\015+$|!~%?=*#&"	/* special characters to ignore */
+/* characters ignored by default */
+#define IGNCHARS "\015+$|!~%?=#&"	/* special characters to ignore */
+
+/* these one is used only during startup, due to ^Z sending certain characters such as # */
 #define MINIGNCHARS "\015+$|!"	/* minimum set of special characters to ignore */
 
 /* normal polls: characters we don't want to parse (including a few alerts) */
-#define POLL_IGNORE "\015?=*&|"
+#define POLL_IGNORE "\015&|"
 
-/* alert characters we care about - OL, OB, LB, not LB, RB */
-#define POLL_ALERT "$!%+#"
+/* alert characters we care about - OL, OB, LB, not LB, RB, OVER, not OVER */
+#define POLL_ALERT "$!%+#?="
 
 #define UPSDELAY	  50000	/* slow down multicharacter commands        */
 #define CMDLONGDELAY	1500000	/* some commands need a 1.5s gap for safety */
@@ -63,6 +65,7 @@
 #define APC_STAT_OVER	32	/* overload */
 #define APC_STAT_LB	64	/* low battery */
 #define APC_STAT_RB	128	/* replace battery */
+#define APC_STAT_ULB	256	/* low battery asserted by battery.charge / battery.runtime */
 
 /* serial protocol: special commands - initialization and such */
 #define APC_STATUS	'Q'
@@ -86,6 +89,7 @@
 
 #define APC_NASTY	0x0100	/* Nasty command - take care		*/
 #define APC_REPEAT	0x0200	/* Command needs sending twice		*/
+#define APC_CRUCIAL	0x0400	/* "crucial" variable, always check if it works	*/
 
 #define APC_FORMATMASK	0xFF0000 /* Mask for apc data formats */
 
@@ -109,6 +113,7 @@ typedef struct {
 
 apc_vartab_t	apc_vartab[] = {
 
+	{ "ups.firmware.old",  	0,			'V' },
 	{ "ups.firmware",  	0,			'b' },
 	{ "ups.firmware.aux",	0,			'v' },
 	{ "ups.model",		0,			0x01 },
@@ -172,7 +177,7 @@ apc_vartab_t	apc_vartab[] = {
 
 	{ "battery.date",	APC_STRING,		'x' },
 
-	{ "battery.charge",  	APC_POLL|APC_F_PERCENT,	'f' },
+	{ "battery.charge",  	APC_POLL|APC_F_PERCENT|APC_CRUCIAL,	'f' },
 	{ "battery.charge.restart",  
 				APC_F_PERCENT,		'e' },
 
@@ -180,7 +185,7 @@ apc_vartab_t	apc_vartab[] = {
 	{ "battery.voltage.nominal", 
 				0,			'g' },
 
-	{ "battery.runtime", 	APC_POLL|APC_F_MINUTES,	'j' },
+	{ "battery.runtime", 	APC_POLL|APC_F_MINUTES|APC_CRUCIAL,	'j' },
 	{ "battery.runtime.low",
 			 	APC_F_MINUTES,		'q' },
 
@@ -207,6 +212,7 @@ apc_vartab_t	apc_vartab[] = {
 #define APC_CMD_CALTOGGLE	'D'
 #define APC_CMD_SHUTDOWN	'K'
 #define APC_CMD_SOFTDOWN	'S'
+#define APC_CMD_GRACEDOWN	'@'
 #define APC_CMD_SIMPWF		'U'
 #define APC_CMD_BTESTTOGGLE	'W'
 #define APC_CMD_OFF		'Z'
@@ -232,6 +238,7 @@ apc_cmdtab_t	apc_cmdtab[] =
 	{ "test.battery.start",	0,			APC_CMD_BTESTTOGGLE },
 	{ "test.battery.stop",	0,			APC_CMD_BTESTTOGGLE },
 
+	{ "shutdown.return.grace",	APC_NASTY,	APC_CMD_GRACEDOWN },
 	{ "shutdown.return",	APC_NASTY,		APC_CMD_SOFTDOWN  },
 	{ "shutdown.stayoff",	APC_NASTY|APC_REPEAT,	APC_CMD_SHUTDOWN  },
 
@@ -260,28 +267,33 @@ struct {
 	{ "5ZM",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz/<>", 0 },
 	/* APC600 */
 	{ "6QD",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	{ "6QI",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	{ "6QI",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
 	{ "6TD",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
 	{ "6TI",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
 	/* SmartUPS 900 */
-	{ "7QD",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	{ "7QI",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	{ "7TD",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	{ "7TI",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	/* SmartUPS 1250. */
+	{ "7QD",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	{ "7QI",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	{ "7TD",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	{ "7TI",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	/* SmartUPS 900I */
+	{ "7II",	"79ABCEFGKLMNOPQSUVWXYZcfg", 0 },
+	/* SmartUPS 2000I */
+	{ "9II",	"79ABCEFGKLMNOPQSUVWXYZcfg", 0 },
+	{ "9GI",        "79ABCEFGKLMNOPQSUVWXYZcfg", 0 },
+	/* SmartUPS 1250 */
 	{ "8QD",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
 	{ "8QI",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	{ "8TD",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
-	{ "8TI",    "79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	{ "8TD",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
+	{ "8TI",	"79ABCDEFGKLMNOPQRSUVWXYZcefgjklmnopqrsuxz", 0 },
 	/* CS 350 */
 	{ "5.4.D",	"\1ABPQRSUYbdfgjmnx9",	0 },
 	/* Smart-UPS 600 */
-	{ "D9",     "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
-	{ "D8",     "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
-	{ "D7",     "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
-	{ "D6",     "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
-	{ "D5",     "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
-	{ "D4",     "789ABCEFGKLMNOPQRSUVWXYZ", 0 },
+	{  "D9",	"789ABCEFGKLMNOPQRSUVWXYZ", 0 },
+	{  "D8",	"789ABCEFGKLMNOPQRSUVWXYZ", 0 },
+	{  "D7",	"789ABCEFGKLMNOPQRSUVWXYZ", 0 },
+	{  "D6",	"789ABCEFGKLMNOPQRSUVWXYZ", 0 },
+	{  "D5",	"789ABCEFGKLMNOPQRSUVWXYZ", 0 },
+	{  "D4",	"789ABCEFGKLMNOPQRSUVWXYZ", 0 },
 
 	{ NULL,		NULL,			0 },
 };
-- 
1.7.2.1




More information about the Nut-upsdev mailing list