[Nut-upsdev] [PATCH/RFC v2 3/3] drivers/apcsmart: updates

Michal Soltys soltys at ziu.info
Mon Feb 7 13:11:59 UTC 2011


- adjust firmware_table_lookup(), so 'V' is used before 'b' - as 'b'
  might return something else than firmware version on older apc models

- remove APC_IGNORE flag, as APC_PRESENT can easily be used instead;
  adjust query_ups() accordingly

- add APC_CRUCIAL flag to mark variables that always must be verified,
  even if they are present in compatibility table or returned by 'a'

- add APC_USERCTRL to mark variables, than can be overriden at ups.conf
  level, even if they are modifiable (currently only
  battery.runtime.low)

- adjust protocol_verify() to handle APC_CRUCIAL and APC_USERCTRL

- add 'ignorelb' option, to not rely on internal ups' LB status. Adjust
  update_status() and alert_handler() accordingly

- add 'wugrace' option to specify additional wakeup delay for @nnn and
  @nn commands

- add sdcmd_*() and sdok() family of functions, to issue different kinds
  of shutdown commands (S, K, Z, CS, @nnn, @nn) and verify if they
  succeeded

- add upsdrv_shutdown_simple() and upsdrv_shutdown_advanced() functions,
  to handle old 'sdtype' and new 'advorder' options to control shutdown
  commands; upsdrv_shutdown() calls either of the two

- add 2 shutdown methods to 'sdtype' (@nnn and @nn)

- adjust do_capabilities() to verify APC_PRESENT, which is important for
  proper functioning of 'ignorelb' and overrides

- handle '?' and '=' alerts which generally correspond to OVER and ~OVER
  conditions; remove from ignored chars, adjust alert_handler()
  accordingly

- remove '*' from ignored characters, as it's a response to a successful
  shutdown command (mostly seen on older units, as newer ones prefer
  'OK')

- add 'V' (ups.firmware.old) variable and '@' (shutdown.return.grace)
  command to the tables

- add few old ups models to compatibility table

- update help in upsdrv_makevartable() and upsdrv_help()

- cosmetics: author, version, printf -> upsdebugx, tabs, conditions,
  comments, minor fixes

Signed-off-by: Michal Soltys <soltys at ziu.info>
---
 drivers/apcsmart.c |  562 +++++++++++++++++++++++++++++++++++++++++-----------
 drivers/apcsmart.h |   70 ++++---
 2 files changed, 490 insertions(+), 142 deletions(-)

diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c
index b3d482d..e05d687 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;
@@ -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;
@@ -252,8 +268,10 @@ static int query_ups(const char *var, int first)
 		return 0;
 	}
 
-	/* already known to not be supported? */
-	if (vt->flags & APC_IGNORE)
+	/*
+	 * not first run and already known to not be supported ?
+	 */
+	if (!first && !(vt->flags & APC_PRESENT))
 		return 0;
 
 	/* empty the input buffer (while allowing the alert handler to run) */
@@ -278,11 +296,10 @@ static int query_ups(const char *var, int first)
 
 	ser_comm_good();
 
-	if ((ret < 1) || (!strcmp(temp, "NA"))) {	/* not supported */
-		vt->flags |= APC_IGNORE;
+	if ((ret < 1) || (!strcmp(temp, "NA")))		/* not supported */
 		return 0;
-	}
 
+	vt->flags |= APC_PRESENT;
 	ptr = convert_data(vt, temp);
 	dstate_setinfo(vt->name, "%s", ptr);
 
@@ -293,7 +310,7 @@ static void do_capabilities(void)
 {
 	const	char	*ptr, *entptr;
 	char	upsloc, temp[512], cmd, loc, etmp[16], *endtemp;
-	int	nument, entlen, i, matrix, ret;
+	int	nument, entlen, i, matrix, ret, valid;
 	apc_vartab_t *vt;
 
 	upsdebugx(1, "APC - About to get capabilities string");
@@ -333,8 +350,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]", temp);
 		upslogx(LOG_ERR, "ERROR: unknown capability start char %c!", 
 			temp[0]);
 
@@ -377,9 +394,15 @@ static void do_capabilities(void)
 		entptr = &ptr[4];
 
 		vt = vartab_lookup_char(cmd);
+		/*
+		 * all the capabilities must first pass protocol_verify() tests
+		 * APC_PRESENT check below is crucial, as some of the variables are allowed
+		 * to be overriden by a user (currently for the sake of "ignorelb" option)
+		 */
+		valid = vt && ((loc == upsloc) || (loc == '4')) && (vt->flags & APC_PRESENT);
 
 		/* mark this as writable */
