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 ?