- 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 <[email protected]>
---
 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 <[email protected]>\n" \
-       "Nigel Metheringham <[email protected]>",
+       "Nigel Metheringham <[email protected]>\n"
+       "Michal Soltys <[email protected]>",
        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@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


_______________________________________________
Nut-upsdev mailing list
[email protected]
http://lists.alioth.debian.org/mailman/listinfo/nut-upsdev

Reply via email to