-		if (vt && ((loc == upsloc) || (loc == '4'))) {
+		if (valid) {
 			upsdebugx(1, "Supported capability: %02x (%c) - %s", 
 				cmd, loc, vt->name);
 
@@ -390,12 +413,10 @@ static void do_capabilities(void)
 		}
 
 		for (i = 0; i < nument; i++) {
-			snprintf(etmp, entlen + 1, "%s", entptr);
-
-			if (vt && ((loc == upsloc) || (loc == '4')))
-				dstate_addenum(vt->name, "%s",
-					convert_data(vt, etmp));
-
+			if (valid) {
+				snprintf(etmp, entlen + 1, "%s", entptr);
+				dstate_addenum(vt->name, "%s", convert_data(vt, etmp));
+			}
 			entptr += entlen;
 		}
 
@@ -428,9 +449,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;
+	}
+
 	ups_status_set();
 
-	status_commit();
 	dstate_dataok();
 
 	return 1;
@@ -464,7 +489,7 @@ static void oldapcsetup(void)
 
 static void protocol_verify(unsigned char cmd)
 {
-	int	i, found;
+	int	i, found, flags;
 
 	/* we might not care about this one */
 	if (strchr(CMD_IGN_CHARS, cmd))
@@ -479,7 +504,27 @@ static void protocol_verify(unsigned char cmd)
 			upsdebugx(3, "UPS supports variable [%s]",
 				apc_vartab[i].name);
 
-			/* load initial data */
+			if ((flags = dstate_getflags(apc_vartab[i].name)) >= 0) {
+				/*
+				 * variable has been defined at ups.conf level,
+				 * check if it's of an override.* kind and
+				 * overriding is allowed as per APC_USERCTRL
+				 * flag; if so - return without setting
+				 * its presence
+				 */
+				if ((apc_vartab[i].flags & APC_USERCTRL) && (flags & ST_FLAG_IMMUTABLE))
+					return;
+			}
+
+			/* be extra careful about the presence of certain
+			 * variables */
+			if ((apc_vartab[i].flags & APC_CRUCIAL) && !query_ups(apc_vartab[i].name, 1)) {
+				upsdebugx(1, "Your UPS doesn't actually support variable [%s]", apc_vartab[i].name);
+				upsdebugx(1, "Please report this error");
+				return;
+			}
+
+			/* mark as present, load initial data */
 			apc_vartab[i].flags |= APC_PRESENT;
 			poll_data(&apc_vartab[i]);
 
@@ -531,27 +576,28 @@ 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");
+		upslog_with_errno(LOG_ERR, "firmware_table_lookup: ser_send_char failed");
 		return 0;
 	}
 
 	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'. As 'b' doesn't always return
+	 * firmware version, we attempt that only if 'V' doesn't work.
 	 */
 	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");
+			upslog_with_errno(LOG_ERR, "firmware_table_lookup: ser_send_char failed");
 			return 0;
 		}
 
@@ -563,9 +609,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 +649,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
+	 *  sometimes return other things than a command set
+	 */
 	if (firmware_table_lookup() == 1)
 		return;
 
@@ -622,7 +673,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 +824,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];
 
-	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 minutes 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);
+
+	return 0;
+}
 
-	case 2:		/* instant shutdown */
-		printf("Sending power off command to UPS\n");
+/* shutdown: K - delayed poweroff */
+static int sdcmd_K(int dummy)
+{
+	ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
+	upsdebugx(1, "Issuing delayed poweroff");
 
-		ser_send_char(upsfd, APC_CMD_OFF);
-		usleep(CMDLONGDELAY);
-		ser_send_char(upsfd, APC_CMD_OFF);
+	ser_send_char(upsfd, APC_CMD_SHUTDOWN);
+	usleep(CMDLONGDELAY);
+	ser_send_char(upsfd, APC_CMD_SHUTDOWN);
 
-		break;
+	return sdok();
+}
 
-	case 1:
+/* 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();
+}
 
-		/* 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");
+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
 
-		ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
-		ser_send_pace(upsfd, UPSDELAY, "S at 000");
+#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;
+
+	default:
+		/*
+		 * Send @nnn or S, depending on OB / OL status
+		 */
+		if (status & APC_STAT_OL)		/* on line */
+			sdcmd_ATn(3);
+		else
+			sdcmd_S(0);
+	}
+}
 
