Michal Soltys
2011-Feb-07 13:11 UTC
[Nut-upsdev] [PATCH/RFC v2 0/3] Updates to ACP smart driver
This is 2nd version of the earlier patch featuring a few new features and fixes to the apcsmart driver, following the remarks in: http://www.mail-archive.com/nut-upsdev at lists.alioth.debian.org/msg02294.html Major changes from v1: - handle battery.charge and battery.runtime checks at main.c level - handle "immutable but writable" conflict gracefully at driver level - dstate_getflags() and status_set_uniq() added (hope this is fine) Apart from that, minor changes and adjusted help. Michal Soltys (3): drivers/dstate: add dstate_getflags() and status_set_uniq() drivers/main.c: add tests for LB condition drivers/apcsmart: updates drivers/apcsmart.c | 562 +++++++++++++++++++++++++++++++++++++++++----------- drivers/apcsmart.h | 70 ++++--- drivers/dstate.c | 21 ++ drivers/dstate.h | 2 + drivers/main.c | 69 +++++++ 5 files changed, 582 insertions(+), 142 deletions(-) -- 1.7.2.1
Michal Soltys
2011-Feb-07 13:11 UTC
[Nut-upsdev] [PATCH/RFC v2 1/3] drivers/dstate: add dstate_getflags() and status_set_uniq()
Add dstate_getflags() to the interface, allowing flag interrogation. Add status_set_uniq() working similary to status_set() but making sure that the added status word is not duplicated. Signed-off-by: Michal Soltys <soltys at ziu.info> --- drivers/dstate.c | 21 +++++++++++++++++++++ drivers/dstate.h | 2 ++ 2 files changed, 23 insertions(+), 0 deletions(-) diff --git a/drivers/dstate.c b/drivers/dstate.c index b95cec9..0cb5896 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -691,6 +691,11 @@ const char *dstate_getinfo(const char *var) return state_getinfo(dtree_root, var); } +int dstate_getflags(const char *var) +{ + return state_getflags(dtree_root, var); +} + void dstate_addcmd(const char *cmdname) { int ret; @@ -806,6 +811,22 @@ void status_set(const char *buf) } } +/* add a status element, making sure it's not already present */ +void status_set_uniq(const char *buf) +{ + const char *ptr = status_buf; + int len = strlen(buf); + + /* make sure it's on a word boundary */ + while ((ptr = strstr(ptr, buf))) { + if ((!ptr[len] || ptr[len] == ' ') && (ptr == status_buf || ptr[-1] == ' ')) + return; + ptr += len; + } + + status_set(buf); +} + /* write the status_buf into the externally visible dstate storage */ void status_commit(void) { diff --git a/drivers/dstate.h b/drivers/dstate.h index 7ffe899..91bf873 100644 --- a/drivers/dstate.h +++ b/drivers/dstate.h @@ -48,6 +48,7 @@ int dstate_addenum(const char *var, const char *fmt, ...) void dstate_setflags(const char *var, int flags); void dstate_setaux(const char *var, int aux); const char *dstate_getinfo(const char *var); +int dstate_getflags(const char *var); void dstate_addcmd(const char *cmdname); int dstate_delinfo(const char *var); int dstate_delenum(const char *var, const char *val); @@ -66,6 +67,7 @@ void status_init(void); /* add a status element */ void status_set(const char *buf); +void status_set_uniq(const char *buf); /* write the temporary status_buf into ups.status */ void status_commit(void); -- 1.7.2.1
Michal Soltys
2011-Feb-07 13:11 UTC
[Nut-upsdev] [PATCH/RFC v2 2/3] drivers/main.c: add tests for LB condition
This patch implements the generic test of the following conditions: battery.charge < battery.charge.low OR battery.runtime < battery.runtime.low Basing on those, LB can be set in addition to what driver-specific upsdrv_updateinfo() does. Signed-off-by: Michal Soltys <soltys at ziu.info> --- drivers/main.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 69 insertions(+), 0 deletions(-) diff --git a/drivers/main.c b/drivers/main.c index d14f990..8269c3c 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -461,6 +461,74 @@ static void setup_signals(void) sigaction(SIGPIPE, &sa, NULL); } +/* + * check battery charge condition (float) + */ +static int lb_chk_charge(void) +{ + const char *curr, *low; + float x, n; + + curr = dstate_getinfo("battery.charge"); + low = dstate_getinfo("battery.charge.low"); + + if (curr && low) { + errno = 0; + x = strtof(curr, NULL); + n = strtof(low, NULL); + upsdebugx(4, "lb_chk_charge: %.2f [str: %s] < %.2f [str: %s] ?", x, curr, n, low); + if (!errno && x < n) + return 1; + } + return 0; +} + +/* + * check battery runtime condition (integer) + */ +static int lb_chk_runtime(void) +{ + const char *curr, *low; + long int x, n; + + curr = dstate_getinfo("battery.runtime"); + low = dstate_getinfo("battery.runtime.low"); + + if (curr && low) { + errno = 0; + x = strtol(curr, NULL, 10); + n = strtol(low, NULL, 10); + upsdebugx(4, "lb_chk_runtime: %ld [str: %s] < %ld [str: %s] ?", x, curr, n, low); + if (!errno && x < n) + return 1; + } + return 0; +} + +/* + * try to manually assert LB, considering following conditions: + * + * battery.charge < battery.charge.low + * OR + * battery.runtime < battery.runtime.low + */ +static void test_lb_condition(void) +{ + const char *status; + + if (!(lb_chk_charge() || lb_chk_runtime())) + return; /* nothing to do */ + + /* get current ups.status */ + if (!(status = dstate_getinfo("ups.status"))) + fatalx(EXIT_FAILURE, "test_lb_condition: ups.status not defined ?"); + + status_init(); + status_set(status); + status_set_uniq("LB"); + status_commit(); +} + int main(int argc, char **argv) { struct passwd *new_uid = NULL; @@ -627,6 +695,7 @@ int main(int argc, char **argv) timeout.tv_sec += poll_interval; upsdrv_updateinfo(); + test_lb_condition(); while (!dstate_poll_fds(timeout, extrafd) && !exit_flag) { /* repeat until time is up or extrafd has data */ -- 1.7.2.1
Michal Soltys
2011-Feb-07 13:11 UTC
[Nut-upsdev] [PATCH/RFC v2 3/3] drivers/apcsmart: updates
- 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
Michal Soltys
2011-Feb-15 23:10 UTC
[Nut-upsdev] [PATCH/RFC v2 0/3] Updates to ACP smart driver
On 11-02-07 14:11, Michal Soltys wrote:> This is 2nd version of the earlier patch featuring a few new features > and fixes to the apcsmart driver, following the remarks in: > > http://www.mail-archive.com/nut-upsdev at lists.alioth.debian.org/msg02294.html > > Major changes from v1: > > - handle battery.charge and battery.runtime checks at main.c level > - handle "immutable but writable" conflict gracefully at driver level > - dstate_getflags() and status_set_uniq() added (hope this is fine) > > Apart from that, minor changes and adjusted help. > > > Michal Soltys (3): > drivers/dstate: add dstate_getflags() and status_set_uniq() > drivers/main.c: add tests for LB condition > drivers/apcsmart: updates > > drivers/apcsmart.c | 562 +++++++++++++++++++++++++++++++++++++++++----------- > drivers/apcsmart.h | 70 ++++--- > drivers/dstate.c | 21 ++ > drivers/dstate.h | 2 + > drivers/main.c | 69 +++++++ > 5 files changed, 582 insertions(+), 142 deletions(-) >So, any +/- comments about this approach ?