[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