-		/* S    - shutdown after 'p' grace period, only on battery */
-		/*        returns after 'e' charge % plus 'r' seconds      */
+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;
+		}
+	}
 
-		ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
+	/*
+	 * 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 +1442,10 @@ 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, "advorder", "Enable advanced shutdown control");
 }
 
 void upsdrv_initups(void)
@@ -1234,9 +1457,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 +1466,132 @@ 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 the driver's options:\n\n"
+
+	"  sdtype:\n"
+	"    see \"Simple shutdown method\" below for details\n\n"
+
+	"  advorder:\n"
+	"    see \"Advanced shutdown control\" below for details\n\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\n"
+
+	"  ignorelb:\n"
+	"    Normally, APC upses will assert LB signal themselves - basing the\n"
+	"    decision on load / battery state / calculated runtimes / etc. The\n"
+	"    ups will not power down itself - nut will have to issue the\n"
+	"    appropriate command (which you can provide through 'sdtype' and\n"
+	"    'advorder' options). In practice though - it often happens\n"
+	"    that LB state provided by an ups is oversensitive. For example, a bit\n"
+	"    heavier loaded unit might almost instantly hit battery.runtime.low\n"
+	"    (minimum allowed in eeprom is usually 2 minutes)\n"
+	"    on power outage, even if after a few moments, battery.runtime would\n"
+	"    rise by a few minutes - and in reality, will have no problems being\n"
+	"    on battery for significant amount of time. Also, there're valid\n"
+	"    reasons for user to want to rely only on battery.charge.\n\n"
+
+	"    'ignorelb' options will cause nut to *ignore* LB state as set by\n"
+	"    ups, and derive it only from the following conditions:\n\n"
+
+	"    battery.charge  < battery.charge.low\n"
+	"    OR\n"
+	"    battery.runtime < battery.runtime.low\n\n"
+
+	"    To use the option properly, you should provide\n"
+	"    override.battery.charge.low and override.battery.runtime.low in\n"
+	"    ups.conf in appropriate ups section. Override prefix will cause the\n"
+	"    variable to become read only and immutable. This doesn't matter with\n"
+	"    battery.charge.low (APC units don't have one), but it's important to\n"
+	"    remember that fact regarding battery.runtime.low - driver will\n"
+	"    neither poll the variable from the unit, nor allow the variable to\n"
+	"    be programmed (note that not all units have that variable present\n"
+	"    either).\n\n"
+
+	"    For example:\n"
+	"      [myapc]\n"
+	"        ignorelb\n"
+	"        override.battery.charge.low = 10\n"
+	"        override.battery.runtime.low = -1\n\n"
+
+	"    This will make nut explicitly ignore battery.runtime, and assert\n"
+	"    LB when current battery.charge goes under 10%%\n\n"
+
+	"Shutdown 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 method:\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\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"
+	);
 }
 
 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 +1604,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();
 }
diff --git a/drivers/apcsmart.h b/drivers/apcsmart.h
index 41ebbe1..3d29450 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 */
@@ -76,9 +78,10 @@
 
 /* Driver command table flag values */
 
-#define APC_POLL	0x0001	/* Poll this variable regularly		*/
-#define APC_IGNORE	0x0002	/* Never poll this			*/
-#define APC_PRESENT	0x0004	/* Capability seen on this UPS		*/
+#define APC_POLL	0x0001	/* Poll this variable regularly, if present		*/
+#define APC_CRUCIAL	0x0002	/* "crucial" variable, always check if it's present	*/
+#define APC_PRESENT	0x0004	/* presence verified - command can be polled / executed	*/
+#define APC_USERCTRL	0x0008	/* variable's readout value can be controlled by a user	*/
 
 #define APC_RW		0x0010	/* read-write variable			*/
 #define APC_ENUM	0x0020	/* enumerated type			*/
@@ -109,15 +112,10 @@ 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 },
-
-/* FUTURE: depends on variable naming scheme */
-#if 0
-	{ "ups.model.code",	0,			'V' },
-#endif
-
 	{ "ups.serial",		0,			'n' },
 	{ "ups.mfr.date", 	0,			'm' },
 
@@ -172,7 +170,8 @@ 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,9 +179,11 @@ 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' },
+				APC_F_MINUTES|APC_USERCTRL,
+							'q' },
 
 	{ "battery.packs", 	APC_F_DEC,		'>' },
 	{ "battery.packs.bad", 	APC_F_DEC,		'<' },
@@ -207,6 +208,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 +234,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 +263,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