diff --git a/NEWS.adoc b/NEWS.adoc index 91b22aa97f..1501768da4 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -71,6 +71,12 @@ https://github.com/networkupstools/nut/milestone/11 a `*_s()` variant was available was not handled correctly. [PR #2583] * A recently introduced `allow_killpower` did not actually work as an `ups.conf` flag (only as a protocol command). [issue #2605, PR #2606] + * The ability of two copies of the driver program to talk to each other + with `upsdrvquery.c` code was not complete for the case of indefinite + `select()` wait timeout. Now `upsdrvquery_read_timeout()` fixed private + use of `struct timeval={-1,-1}` as a trigger to `select(..., NULL)`, + as logged in one part of code and not handled in the other, for the + indefinite wait [#1922, #2392, #2686, #2670] - development iterations of NUT should now identify with not only the semantic version of a preceding release, but with git-derived information about the @@ -153,6 +159,12 @@ https://github.com/networkupstools/nut/milestone/11 models with serial port, made by NHS Sistemas Eletronicos LTDA and popular in Brazil. Currently this driver only builds on Linux. [#2692] + - `usbhid-ups` and `netxml-ups` updated to handle "No battery installed!" + alarm also to set the `RB` (Replace Battery) value in `ups.status`. + This may cause dual triggering of notifications (as an `ALARM` generally + and as an important `REPLBATT` status in particular) in `upsmon`, but + better safe than sorry. [#415] + - usbhid-ups updates: * Support of the `onlinedischarge_log_throttle_hovercharge` in the NUT v2.8.2 release was found to be incomplete. [#2423, follow-up to #2215] @@ -180,7 +192,7 @@ https://github.com/networkupstools/nut/milestone/11 the old behavior (if some devices do need it), while a fix is applied by default: `powercom_sdcmd_byte_order_fallback`. [PR #2480] * `cps-hid` subdriver now supports more variables, as available on e.g. - CP1350EPFCLCD model. [PR #2540] + CP1350EPFCLCD model, including temperature. [PRs #2540, #2711] * USB parameters (per `usb_communication_subdriver_t`) are now set back to their default values during enumeration after probing each subdriver. Having an unrelated device connected with a VID:PID matching the @@ -246,7 +258,7 @@ https://github.com/networkupstools/nut/milestone/11 that there is no common standard for what constitutes an alarm and such alarm states were also previously observed for less severe reasons. This depends on the manufacturer/device-specific implementation in the driver. - [issue #2657, PR #2658] + [issues #415, #2657, PR #2658] * Updated documentation, end-user clients (CGI, NUT-Monitor UI); * Updated `upsmon` client with ability to report entering and exiting the ALARM status if reported by the driver; @@ -261,6 +273,14 @@ https://github.com/networkupstools/nut/milestone/11 from templates). [issue #321, PR #2383] * added an `OBLBDURATION` (seconds) setting to optionally delay raising the alarm for immediate shutdown in critical situation. [#321] + * optimized `parse_status()` by not checking further strings if we had + a match; report unexpected tokens in debug log. [#415] + * revised internal `do_notify()` method to support formatting strings + with two `%s` placeholders, to use if certain use-cases pass any extra + information (e.g. not just "we have alarms" but their values too). [#415] + * introduced handling for "unknown" `ups.status` tokens, reporting them + as "OTHER" notification type (whenever the set of such tokens appears + or changes) or "NOTOTHER" when they disappear. [#415] - More systemd integration: * Introduced a `nut-sleep.service` unit which stops `nut.target` when a @@ -325,6 +345,24 @@ during a NUT build. for `bcmxcp_usb`, `richcomm_usb` and `nutdrv_atcl_usb` drivers for now [#1763, #1764, #1768, #2580] + - all drivers should now support the optional `sdcommands` setting with + a site-local list of instant commands to handle `upsdrv_shutdown()`, + which may be useful in cases when the driver's built-in commands + (or their order) do not meet the goals of particular NUT deployment. + This can also help with shutdown endgame testing, using a mock command like + starting the beeper (where supported) to verify that the UPS communications + happen as expected, without compromising the load connected to the UPS. ++ +Also defined `EF_EXIT_SUCCESS` and `EF_EXIT_FAILURE` in `include/common.h` +to avoid magic numbers in code like `set_exit_flag(-2)`, and revised whether +it is getting set at all in "killpower" vs. other cases, based on new +`handling_upsdrv_shutdown` internal flag. ++ +NOTE: during this overhaul, many older drivers got their first ever supported +INSTCMD such as `shutdown.return`, `shutdown.stayoff` or `load.off`. Default +logic that was previously the content of `upsdrv_shutdown()` methods was often +relocated into new `shutdown.default` INSTCMD definitions. [#2670] + - common code: * introduced a `NUT_DEBUG_SYSLOG` environment variable to tweak activation of syslog message emission (and related detachment of `stderr` when diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 2a2bfe79ff..3912b1149a 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -59,7 +59,13 @@ Changes from 2.8.2 to 2.8.3 some alarms can contribute to unwanted/early shutdowns. For this reason a `0|1` setting `ALARMCRITICAL` was introduced into `upsmon.conf` (default is `1`), for such users to be able to prevent their `upsmon` from treating - the `ALARM` status as overly severe when it is not in fact. [#2658] + the `ALARM` status as overly severe when it is not in fact. [#2658, #415] + +- `usbhid-ups` and `netxml-ups` updated to handle "No battery installed!" + alarm also to set the `RB` (Replace Battery) value in `ups.status`. + This may cause dual triggering of notifications (as an `ALARM` generally + and as an important `REPLBATT` status in particular) in `upsmon`, but + better safe than sorry. [#415] - `usbhid-ups` subdriver `PowerCOM HID` subdriver sent UPS `shutdown` and `stayoff` commands in wrong byte order, at least for devices currently diff --git a/clients/upscmd.c b/clients/upscmd.c index 868009bf53..3745014906 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -195,6 +195,9 @@ static void do_cmd(char **argv, const int argc) ) { /* reply as usual */ fprintf(stderr, "%s\n", buf); + upsdebugx(1, "%s: 'OK' only means the NUT data server accepted the request as valid, " + "but as we did not wait for result, we do not know if it was handled in fact.", + __func__); return; } @@ -282,9 +285,20 @@ int main(int argc, char **argv) uint16_t port; ssize_t ret; int have_un = 0, have_pw = 0, cmdlist = 0; - char buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF]; + char buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF], *s = NULL; const char *prog = xbasename(argv[0]); + /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc + * and NUT methods called from it. This line aims to just initialize + * the subsystem, and set initial timestamp. Debugging the client is + * primarily of use to developers, so is not exposed via `-D` args. + */ + s = getenv("NUT_DEBUG_LEVEL"); + if (s && str_to_int(s, &i, 10) && i > 0) { + nut_debug_level = i; + } + upsdebugx(1, "Starting NUT client: %s", prog); + while ((i = getopt(argc, argv, "+lhu:p:t:wV")) != -1) { switch (i) @@ -459,6 +473,6 @@ int main(int argc, char **argv) /* Formal do_upsconf_args implementation to satisfy linker on AIX */ #if (defined NUT_PLATFORM_AIX) void do_upsconf_args(char *upsname, char *var, char *val) { - fatalx(EXIT_FAILURE, "INTERNAL ERROR: formal do_upsconf_args called"); + fatalx(EXIT_FAILURE, "INTERNAL ERROR: formal do_upsconf_args called"); } #endif /* end of #if (defined NUT_PLATFORM_AIX) */ diff --git a/clients/upsmon.c b/clients/upsmon.c index 647418c4ba..8c6a7876d2 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3,6 +3,7 @@ Copyright (C) 1998 Russell Kroll 2012 Arnaud Quette + 2017 Eaton (author: Arnaud Quette ) 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify @@ -175,6 +176,9 @@ static int nut_debug_level_global = -1; */ static int nut_debug_level_args = 0; +/* pre-declare internal methods */ +static int get_var(utype_t *ups, const char *var, char *buf, size_t bufsize); + static void setflag(int *val, int flag) { *val |= flag; @@ -295,7 +299,7 @@ static void notify(const char *notice, int flags, const char *ntype, #endif upsdebugx(6, "%s: sending notification for [%s]: type %s with flags 0x%04x: %s", - __func__, upsname, ntype, flags, notice); + __func__, upsname ? upsname : "upsmon itself", ntype, flags, notice); if (flag_isset(flags, NOTIFY_IGNORE)) { upsdebugx(6, "%s: NOTIFY_IGNORE", __func__); @@ -376,7 +380,7 @@ static void notify(const char *notice, int flags, const char *ntype, #endif } -static void do_notify(const utype_t *ups, int ntype) +static void do_notify(const utype_t *ups, int ntype, const char *extra) { int i; char msg[SMALLBUF], *upsname = NULL; @@ -387,6 +391,9 @@ static void do_notify(const utype_t *ups, int ntype) for (i = 0; notifylist[i].name != NULL; i++) { if (notifylist[i].type == ntype) { + const char *msgfmt = (notifylist[i].msg ? notifylist[i].msg : notifylist[i].stockmsg); + char *s = strstr(msgfmt, "%s"); + upsdebugx(2, "%s: ntype 0x%04x (%s)", __func__, ntype, notifylist[i].name); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL @@ -398,9 +405,33 @@ static void do_notify(const utype_t *ups, int ntype) #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif - snprintf(msg, sizeof(msg), - notifylist[i].msg ? notifylist[i].msg : notifylist[i].stockmsg, - ups ? ups->sys : ""); + if (s) { + /* FIXME: Check user inputs to rule out + * any *OTHER* formatting characters. + * NOTE: Not addressing now, PR pending + * and queued for NUT v2.8.4 */ + + /* Got at least one "%s" */ + if (strstr(s + 1, "%s")) { + /* Got a second "%s" */ + /* FIXME: if (extra != NULL) and we + * only have one "%s", plaster another + * to "msgfmt" and follow this path? + */ + snprintf(msg, sizeof(msg), + msgfmt, + upsname ? upsname : "", + NUT_STRARG(extra)); + } else { + snprintf(msg, sizeof(msg), + msgfmt, + upsname ? upsname : ""); + } + } else { + /* No "%s" in this msgfmt! e.g. NOTIFY_NOPARENT, + * SUSPEND_STARTING, SUSPEND_FINISHED ... */ + snprintf(msg, sizeof(msg), "%s", msgfmt); + } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif @@ -597,7 +628,7 @@ static void ups_is_alive(utype_t *ups) /* only notify for 0->1 transitions (to ignore the first connect) */ if (ups->commstate == 0) - do_notify(ups, NOTIFY_COMMOK); + do_notify(ups, NOTIFY_COMMOK, NULL); ups->commstate = 1; } @@ -612,7 +643,7 @@ static void ups_is_gone(utype_t *ups) ups->commstate = 0; /* COMMBAD is the initial loss of communications */ - do_notify(ups, NOTIFY_COMMBAD); + do_notify(ups, NOTIFY_COMMBAD, NULL); return; } @@ -625,7 +656,7 @@ static void ups_is_gone(utype_t *ups) /* now only complain if we haven't lately */ if ((now - ups->lastncwarn) > nocommwarntime) { /* NOCOMM indicates a persistent condition */ - do_notify(ups, NOTIFY_NOCOMM); + do_notify(ups, NOTIFY_NOCOMM, NULL); ups->lastncwarn = now; } } @@ -672,7 +703,7 @@ static void ups_is_off(utype_t *ups) upsdebugx(3, "%s: %s (first time)", __func__, ups->sys); /* must have changed from !OFF to OFF, so notify */ - do_notify(ups, NOTIFY_OFF); + do_notify(ups, NOTIFY_OFF, NULL); setflag(&ups->status, ST_OFF); } @@ -682,7 +713,7 @@ static void ups_is_notoff(utype_t *ups) ups->offsince = 0; ups->offstate = 0; if (flag_isset(ups->status, ST_OFF)) { /* actual change */ - do_notify(ups, NOTIFY_NOTOFF); + do_notify(ups, NOTIFY_NOTOFF, NULL); clearflag(&ups->status, ST_OFF); try_restore_pollfreq(ups); } @@ -703,7 +734,7 @@ static void ups_is_bypass(utype_t *ups) /* must have changed from !BYPASS to BYPASS, so notify */ - do_notify(ups, NOTIFY_BYPASS); + do_notify(ups, NOTIFY_BYPASS, NULL); setflag(&ups->status, ST_BYPASS); } @@ -712,7 +743,7 @@ static void ups_is_notbypass(utype_t *ups) /* Called when BYPASS is NOT among known states */ ups->bypassstate = 0; if (flag_isset(ups->status, ST_BYPASS)) { /* actual change */ - do_notify(ups, NOTIFY_NOTBYPASS); + do_notify(ups, NOTIFY_NOTBYPASS, NULL); clearflag(&ups->status, ST_BYPASS); try_restore_pollfreq(ups); } @@ -739,7 +770,11 @@ static void ups_is_eco(utype_t *ups) /* must have changed from !ECO to ECO, so notify */ - do_notify(ups, NOTIFY_ECO); + /* FIXME: Dig in driver/vendor specific info points to discern + * ECO, HE, ESS, ABM and other fancy words, with optional extra + * string argument? + */ + do_notify(ups, NOTIFY_ECO, NULL); setflag(&ups->status, ST_ECO); } @@ -748,7 +783,7 @@ static void ups_is_noteco(utype_t *ups) /* Called when ECO is NOT among known states */ ups->ecostate = 0; if (flag_isset(ups->status, ST_ECO)) { /* actual change */ - do_notify(ups, NOTIFY_NOTECO); + do_notify(ups, NOTIFY_NOTECO, NULL); clearflag(&ups->status, ST_ECO); try_restore_pollfreq(ups); } @@ -756,8 +791,26 @@ static void ups_is_noteco(utype_t *ups) static void ups_is_alarm(utype_t *ups) { - if (flag_isset(ups->status, ST_ALARM)) { /* no change */ - upsdebugx(4, "%s: %s (no change)", __func__, ups->sys); + /* Track the earlier reported string, re-notify if needed */ + static char alarms_prev[SMALLBUF]; + char alarms[SMALLBUF]; + + if (flag_isset(ups->status, ST_ALARM)) { /* potentially no change */ + /* TODO: add device.alarm.count? */ + if (get_var(ups, "alarm", alarms, sizeof(alarms)) == 0) { + if (!strcmp(alarms_prev, alarms)) { + upsdebugx(4, "%s: %s (no change)", __func__, ups->sys); + } else { + upsdebugx(4, "%s: %s: updated ups.alarm value is %s", + __func__, ups->sys, alarms); + strncpy(alarms_prev, alarms, sizeof(alarms_prev)); + /* FIXME: Check for two "%s" placeholders? */ + do_notify(ups, NOTIFY_ALARM, alarms); + } + } else { + upsdebugx(4, "%s: %s: still on alarm, failed to get current ups.alarm value", + __func__, ups->sys); + } return; } @@ -769,7 +822,20 @@ static void ups_is_alarm(utype_t *ups) /* must have changed from !ALARM to ALARM, so notify */ - do_notify(ups, NOTIFY_ALARM); + /* Pass `ups.status` string as the extra argument, + * so if the formatting string allows - it is detailed + * in the notification. */ + if (get_var(ups, "alarm", alarms, sizeof(alarms)) == 0) { + upsdebugx(4, "%s: %s: current ups.alarm value is %s", + __func__, ups->sys, alarms); + do_notify(ups, NOTIFY_ALARM, alarms); + } else { + upsdebugx(4, "%s: %s: failed to get current ups.alarm value", + __func__, ups->sys); + do_notify(ups, NOTIFY_ALARM, NULL); + alarms[0] = '\0'; + } + strncpy(alarms_prev, alarms, sizeof(alarms_prev)); setflag(&ups->status, ST_ALARM); } @@ -778,7 +844,7 @@ static void ups_is_notalarm(utype_t *ups) /* Called when ALARM is NOT among known states */ ups->alarmstate = 0; if (flag_isset(ups->status, ST_ALARM)) { /* actual change */ - do_notify(ups, NOTIFY_NOTALARM); + do_notify(ups, NOTIFY_NOTALARM, NULL); clearflag(&ups->status, ST_ALARM); try_restore_pollfreq(ups); } @@ -799,7 +865,7 @@ static void ups_on_batt(utype_t *ups) /* must have changed from OL to OB, so notify */ - do_notify(ups, NOTIFY_ONBATT); + do_notify(ups, NOTIFY_ONBATT, NULL); setflag(&ups->status, ST_ONBATT); clearflag(&ups->status, ST_ONLINE); } @@ -817,7 +883,7 @@ static void ups_on_line(utype_t *ups) /* ignore the first OL at startup, otherwise send the notifier */ if (ups->linestate != -1) - do_notify(ups, NOTIFY_ONLINE); + do_notify(ups, NOTIFY_ONLINE, NULL); ups->linestate = 1; @@ -855,7 +921,7 @@ static void doshutdown(void) upslogx(LOG_CRIT, "Executing automatic power-fail shutdown"); wall("Executing automatic power-fail shutdown\n"); - do_notify(NULL, NOTIFY_SHUTDOWN); + do_notify(NULL, NOTIFY_SHUTDOWN, NULL); sleep(finaldelay); @@ -1029,6 +1095,14 @@ static int get_var(utype_t *ups, const char *var, char *buf, size_t bufsize) numq = 3; } + if (!strcmp(var, "alarm")) { + /* Opaque string */ + query[0] = "VAR"; + query[1] = ups->upsname; + query[2] = "ups.alarm"; + numq = 3; + } + if (numq == 0) { upslogx(LOG_ERR, "get_var: programming error: var=%s", var); return -1; @@ -1416,7 +1490,7 @@ static void ups_low_batt(utype_t *ups) /* must have changed from !LB to LB, so notify */ - do_notify(ups, NOTIFY_LOWBATT); + do_notify(ups, NOTIFY_LOWBATT, NULL); setflag(&ups->status, ST_LOWBATT); } @@ -1427,7 +1501,7 @@ static void upsreplbatt(utype_t *ups) time(&now); if ((now - ups->lastrbwarn) > rbwarntime) { - do_notify(ups, NOTIFY_REPLBATT); + do_notify(ups, NOTIFY_REPLBATT, NULL); ups->lastrbwarn = now; } } @@ -1443,7 +1517,7 @@ static void ups_is_cal(utype_t *ups) /* must have changed from !CAL to CAL, so notify */ - do_notify(ups, NOTIFY_CAL); + do_notify(ups, NOTIFY_CAL, NULL); setflag(&ups->status, ST_CAL); } @@ -1451,7 +1525,7 @@ static void ups_is_notcal(utype_t *ups) { /* Called when CAL is NOT among known states */ if (flag_isset(ups->status, ST_CAL)) { /* actual change */ - do_notify(ups, NOTIFY_NOTCAL); + do_notify(ups, NOTIFY_NOTCAL, NULL); clearflag(&ups->status, ST_CAL); try_restore_pollfreq(ups); } @@ -1468,7 +1542,7 @@ static void ups_fsd(utype_t *ups) /* must have changed from !FSD to FSD, so notify */ - do_notify(ups, NOTIFY_FSD); + do_notify(ups, NOTIFY_FSD, NULL); setflag(&ups->status, ST_FSD); } @@ -1519,6 +1593,17 @@ static void redefine_ups(utype_t *ups, unsigned int pv, const char *un, ups->retain = 1; if (ups->pv != pv) { + /* TOCHECK: We start a config loading loop with totalpv=0 + * initially or in reload_conf(), and ignore duplicates + * in addups() unless reloading. If we reload a config + * with two definitions of same ups->sys name, will their + * power values be not ignored and add up? Should we + * subtract the older value from totalpv here? How do + * we differentiate (cookie?) first seeing an ups->sys + * which we knew about earlier during a reload_conf() + * from seeing it again? This is a somewhat rare case + * of self-inflicted pain, if it does even happen :) + */ upslogx(LOG_INFO, "UPS [%s]: redefined power value to %d", ups->sys, pv); ups->pv = pv; @@ -1595,7 +1680,8 @@ static void redefine_ups(utype_t *ups, unsigned int pv, const char *un, /* secondary|slave -> primary|master */ if ( ( (!strcasecmp(managerialOption, "primary")) || (!strcasecmp(managerialOption, "master")) ) - && (!flag_isset(ups->status, ST_PRIMARY)) ) { + && (!flag_isset(ups->status, ST_PRIMARY)) + ) { upslogx(LOG_INFO, "UPS [%s]: redefined as a primary", ups->sys); setflag(&ups->status, ST_PRIMARY); @@ -1607,7 +1693,8 @@ static void redefine_ups(utype_t *ups, unsigned int pv, const char *un, /* primary|master -> secondary|slave */ if ( ( (!strcasecmp(managerialOption, "secondary")) || (!strcasecmp(managerialOption, "slave")) ) - && (flag_isset(ups->status, ST_PRIMARY)) ) { + && (flag_isset(ups->status, ST_PRIMARY)) + ) { upslogx(LOG_INFO, "UPS [%s]: redefined as a secondary", ups->sys); clearflag(&ups->status, ST_PRIMARY); return; @@ -1681,7 +1768,8 @@ static void addups(int reloading, const char *sys, const char *pvs, tmp = tmp->next; } - tmp = xmalloc(sizeof(utype_t)); + tmp = xcalloc(1, sizeof(utype_t)); + /* TOTHINK: init (UPSCONN_t)tmp->conn struct fields too? */ tmp->sys = xstrdup(sys); tmp->pv = pv; @@ -1692,11 +1780,16 @@ static void addups(int reloading, const char *sys, const char *pvs, tmp->pw = xstrdup(pw); tmp->status = 0; + tmp->status_tokens = NULL; tmp->retain = 1; - /* ignore initial COMMOK and ONLINE by default */ + /* ignore initial COMMOK, ONLINE, etc. by default */ tmp->commstate = -1; tmp->linestate = -1; + tmp->offstate = -1; + tmp->bypassstate = -1; + tmp->ecostate = -1; + tmp->alarmstate = -1; /* forget poll-failure logging throttling */ tmp->pollfail_log_throttle_count = -1; @@ -1707,6 +1800,9 @@ static void addups(int reloading, const char *sys, const char *pvs, tmp->lastrbwarn = 0; tmp->lastncwarn = 0; + tmp->offsince = 0; + tmp->oblbsince = 0; + if ( (!strcasecmp(managerialOption, "primary")) || (!strcasecmp(managerialOption, "master")) ) { setflag(&tmp->status, ST_PRIMARY); @@ -2248,6 +2344,14 @@ static void ups_free(utype_t *ups) free(ups->hostname); free(ups->un); free(ups->pw); + + /* We usually free and nullify these as we have no use for them, + * but if something remains (had active alert etc.) - do it here: */ + if (ups->status_tokens) { + state_infofree(ups->status_tokens); + ups->status_tokens = NULL; + } + free(ups); } @@ -2272,6 +2376,7 @@ static void upsmon_cleanup(void) free(shutdowncmd); free(notifycmd); free(powerdownflag); + free(configfile); for (i = 0; notifylist[i].name != NULL; i++) { free(notifylist[i].msg); @@ -2429,7 +2534,9 @@ static int try_connect(utype_t *ups) /* deal with the contents of STATUS or ups.status for this ups */ static void parse_status(utype_t *ups, char *status) { - char *statword, *ptr; + char *statword, *ptr, other_stat_words[SMALLBUF]; + int handled_stat_words = 0, changed_other_stat_words = 0; + st_tree_timespec_t st_start; clear_alarm(); @@ -2462,41 +2569,179 @@ static void parse_status(utype_t *ups, char *status) ups_is_notalarm(ups); statword = status; + /* Track what status tokens we no longer see */ + state_get_timestamp(&st_start); + other_stat_words[0] = '\0'; /* split up the status words and parse each one separately */ while (statword != NULL) { + int handled = 0; + ptr = strchr(statword, ' '); if (ptr) *ptr++ = '\0'; upsdebugx(3, "parsing: [%s]", statword); + handled_stat_words++; - if (!strcasecmp(statword, "OL")) + /* Keep in sync with "Status data" chapter of docs/new-drivers.txt */ + if (!strcasecmp(statword, "OL")) { ups_on_line(ups); - if (!strcasecmp(statword, "OB")) + handled++; + } + else if (!strcasecmp(statword, "OB")) { ups_on_batt(ups); - if (!strcasecmp(statword, "LB")) + handled++; + } + else if (!strcasecmp(statword, "LB")) { ups_low_batt(ups); - if (!strcasecmp(statword, "RB")) + handled++; + } + else if (!strcasecmp(statword, "RB")) { upsreplbatt(ups); - if (!strcasecmp(statword, "CAL")) + handled++; + } + else if (!strcasecmp(statword, "CAL")) { ups_is_cal(ups); - if (!strcasecmp(statword, "OFF")) + handled++; + } + else if (!strcasecmp(statword, "OFF")) { ups_is_off(ups); - if (!strcasecmp(statword, "BYPASS")) + handled++; + } + else if (!strcasecmp(statword, "BYPASS")) { ups_is_bypass(ups); - if (!strcasecmp(statword, "ECO")) + handled++; + } + else if (!strcasecmp(statword, "ECO")) { ups_is_eco(ups); - if (!strcasecmp(statword, "ALARM")) + handled++; + } + else if (!strcasecmp(statword, "ALARM")) { ups_is_alarm(ups); + handled++; + } /* do it last to override any possible OL */ - if (!strcasecmp(statword, "FSD")) + else if (!strcasecmp(statword, "FSD")) { ups_fsd(ups); + handled++; + } + /* Known standard status tokens, some being obsoleted, no upsmon reaction assigned */ + else if ( + !strcasecmp(statword, "HB") + || !strcasecmp(statword, "CHRG") + || !strcasecmp(statword, "DISCHRG") + || !strcasecmp(statword, "OVER") + || !strcasecmp(statword, "TRIM") + || !strcasecmp(statword, "BOOST") + ) { + /* FIXME: Do we want these logged similar to OTHERs? */ + upsdebugx(4, "Known and ignored status token: [%s]", statword); + handled++; + } + + if (!handled) { + /* NOTE: Could just state_getinfo() but then + * to refresh the timestamp we'd need to walk + * it again. So replicating a bit of that code. */ + st_tree_t *sttmp = (ups->status_tokens ? state_tree_find(ups->status_tokens, statword) : NULL); + + /* Driver reported something non-standard? */ + upsdebugx(4, "Unexpected status token: [%s]", statword); + + snprintfcat(other_stat_words, + sizeof(other_stat_words), "%s%s", + *other_stat_words ? " " : "", + statword); + + /* Part of our job is to update the timestamp, + * so we can report which tokens disappeared + * and eject them from the list. + * + * The recorded value currently does not matter. + * + * FIXME: Use the value as e.g. "0"/"1" and + * maybe the state->aux as counter, to track + * and report that the non-standard token + * was seen more than once? + */ + if (sttmp) { + /* Start from the discovered node to shortcut to + * (static) st_tree_node_refresh_timestamp(sttmp) + * and complete the info-setting quickly. + */ + state_setinfo(&sttmp, statword, "1"); + } else { + /* This token was not seen last time => new state + * Handle with a notification type "OTHER" + * to report the new un-handled status. */ + changed_other_stat_words++; + upsdebugx(5, "Unexpected status token: [%s]: appeared", statword); + state_setinfo(&(ups->status_tokens), statword, "1"); + } + } update_crittimer(ups); statword = ptr; } + + if (ups->status_tokens) { + st_tree_t *node = ups->status_tokens, *sttmp = node; + + /* Scroll to the leftmost entry for alphanumeric sorted processing */ + while (sttmp) { + node = sttmp; + sttmp = sttmp->left; + } + + /* Go from left to right, on a freeing spree if need be */ + while (node) { + sttmp = node->right; + if (st_tree_node_compare_timestamp(node, &st_start) < 0) { + upsdebugx(5, "Unexpected status token: [%s]: disappeared", + NUT_STRARG(node->var)); + changed_other_stat_words++; + + /* whatever is on the left, hang it off current right */ + if (node->right) { + node->right->left = node->left; /* May be NULL*/ + } + /* whatever is on the right, hang it off current left */ + if (node->left) { + node->left->right = node->right; /* May be NULL*/ + } + + if (node == ups->status_tokens) { + ups->status_tokens = node->left ? node->left : node->right; + } + + /* forget the neighbors before dropping the remaining + * "tree" of one node */ + node->right = NULL; + node->left = NULL; + + state_infofree(node); + } + node = sttmp; + } + } + + if (changed_other_stat_words) { + if (*other_stat_words) { + do_notify(ups, NOTIFY_OTHER, other_stat_words); + } else { + do_notify(ups, NOTIFY_NOTOTHER, NULL); + + /* No words remain, drop the tree if still there */ + if (ups->status_tokens) { + state_infofree(ups->status_tokens); + ups->status_tokens = NULL; + } + } + } + + upsdebugx(3, "Handled %d status tokens", handled_stat_words); } /* see what the status of the UPS is and handle any changes */ @@ -2994,7 +3239,7 @@ static void check_parent(void) return; lastwarn = now; - do_notify(NULL, NOTIFY_NOPARENT); + do_notify(NULL, NOTIFY_NOPARENT, NULL); /* also do this in case the notifier isn't being effective */ upslogx(LOG_ALERT, "Parent died - shutdown impossible"); @@ -3377,11 +3622,13 @@ int main(int argc, char *argv[]) while (exit_flag == 0) { utype_t *ups; - time_t start, end, now; + time_t ttNow; + struct timeval start, end, now; #ifndef WIN32 - time_t prev; + struct timeval prev; #endif double dt = 0; + int sleep_overhead_tolerance = 5; if (isInhibitSupported()) init_Inhibitor(prog); @@ -3425,7 +3672,7 @@ int main(int argc, char *argv[]) } if (sleep_inhibitor_status == 1) { /* Preparing for sleep */ - do_notify(NULL, NOTIFY_SUSPEND_STARTING); + do_notify(NULL, NOTIFY_SUSPEND_STARTING, NULL); upslogx(LOG_INFO, "%s: Processing OS going to sleep", prog); Uninhibit(&sleep_inhibitor_fd); @@ -3463,17 +3710,18 @@ int main(int argc, char *argv[]) switch (sleep_inhibitor_status) { case 0: /* Waking up */ - do_notify(NULL, NOTIFY_SUSPEND_FINISHED); + do_notify(NULL, NOTIFY_SUSPEND_FINISHED, NULL); upslogx(LOG_INFO, "%s: Processing OS wake-up after sleep", prog); upsnotify(NOTIFY_STATE_WATCHDOG, NULL); upsnotify(NOTIFY_STATE_RELOADING, NULL); init_Inhibitor(prog); - time(&now); + gettimeofday(&now, NULL); + time(&ttNow); for (ups = firstups; ups != NULL; ups = ups->next) { ups->status = 0; - ups->lastpoll = now; + ups->lastpoll = ttNow; } set_reload_flag(1); @@ -3511,38 +3759,63 @@ int main(int argc, char *argv[]) /* reap children that have exited */ waitpid(-1, NULL, WNOHANG); - time(&start); + gettimeofday(&start, NULL); + upsdebugx(4, "Beginning %u-sec delay between main loop cycles", sleepval); if (isPreparingForSleepSupported()) { sleep_inhibitor_status = -2; + /* talking to sdbus and/or many short sleeps + * on congested systems can take time */ + sleep_overhead_tolerance = 15; now = start; - while (sleep_inhibitor_status < 0 && dt < sleepval) { + while (sleep_inhibitor_status < 0 && dt < sleepval && !exit_flag) { prev = now; + upsdebugx(7, "delay between main loop cycles: before sleep 1..."); + /* WARNING: This call can take several seconds itself + * on some systems, seen e.g. with Ubuntu in WSL after + * the PC spent some life-time sleeping */ sleep(1); + upsdebugx(7, "delay between main loop cycles: after sleep 1..."); sleep_inhibitor_status = isPreparingForSleep(); - time(&now); - dt = difftime(now, start); - upsdebugx(7, "start=%" PRIiMAX " now=%" PRIiMAX " dt=%g sleepval=%u sleep_inhibitor_status=%d", - (intmax_t)start, (intmax_t)now, dt, sleepval, sleep_inhibitor_status); - if (dt > (sleepval + 5) || difftime(now, prev) > 5) { - upsdebugx(2, "It seems we have slept without warning or the system clock was changed"); + upsdebugx(7, "delay between main loop cycles: after isPreparingForSleep()..."); + gettimeofday(&now, NULL); + dt = difftimeval(now, start); + upsdebugx(7, "start=%" PRIiMAX ".%06" PRIiMAX + " prev=%" PRIiMAX ".%06" PRIiMAX + " now=%" PRIiMAX ".%06" PRIiMAX + " dt=%.06f sleepval=%u sleep_inhibitor_status=%d", + (intmax_t)start.tv_sec, (intmax_t)start.tv_usec, + (intmax_t)prev.tv_sec, (intmax_t)prev.tv_usec, + (intmax_t)now.tv_sec, (intmax_t)now.tv_usec, + dt, sleepval, sleep_inhibitor_status); + if (dt > (sleepval + sleep_overhead_tolerance) || difftimeval(now, prev) > sleep_overhead_tolerance) { + upsdebugx(2, "It seems we have slept without warning or the system clock was changed (while in delay between main loop cycles)"); if (sleep_inhibitor_status < 0) sleep_inhibitor_status = 0; /* behave as woken up */ } else if (dt < 0) { - upsdebugx(2, "It seems the system clock was changed into the past"); + upsdebugx(2, "It seems the system clock was changed into the past (while in delay between main loop cycles)"); sleep_inhibitor_status = 0; /* behave as woken up */ } } if (sleep_inhibitor_status >= 0) { + gettimeofday(&end, NULL); + upsdebugx(4, "%u-sec delay between main loop cycles finished, took %.06f", + sleepval, difftimeval(end, start)); upsdebugx(2, "Aborting polling delay between main loop cycles because OS is preparing for sleep or just woke up"); goto end_loop_cycle; } + + /* NOTE: if exit_flag was raised in sleep-loop above, + * so we aborted it, we end soon after ifdef/endif, + * and so not handling here specially */ } else { /* sleep tight */ sleep(sleepval); } - time(&end); + gettimeofday(&end, NULL); + upsdebugx(4, "%u-sec delay between main loop cycles finished, took %.06f", + sleepval, difftimeval(end, start)); #else maxhandle = 0; memset(&handles, 0, sizeof(handles)); @@ -3560,9 +3833,12 @@ int main(int argc, char *argv[]) handles[maxhandle] = pipe_connection_overlapped.hEvent; maxhandle++; - time(&start); + gettimeofday(&start, NULL); + upsdebugx(4, "Beginning %u-sec delay between main loop cycles", sleepval); ret = WaitForMultipleObjects(maxhandle, handles, FALSE, sleepval*1000); - time(&end); + gettimeofday(&end, NULL); + upsdebugx(4, "%u-sec delay between main loop cycles finished, took %.06f", + sleepval, difftimeval(end, start)); if (ret == WAIT_FAILED) { upslogx(LOG_ERR, "Wait failed"); @@ -3611,8 +3887,8 @@ int main(int argc, char *argv[]) /* General-purpose handling of time jumps for OSes/run-times * without NUT direct support for suspend/inhibit */ - dt = difftime(end, start); - if (dt > (sleepval + 5)) { + dt = difftimeval(end, start); + if (dt > (sleepval + sleep_overhead_tolerance)) { upsdebugx(2, "It seems we have slept without warning or the system clock was changed"); if (sleep_inhibitor_status < 0) sleep_inhibitor_status = 0; /* behave as woken up */ diff --git a/clients/upsmon.h b/clients/upsmon.h index ec68bb1a07..ca0dba5c99 100644 --- a/clients/upsmon.h +++ b/clients/upsmon.h @@ -3,6 +3,7 @@ Copyright (C) 2000 Russell Kroll 2012 Arnaud Quette + 2017 Eaton (author: Arnaud Quette ) 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify @@ -23,6 +24,8 @@ #ifndef NUT_UPSMON_H_SEEN #define NUT_UPSMON_H_SEEN 1 +#include "state.h" + /* flags for ups->status */ #define ST_ONLINE (1 << 0) /* UPS is on line (OL) */ @@ -38,6 +41,7 @@ #define ST_BYPASS (1 << 9) /* UPS is on bypass so not protecting */ #define ST_ECO (1 << 10) /* UPS is in ECO (High Efficiency) mode or similar tweak, e.g. Energy Saver System mode */ #define ST_ALARM (1 << 11) /* UPS has at least one active alarm */ +#define ST_OTHER (1 << 12) /* UPS has at least one unclassified status token */ /* required contents of flag file */ #define SDMAGIC "upsmon-shutdown-file" @@ -62,6 +66,7 @@ typedef struct { char *un; /* username (optional for now) */ char *pw; /* password from conf */ int status; /* status (see flags above) */ + st_tree_t *status_tokens; /* parsed ups.status, mapping each token to whatever value if it is currently set (evicted when not) */ int retain; /* tracks deletions at reload */ /* handle suppression of COMMOK and ONLINE at startup */ @@ -116,6 +121,10 @@ typedef struct { #define NOTIFY_ALARM 18 /* UPS has at least one active alarm */ #define NOTIFY_NOTALARM 19 /* UPS has no active alarms */ +/* Special handling below */ +#define NOTIFY_OTHER 28 /* UPS has at least one unclassified status token */ +#define NOTIFY_NOTOTHER 29 /* UPS has no unclassified status tokens anymore */ + #define NOTIFY_SUSPEND_STARTING 30 /* OS is entering sleep/suspend/hibernate slumber mode, and we know it */ #define NOTIFY_SUSPEND_FINISHED 31 /* OS just finished sleep/suspend/hibernate slumber mode, and we know it */ @@ -165,9 +174,21 @@ static struct { { NOTIFY_NOTBYPASS,"NOTBYPASS",NULL, "UPS %s: no longer on bypass", NOTIFY_DEFAULT }, { NOTIFY_ECO, "ECO", NULL, "UPS %s: in ECO mode (as defined by vendor)", NOTIFY_DEFAULT }, { NOTIFY_NOTECO, "NOTECO", NULL, "UPS %s: no longer in ECO mode", NOTIFY_DEFAULT }, - { NOTIFY_ALARM, "ALARM", NULL, "UPS %s is in an alarm state (has active alarms)", NOTIFY_DEFAULT }, + + /* NOTE: We remember the ups.alarm value and report it here, + * maybe optionally - e.g. check if the "ALARM" formatting + * string has actually one or two "%s" placeholders inside. + * Do issue a new notification if ups.alarm value changes. + */ + { NOTIFY_ALARM, "ALARM", NULL, "UPS %s: one or more active alarms: [%s]", NOTIFY_DEFAULT }, { NOTIFY_NOTALARM, "NOTALARM", NULL, "UPS %s is no longer in an alarm state (no active alarms)", NOTIFY_DEFAULT }, + /* Special handling, two string placeholders! + * Reported when status_tokens tree changes (and is not empty in the end) */ + { NOTIFY_OTHER, "OTHER", NULL, "UPS %s: has at least one unclassified status token: [%s]", NOTIFY_DEFAULT }, + /* Reported when status_tokens tree becomes empty */ + { NOTIFY_NOTOTHER, "NOTOTHER", NULL, "UPS %s has no unclassified status tokens anymore", NOTIFY_DEFAULT }, + { NOTIFY_SUSPEND_STARTING, "SUSPEND_STARTING", NULL, "OS is entering sleep/suspend/hibernate mode", NOTIFY_DEFAULT }, { NOTIFY_SUSPEND_FINISHED, "SUSPEND_FINISHED", NULL, "OS just finished sleep/suspend/hibernate mode, de-activating obsolete UPS readings to avoid an unfortunate shutdown", NOTIFY_DEFAULT }, diff --git a/clients/upsrw.c b/clients/upsrw.c index 710d39d51e..892e6825e3 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -121,6 +121,9 @@ static void do_set(const char *varname, const char *newval) ) { /* reply as usual */ fprintf(stderr, "%s\n", buf); + upsdebugx(1, "%s: 'OK' only means the NUT data server accepted the request as valid, " + "but as we did not wait for result, we do not know if it was handled in fact.", + __func__); return; } @@ -641,7 +644,18 @@ int main(int argc, char **argv) int i; uint16_t port; const char *prog = xbasename(argv[0]); - char *password = NULL, *username = NULL, *setvar = NULL; + char *password = NULL, *username = NULL, *setvar = NULL, *s = NULL; + + /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc + * and NUT methods called from it. This line aims to just initialize + * the subsystem, and set initial timestamp. Debugging the client is + * primarily of use to developers, so is not exposed via `-D` args. + */ + s = getenv("NUT_DEBUG_LEVEL"); + if (s && str_to_int(s, &i, 10) && i > 0) { + nut_debug_level = i; + } + upsdebugx(1, "Starting NUT client: %s", prog); while ((i = getopt(argc, argv, "+hls:p:t:u:wV")) != -1) { switch (i) @@ -717,6 +731,6 @@ int main(int argc, char **argv) /* Formal do_upsconf_args implementation to satisfy linker on AIX */ #if (defined NUT_PLATFORM_AIX) void do_upsconf_args(char *upsname, char *var, char *val) { - fatalx(EXIT_FAILURE, "INTERNAL ERROR: formal do_upsconf_args called"); + fatalx(EXIT_FAILURE, "INTERNAL ERROR: formal do_upsconf_args called"); } #endif /* end of #if (defined NUT_PLATFORM_AIX) */ diff --git a/common/common.c b/common/common.c index 3e0aa4336f..b4f1b5456f 100644 --- a/common/common.c +++ b/common/common.c @@ -308,6 +308,8 @@ int isPreparingForSleep(void) if (isSupported_PreparingForSleep == 0) { /* Already determined that we can not use it, e.g. due to perms */ errno = isSupported_PreparingForSleep_errno; + upsdebug_with_errno(8, "%s: isSupported_PreparingForSleep=%d", + __func__, isSupported_PreparingForSleep); return -errno; } @@ -357,16 +359,19 @@ int isPreparingForSleep(void) if (val == prev) { /* Unchanged */ + upsdebugx(8, "%s: state unchanged", __func__); return -1; } /* First run and not immediately going to sleep, assume unchanged (no-op for upsmon et al) */ if (prev < 0 && !val) { prev = val; + upsdebugx(8, "%s: state unchanged (assumed): first run and not immediately going to sleep", __func__); return -1; } /* 0 or 1 */ + upsdebugx(8, "%s: state changed): %" PRIi32 " -> %" PRIi32, __func__, prev, val); prev = val; return val; } @@ -2208,6 +2213,21 @@ const char *str_upsnotify_state(upsnotify_state_t state) { } } +static void upsnotify_suggest_NUT_QUIET_INIT_UPSNOTIFY_once(void) { + static int reported = 0; + + if (reported) + return; + + reported = 1; + + if (getenv("NUT_QUIET_INIT_UPSNOTIFY")) + return; + + upsdebugx(1, "On systems without service units, " + "consider `export NUT_QUIET_INIT_UPSNOTIFY=true`"); +} + /* Send (daemon) state-change notifications to an * external service management framework such as systemd */ @@ -2307,9 +2327,9 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) "skipped for libcommonclient build, " "will not spam more about it", __func__, str_upsnotify_state(state)); - upsdebugx(1, "On systems without service units, " - "consider `export NUT_QUIET_INIT_UPSNOTIFY=true`"); + upsnotify_suggest_NUT_QUIET_INIT_UPSNOTIFY_once(); } + upsnotify_reported_disabled_systemd = 1; # else /* not WITHOUT_LIBSYSTEMD */ if (!getenv("NOTIFY_SOCKET")) { @@ -2319,8 +2339,7 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) "was requested, but not running as a service " "unit now, will not spam more about it", __func__, str_upsnotify_state(state)); - upsdebugx(1, "On systems without service units, " - "consider `export NUT_QUIET_INIT_UPSNOTIFY=true`"); + upsnotify_suggest_NUT_QUIET_INIT_UPSNOTIFY_once(); } upsnotify_reported_disabled_systemd = 1; } else { @@ -2616,9 +2635,8 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) "no notification tech defined, " "will not spam more about it", __func__, str_upsnotify_state(state)); - upsdebugx(1, "On systems without service units, " - "consider `export NUT_QUIET_INIT_UPSNOTIFY=true`"); upsnotify_reported_disabled_notech = 1; + upsnotify_suggest_NUT_QUIET_INIT_UPSNOTIFY_once(); } else { upsdebugx(6, "%s: failed to notify about state %s", @@ -2632,9 +2650,8 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) upsdebugx(upsnotify_report_verbosity, "%s: logged the systemd watchdog situation once, " "will not spam more about it", __func__); - upsdebugx(1, "On systems without service units, " - "consider `export NUT_QUIET_INIT_UPSNOTIFY=true`"); upsnotify_reported_watchdog_systemd = 1; + upsnotify_suggest_NUT_QUIET_INIT_UPSNOTIFY_once(); } # endif #endif diff --git a/common/nutconf.cpp b/common/nutconf.cpp index cd8a76d5e9..3f1eef9aef 100644 --- a/common/nutconf.cpp +++ b/common/nutconf.cpp @@ -1324,6 +1324,10 @@ UpsmonConfiguration::NotifyType UpsmonConfiguration::NotifyTypeFromString(const return NOTIFY_ALARM; else if(str=="NOTALARM") return NOTIFY_NOTALARM; + else if(str=="OTHER") + return NOTIFY_OTHER; + else if(str=="NOTOTHER") + return NOTIFY_NOTOTHER; else if(str=="SUSPEND_STARTING") return NOTIFY_SUSPEND_STARTING; else if(str=="SUSPEND_FINISHED") diff --git a/common/nutwriter.cpp b/common/nutwriter.cpp index cafac79e48..bc26783d78 100644 --- a/common/nutwriter.cpp +++ b/common/nutwriter.cpp @@ -408,6 +408,8 @@ const NotifyFlagsStrings::TypeStrings NotifyFlagsStrings::type_str = { "NOTECO", // NOTIFY_NOTECO "ALARM", // NOTIFY_ALARM "NOTALARM", // NOTIFY_NOTALARM + "OTHER", // NOTIFY_OTHER + "NOTOTHER", // NOTIFY_NOTOTHER "SUSPEND_STARTING", // NOTIFY_SUSPEND_STARTING "SUSPEND_FINISHED", // NOTIFY_SUSPEND_FINISHED }; diff --git a/conf/ups.conf.sample b/conf/ups.conf.sample index c9b011cd0a..6a7a72c8fd 100644 --- a/conf/ups.conf.sample +++ b/conf/ups.conf.sample @@ -160,6 +160,12 @@ maxretry = 3 # # The default value for this parameter is 0. # +# sdcommands: OPTIONAL. Comma-separated list of instant command name(s) +# to send to the UPS when you request its shutdown. For more +# details about relevant use-cases see the ups.conf manual page. +# +# The default value is built into each driver (where supported). +# # desc: optional, to keep a note of the UPS purpose, location, etc. # # nolock: optional, and not recommended for use in this file. diff --git a/conf/upsmon.conf.sample.in b/conf/upsmon.conf.sample.in index e5d7235859..60bb61f1b5 100644 --- a/conf/upsmon.conf.sample.in +++ b/conf/upsmon.conf.sample.in @@ -334,9 +334,18 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYMSG NOTBYPASS "UPS %s: no longer on bypass" # NOTIFYMSG ECO "UPS %s: in ECO mode (as defined by vendor)" # NOTIFYMSG NOTECO "UPS %s: no longer in ECO mode (as defined by vendor)" -# NOTIFYMSG ALARM "UPS %s is in an alarm state (has active alarms)" +# NOTIFYMSG ALARM "UPS %s: one or more active alarms: [%s]" +# or NOTIFYMSG ALARM "UPS %s: one or more active alarms (check ups.alarm)" # NOTIFYMSG NOTALARM "UPS %s is no longer in an alarm state (no active alarms)" # +# Special handling is provided for surprise tokens seen in ups.status, which +# are not in the standard NUT dictionary (but some drivers are known to use); +# note that unlike other formatting strings, the "OTHER" one has two string +# placeholders "%s" (it is safe to use one, leaving just the UPS name, or none): +# +# NOTIFYMSG OTHER "UPS %s: has at least one unclassified status token: [%s]" +# NOTIFYMSG NOTOTHER "UPS %s has no unclassified status tokens anymore" +# # A few messages not directly related to UPS events are also available: # # NOTIFYMSG SUSPEND_STARTING "OS is entering sleep/suspend/hibernate mode" @@ -356,6 +365,7 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # REPLBATT : The UPS battery is bad and needs to be replaced # NOCOMM : A UPS is unavailable (can't be contacted for monitoring) # NOPARENT : The process that shuts down the system has died (shutdown impossible) +# ALARM : UPS has one or more alarms (look at "ups.alarm" for details) # -------------------------------------------------------------------------- # NOTIFYFLAG - change behavior of upsmon when NOTIFY events occur @@ -388,6 +398,9 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYFLAG ALARM SYSLOG+WALL # NOTIFYFLAG NOTALARM SYSLOG+WALL # +# NOTIFYFLAG OTHER SYSLOG+WALL +# NOTIFYFLAG NOTOTHER SYSLOG+WALL +# # NOTIFYFLAG SUSPEND_STARTING SYSLOG+WALL # NOTIFYFLAG SUSPEND_FINISHED SYSLOG+WALL # diff --git a/data/cmdvartab b/data/cmdvartab index 0fa48d55aa..8fc4f48948 100644 --- a/data/cmdvartab +++ b/data/cmdvartab @@ -42,6 +42,7 @@ VARDESC ups.type "UPS type" VARDESC ups.start.auto "UPS starts when mains is (re)applied" VARDESC ups.start.battery "Allow to start UPS from battery" VARDESC ups.start.reboot "UPS reboots when power returns during shutdown delay" +VARDESC ups.shutdown "Enable or disable UPS shutdown ability (poweroff)" VARDESC input.voltage "Input voltage (V)" VARDESC input.voltage.extended "Extended input voltage range" @@ -74,6 +75,8 @@ VARDESC input.transfer.bypass.outlimits "Rule for auto transfer on Bypass when o VARDESC input.bypass.switchable "Input auto transfer on Bypass when overload or out of tolerance (enabled or disabled)" VARDESC input.bypass.switch.on "Put the UPS in Bypass mode" VARDESC input.bypass.switch.off "Take the UPS out of Bypass mode" +VARDESC input.bypass.voltage "Input bypass voltage (V)" +VARDESC input.bypass.frequency "Input bypass frequency (Hz)" VARDESC input.sensitivity "Input power sensitivity" VARDESC input.quality "Input power quality" VARDESC input.current "Input current (A)" @@ -131,6 +134,7 @@ VARDESC battery.energysave.load "Switch off UPS if on battery and load level low VARDESC battery.energysave.delay "Delay before switch off UPS if on battery and load level low (min)" VARDESC battery.energysave.realpower "Switch off UPS if on battery and load level lower (Watts)" VARDESC battery.charger.status "Battery charger status" +VARDESC battery.charger.type "Type of battery charger" VARDESC ambient.temperature "Ambient temperature (degrees C)" VARDESC ambient.temperature.alarm "Ambient temperature alarm is active" @@ -183,6 +187,7 @@ VARDESC outlet.1.ecocontrol "Master Outlet used to automatically power off the s VARDESC outlet.1.autoswitch.charge.low "Remaining battery level to power off this outlet (percent)" VARDESC outlet.1.delay.shutdown "Interval to wait before shutting down this outlet (seconds)" VARDESC outlet.1.delay.start "Interval to wait before restarting this outlet (seconds)" +VARDESC outlet.1.designator "Outlet designator" VARDESC outlet.2.id "Outlet system identifier" VARDESC outlet.2.desc "Outlet description" VARDESC outlet.2.switch "Outlet switch control" @@ -194,7 +199,18 @@ VARDESC outlet.2.autoswitch.charge.low "Remaining battery level to power off thi VARDESC outlet.2.delay.shutdown "Interval to wait before shutting down this outlet (seconds)" VARDESC outlet.2.delay.start "Interval to wait before restarting this outlet (seconds)" -VARDESC device.part "Device Part Number" +VARDESC device.part "Device part number" +VARDESC device.mfr "Device manufacturer" +VARDESC device.model "Device model" +VARDESC device.serial "Device serial number" +VARDESC device.type "Device type" +VARDESC device.description "Device description" +VARDESC device.contact "Device administrator name" +VARDESC device.location "Device physical location" +VARDESC device.macaddr "Physical network address of the device" +VARDESC device.uptime "Device uptime in seconds" +VARDESC device.count "Total number of daisychained devices" +VARDESC device.usb.version "Device USB version" VARDESC server.info "Server information" VARDESC server.version "Server version" @@ -207,6 +223,8 @@ VARDESC driver.flag.allow_killpower "Safety flip-switch to allow the driver daem VARDESC driver.version "Driver version - NUT release" VARDESC driver.version.internal "Internal driver version" VARDESC driver.version.usb "USB library version" +VARDESC driver.version.data "Version of the internal data mapping, for generic drivers" +VARDESC driver.state "Current state in driver's lifecycle" # FIXME: driver.parameter and driver.flag can have many possible members # @@ -221,6 +239,7 @@ CMDDESC driver.reload-or-exit "Reload running driver configuration from the file CMDDESC load.off "Turn off the load immediately" CMDDESC load.on "Turn on the load immediately" +CMDDESC shutdown.default "Run the driver-defined UPS shutdown sequence (as opposed to user-configured 'sdcommands')" CMDDESC shutdown.return "Turn off the load and return when power is back" CMDDESC shutdown.stayoff "Turn off the load and remain off" CMDDESC shutdown.stop "Stop a shutdown in progress" diff --git a/docs/man/nutconf.txt b/docs/man/nutconf.txt index ebacfb86bd..74835f3a50 100644 --- a/docs/man/nutconf.txt +++ b/docs/man/nutconf.txt @@ -115,6 +115,8 @@ Notification types are: - 'NOTBYPASS' (no longer on bypass) - 'ALARM' (UPS is in an alarm state (has active alarms)) - 'NOTALARM' (UPS is no longer in an alarm state (no active alarms)) +- 'OTHER' (UPS has at least one unclassified status token) +- 'NOTOTHER' (UPS has no unclassified status tokens anymore) - 'SUSPEND_STARTING' (OS is entering sleep/suspend/hibernate mode) - 'SUSPEND_FINISHED' (OS just finished sleep/suspend/hibernate mode) diff --git a/docs/man/ups.conf.txt b/docs/man/ups.conf.txt index 50fea2d27f..cf2020603a 100644 --- a/docs/man/ups.conf.txt +++ b/docs/man/ups.conf.txt @@ -215,6 +215,31 @@ set this to -1. + The default value for this parameter is 0. +*sdcommands*:: + +Optional. Comma-separated list of instant command name(s) to send to +the UPS when you request its shutdown. ++ +Default logic is built into each driver (where supported) and can be +referenced here as the `shutdown.default` value. ++ +The primary use-case is for devices whose drivers "natively" support +trying several commands, but the built-in order of those calls a +command that is mis-handled by the specific device model (so the +handling is reported as successful and the loop stops, but nothing +happens as far as the load power-down is concerned). ++ +Another use-case is differentiation of automated power-off scenarios +where the UPS and its load should stay "OFF" (e.g. by building emergency +power-off) vs. those where the load should return to work automatically +when it is safe to do so. NOTE: This would *currently* need editing of +`ups.conf` for such cases before `nutshutdown` sees the file; but could +be better automated in future NUT releases. ++ +NOTE: User-provided commands may be something other than actual shutdown, +e.g. a beeper to test that the INSTCMD happened such and when expected, +and the device was contacted, without impacting the load fed by the UPS. + *allow_killpower*:: Optional. This allows you to request `driver.killpower` instant command, to immediately call the driver-specific default implementation of diff --git a/docs/man/upsmon.conf.txt b/docs/man/upsmon.conf.txt index 144c115ee9..15620e1df5 100644 --- a/docs/man/upsmon.conf.txt +++ b/docs/man/upsmon.conf.txt @@ -280,10 +280,18 @@ for more details see linkman:upsmon[8]. NOTECO;; UPS no longer in ECO mode (see above) -ALARM;; UPS is in an alarm state (has active alarms) +ALARM;; UPS has one or more active alarms (check ups.alarm); +for this notification, the `message` can contain a second `%s` placeholder +to substitute the current value of `ups.alarm`. NOTALARM;; UPS is no longer in an alarm state (no active alarms) +OTHER;; UPS has at least one unclassified `ups.status` token; +for this notification, the `message` can contain a second `%s` placeholder +to substitute the current collection of such tokens. + +NOTOTHER;; UPS has no unclassified status tokens anymore + SUSPEND_STARTING;; OS is entering sleep/suspend/hibernate mode SUSPEND_FINISHED;; OS just finished sleep/suspend/hibernate mode, diff --git a/docs/man/upsmon.txt b/docs/man/upsmon.txt index 7529bf2e72..55110802bc 100644 --- a/docs/man/upsmon.txt +++ b/docs/man/upsmon.txt @@ -193,11 +193,17 @@ on battery life). Older devices only implemented one or the other. UPS no longer in ECO mode (see above). *ALARM*:: -UPS is in an alarm state (has active alarms). +UPS has one or more active alarms (look at `ups.alarm` for details). *NOTALARM*:: UPS is no longer in an alarm state (no active alarms). +*OTHER*:: +UPS has at least one unclassified status token. + +*NOTOTHER*:: +UPS has no unclassified status tokens anymore. + *SUSPEND_STARTING*:: OS is entering sleep/suspend/hibernate mode. diff --git a/docs/new-drivers.txt b/docs/new-drivers.txt index 6c85afcc06..b696141bc3 100644 --- a/docs/new-drivers.txt +++ b/docs/new-drivers.txt @@ -124,7 +124,9 @@ and it doesn't appear to be connected, display an error and exit. This is the last time your driver is allowed to bail out. This is usually a good place to create variables like `ups.mfr`, -`ups.model`, `ups.serial`, and other "one time only" items. +`ups.model`, `ups.serial`, determine and declare supported instant +commands (maybe model-dependent, typically for all devices supported +by the driver), and other "one time only" items. upsdrv_updateinfo ~~~~~~~~~~~~~~~~~ @@ -165,9 +167,10 @@ process. This method should not directly `exit()` the driver program (neither should it call `fatalx()` nor `fatal_with_errno()` methods). It can `upslogx(LOG_ERR, ...)` or `upslog_with_errno(LOG_ERR, ...)`, and then -`set_exit_flag(N)` if required (`-1` for `EXIT_FAILURE` and `-2` for -`EXIT_SUCCESS` which would be handled in the standard driver loop or -`forceshutdown()` method of `main.c`). +`set_exit_flag(N)` if required, using values `EF_EXIT_FAILURE` (`-1`) +for eventual `exit(EXIT_FAILURE)` and `EF_EXIT_SUCCESS` (`-2`) for +`exit(EXIT_SUCCESS)`, which would be handled in the standard driver +loop or in `forceshutdown()` method of `main.c`. Data types ---------- @@ -244,9 +247,20 @@ Possible values for status_set: BOOST -- UPS is boosting incoming voltage FSD -- Forced Shutdown (restricted use, see the note below) -Anything else will not be recognized by the usual clients. Coordinate -with the nut-upsdev list before creating something new, since there will be -duplication and ugliness otherwise. +Anything else will not be recognized by the usual clients expecting a +particular NUT standard release. New tokens may appear over time, but +driver developers should coordinate with the nut-upsdev list before creating +something new, since there will be duplication and ugliness otherwise. +It is possible that eventually, due to hardware and software design evolution, +some concepts would be superseded by others. Fundamental meanings of the +flags listed above should not change (but these flags may become no longer +issued by the current NUT drivers; then may still be used e.g. in forks or +older packaged builds). + +Clients however MUST accept any space-separated tokens in `ups.status` +without error or crash, and MUST treat those defined above with the +ascribed meanings, but MAY ignore unidentified tokens (quietly by default, +or acknowledge the skip with a debug log message). [NOTE] ============================================================================== @@ -728,6 +742,12 @@ If your hardware and driver can support a command, register it. dstate_addcmd("load.on"); +Don't forget to define the implementation for such commands in a common +method, and register that your driver has an instant command handler at +all -- with a line in `upsdrv_initinfo()` like: + + upsh.instcmd = blazer_instcmd; + Delays and ser_* functions -------------------------- diff --git a/docs/nut-names.txt b/docs/nut-names.txt index 74bb0eb11f..cdd7d69b9f 100644 --- a/docs/nut-names.txt +++ b/docs/nut-names.txt @@ -134,6 +134,7 @@ during a transition period. The ups.* data will then be removed. | device.macaddr | Physical network address of the device | 68:b5:99:f5:89:27 | device.uptime | Device uptime in seconds | 1782 | device.count | Total number of daisychained devices | 1 +| device.usb.version | Device USB version | 01.29 |==================================================================================== [NOTE] @@ -320,6 +321,8 @@ input: Incoming line/power information | enabled | input.bypass.switch.on | Put the UPS in Bypass mode | on | input.bypass.switch.off | Take the UPS out of Bypass mode | disabled +| input.bypass.voltage | Input bypass voltage (V) | 233 +| input.bypass.frequency | Input bypass frequency (Hz) | 50 | input.load | Load on (ePDU) input (percent of full) | 25 | input.realpower | Current sum value of all (ePDU) @@ -443,7 +446,8 @@ Valid SPECs NOTE: For cursory readers -- the following couple of tables lists just the short `SPEC` component of the larger `DOMAIN.CONTEXT.SPEC` naming scheme -for phase-aware values, as discussed in other sections of this chapter. +for phase-aware values, as discussed in other sections of this chapter just +above. These are NOT to be used verbatim as complete data-point names! Valid with/without context (i.e. per phase or aggregated/averaged) @@ -534,6 +538,7 @@ battery: Any battery details to "Warning" state (percent) | 50 | battery.charger.status | Status of the battery charger (see the note below) | charging +| battery.charger.type | Type of battery charger | ABM | battery.voltage | Battery voltage (V) | 24.84 | battery.voltage.cell.max | Maximum battery voltage seen of the Li-ion cell (V) | 3.44 @@ -718,6 +723,7 @@ of the user manual. | outlet.n.ecocontrol | Master Outlet used to automatically power off the slave outlets | The outlet is not ECO controlled +| outlet.n.designator | Outlet designator | AC OUTPUT | outlet.n.autoswitch.charge.low | Remaining battery level to power off this outlet (percent) | 80 @@ -861,6 +867,13 @@ Instant commands | load.on | Turn on the load immediately | load.off.delay | Turn off the load possibly after a delay | load.on.delay | Turn on the load possibly after a delay +| shutdown.default | Run default driver-defined (device-specific) + routine, primarily intended for emergency + poweroff performed as part of FSD handling; + often an alias to other `shutdown.*` and/or + `load.off` operations or a chain to try + several of those. See also `sdcommands` in + common driver options. | shutdown.return | Turn off the load possibly after a delay and return when power is back | shutdown.stayoff | Turn off the load possibly after a delay diff --git a/docs/nut.dict b/docs/nut.dict index b50bb33833..cd8ba0917f 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3259 utf-8 +personal_ws-1.1 en 3264 utf-8 AAC AAS ABI @@ -755,6 +755,7 @@ NOTIFYFLAG NOTIFYFLAGS NOTIFYMSG NOTOFF +NOTOTHER NQA NTP NUT's @@ -1785,6 +1786,7 @@ desc deschis descr desde +designator dev devctl devd @@ -2764,6 +2766,7 @@ screenshots scriptname sd sdcmd +sdcommands sddelay sdk sdl @@ -2988,6 +2991,7 @@ timehead timername timestamp timeticks +timeval tios tmp tmpfiles @@ -3086,6 +3090,7 @@ upsdebugx upsdev upsdrv upsdrvctl +upsdrvquery upsdrvsvcctl upserror upsfetch diff --git a/drivers/adelsystem_cbi.c b/drivers/adelsystem_cbi.c index 2247b6e6a2..0539b796ec 100644 --- a/drivers/adelsystem_cbi.c +++ b/drivers/adelsystem_cbi.c @@ -30,7 +30,7 @@ #include #define DRIVER_NAME "NUT ADELSYSTEM DC-UPS CB/CBI driver" -#define DRIVER_VERSION "0.03" +#define DRIVER_VERSION "0.04" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ @@ -49,7 +49,6 @@ static uint32_t mod_resp_to_us = MODRESP_TIMEOUT_us; /* set the modbus response static uint32_t mod_byte_to_s = MODBYTE_TIMEOUT_s; /* set the modbus byte time out (us) */ static uint32_t mod_byte_to_us = MODBYTE_TIMEOUT_us; /* set the modbus byte time out (us) */ - /* initialize alarm structs */ void alrminit(void); @@ -193,6 +192,11 @@ void upsdrv_initinfo(void) /* register instant commands */ dstate_addcmd("load.off"); + /* FIXME: Check with the device what this instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("shutdown.stayoff"); + /* set callback for instant commands */ upsh.instcmd = upscmd; } @@ -464,33 +468,24 @@ void upsdrv_updateinfo(void) /* shutdown UPS */ void upsdrv_shutdown(void) { - int rval; - int cnt = FSD_REPEAT_CNT; /* shutdown repeat counter */ - struct timeval start; - long etime; - - /* retry sending shutdown command on error */ - while ((rval = upscmd("load.off", NULL)) != STAT_INSTCMD_HANDLED && cnt > 0) { - rval = gettimeofday(&start, NULL); - if (rval < 0) { - upslog_with_errno(LOG_ERR, "upscmd: gettimeofday"); - } + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - /* wait for an increasing time interval before sending shutdown command */ - while ((etime = time_elapsed(&start)) < ( FSD_REPEAT_INTRV / cnt)); - upsdebugx(2, "ERROR: load.off failed, wait for %lims, retries left: %d\n", etime, cnt - 1); - cnt--; - } - switch (rval) { - case STAT_INSTCMD_FAILED: - case STAT_INSTCMD_INVALID: - fatalx(EXIT_FAILURE, "shutdown failed"); - case STAT_INSTCMD_UNKNOWN: - fatalx(EXIT_FAILURE, "shutdown not supported"); - default: - break; - } - upslogx(LOG_INFO, "shutdown command executed"); + /* + * WARNING: When using RTU TCP, this driver will probably + * never support shutdowns properly, except on some systems: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + int ret = do_loop_shutdown_commands("shutdown.stayoff", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /* print driver usage info */ @@ -832,6 +827,43 @@ int upscmd(const char *cmd, const char *arg) upsdebugx(2, "load.off: addr: 0x%x, data: %d", regs[FSD].xaddr, data); rval = STAT_INSTCMD_HANDLED; } + } else if (!strcasecmp(cmd, "shutdown.stayoff")) { + /* FIXME: Which one is this actually - + * "shutdown.stayoff" or "shutdown.return"? */ + int cnt = FSD_REPEAT_CNT; /* shutdown repeat counter */ + struct timeval start; + long etime; + + /* retry sending shutdown command on error */ + while ((rval = upscmd("load.off", NULL)) != STAT_INSTCMD_HANDLED && cnt > 0) { + rval = gettimeofday(&start, NULL); + if (rval < 0) { + upslog_with_errno(LOG_ERR, "upscmd: gettimeofday"); + } + + /* wait for an increasing time interval before sending shutdown command */ + while ((etime = time_elapsed(&start)) < ( FSD_REPEAT_INTRV / cnt)); + upsdebugx(2, "ERROR: load.off failed, wait for %lims, retries left: %d\n", etime, cnt - 1); + cnt--; + } + switch (rval) { + case STAT_INSTCMD_FAILED: + case STAT_INSTCMD_INVALID: + upslog_with_errno(LOG_ERR, "instcmd: %s failed", cmd); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); + break; + case STAT_INSTCMD_UNKNOWN: + upslog_with_errno(LOG_ERR, "instcmd: %s not supported", cmd); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); + break; + default: + upslogx(LOG_INFO, "shutdown command executed"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); + break; + } } else { upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmd, arg); rval = STAT_INSTCMD_UNKNOWN; diff --git a/drivers/al175.c b/drivers/al175.c index 8438882144..251c054339 100644 --- a/drivers/al175.c +++ b/drivers/al175.c @@ -52,7 +52,7 @@ typedef uint8_t byte_t; #define DRIVER_NAME "Eltek AL175/COMLI driver" -#define DRIVER_VERSION "0.15" +#define DRIVER_VERSION "0.16" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1267,6 +1267,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* TODO use TOGGLE_PRS_ONOFF for shutdown */ /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ @@ -1276,7 +1279,8 @@ void upsdrv_shutdown(void) /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); /* you may have to check the line status since the commands for toggling power are frequently different for OL vs. OB */ diff --git a/drivers/apc_modbus.c b/drivers/apc_modbus.c index 07f6a46354..978948b3ea 100644 --- a/drivers/apc_modbus.c +++ b/drivers/apc_modbus.c @@ -38,7 +38,7 @@ #else # define DRIVER_NAME "NUT APC Modbus driver without USB support" #endif -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" #if defined NUT_MODBUS_HAS_USB @@ -1569,7 +1569,31 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - modbus_write_register(modbus_ctx, APC_MODBUS_OUTLETCOMMAND_BF_REG, APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* + * WARNING: When using RTU TCP, this driver will probably + * never support shutdowns properly, except on some systems: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + + /* FIXME: got no direct equivalent in apc_modbus_command_map[] + * used for instcmd above. Investigate if we can add this + * combo into that map and name it as an INSTCMD to call by + * this driver's standard approach. + */ + modbus_write_register(modbus_ctx, + APC_MODBUS_OUTLETCOMMAND_BF_REG, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP + ); } void upsdrv_help(void) diff --git a/drivers/apcsmart-old.c b/drivers/apcsmart-old.c index 5a56c99106..1e28b09b2e 100644 --- a/drivers/apcsmart-old.c +++ b/drivers/apcsmart-old.c @@ -25,7 +25,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "APC Smart protocol driver (old)" -#define DRIVER_VERSION "2.33" +#define DRIVER_VERSION "2.34" static upsdrv_info_t table_info = { "APC command table", @@ -1063,6 +1063,9 @@ static void upsdrv_shutdown_advanced(long status) /* power down the attached load immediately */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + char temp[32]; ssize_t ret; long status; diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c index 54c04dcda7..80f4cc3e05 100644 --- a/drivers/apcsmart.c +++ b/drivers/apcsmart.c @@ -37,7 +37,7 @@ #include "apcsmart_tabs.h" #define DRIVER_NAME "APC Smart protocol driver" -#define DRIVER_VERSION "3.34" +#define DRIVER_VERSION "3.35" #ifdef WIN32 # ifndef ECANCELED @@ -1753,7 +1753,10 @@ static void upsdrv_shutdown_advanced(void) /* power down the attached load immediately */ void upsdrv_shutdown(void) { - char temp[APC_LBUF]; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + char temp[APC_LBUF]; if (!smartmode(1)) upslogx(LOG_WARNING, "%s: %s", __func__, "setting SmartMode failed !"); diff --git a/drivers/apcupsd-ups.c b/drivers/apcupsd-ups.c index 9f4b71bc20..66f2dcf42e 100644 --- a/drivers/apcupsd-ups.c +++ b/drivers/apcupsd-ups.c @@ -57,7 +57,7 @@ typedef struct pollfd { #include "nut_stdint.h" #define DRIVER_NAME "apcupsd network client UPS driver" -#define DRIVER_VERSION "0.72" +#define DRIVER_VERSION "0.73" #define POLL_INTERVAL_MIN 10 @@ -363,9 +363,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/asem.c b/drivers/asem.c index 82b4746627..77a826848f 100644 --- a/drivers/asem.c +++ b/drivers/asem.c @@ -67,7 +67,7 @@ #endif #define DRIVER_NAME "ASEM" -#define DRIVER_VERSION "0.13" +#define DRIVER_VERSION "0.14" /* Valid on ASEM PB1300 UPS */ #define BQ2060_ADDRESS 0x0B @@ -326,6 +326,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ /* maybe try to detect the UPS here, but try a shutdown even if @@ -333,7 +336,8 @@ void upsdrv_shutdown(void) /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); /* you may have to check the line status since the commands for toggling power are frequently different for OL vs. OB */ diff --git a/drivers/bcmxcp.c b/drivers/bcmxcp.c index 63822ae876..91218b1b8f 100644 --- a/drivers/bcmxcp.c +++ b/drivers/bcmxcp.c @@ -116,7 +116,7 @@ TODO List: #include "bcmxcp.h" #define DRIVER_NAME "BCMXCP UPS driver" -#define DRIVER_VERSION "0.35" +#define DRIVER_VERSION "0.36" #define MAX_NUT_NAME_LENGTH 128 #define NUT_OUTLET_POSITION 7 @@ -1948,22 +1948,23 @@ float calculate_ups_load(const unsigned char *answer) void upsdrv_shutdown(void) { - upsdebugx(1, "upsdrv_shutdown..."); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - /* Try to shutdown with delay */ - if (instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ - return; - } + upsdebugx(1, "upsdrv_shutdown..."); - /* If the above doesn't work, try shutdown.stayoff */ - if (instcmd("shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) { + /* First try to shutdown with delay; + * if the above doesn't work, try shutdown.stayoff */ + if (do_loop_shutdown_commands("shutdown.return,shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) { /* Shutdown successful */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } diff --git a/drivers/belkin.c b/drivers/belkin.c index 8b339d2260..7044fae268 100644 --- a/drivers/belkin.c +++ b/drivers/belkin.c @@ -29,7 +29,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Belkin Smart protocol driver" -#define DRIVER_VERSION "0.27" +#define DRIVER_VERSION "0.28" static ssize_t init_communication(void); static ssize_t get_belkin_reply(char *buf); @@ -352,25 +352,12 @@ void upsdrv_updateinfo(void) /* power down the attached load immediately */ void upsdrv_shutdown(void) { - ssize_t res; - - res = init_communication(); - if (res < 0) { - printf("Detection failed. Trying a shutdown command anyway.\n"); - } + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - /* tested on a F6C525-SER: this works when OL and OB */ - - /* shutdown type 2 (UPS system) */ - send_belkin_command(CONTROL, "SDT", "2"); - - /* SDR means "do SDT and SDA, then reboot after n minutes" */ - send_belkin_command(CONTROL, "SDR", "1"); - - printf("UPS should power off load in 5 seconds\n"); - - /* shutdown in 5 seconds */ - send_belkin_command(CONTROL, "SDA", "5"); + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /* handle "beeper.disable" */ @@ -432,6 +419,30 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } + if (!strcasecmp(cmdname, "shutdown.return")) { + ssize_t res; + + res = init_communication(); + if (res < 0) { + printf("Detection failed. Trying a shutdown command anyway.\n"); + } + + /* tested on a F6C525-SER: this works when OL and OB */ + + /* shutdown type 2 (UPS system) */ + send_belkin_command(CONTROL, "SDT", "2"); + + /* SDR means "do SDT and SDA, then reboot after n minutes" */ + send_belkin_command(CONTROL, "SDR", "1"); + + printf("UPS should power off load in 5 seconds\n"); + + /* shutdown in 5 seconds */ + send_belkin_command(CONTROL, "SDA", "5"); + + return STAT_INSTCMD_HANDLED; + } + if (!strcasecmp(cmdname, "load.off")) { do_off(); return STAT_INSTCMD_HANDLED; @@ -530,6 +541,7 @@ void upsdrv_initinfo(void) dstate_addcmd("beeper.disable"); dstate_addcmd("beeper.enable"); + dstate_addcmd("shutdown.return"); dstate_addcmd("load.off"); dstate_addcmd("load.on"); dstate_addcmd("test.battery.start.quick"); diff --git a/drivers/belkinunv.c b/drivers/belkinunv.c index 0c82865321..09f7e40a99 100644 --- a/drivers/belkinunv.c +++ b/drivers/belkinunv.c @@ -94,7 +94,7 @@ #include "serial.h" #define DRIVER_NAME "Belkin 'Universal UPS' driver" -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1166,6 +1166,9 @@ void upsdrv_updateinfo(void) /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* Note: this UPS cannot (apparently) be put into "soft shutdown" mode; thus the -k option should not normally be used; instead, a workaround using the "-x wait" option @@ -1180,9 +1183,13 @@ void upsdrv_shutdown(void) Don't use this! Use the solution involving the "-x wait" option instead, as suggested on the belkinunv(8) man - page. */ + page. + */ - upslogx(LOG_WARNING, "You are using the -k option, which is broken for this driver.\nShutting down for 10 minutes and hoping for the best"); + upslogx(LOG_WARNING, + "You are using the -k option, which is broken for this driver.\n" + "Check belkinunv(8) man page about '-x wait' option instead.\n" + "Shutting down for 10 minutes and hoping for the best"); belkin_nut_write_int(REG_RESTARTTIMER, 10); /* 10 minutes */ belkin_nut_write_int(REG_SHUTDOWNTIMER, 1); /* 1 second */ diff --git a/drivers/bestfcom.c b/drivers/bestfcom.c index 5402114652..c2a437a593 100644 --- a/drivers/bestfcom.c +++ b/drivers/bestfcom.c @@ -45,7 +45,7 @@ #include "serial.h" #define DRIVER_NAME "Best Ferrups/Fortress driver" -#define DRIVER_VERSION "0.15" +#define DRIVER_VERSION "0.16" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -103,6 +103,7 @@ static struct { static int inverter_status; /* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); /* Set up all the funky shared memory stuff used to communicate with upsd */ void upsdrv_initinfo (void) @@ -158,6 +159,12 @@ void upsdrv_initinfo (void) fc.fullvolts, fc.lowvolts, fc.emptyvolts); + + /* commands ----------------------------------------------- */ + dstate_addcmd("shutdown.return"); + + /* install handlers */ + upsh.instcmd = instcmd; } @@ -461,12 +468,35 @@ static void ups_sync(void) ser_get_line(upsfd, buf, sizeof(buf), '>', "\012", 3, 0); } +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) +{ + NUT_UNUSED_VARIABLE(extra); + + if (!strcasecmp(cmdname, "shutdown.return")) { + /* NB: hard-wired password */ + ser_send(upsfd, "pw377\r"); + /* power off in 10 seconds and restart when line power returns, + * FE7K required a min of 5 seconds for off to function */ + ser_send(upsfd, "o 10 a\r"); + + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} + /* power down the attached load immediately */ void upsdrv_shutdown(void) { - /* NB: hard-wired password */ - ser_send(upsfd, "pw377\r"); - ser_send(upsfd, "o 10 a\r"); /* power off in 10 seconds and restart when line power returns, FE7K required a min of 5 seconds for off to function */ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /* list flags and values that you want to receive via -x */ diff --git a/drivers/bestfortress.c b/drivers/bestfortress.c index 8c650abb60..8869840c6f 100644 --- a/drivers/bestfortress.c +++ b/drivers/bestfortress.c @@ -35,7 +35,7 @@ #endif #define DRIVER_NAME "Best Fortress UPS driver" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -514,24 +514,16 @@ static int upsdrv_setvar (const char *var, const char * data) { */ void upsdrv_shutdown(void) { - const char *grace; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - upsdebugx(2, "%s: begin", __func__); - - grace = dstate_getinfo("ups.delay.shutdown"); - if (!grace) { - upsdebugx(1, "%s: ups.delay.shutdown is NULL!", __func__); - /* Pick a different value than 20 so we can see it in the logs. */ - grace = "30"; - } + int ret = -1; - upslogx(LOG_CRIT, "%s: OFF/restart in %s seconds", __func__, grace); - - /* Start again, overriding front panel setting. */ - autorestart (1); + upsdebugx(2, "%s: begin", __func__); - upssend ("OFF%s\r", grace); - /* I'm nearly dead, Jim */ + ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); upsdebugx(2, "%s: end", __func__); } @@ -546,8 +538,26 @@ static int instcmd (const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } else if (!strcasecmp(cmdname, "shutdown.return")) { + const char *grace; + upsdebugx(2, "%s: %s: start", __func__, cmdname); - upsdrv_shutdown(); + + grace = dstate_getinfo("ups.delay.shutdown"); + if (!grace) { + upsdebugx(1, "%s: ups.delay.shutdown is NULL!", __func__); + /* Pick a different value than 20 so we can see it in the logs. */ + grace = "30"; + } + + upslogx(LOG_CRIT, "%s: OFF/restart in %s seconds", __func__, grace); + + /* Start again, overriding front panel setting. */ + autorestart (1); + + upssend ("OFF%s\r", grace); + /* I'm nearly dead, Jim */ + + upsdebugx(2, "%s: %s: end", __func__, cmdname); return STAT_INSTCMD_HANDLED; } /* \todo Software error or user error? */ diff --git a/drivers/bestuferrups.c b/drivers/bestuferrups.c index 1585d6a903..0a1bbda1d6 100644 --- a/drivers/bestuferrups.c +++ b/drivers/bestuferrups.c @@ -33,7 +33,7 @@ #include "serial.h" #define DRIVER_NAME "Best Ferrups Series ME/RE/MD driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -77,6 +77,7 @@ static struct { /* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); /* Set up all the funky shared memory stuff used to communicate with upsd */ void upsdrv_initinfo (void) @@ -101,9 +102,15 @@ void upsdrv_initinfo (void) fprintf(stderr, "Best Power %s detected\n", dstate_getinfo("ups.model")); fprintf(stderr, "Battery voltages %5.1f nominal, %5.1f full, %5.1f empty\n", - fc.idealbvolts, - fc.fullvolts, - fc.emptyvolts); + fc.idealbvolts, + fc.fullvolts, + fc.emptyvolts); + + /* commands ----------------------------------------------- */ + dstate_addcmd("shutdown.return"); + + /* install handlers */ + upsh.instcmd = instcmd; } @@ -361,12 +368,34 @@ static void ups_sync(void) } } +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) +{ + NUT_UNUSED_VARIABLE(extra); + + if (!strcasecmp(cmdname, "shutdown.return")) { + /* NB: hard-wired password */ + ser_send(upsfd, "pw377\r"); + /* power off in 1 second and restart when line power returns */ + ser_send(upsfd, "off 1 a\r"); + + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} + /* power down the attached load immediately */ void upsdrv_shutdown(void) { -/* NB: hard-wired password */ - ser_send(upsfd, "pw377\r"); - ser_send(upsfd, "off 1 a\r"); /* power off in 1 second and restart when line power returns */ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /* list flags and values that you want to receive via -x */ diff --git a/drivers/bestups.c b/drivers/bestups.c index b34e804c5b..d3746d02ef 100644 --- a/drivers/bestups.c +++ b/drivers/bestups.c @@ -29,7 +29,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Best UPS driver" -#define DRIVER_VERSION "1.09" +#define DRIVER_VERSION "1.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -319,6 +319,9 @@ static int ups_on_line(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + printf("The UPS will shut down in approximately one minute.\n"); if (ups_on_line()) diff --git a/drivers/bicker_ser.c b/drivers/bicker_ser.c index a5c2c4db49..0d2a121d58 100644 --- a/drivers/bicker_ser.c +++ b/drivers/bicker_ser.c @@ -108,7 +108,7 @@ #include "serial.h" #define DRIVER_NAME "Bicker serial protocol" -#define DRIVER_VERSION "0.02" +#define DRIVER_VERSION "0.03" #define BICKER_SOH 0x01 #define BICKER_EOT 0x04 @@ -860,17 +860,22 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - int retry; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int retry; for (retry = 1; retry <= BICKER_RETRIES; retry++) { if (bicker_shutdown() > 0) { - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/blazer.c b/drivers/blazer.c index af224fb5bb..69add382bd 100644 --- a/drivers/blazer.c +++ b/drivers/blazer.c @@ -851,17 +851,18 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + int retry; /* Stop pending shutdowns */ for (retry = 1; retry <= MAXTRIES; retry++) { - if (blazer_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { continue; } break; - } if (retry > MAXTRIES) { @@ -870,16 +871,17 @@ void upsdrv_shutdown(void) /* Shutdown */ for (retry = 1; retry <= MAXTRIES; retry++) { - if (blazer_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) { continue; } upslogx(LOG_ERR, "Shutting down in %ld seconds", offdelay); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } diff --git a/drivers/blazer_ser.c b/drivers/blazer_ser.c index 5f830e3e6a..8bdc2c423f 100644 --- a/drivers/blazer_ser.c +++ b/drivers/blazer_ser.c @@ -31,7 +31,7 @@ #include "blazer.h" #define DRIVER_NAME "Megatec/Q1 protocol serial driver" -#define DRIVER_VERSION "1.62" +#define DRIVER_VERSION "1.63" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/blazer_usb.c b/drivers/blazer_usb.c index fb0f75a2d5..f2cf3432aa 100644 --- a/drivers/blazer_usb.c +++ b/drivers/blazer_usb.c @@ -37,7 +37,7 @@ #endif #define DRIVER_NAME "Megatec/Q1 protocol USB driver" -#define DRIVER_VERSION "0.20" +#define DRIVER_VERSION "0.21" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/clone-outlet.c b/drivers/clone-outlet.c index 791a9648c9..0cc8e7fa98 100644 --- a/drivers/clone-outlet.c +++ b/drivers/clone-outlet.c @@ -31,7 +31,7 @@ #endif #define DRIVER_NAME "Clone outlet UPS driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -512,11 +512,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* replace with a proper shutdown function */ -/* upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); - */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } diff --git a/drivers/clone.c b/drivers/clone.c index b15819eb78..ec45672dee 100644 --- a/drivers/clone.c +++ b/drivers/clone.c @@ -33,7 +33,7 @@ #endif #define DRIVER_NAME "Clone UPS driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -660,9 +660,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } diff --git a/drivers/cps-hid.c b/drivers/cps-hid.c index 651807a454..f29d974046 100644 --- a/drivers/cps-hid.c +++ b/drivers/cps-hid.c @@ -32,7 +32,7 @@ #include "cps-hid.h" #include "usb-common.h" -#define CPS_HID_VERSION "CyberPower HID 0.81" +#define CPS_HID_VERSION "CyberPower HID 0.82" /* Cyber Power Systems */ #define CPS_VENDORID 0x0764 @@ -234,6 +234,7 @@ static hid_info_t cps_hid2nut[] = { { "ups.timer.shutdown", 0, 0, "UPS.Output.DelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, { "ups.timer.reboot", 0, 0, "UPS.Output.DelayBeforeReboot", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, { "ups.firmware", 0, 0, "UPS.PowerSummary.CPSFirmwareVersion", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, + { "ups.temperature", 0, 0, "UPS.PowerSummary.Temperature", NULL, "%s", 0, kelvin_celsius_conversion }, /* Special case: ups.status & ups.alarm */ { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info }, diff --git a/drivers/dstate.c b/drivers/dstate.c index f4132a829b..933231d979 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -855,7 +855,16 @@ static int sock_arg(conn_t *conn, size_t numarg, char **arg) return 1; } - upslogx(LOG_NOTICE, "Got INSTCMD, but driver lacks a handler"); + if (cmdparam) { + upslogx(LOG_NOTICE, + "Got INSTCMD '%s' '%s', but driver lacks a handler", + NUT_STRARG(cmdname), NUT_STRARG(cmdparam)); + } else { + upslogx(LOG_NOTICE, + "Got INSTCMD '%s', but driver lacks a handler", + NUT_STRARG(cmdname)); + } + return 1; } diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 939bf16b1b..c20aabe376 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -2,7 +2,7 @@ Copyright (C) 2005 - 2015 Arnaud Quette - 2014 - 2023 Jim Klimov + 2014 - 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -48,7 +48,7 @@ #include "dummy-ups.h" #define DRIVER_NAME "Device simulation and repeater driver" -#define DRIVER_VERSION "0.19" +#define DRIVER_VERSION "0.20" /* driver description structure */ upsdrv_info_t upsdrv_info = @@ -382,9 +382,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } static int instcmd(const char *cmdname, const char *extra) diff --git a/drivers/etapro.c b/drivers/etapro.c index 6e4f593fe8..1e09d518cc 100644 --- a/drivers/etapro.c +++ b/drivers/etapro.c @@ -55,7 +55,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "ETA PRO driver" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -66,6 +66,14 @@ upsdrv_info_t upsdrv_info = { { NULL } }; +/* TODO: delays should be tunable, the UPS supports max 32767 minutes. */ + +/* Shutdown command to off delay in seconds. */ +#define SHUTDOWN_GRACE_TIME 10 + +/* Shutdown to return delay in seconds. */ +#define SHUTDOWN_TO_RETURN_TIME 15 + static int etapro_get_response(const char *resp_type) { @@ -192,7 +200,8 @@ static int instcmd(const char *cmdname, const char *extra) } if (!strcasecmp(cmdname, "shutdown.return")) { - upsdrv_shutdown(); + etapro_set_on_timer(SHUTDOWN_GRACE_TIME + SHUTDOWN_TO_RETURN_TIME); + etapro_set_off_timer(SHUTDOWN_GRACE_TIME); return STAT_INSTCMD_HANDLED; } @@ -331,19 +340,15 @@ upsdrv_updateinfo(void) dstate_dataok(); } -/* TODO: delays should be tunable, the UPS supports max 32767 minutes. */ - -/* Shutdown command to off delay in seconds. */ -#define SHUTDOWN_GRACE_TIME 10 - -/* Shutdown to return delay in seconds. */ -#define SHUTDOWN_TO_RETURN_TIME 15 - void upsdrv_shutdown(void) { - etapro_set_on_timer(SHUTDOWN_GRACE_TIME + SHUTDOWN_TO_RETURN_TIME); - etapro_set_off_timer(SHUTDOWN_GRACE_TIME); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void diff --git a/drivers/everups.c b/drivers/everups.c index 6a3b09d57f..b9a8f909d2 100644 --- a/drivers/everups.c +++ b/drivers/everups.c @@ -21,7 +21,7 @@ #include "serial.h" #define DRIVER_NAME "Ever UPS driver (serial)" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -34,6 +34,9 @@ upsdrv_info_t upsdrv_info = { static unsigned char upstype = 0; +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + static void init_serial(void) { ser_set_dtr(upsfd, 0); @@ -86,6 +89,15 @@ void upsdrv_initinfo(void) { dstate_setinfo("ups.mfr", "Ever"); dstate_setinfo("ups.model", "%s", GetTypeUpsName()); + + /* commands ----------------------------------------------- */ + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("load.off"); + + /* install handlers */ + upsh.instcmd = instcmd; } void upsdrv_updateinfo(void) @@ -161,20 +173,46 @@ void upsdrv_updateinfo(void) dstate_dataok(); } -void upsdrv_shutdown(void) +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) { - if (!Code(2)) { - upslog_with_errno(LOG_INFO, "Code failed"); - return; - } - ser_send_char(upsfd, 28); - ser_send_char(upsfd, 1); /* 1.28 sec */ - if (!Code(1)) { - upslog_with_errno(LOG_INFO, "Code failed"); - return; + NUT_UNUSED_VARIABLE(extra); + + /* FIXME: Which one is this - "load.off", + * "shutdown.stayoff" or "shutdown.return"? */ + + /* Shutdown UPS */ + if (!strcasecmp(cmdname, "load.off")) + { + if (!Code(2)) { + upslog_with_errno(LOG_INFO, "Code failed"); + return STAT_INSTCMD_UNKNOWN; + } + ser_send_char(upsfd, 28); + ser_send_char(upsfd, 1); /* 1.28 sec */ + if (!Code(1)) { + upslog_with_errno(LOG_INFO, "Code failed"); + return STAT_INSTCMD_UNKNOWN; + } + ser_send_char(upsfd, 13); + ser_send_char(upsfd, 8); + + return STAT_INSTCMD_HANDLED; } - ser_send_char(upsfd, 13); - ser_send_char(upsfd, 8); + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} + +void upsdrv_shutdown(void) +{ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("load.off", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/gamatronic.c b/drivers/gamatronic.c index 9c1b6fa6eb..3f37046ab9 100644 --- a/drivers/gamatronic.c +++ b/drivers/gamatronic.c @@ -33,7 +33,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Gamatronic UPS driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -63,6 +63,9 @@ upsdrv_info_t upsdrv_info = { * This should normally be a parameter! */ #define GAMATRONIC_BUF_LEN 140 +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + static int sec_upsrecv (char *buf) { char lenbuf[4]; @@ -80,7 +83,7 @@ static int sec_upsrecv (char *buf) lenbuf[3] = '\0'; ret = atoi(lenbuf); if (ret > GAMATRONIC_BUF_LEN) { - upslogx(1, "%s: got a longer response message " + upsdebugx(1, "%s: got a longer response message " "than expected for protocol: %d (%s) > %d", __func__, ret, lenbuf, GAMATRONIC_BUF_LEN); ret = GAMATRONIC_BUF_LEN; @@ -93,7 +96,7 @@ static int sec_upsrecv (char *buf) } /* else (ret <= 0) : */ - upslogx(1, "%s: invalid response message length: %s", + upsdebugx(1, "%s: invalid response message length: %s", __func__, lenbuf); return (-2); @@ -312,6 +315,12 @@ void upsdrv_initinfo(void) sec_poll(FLAG_POLLONCE); printf("UPS: %s %s\n", dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model")); + + /* commands ----------------------------------------------- */ + dstate_addcmd("shutdown.return"); + + /* install handlers */ + upsh.instcmd = instcmd; } void upsdrv_updateinfo(void) @@ -326,34 +335,46 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - ssize_t msglen; - char msgbuf[SMALLBUF]; - - msglen = snprintf(msgbuf, sizeof(msgbuf), "-1"); - sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msglen); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - msglen = snprintf(msgbuf, sizeof(msgbuf), "1"); - sec_cmd(SEC_SETCMD, SEC_AUTORESTART, msgbuf, &msglen); - - msglen = snprintf(msgbuf, sizeof(msgbuf), "2"); - sec_cmd(SEC_SETCMD, SEC_SHUTTYPE,msgbuf, &msglen); - - msglen = snprintf(msgbuf, sizeof(msgbuf), "5"); - sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msglen); + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } -/* +static int instcmd(const char *cmdname, const char *extra) { +/* if (!strcasecmp(cmdname, "test.battery.stop")) { ser_send_buf(upsfd, ...); return STAT_INSTCMD_HANDLED; } +*/ - upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname); + if (!strcasecmp(cmdname, "shutdown.return")) { + ssize_t msglen; + char msgbuf[SMALLBUF]; + + msglen = snprintf(msgbuf, sizeof(msgbuf), "-1"); + sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msglen); + + msglen = snprintf(msgbuf, sizeof(msgbuf), "1"); + sec_cmd(SEC_SETCMD, SEC_AUTORESTART, msgbuf, &msglen); + + msglen = snprintf(msgbuf, sizeof(msgbuf), "2"); + sec_cmd(SEC_SETCMD, SEC_SHUTTYPE,msgbuf, &msglen); + + msglen = snprintf(msgbuf, sizeof(msgbuf), "5"); + sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msglen); + + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); return STAT_INSTCMD_UNKNOWN; } -*/ void upsdrv_help(void) { diff --git a/drivers/generic_gpio_common.c b/drivers/generic_gpio_common.c index 6457f8c7d8..fd7229c811 100644 --- a/drivers/generic_gpio_common.c +++ b/drivers/generic_gpio_common.c @@ -471,9 +471,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/generic_modbus.c b/drivers/generic_modbus.c index 4aa87f17e7..8dd986a769 100644 --- a/drivers/generic_modbus.c +++ b/drivers/generic_modbus.c @@ -27,7 +27,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "NUT Generic Modbus driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ @@ -47,7 +47,6 @@ static uint32_t mod_resp_to_us = MODRESP_TIMEOUT_us; /* set the modbus res static uint32_t mod_byte_to_s = MODBYTE_TIMEOUT_s; /* set the modbus byte time out (us) */ static uint32_t mod_byte_to_us = MODBYTE_TIMEOUT_us; /* set the modbus byte time out (us) */ - /* get config vars set by -x or defined in ups.conf driver section */ void get_config_vars(void); @@ -95,6 +94,11 @@ void upsdrv_initinfo(void) { /* register instant commands */ if (sigar[FSD_T].addr != NOTUSED) { dstate_addcmd("load.off"); + + /* FIXME: Check with the device what this instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("shutdown.stayoff"); } /* set callback for instant commands */ @@ -316,37 +320,25 @@ void upsdrv_updateinfo(void) /* shutdown UPS */ void upsdrv_shutdown(void) { - int rval; - int cnt = FSD_REPEAT_CNT; /* shutdown repeat counter */ - struct timeval start; - long etime; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - /* retry sending shutdown command on error */ - while ((rval = upscmd("load.off", NULL)) != STAT_INSTCMD_HANDLED && cnt > 0) { - rval = gettimeofday(&start, NULL); - if (rval < 0) { - upslog_with_errno(LOG_ERR, "upscmd: gettimeofday"); - } + /* + * WARNING: When using RTU TCP, this driver will probably + * never support shutdowns properly, except on some systems: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ - /* wait for an increasing time interval before sending shutdown command */ - while ((etime = time_elapsed(&start)) < ( FSD_REPEAT_INTRV / cnt)); - upsdebugx(2,"ERROR: load.off failed, wait for %lims, retries left: %d\n", etime, cnt - 1); - cnt--; - } - switch (rval) { - case STAT_INSTCMD_FAILED: - case STAT_INSTCMD_INVALID: - upslogx(LOG_ERR, "shutdown failed"); - set_exit_flag(-1); - return; - case STAT_INSTCMD_UNKNOWN: - upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); - return; - default: - break; - } - upslogx(LOG_INFO, "shutdown command executed"); + int ret = do_loop_shutdown_commands("shutdown.stayoff", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /* print driver usage info */ @@ -630,6 +622,41 @@ int upscmd(const char *cmd, const char *arg) ); rval = STAT_INSTCMD_FAILED; } + } else if (!strcasecmp(cmd, "shutdown.stayoff")) { + /* FIXME: Which one is this actually - + * "shutdown.stayoff" or "shutdown.return"? */ + int cnt = FSD_REPEAT_CNT; /* shutdown repeat counter */ + + /* retry sending shutdown command on error */ + while ((rval = upscmd("load.off", NULL)) != STAT_INSTCMD_HANDLED && cnt > 0) { + rval = gettimeofday(&start, NULL); + if (rval < 0) { + upslog_with_errno(LOG_ERR, "upscmd: gettimeofday"); + } + + /* wait for an increasing time interval before sending shutdown command */ + while ((etime = time_elapsed(&start)) < ( FSD_REPEAT_INTRV / cnt)); + upsdebugx(2,"ERROR: load.off failed, wait for %lims, retries left: %d\n", etime, cnt - 1); + cnt--; + } + switch (rval) { + case STAT_INSTCMD_FAILED: + case STAT_INSTCMD_INVALID: + upslogx(LOG_ERR, "shutdown failed"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); + return rval; + case STAT_INSTCMD_UNKNOWN: + upslogx(LOG_ERR, "shutdown not supported"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); + return rval; + default: + upslogx(LOG_INFO, "shutdown command executed"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); + break; + } } else { upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmd, arg); rval = STAT_INSTCMD_UNKNOWN; diff --git a/drivers/genericups.c b/drivers/genericups.c index fe9f1dbc9b..2fde2f8555 100644 --- a/drivers/genericups.c +++ b/drivers/genericups.c @@ -31,7 +31,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Generic contact-closure UPS driver" -#define DRIVER_VERSION "1.40" +#define DRIVER_VERSION "1.41" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -310,11 +310,15 @@ static void set_ups_type(void) /* power down the attached load immediately */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + int flags, ret; if (upstype == -1) { upslogx(LOG_ERR, "No upstype set - see help text / man page!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } @@ -322,7 +326,8 @@ void upsdrv_shutdown(void) if (flags == -1) { upslogx(LOG_ERR, "No shutdown command defined for this model!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } @@ -331,7 +336,8 @@ void upsdrv_shutdown(void) #ifndef WIN32 #ifndef HAVE_TCSENDBREAK upslogx(LOG_ERR, "Need to send a BREAK, but don't have tcsendbreak!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; #endif #endif @@ -340,7 +346,8 @@ void upsdrv_shutdown(void) if (ret != 0) { upslog_with_errno(LOG_ERR, "tcsendbreak"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } return; @@ -354,7 +361,8 @@ void upsdrv_shutdown(void) if (ret != 0) { upslog_with_errno(LOG_ERR, "ioctl TIOCMSET"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } @@ -434,8 +442,8 @@ void upsdrv_initups(void) } /* - See if the user wants to override the output signal definitions - this must be done here, since we might go to upsdrv_shutdown() + See if the user wants to override the output signal definitions? + This must be done here, since we might go to upsdrv_shutdown() immediately. Input signal definition override is handled in upsdrv_initinfo() */ diff --git a/drivers/huawei-ups2000.c b/drivers/huawei-ups2000.c index 403eb1bdcd..31afe6bcb4 100644 --- a/drivers/huawei-ups2000.c +++ b/drivers/huawei-ups2000.c @@ -51,7 +51,7 @@ #include "timehead.h" /* fallback gmtime_r() variants if needed (e.g. some WIN32) */ #define DRIVER_NAME "NUT Huawei UPS2000 (1kVA-3kVA) RS-232 Modbus driver" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 @@ -1801,12 +1801,14 @@ static int ups2000_update_timers(void) void upsdrv_shutdown(void) { - int r; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - r = instcmd("shutdown.reboot", ""); - if (r != STAT_INSTCMD_HANDLED) { + int ret = do_loop_shutdown_commands("shutdown.reboot", NULL); + if (ret != STAT_INSTCMD_HANDLED) { upslogx(LOG_ERR, "upsdrv_shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } } diff --git a/drivers/hwmon_ina219.c b/drivers/hwmon_ina219.c index 988208a2a1..dc2b3336e6 100644 --- a/drivers/hwmon_ina219.c +++ b/drivers/hwmon_ina219.c @@ -35,8 +35,9 @@ #define SYSFS_HWMON_DIR "/sys/class/hwmon" #define BATTERY_CHARGE_LOW 15 + #define DRIVER_NAME "hwmon-INA219 UPS driver" -#define DRIVER_VERSION "0.01" +#define DRIVER_VERSION "0.02" upsdrv_info_t upsdrv_info = { DRIVER_NAME, @@ -462,8 +463,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/isbmex.c b/drivers/isbmex.c index eff64b12f1..e9c32ece6d 100644 --- a/drivers/isbmex.c +++ b/drivers/isbmex.c @@ -27,7 +27,7 @@ #include #define DRIVER_NAME "ISBMEX UPS driver" -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -53,6 +53,9 @@ upsdrv_info_t upsdrv_info = { #define MAXTRIES 15 /* #define IGNCHARS "" */ +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + static float lagrange(unsigned int vbyte) { float f0, f1, f2, f3, f4, f5, f6; @@ -145,6 +148,15 @@ void upsdrv_initinfo(void) /* addinfo(INFO_, "", 0, 0); */ /*printf("Using %s %s on %s\n", getdata(INFO_MFR), getdata(INFO_MODEL), device_path);*/ + + /* commands ----------------------------------------------- */ + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("shutdown.stayoff"); + + /* install handlers */ + upsh.instcmd = instcmd; } static const char *getpacket(int *we_know){ @@ -346,30 +358,61 @@ void upsdrv_updateinfo(void) return; } -void upsdrv_shutdown(void) +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) { - /* shutdown is supported on models with - * contact closure. Some ISB models with serial - * support support contact closure, some don't. - * If yours does support it, then a 12V signal - * on pin 9 does the trick (only when ups is - * on OB condition) */ - /* - * here try to do the pin 9 trick, if it does not - * work, else:*/ + NUT_UNUSED_VARIABLE(extra); + + /* FIXME: Which one is this - "load.off", + * "shutdown.stayoff" or "shutdown.return"? */ + + /* Shutdown UPS */ + if (!strcasecmp(cmdname, "shutdown.stayoff")) + { + int i; + + /* shutdown is supported on models with + * contact closure. Some ISB models with serial + * support support contact closure, some don't. + * If yours does support it, then a 12V signal + * on pin 9 does the trick (only when ups is + * on OB condition) */ + /* + * here try to do the pin 9 trick, if it does not + * work, else:*/ /* - upslogx(LOG_ERR, "Shutdown only supported with the Generic Driver, type 6 and special cable"); - //upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + upslogx(LOG_ERR, "Shutdown only supported with the Generic Driver, type 6 and special cable"); + //upslogx(LOG_ERR, "shutdown not supported"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); */ - int i; - for(i=0;i<=5;i++) - { - ser_send_char(upsfd, '#'); - usleep(50000); + + for (i = 0; i <= 5; i++) + { + ser_send_char(upsfd, '#'); + usleep(50000); + } + + return STAT_INSTCMD_HANDLED; } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; } +void upsdrv_shutdown(void) +{ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + int ret = do_loop_shutdown_commands("shutdown.stayoff", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); +} void upsdrv_help(void) { diff --git a/drivers/ivtscd.c b/drivers/ivtscd.c index 318e09c241..01575fe6ca 100644 --- a/drivers/ivtscd.c +++ b/drivers/ivtscd.c @@ -25,7 +25,7 @@ #include "attribute.h" #define DRIVER_NAME "IVT Solar Controller driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -183,8 +183,16 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* FIXME: This driver (and solar device?) does not seem to + * really support a shutdown. It also blocks in this method + * until battery.voltage.act becomes(?) greater than nominal, + * meaning power is back, and then exits the driver. + * All in all, looks odd. + */ while (1) { - if (ivt_status() < 7) { continue; } @@ -195,7 +203,8 @@ void upsdrv_shutdown(void) /* Hmmm, why was this an exit-case before? fatalx(EXIT_SUCCESS...) */ upslogx(LOG_ERR, "Power is back!"); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } } diff --git a/drivers/liebert-esp2.c b/drivers/liebert-esp2.c index 10b67b789e..08e3e1b0cd 100644 --- a/drivers/liebert-esp2.c +++ b/drivers/liebert-esp2.c @@ -28,7 +28,7 @@ #define IsBitSet(val, bit) ((val) & (1 << (bit))) #define DRIVER_NAME "Liebert ESP-II serial UPS driver" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" #define UPS_SHUTDOWN_DELAY 12 /* it means UPS will be shutdown 120 sec */ #define SHUTDOWN_CMD_LEN 8 @@ -553,13 +553,20 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - char reply[8]; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + char reply[8]; if(!(do_command(cmd_setOutOffMode, reply, 8) != -1) && (do_command(cmd_setOutOffDelay, reply, 8) != -1) && (do_command(cmd_sysLoadKey, reply, 6) != -1) && - (do_command(cmd_shutdown, reply, 8) != -1)) - upslogx(LOG_ERR, "Failed to shutdown UPS"); + (do_command(cmd_shutdown, reply, 8) != -1) + ) { + upslogx(LOG_ERR, "Failed to shutdown UPS"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); + } } static int instcmd(const char *cmdname, const char *extra) diff --git a/drivers/liebert-gxe.c b/drivers/liebert-gxe.c index f4c344a0da..0412070625 100644 --- a/drivers/liebert-gxe.c +++ b/drivers/liebert-gxe.c @@ -24,7 +24,7 @@ #include "ydn23.h" #define DRIVER_NAME "Liebert GXE Series UPS driver" -#define DRIVER_VERSION "0.01" +#define DRIVER_VERSION "0.02" #define PROBE_RETRIES 3 #define DEFAULT_STALE_RETRIES 3 @@ -511,7 +511,13 @@ void upsdrv_initups(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* FIXME: There seems to be instcmd(load.off), why not that? */ upslogx(LOG_INFO, "Liebert GXE UPS can't fully shutdown, NOOP"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_cleanup(void) diff --git a/drivers/liebert.c b/drivers/liebert.c index 0c1bc176cc..67710c5abc 100644 --- a/drivers/liebert.c +++ b/drivers/liebert.c @@ -27,7 +27,7 @@ #include "attribute.h" #define DRIVER_NAME "Liebert MultiLink UPS driver" -#define DRIVER_VERSION "1.05" +#define DRIVER_VERSION "1.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -43,12 +43,16 @@ upsdrv_info_t upsdrv_info = { void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* XXX: replace with a proper shutdown function (raise DTR) */ /* worse yet: stock cables don't support shutdown at all */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_initinfo(void) diff --git a/drivers/macosx-ups.c b/drivers/macosx-ups.c index b35585ef93..de2e4f73a2 100644 --- a/drivers/macosx-ups.c +++ b/drivers/macosx-ups.c @@ -29,7 +29,7 @@ #include "IOKit/ps/IOPSKeys.h" #define DRIVER_NAME "Mac OS X UPS meta-driver" -#define DRIVER_VERSION "1.41" +#define DRIVER_VERSION "1.42" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -264,6 +264,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ /* maybe try to detect the UPS here, but try a shutdown even if @@ -272,8 +275,11 @@ void upsdrv_shutdown(void) /* NOTE: Mac OS X already has shutdown routines - this driver is more for monitoring and notification purposes. Still, there is a key that might be useful to set in SystemConfiguration land. */ + + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); /* you may have to check the line status since the commands for toggling power are frequently different for OL vs. OB */ diff --git a/drivers/main.c b/drivers/main.c index cb21941146..f89142372c 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -38,7 +38,11 @@ /* data which may be useful to the drivers */ TYPE_FD upsfd = ERROR_FD; -char *device_path = NULL; +/* Cached values of dstate_getinfo("driver.parameter.port") + * and dstate_getinfo("driver.parameter.sdcommands") - set + * during their assignment when reading program parameters + * from CLI or config file. */ +char *device_path = NULL, *device_sdcommands = NULL; const char *progname = NULL, *upsname = NULL, *device_name = NULL; /* may be set by the driver to wake up while in dstate_poll_fds */ @@ -47,6 +51,19 @@ TYPE_FD extrafd = ERROR_FD; static HANDLE mutex = INVALID_HANDLE_VALUE; #endif +/* Set by INSTCMD to killpower or by running `drivername -k` to + * help differentiate calls into upsdrv_shutdown() and further + * into instcmd() implementations that set UPS power state as + * needing the driver to set_exit_flag() or not. Values: + * -1 Do not exit even if killing power (e.g. when shutting + * down an UPS that we only monitor, but are not fed from + * and not shutting down THIS system). TODO: Ways to set. + * 0 Default do not exit (routinely handling an INSTCMD like + * "shutdown.return") + * 1 Exit (set when this driver is killing power) + */ +int handling_upsdrv_shutdown = 0; + /* for ser_open */ int do_lock_port = 1; @@ -170,13 +187,21 @@ static void forceshutdown(void) static void forceshutdown(void) { - upslogx(LOG_NOTICE, "Initiating UPS shutdown"); + upslogx(LOG_NOTICE, "Initiating UPS [%s] shutdown", upsname); - /* the driver must not block in this function */ - upsdrv_shutdown(); + /* NOTE: This is currently called exclusively as `drivername -k` + * CLI argument handling, so we exit afterwards and do not care + * about possible `handling_upsdrv_shutdown = -1` use-cases here. + */ + handling_upsdrv_shutdown = 1; - /* the driver always exits here, to not block probable ongoing shutdown */ - exit(exit_flag == -1 ? EXIT_FAILURE : EXIT_SUCCESS); + /* the driver must not block in this function (calling INSTCMD from + * `sdcommands` passed by user, or upsdrv_shutdown() by default */ + upsdrv_shutdown_sdcommands_or_default(NULL, NULL); + + /* the driver always exits here, to + * not block probable ongoing shutdown */ + exit(exit_flag == EF_EXIT_FAILURE ? EXIT_FAILURE : EXIT_SUCCESS); } /* this function only prints the usage message; it does not call exit() */ @@ -665,7 +690,7 @@ int testval_reloadable(const char *var, const char *oldval, const char *newval, /* Similar to testvar_reloadable() above which is for addvar*() defined * entries, but for less streamlined stuff defined right here in main.c. - * See if (by name saved in dstate) can be (re-)loaded now: + * See if (by saved in dstate) can be (re-)loaded now: * either it is reloadable by parameter definition, or no value has been * saved into it yet ( is NULL). * Returns "-1" if nothing needs to be done and that is not a failure @@ -741,6 +766,196 @@ void addvar(int vartype, const char *name, const char *desc) do_addvar(vartype, name, desc, 0); } +/* Try each instant command in the comma-separated list of + * sdcmds, until the first one that reports it was handled. + * Returns STAT_INSTCMD_HANDLED if one of those was accepted + * by the device, or STAT_INSTCMD_INVALID if none succeeded. + * If cmdused is not NULL, it is populated by the command + * string which succeeded (or NULL if none), and the caller + * should free() it eventually. This method also frees any + * non-NULL *cmdused, so it should be pre-initialized to NULL. + */ +int do_loop_shutdown_commands(const char *sdcmds, char **cmdused) { + int cmdret = STAT_INSTCMD_UNKNOWN; + char *buf = NULL, *s = NULL; + static int call_depth = 0; + + call_depth++; + upsdebugx(1, "Starting %s(%s), call depth %d...", + __func__, NUT_STRARG(sdcmds), call_depth); + + if (call_depth > MAX_SDCOMMANDS_DEPTH) { + /* Might get here e.g. if someone were to call + * upsdrv_shutdown_sdcommands_or_default() along + * an implementation code path from upsdrv_shutdown() + * with sdcmds=="shutdown.default"?.. */ + fatalx(EXIT_FAILURE, "Shutdown handlers for UPS [%s] are " + "too deeply nested, this seems to be either " + "a NUT programming error or a mis-configuration " + "of your 'sdcommands' setting", NUT_STRARG(upsname)); + } + + if (cmdused) { + if (*cmdused) + free(*cmdused); + *cmdused = NULL; + } + + if (!sdcmds || !*sdcmds) { + upsdebugx(1, "This driver or its configuration did not pass any instant commands to run"); + goto done; + } + + if (upsh.instcmd == NULL) { + /* FIXME: support main_instcmd() too? */ + upsdebugx(1, "This driver does not implement INSTCMD support"); + + /* ...but the default one we can short-circuit without + * registered INSTCMDs (FIXME: loop detection/protection): + */ + s = strstr(sdcmds, "shutdown.default"); + if (s) { + /* check this is really a sub-string */ + size_t cmdlen = strlen("shutdown.default"); + if ( + (s == sdcmds || *(s-1) == ',') && + (s[cmdlen] == '\0' || s[cmdlen] == ',') + ) { + upsdebugx(1, "Handle 'shutdown.default' directly, " + "ignore other `sdcommands` (if any): %s", + sdcmds); + upsdrv_shutdown(); + cmdret = STAT_INSTCMD_HANDLED; + /* commented below */ + if (cmdused && !(*cmdused)) + *cmdused = xstrdup("shutdown.default"); + } + } + goto done; + } + + buf = xstrdup(sdcmds); + while ((s = strtok(s == NULL ? buf : NULL, ",")) != NULL) { + if (!*s) + continue; + + if (!strcmp(s, "shutdown.default")) { + upsdrv_shutdown(); + cmdret = STAT_INSTCMD_HANDLED; + } else { + cmdret = upsh.instcmd(s, NULL); + } + + if (cmdret == STAT_INSTCMD_HANDLED) { + /* Shutdown successful */ + + /* Note: If we are handling "shutdown.default" here, + * it is anticipated that it calls some other INSTCMD + * as the implementation, and that could set a value + * which we actually want to keep and tell the caller. + * We had freed *cmdused above, so it if is not empty + * here - something during the handling populated it. + */ + if (cmdused && !(*cmdused)) + *cmdused = xstrdup(s); + upsdebugx(1, "%s(): command '%s' was handled successfully", __func__, NUT_STRARG(s)); + goto done; + } + } + +done: + if (buf) + free(buf); + if (cmdret != STAT_INSTCMD_HANDLED) + cmdret = STAT_INSTCMD_INVALID; + + upsdebugx(1, "Ending %s(%s), call depth %d: return-code %d", + __func__, NUT_STRARG(sdcmds), call_depth, cmdret); + call_depth--; + + return cmdret; +} + +/* Use driver-provided sdcmds_default, unless a custom driver parameter value + * "sdcommands" is set - then use it instead. Call do_loop_shutdown_commands() + * for actual work; return STAT_INSTCMD_HANDLED or STAT_INSTCMD_HANDLED as + * applicable; if caller-provided cmdused is not NULL, populate it with the + * command that was used successfully (if any). + */ +int loop_shutdown_commands(const char *sdcmds_default, char **cmdused) { + const char *sdcmds_custom = device_sdcommands; + + /* Belts and suspenders... */ + if (!sdcmds_custom) + sdcmds_custom = dstate_getinfo("driver.parameter.sdcommands"); + + if (sdcmds_custom) { + /* NOTE: User-provided commands may be something other + * than actual shutdown, e.g. a beeper to test that the + * INSTCMD happened such and when expected without + * impacting the load fed by the UPS. + */ + upsdebugx(1, "%s: call do_loop_shutdown_commands() with custom sdcommands", __func__); + return do_loop_shutdown_commands(sdcmds_custom, cmdused); + } else { + upsdebugx(1, "%s: call do_loop_shutdown_commands() with driver-default sdcommands", __func__); + return do_loop_shutdown_commands(sdcmds_default, cmdused); + } +} + +/* Since NUT v2.8.3 updated driver model, here we typically call the + * user-provided list of `sdcommands` (if any), or the caller-provided + * list (hard-coded in a driver), or the default implementation as + * "shutdown.default" (usually ends up as upsdrv_shutdown()) if nothing + * else was requested by the configuration and call chain. + * Any of these call chains, especially the default one, in turn may + * call instcmd() for whatever practical action is needed (unless the + * default logic is all-custom implemented in upsdrv_shutdown()), and + * reports the result (including the case of lacking a registered + * upsh.instcmd handler). + * + * A practical shutdown implementation logic would be coded + * in instcmd("shutdown.default") and may be structured like + * detailed by comments in skel.c::upsdrv_shutdown(), or can + * just call a fitting other instcmd (typically "shutdown.stayoff", + * "shutdown.return" or "load.off") -- possibly chosen by current + * on-line/on-battery state. + */ +int upsdrv_shutdown_sdcommands_or_default(const char *sdcmds_default, char **cmdused) { + char *sdcmd_used = NULL; + int sdret = loop_shutdown_commands( + sdcmds_default ? sdcmds_default : "shutdown.default", + &sdcmd_used); + + if (cmdused) { + if (*cmdused) + free(*cmdused); + *cmdused = NULL; + } + + if (sdret == STAT_INSTCMD_HANDLED) { + upslogx(LOG_INFO, "UPS [%s]: shutdown request was successful with '%s'", + NUT_STRARG(upsname), NUT_STRARG(sdcmd_used)); + + /* Pass it up to caller? */ + if (cmdused) { + *cmdused = sdcmd_used; + } else { + if (sdcmd_used) + free(sdcmd_used); + } + } else if (!upsh.instcmd) { + upslogx(LOG_ERR, "UPS [%s]: shutdown not supported", NUT_STRARG(upsname)); + } else { + upslogx(LOG_ERR, "UPS [%s]: shutdown request(s) failed", NUT_STRARG(upsname)); + } + + if (handling_upsdrv_shutdown > 0) + set_exit_flag(sdret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); + + return sdret; +} + /* handle instant commands common for all drivers */ int main_instcmd(const char *cmdname, const char *extra, conn_t *conn) { char buf[SMALLBUF]; @@ -756,12 +971,40 @@ int main_instcmd(const char *cmdname, const char *extra, conn_t *conn) { upsdebugx(2, "entering main_instcmd(%s, %s) for [%s] on %s", cmdname, extra, NUT_STRARG(upsname), buf); + if (!strcmp(cmdname, "shutdown.default")) { + /* Call the default implementation of UPS shutdown as + * an instant command, should not halt nor exit the + * driver program, so a subsequent power-on command + * may be possible (depending on driver and device). + * May well be implemented as recursion into "load.off", + * "shutdown.return" or "shutdown.stayoff" - possibly + * with a choice which one to call made at run-time, + * but in some drivers may involve logic not equal to + * any one of those other command implementations. + * Primarily intended to be a choice among `sdcommands`, + * called by default if none were passed (or this one + * was passed explicitly). + */ + upsdrv_shutdown(); + return STAT_INSTCMD_HANDLED; + } + if (!strcmp(cmdname, "driver.killpower")) { + /* An implementation of `drivername -k` requested from + * the running and connected driver instance by protocol + * over local Unix socket or pipe, primarily intended + * for (emergency) FSD use-cases and so with a fail-safe + * flag involved. + */ if (!strcmp("1", dstate_getinfo("driver.flag.allow_killpower"))) { upslogx(LOG_WARNING, "Requesting UPS [%s] to power off, " "as/if handled by its driver by default (may exit), " "due to socket protocol request", NUT_STRARG(upsname)); - upsdrv_shutdown(); + if (handling_upsdrv_shutdown == 0) + handling_upsdrv_shutdown = 1; + dstate_setinfo("driver.state", "fsd.killpower"); + upsdrv_shutdown_sdcommands_or_default(NULL, NULL); + dstate_setinfo("driver.state", "quiet"); return STAT_INSTCMD_HANDLED; } else { upslogx(LOG_WARNING, "Got socket protocol request for UPS [%s] " @@ -1058,6 +1301,16 @@ static int main_arg(char *var, char *val) if (!strcmp(var, "desc")) return 1; /* handled */ + if (!strcmp(var, "sdcommands")) { + if (testinfo_reloadable(var, "driver.parameter.sdcommands", val, 1) > 0) { + if (device_sdcommands) + free(device_sdcommands); + device_sdcommands = xstrdup(val); + dstate_setinfo("driver.parameter.sdcommands", "%s", val); + } + return 1; /* handled */ + } + /* Allow each driver to specify its minimal debugging level - * admins can set more with command-line args, but can't set * less without changing config. Should help debug of services. @@ -1630,7 +1883,7 @@ static void set_reload_flag( reload_flag = 15; break; */ - set_exit_flag(-2); + set_exit_flag(EF_EXIT_SUCCESS); return; case SIGCMD_RELOAD: /* SIGHUP */ @@ -1647,7 +1900,7 @@ static void set_reload_flag( /* reload what we can, log what needs a restart so skipped */ reload_flag = 1; } else if (sig && !strcmp(sig, SIGCMD_EXIT)) { - set_exit_flag(-2); + set_exit_flag(EF_EXIT_SUCCESS); return; } else { /* non-fatal reload as a fallback */ @@ -2064,7 +2317,7 @@ int main(int argc, char **argv) /* Only switch to statepath if we're not powering off * or not just dumping data (for discovery) */ - /* This avoids case where ie /var is unmounted already */ + /* This avoids case where i.e. /var is unmounted already */ #ifndef WIN32 if ((!do_forceshutdown) && (!dump_data)) { if (chdir(dflt_statepath())) @@ -2509,9 +2762,6 @@ int main(int argc, char **argv) fatalx(EXIT_FAILURE, "Fatal error: broken driver. It probably needs to be converted.\n"); } - if (do_forceshutdown) - forceshutdown(); - /* publish the top-level data: version numbers, driver name */ dstate_setinfo("driver.version", "%s", UPS_VERSION); dstate_setinfo("driver.version.internal", "%s", upsdrv_info.version); @@ -2528,6 +2778,15 @@ int main(int argc, char **argv) /* get the base data established before allowing connections */ dstate_setinfo("driver.state", "init.info"); upsdrv_initinfo(); + + /* Register a way to call upsdrv_shutdown() among `sdcommands` */ + dstate_addcmd("shutdown.default"); + + if (do_forceshutdown) { + dstate_setinfo("driver.state", "fsd.killpower"); + forceshutdown(); + } + /* Note: a few drivers also call their upsdrv_updateinfo() during * their upsdrv_initinfo(), possibly to impact the initialization */ dstate_setinfo("driver.state", "init.updateinfo"); @@ -2712,7 +2971,8 @@ int main(int argc, char **argv) upslogx(LOG_WARNING, "Running as foreground process, not saving a PID file"); } - /* May already be set by parsed configuration flag, only set default if not: */ + /* May already be set by parsed configuration flag, + * only set default if not: */ if (dstate_getinfo("driver.flag.allow_killpower") == NULL) dstate_setinfo("driver.flag.allow_killpower", "0"); diff --git a/drivers/main.h b/drivers/main.h index 5276c98ece..864eb11b7a 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -10,12 +10,13 @@ #include "wincompat.h" #endif -/* public functions & variables from main.c */ +/* public functions & variables from main.c, documented in detail there */ extern const char *progname, *upsname, *device_name; -extern char *device_path; -extern int broken_driver, experimental_driver, do_lock_port, exit_flag; +extern char *device_path, *device_sdcommands; +extern int broken_driver, experimental_driver, + do_lock_port, exit_flag, handling_upsdrv_shutdown; extern TYPE_FD upsfd, extrafd; -extern time_t poll_interval; +extern time_t poll_interval; /* functions & variables required in each driver */ void upsdrv_initups(void); /* open connection to UPS, fail if not found */ @@ -30,6 +31,33 @@ void set_exit_flag(int sig); /* --- details for the variable/value sharing --- */ +/* Try each instant command in the comma-separated list of + * sdcmds, until the first one that reports it was handled. + * Returns STAT_INSTCMD_HANDLED if one of those was accepted + * by the device, or STAT_INSTCMD_INVALID if none succeeded. + * If cmdused is not NULL, it is populated by the command + * string which succeeded (or NULL if none), and the caller + * should free() it eventually. + */ +int do_loop_shutdown_commands(const char *sdcmds, char **cmdused); +#define MAX_SDCOMMANDS_DEPTH 15 + +/* Use driver-provided sdcmds_default, unless a custom driver parameter value + * "sdcommands" is set - then use it instead. Call do_loop_shutdown_commands() + * for actual work; return STAT_INSTCMD_HANDLED or STAT_INSTCMD_HANDLED as + * applicable; if caller-provided cmdused is not NULL, populate it with the + * command that was used successfully (if any). + */ +int loop_shutdown_commands(const char *sdcmds_default, char **cmdused); + +/* + * Effectively call loop_shutdown_commands("shutdown.default") (which in turn + * probably calls some other INSTCMD, but may be using a more custom logic), + * and report how that went. + * Depending on run-time circumstances, probably set_exit_flag() too. + */ +int upsdrv_shutdown_sdcommands_or_default(const char *sdcmds_default, char **cmdused); + /* handle instant commands common for all drivers * (returns STAT_INSTCMD_* state values per enum in upshandler.h) */ diff --git a/drivers/masterguard.c b/drivers/masterguard.c index f5bbb397ce..49c1c4dc31 100644 --- a/drivers/masterguard.c +++ b/drivers/masterguard.c @@ -9,6 +9,8 @@ generally covers all Megatec/Qx protocol family and aggregates device support from such legacy drivers over time. + FIXME: `if(DEBUG) print(...)` ==> `upsdebugx()` + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -29,7 +31,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "MASTERGUARD UPS driver" -#define DRIVER_VERSION "0.27" +#define DRIVER_VERSION "0.28" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -53,6 +55,9 @@ static int type; static char name[31]; static char firmware[6]; +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + /******************************************************************** * * Helper function to split a sting into words by splitting at the @@ -460,6 +465,11 @@ void upsdrv_initinfo(void) dstate_addcmd("test.battery.start"); */ + dstate_addcmd("shutdown.return"); + + /* install handlers */ + upsh.instcmd = instcmd; + if( strlen( name ) > 0 ) dstate_setinfo("ups.model", "%s", name); if( strlen( firmware ) > 0 ) @@ -529,6 +539,25 @@ void upsdrv_updateinfo(void) } } +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) +{ + NUT_UNUSED_VARIABLE(extra); + + /* Shutdown UPS */ + if (!strcasecmp(cmdname, "shutdown.return")) + { + /* ups will come up within a minute if utility is restored */ + ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0001\x0D" ); + + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} + /******************************************************************** * * Called if the driver wants to shutdown the UPS. @@ -540,8 +569,12 @@ void upsdrv_updateinfo(void) ********************************************************************/ void upsdrv_shutdown(void) { - /* ups will come up within a minute if utility is restored */ - ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0001\x0D" ); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /******************************************************************** diff --git a/drivers/metasys.c b/drivers/metasys.c index cae06a174b..dc3ead8969 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -28,7 +28,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Metasystem UPS driver" -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -868,11 +868,16 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - unsigned char command[10], answer[10]; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + unsigned char command[10], answer[10]; /* Ensure that the ups is configured for automatically restart after a complete battery discharge - and when the power comes back after a shutdown */ + and when the power comes back after a shutdown. + Similar code to "shutdown.restart" but different timeouts. + */ if (! autorestart) { command[0]=UPS_SET_TIMES_ON_BATTERY; command[1]=0x00; /* max time on */ @@ -885,7 +890,7 @@ void upsdrv_shutdown(void) command_write_sequence(command, 6, answer); } - /* shedule a shutdown in 120 seconds */ + /* schedule a shutdown in 120 seconds */ command[0]=UPS_SET_SCHEDULING; command[1]=0x96; /* remaining */ command[2]=0x00; /* time */ @@ -939,7 +944,7 @@ static int instcmd(const char *cmdname, const char *extra) command[5]=0x01; /* autorestart after battery depleted enabled */ command_write_sequence(command, 6, answer); } - /* shedule a shutdown in 30 seconds */ + /* schedule a shutdown in 30 seconds */ command[0]=UPS_SET_SCHEDULING; command[1]=0x1e; /* remaining */ command[2]=0x00; /* time */ @@ -955,7 +960,7 @@ static int instcmd(const char *cmdname, const char *extra) } if (!strcasecmp(cmdname, "shutdown.stayoff")) { - /* shedule a shutdown in 30 seconds with no restart (-1) */ + /* schedule a shutdown in 30 seconds with no restart (-1) */ command[0]=UPS_SET_SCHEDULING; command[1]=0x1e; /* remaining */ command[2]=0x00; /* time */ diff --git a/drivers/mge-utalk.c b/drivers/mge-utalk.c index dca2a14fe6..5ac45e5d3e 100644 --- a/drivers/mge-utalk.c +++ b/drivers/mge-utalk.c @@ -69,7 +69,7 @@ /* --------------------------------------------------------------- */ #define DRIVER_NAME "MGE UPS SYSTEMS/U-Talk driver" -#define DRIVER_VERSION "0.96" +#define DRIVER_VERSION "0.97" /* driver description structure */ @@ -477,8 +477,29 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - char buf[BUFFLEN]; - /* static time_t lastcmd = 0; */ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + char buf[BUFFLEN]; + /* static time_t lastcmd = 0; */ + + /* We can enter this method either by handling of INSTCMD (whether it + * was called by user, or by ourselves here via loop method), or by + * handling of `drivername -k`. Avoid infinite loops with this flag + * here and with handling_instcmd_shutdown above. + */ + static char already_shutting_down = 0; + + if (already_shutting_down) + return; + + already_shutting_down = 1; + + /* Here we are if handling explicit INSTCMD to shut down, + * or this method was called and works to handle default + * "sdcommands", or is recursively called with a custom + * value of "sdcommands" pointing here. + */ memset(buf, 0, sizeof(buf)); if (sdtype == SD_RETURN) { @@ -488,8 +509,8 @@ void upsdrv_shutdown(void) upslogx(LOG_INFO, "UPS response to Automatic Restart was %s", buf); } - /* Only call the effective shutoff if restart is ok */ - /* or if we need only a stayoff... */ + /* Only call the effective shutoff if restart is ok, + * or if we need (caller asked for) only a stayoff... */ if (!strcmp(buf, "OK") || (sdtype == SD_STAYOFF)) { /* shutdown UPS */ mge_command(buf, sizeof(buf), "Sx 0"); @@ -498,8 +519,13 @@ void upsdrv_shutdown(void) } /* if(strcmp(buf, "OK")) */ - /* call the cleanup to disable/close the comm link */ - upsdrv_cleanup(); + if (handling_upsdrv_shutdown > 0) { + /* call the cleanup to disable/close the comm link */ + upsdrv_cleanup(); + } else { + /* Reset the flags if driver is not going down */ + already_shutting_down = 0; + } } /* --------------------------------------------------------------- */ diff --git a/drivers/microdowell.c b/drivers/microdowell.c index 0d491b1c7e..7ce650fc99 100644 --- a/drivers/microdowell.c +++ b/drivers/microdowell.c @@ -44,7 +44,7 @@ #define MAX_SHUTDOWN_DELAY_LEN 5 #define DRIVER_NAME "MICRODOWELL UPS driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.05" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -944,10 +944,13 @@ void upsdrv_initinfo(void) void upsdrv_shutdown(void) { - unsigned char OutBuff[20] ; - unsigned char InpBuff[260] ; - unsigned char *p ; - unsigned char BatteryFlag=0 ; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + unsigned char OutBuff[20]; + unsigned char InpBuff[260]; + unsigned char *p; + unsigned char BatteryFlag = 0; OutBuff[0] = CMD_GET_STATUS ; /* get UPS status */ if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL) diff --git a/drivers/microsol-apc.c b/drivers/microsol-apc.c index c8979b67b1..71c16d8775 100644 --- a/drivers/microsol-apc.c +++ b/drivers/microsol-apc.c @@ -35,7 +35,7 @@ #include "microsol-apc.h" #define DRIVER_NAME "APC Back-UPS BR series UPS driver" -#define DRIVER_VERSION "0.71" +#define DRIVER_VERSION "0.72" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/microsol-common.c b/drivers/microsol-common.c index 2ba496b36f..4eab5d4f71 100644 --- a/drivers/microsol-common.c +++ b/drivers/microsol-common.c @@ -754,6 +754,9 @@ void upsdrv_updateinfo(void) */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + if (!line_unpowered) { /* on line */ upslogx(LOG_NOTICE, "On line, sending power cycle command..."); ser_send_char(upsfd, CMD_SHUTRET); diff --git a/drivers/netxml-ups.c b/drivers/netxml-ups.c index cfdc31632c..26c8dc6d79 100644 --- a/drivers/netxml-ups.c +++ b/drivers/netxml-ups.c @@ -42,7 +42,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "network XML UPS" -#define DRIVER_VERSION "0.46" +#define DRIVER_VERSION "0.47" /** *_OBJECT query multi-part body boundary */ #define FORM_POST_BOUNDARY "NUT-NETXML-UPS-OBJECTS" @@ -394,6 +394,27 @@ void upsdrv_updateinfo(void) } void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* + * WARNING: + * This driver will probably never support this properly: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + + /* FIXME: Make a name for default original shutdown + * in particular to make it one of the options and + * call protocol cleanup below, if needed. + */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ /* maybe try to detect the UPS here, but try a shutdown even if @@ -453,7 +474,8 @@ void upsdrv_shutdown(void) { if (STAT_SET_HANDLED != status) { upslogx(LOG_ERR, "Shutdown failed: %d", status); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } } @@ -1020,7 +1042,7 @@ static void netxml_status_set(void) if (STATUS_BIT(OVERLOAD)) { status_set("OVER"); /* overload */ } - if (STATUS_BIT(REPLACEBATT)) { + if (STATUS_BIT(REPLACEBATT) || STATUS_BIT(NOBATTERY)) { status_set("RB"); /* replace batt */ } if (STATUS_BIT(TRIM)) { diff --git a/drivers/nhs_ser.c b/drivers/nhs_ser.c index 30d181b079..a9bfaf3d8d 100644 --- a/drivers/nhs_ser.c +++ b/drivers/nhs_ser.c @@ -50,9 +50,12 @@ #define DEFAULTPORT "/dev/ttyACM0" #define DEFAULTPF 0.9 #define DEFAULTPERC 2.0 -#define DATAPACKETSIZE 100 +#define DATAPACKETSIZE 100 /* NOTE: Practical anticipated max is 50 */ #define DEFAULTBATV 12.0 +/* comms revival attempts before declaring them stale */ +#define MAXTRIES 3 + /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -213,7 +216,7 @@ static int debug_pkt_data = 0, debug_pkt_hwinfo = 0, debug_pkt_raw = 0; static int serial_fd = -1; static unsigned char chr; -static int datapacket_index = 0; +static size_t datapacket_index = 0; static bool datapacketstart = false; static time_t lastdp = 0; static unsigned int checktime = 2000000; /* 2 seconds */ @@ -221,7 +224,7 @@ static unsigned int max_checktime = 6000000; /* max wait time: 6 seconds */ static unsigned int send_extended = 0; static int bwritten = 0; static unsigned char datapacket[DATAPACKETSIZE]; -static char porta[1024] = DEFAULTPORT; +static char porta[PATH_MAX] = DEFAULTPORT; static int baudrate = DEFAULTBAUD; static float minpower = 0; static float maxpower = 0; @@ -339,20 +342,20 @@ static int openfd(const char * portarg, int BAUDRATE); #if 0 static int write_serial(int fd, const char * dados, int size); #endif -static int write_serial_int(int fd, const unsigned int * data, int size); +static int write_serial_int(int fd, const unsigned int *data, size_t size); static void print_pkt_hwinfo(pkt_hwinfo data); static void print_pkt_data(pkt_data data); -static void pdatapacket(unsigned char * datapkt, int size); -static pkt_data mount_datapacket(unsigned char * datapkt, int size, double tempodecorrido, pkt_hwinfo pkt_upsinfo); -static pkt_hwinfo mount_hwinfo(unsigned char *datapkt, int size); +static void pdatapacket(unsigned char *datapkt, size_t size); +static pkt_data mount_datapacket(unsigned char *datapkt, size_t size, double tempodecorrido, pkt_hwinfo pkt_upsinfo); +static pkt_hwinfo mount_hwinfo(unsigned char *datapkt, size_t size); static upsinfo getupsinfo(unsigned int upscode); static unsigned int get_va(int equipment); static unsigned int get_vbat(void); static float get_pf(void); static unsigned int get_ah(void); -static float get_vin_perc(char * var); +static float get_vin_perc(char *var); static unsigned int get_numbat(void); @@ -620,8 +623,8 @@ static unsigned char calculate_checksum(unsigned char *pacote, int inicio, int f return (soma & 0xFF); } -static void pdatapacket(unsigned char * datapkt, int size) { - int i = 0; +static void pdatapacket(unsigned char *datapkt, size_t size) { + size_t i = 0; if (!debug_pkt_raw) return; @@ -631,7 +634,7 @@ static void pdatapacket(unsigned char * datapkt, int size) { upsdebugx(1, "%s: logging received data packet bytes at debug verbosity 5 or more", __func__); for (i = 0; i < size; i++) { - upsdebugx(5, "\tPosition %d -- 0x%02X -- Decimal %d -- Char %c", i, datapkt[i], datapkt[i], datapkt[i]); + upsdebugx(5, "\tPosition %" PRIuSIZE " -- 0x%02X -- Decimal %d -- Char %c", i, datapkt[i], datapkt[i], datapkt[i]); } } } @@ -652,8 +655,8 @@ static unsigned int get_vbat(void) { } } -static pkt_data mount_datapacket(unsigned char * datapkt, int size, double tempodecorrido, pkt_hwinfo pkt_upsinfo) { - int i = 0; +static pkt_data mount_datapacket(unsigned char *datapkt, size_t size, double tempodecorrido, pkt_hwinfo pkt_upsinfo) { + size_t i = 0; unsigned int vbat = 0; unsigned char checksum = 0x00; pkt_data pktdata = { @@ -786,8 +789,8 @@ static pkt_data mount_datapacket(unsigned char * datapkt, int size, double tempo return pktdata; } -static pkt_hwinfo mount_hwinfo(unsigned char *datapkt, int size) { - int i = 0; +static pkt_hwinfo mount_hwinfo(unsigned char *datapkt, size_t size) { + size_t i = 0; unsigned char checksum = 0x00; pkt_hwinfo pkthwinfo = { 0xFF, /* header */ @@ -910,7 +913,7 @@ static pkt_hwinfo mount_hwinfo(unsigned char *datapkt, int size) { } #if 0 -static int write_serial(int fd, const char * dados, int size) { +static int write_serial(int fd, const char *dados, size_t size) { if (fd > 0) { ssize_t bytes_written = write(fd, dados, size); if (bytes_written < 0) @@ -924,11 +927,11 @@ static int write_serial(int fd, const char * dados, int size) { } #endif -static int write_serial_int(int fd, const unsigned int * data, int size) { +static int write_serial_int(int fd, const unsigned int *data, size_t size) { if (fd > 0) { ssize_t bytes_written; uint8_t *message = NULL; - int i = 0; + size_t i = 0; message = xcalloc(size, sizeof(uint8_t)); for (i = 0; i < size; i++) { @@ -1657,30 +1660,22 @@ static float get_vin_perc(char * var) { } void upsdrv_initinfo(void) { - char *b = getval("baud"); + /* From docs/new-drivers.txt: + * Try to detect what kind of UPS is out there, + * if any, assuming that's possible for your hardware. + * If there is a way to detect that hardware and it + * doesn't appear to be connected, display an error + * and exit. This is the last time your driver is + * allowed to bail out. + * This is usually a good place to create variables + * like `ups.mfr`, `ups.model`, `ups.serial`, register + * instant commands, and other "one time only" items. + */ upsdebugx(3, "%s: starting...", __func__); - baudrate = DEFAULTBAUD; - - upsdebugx(1, "%s: Port is %s and baud_rate is %s", __func__, device_path, b); + /* TODO: Any instant commands? */ - if (b) - baudrate = atoi(b); - if (device_path) { - if (strcasecmp(device_path, "auto") == 0) - strcpy(porta, DEFAULTPORT); - else - strcpy(porta, device_path); - serial_fd = openfd(porta, baudrate); - if (serial_fd == -1) - fatalx(EXIT_FAILURE, "Unable to open port %s with baud %d", porta, baudrate); - else { - upsdebugx(1, "%s: Communication started on port %s, baud rate %d. Calling updateinfo()", __func__, porta, baudrate); - } - } - else - fatalx(EXIT_FAILURE, "Unable to define port and baud"); upsdebugx(3, "%s: finished", __func__); } @@ -1697,13 +1692,174 @@ static unsigned int get_numbat(void) { return retval; } -void upsdrv_updateinfo(void) { +/* Return serial_fd after the reconnection attempt, for easier calls */ +static int reconnect_ups_if_needed(void) { /* retries to open port */ - unsigned int retries = 3; - unsigned int i = 0; - char alarm[1024]; - unsigned int va = 0; - unsigned int ah = 0; + static unsigned int retries = 0; + + /* If comms failed earlier, try to resuscitate */ + if (serial_fd <= 0) { + upsdebugx(1, "%s: Serial port '%s' communications problem", + __func__, porta); + + /* Uh oh, got to reconnect! */ + dstate_setinfo("driver.state", "reconnect.trying"); + + while (serial_fd <= 0) { + upsdebugx(1, "%s: Trying to reopen serial...", __func__); + serial_fd = openfd(porta, baudrate); + retries++; + /* Try above at least once per main cycle */ + if (retries >= MAXTRIES) + break; + usleep(checktime); + } + + if (serial_fd > 0) { + if (retries > MAXTRIES) { + upslogx(LOG_NOTICE, "Communications with UPS re-established"); + } + retries = 0; + dstate_setinfo("driver.state", "quiet"); + } else { + if (retries == MAXTRIES) { + upslogx(LOG_WARNING, "Communications with UPS lost: port reopen failed!"); + } + dstate_datastale(); + } + } + + return serial_fd; +} + +static void interpret_pkt_hwinfo(void) { + /* TOTHINK: Consider passing in the packet struct as parameter? */ + upsinfo ups; + char alarm[1024]; /* Also used as a general string buffer */ + + if (!lastpktdata.checksum_ok) { + upslogx(LOG_WARNING, "%s: bad lastpkthwinfo.checksum", + __func__); + return; + } + + if (lastpkthwinfo.size < 1) { + upslogx(LOG_WARNING, "%s: Pkt HWINFO is not OK. " + "See if will be requested next time!", + __func__); + return; + } + + /* checksum is OK, then use it to set values */ + upsdebugx(4, "Pkt HWINFO is OK. Model code is %d, hwversion is %d " + "and swversion is %d", + lastpkthwinfo.model, + lastpkthwinfo.hardwareversion, + lastpkthwinfo.softwareversion); + + /* We need to set data on NUT with data + * that I believe that I can calculate. + * Now setting data on NUT + */ + ups = getupsinfo(lastpkthwinfo.model); + upsdebugx(4, "UPS Struct data: Code %d Model %s VA %d", ups.upscode, ups.upsdesc, ups.VA); + dstate_setinfo("device.model", "%s", ups.upsdesc); + dstate_setinfo("device.mfr", "%s", MANUFACTURER); + dstate_setinfo("device.serial", "%s", lastpkthwinfo.serial); + dstate_setinfo("device.type", "%s", "ups"); + + dstate_setinfo("ups.model", "%s", ups.upsdesc); + dstate_setinfo("ups.mfr", "%s", MANUFACTURER); + dstate_setinfo("ups.serial", "%s", lastpkthwinfo.serial); + dstate_setinfo("ups.firmware", "%u", lastpkthwinfo.softwareversion); + + /* Setting hardware version here. + * Did not find another place to do this. + * Feel free to correct it. + * FIXME: move to upsdrv_initinfo() or so + */ + dstate_setinfo("ups.firmware.aux", "%u", lastpkthwinfo.hardwareversion); + + if (debug_pkt_hwinfo) { + unsigned int i = 0; + + /* Now, creating a structure called NHS.HW, for latest HW + * info packet contents and raw data points, including those + * that were sorted above into NUT standard variables - + * for debug. + */ + dstate_setinfo("experimental.nhs.hw.header", "%u", lastpkthwinfo.header); + dstate_setinfo("experimental.nhs.hw.size", "%u", lastpkthwinfo.size); + dstate_setinfo("experimental.nhs.hw.type", "%c", lastpkthwinfo.type); + dstate_setinfo("experimental.nhs.hw.model", "%u", lastpkthwinfo.model); + dstate_setinfo("experimental.nhs.hw.hardwareversion", "%u", lastpkthwinfo.hardwareversion); + dstate_setinfo("experimental.nhs.hw.softwareversion", "%u", lastpkthwinfo.softwareversion); + dstate_setinfo("experimental.nhs.hw.configuration", "%u", lastpkthwinfo.configuration); + for (i = 0; i < 5; i++) { + /* Reusing variable */ + snprintf(alarm, sizeof(alarm), "experimental.nhs.hw.configuration_array_p%d", i); + dstate_setinfo(alarm, "%u", lastpkthwinfo.configuration_array[i]); + } + dstate_setinfo("experimental.nhs.hw.c_oem_mode", "%s", lastpkthwinfo.c_oem_mode ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.c_buzzer_disable", "%s", lastpkthwinfo.c_buzzer_disable ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.c_potmin_disable", "%s", lastpkthwinfo.c_potmin_disable ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.c_rearm_enable", "%s", lastpkthwinfo.c_rearm_enable ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.c_bootloader_enable", "%s", lastpkthwinfo.c_bootloader_enable ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.numbatteries", "%u", lastpkthwinfo.numbatteries); + dstate_setinfo("experimental.nhs.hw.undervoltagein120V", "%u", lastpkthwinfo.undervoltagein120V); + dstate_setinfo("experimental.nhs.hw.overvoltagein120V", "%u", lastpkthwinfo.overvoltagein120V); + dstate_setinfo("experimental.nhs.hw.undervoltagein220V", "%u", lastpkthwinfo.undervoltagein220V); + dstate_setinfo("experimental.nhs.hw.overvoltagein220V", "%u", lastpkthwinfo.overvoltagein220V); + dstate_setinfo("experimental.nhs.hw.tensionout120V", "%u", lastpkthwinfo.tensionout120V); + dstate_setinfo("experimental.nhs.hw.tensionout220V", "%u", lastpkthwinfo.tensionout220V); + dstate_setinfo("experimental.nhs.hw.statusval", "%u", lastpkthwinfo.statusval); + for (i = 0; i < 6; i++) { + /* Reusing variable */ + snprintf(alarm, sizeof(alarm), "experimental.nhs.hw.status_p%d", i); + dstate_setinfo(alarm, "%u", lastpkthwinfo.status[i]); + } + dstate_setinfo("experimental.nhs.hw.s_220V_in", "%s", lastpkthwinfo.s_220V_in ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.s_220V_out", "%s", lastpkthwinfo.s_220V_out ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.s_sealed_battery", "%s", lastpkthwinfo.s_sealed_battery ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.s_show_out_tension", "%s", lastpkthwinfo.s_show_out_tension ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.s_show_temperature", "%s", lastpkthwinfo.s_show_temperature ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.s_show_charger_current", "%s", lastpkthwinfo.s_show_charger_current ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.chargercurrent", "%u", lastpkthwinfo.chargercurrent); + dstate_setinfo("experimental.nhs.hw.checksum", "%u", lastpkthwinfo.checksum); + dstate_setinfo("experimental.nhs.hw.checksum_calc", "%u", lastpkthwinfo.checksum_calc); + dstate_setinfo("experimental.nhs.hw.checksum_ok", "%s", lastpkthwinfo.checksum_ok ? "true" : "false"); + dstate_setinfo("experimental.nhs.hw.serial", "%s", lastpkthwinfo.serial); + dstate_setinfo("experimental.nhs.hw.year", "%u", lastpkthwinfo.year); + dstate_setinfo("experimental.nhs.hw.month", "%u", lastpkthwinfo.month); + dstate_setinfo("experimental.nhs.hw.wday", "%u", lastpkthwinfo.wday); + dstate_setinfo("experimental.nhs.hw.hour", "%u", lastpkthwinfo.hour); + dstate_setinfo("experimental.nhs.hw.minute", "%u", lastpkthwinfo.minute); + dstate_setinfo("experimental.nhs.hw.second", "%u", lastpkthwinfo.second); + dstate_setinfo("experimental.nhs.hw.alarmyear", "%u", lastpkthwinfo.alarmyear); + dstate_setinfo("experimental.nhs.hw.alarmmonth", "%u", lastpkthwinfo.alarmmonth); + dstate_setinfo("experimental.nhs.hw.alarmwday", "%u", lastpkthwinfo.alarmwday); + dstate_setinfo("experimental.nhs.hw.alarmday", "%u", lastpkthwinfo.alarmday); + dstate_setinfo("experimental.nhs.hw.alarmhour", "%u", lastpkthwinfo.alarmhour); + dstate_setinfo("experimental.nhs.hw.alarmminute", "%u", lastpkthwinfo.alarmminute); + dstate_setinfo("experimental.nhs.hw.alarmsecond", "%u", lastpkthwinfo.alarmsecond); + dstate_setinfo("experimental.nhs.hw.end_marker", "%u", lastpkthwinfo.end_marker); + } +} + +static void interpret_pkt_data(void) { + /* TOTHINK: Consider passing in the packet struct as parameter? + * Note that certain points from lastpkthwinfo do play a role + * in decisions here (so maybe two parameters?) + */ + static unsigned int va = 0; + static unsigned int ah = 0; + static unsigned int numbat = 0; + static unsigned int vbat = 0; + static int min_input_power = 0; + static float pf = 0; + + int got_hwinfo = (lastpkthwinfo.checksum_ok && lastpkthwinfo.size > 0); + char alarm[1024]; /* Also used as a general string buffer */ unsigned int vin_underv = 0; unsigned int vin_overv = 0; unsigned int perc = 0; @@ -1714,484 +1870,493 @@ void upsdrv_updateinfo(void) { float vin_low_crit = 0; float vin_high_warn = 0; float vin_high_crit = 0; - float calculated = 0; float vpower = 0; - float pf = 0; long bcharge = 0; - int min_input_power = 0; - double tempodecorrido = 0.0; - unsigned int numbat = 0; - unsigned int vbat = 0; float abat = 0; float actual_current = 0; - upsinfo ups; - upsdebugx(3, "%s: starting...", __func__); - if ((serial_fd <= 0) && (i < retries)) { - upsdebugx(1, "%s: Serial port communications problem", __func__); - while (serial_fd <= 0) { - serial_fd = openfd(porta, baudrate); - upsdebugx(1, "%s: Trying to reopen serial...", __func__); - usleep(checktime); - retries++; + if (!lastpktdata.checksum_ok) { + upslogx(LOG_WARNING, "%s: bad lastpktdata.checksum", __func__); + return; + } + + /* checksum is OK, then use it to set values */ + upsdebugx(4, "%s: Data Packet seems be OK", __func__); + + if (!got_hwinfo) { + upsdebugx(2, "%s: Pkt HWINFO is not OK. " + "See if will be requested next time. " + "Some data points will not be set on this pass!", + __func__); + /* Not return, but we would miss some data points */ + } + + /* Setting UPS Status: + * OL -- On line (mains is present): Code below + * OB -- On battery (mains is not present) : Code below + * LB -- Low battery: Code below + * HB -- High battery: NHS doesn't have any variable with that information. Feel free to discover a way to set it + * RB -- The battery needs to be replaced: Well, as mentioned, we can write some infos on nobreak fw, on structures like pkt_hwinfo.year, pkt_hwinfo.month, etc. I never found any equipment with these values. + * CHRG -- The battery is charging: Code below + * DISCHRG -- The battery is discharging (inverter is providing load power): Code Below + * BYPASS -- UPS bypass circuit is active -- no battery protection is available: It's another PROBLEM, because NHS can work in bypass mode in some models, even if you have sealed batteries on it (without any external battery device). On the moment, i'll won't work with that. Feel free to discover how it work correctly. + * CAL -- UPS is currently performing runtime calibration (on battery) + * OFF -- UPS is offline and is not supplying power to the load + * OVER -- UPS is overloaded + * TRIM -- UPS is trimming incoming voltage (called "buck" in some hardware) + * BOOST -- UPS is boosting incoming voltage + * FSD -- Forced Shutdown (restricted use, see the note below) + */ + + /* Decision Chain commented below */ + + /* First we check if system is on battery or not */ + upsdebugx(4, "Set UPS status as OFF and start checking. s_battery_mode is %d", + lastpktdata.s_battery_mode); + + if (got_hwinfo) { + if (lastpkthwinfo.s_220V_in) { + upsdebugx(4, "I'm on 220v IN!. My undervoltage is %d", lastpkthwinfo.undervoltagein220V); + min_input_power = lastpkthwinfo.undervoltagein220V; + } + else { + upsdebugx(4, "I'm on 120v IN!. My undervoltage is %d", lastpkthwinfo.undervoltagein120V); + min_input_power = lastpkthwinfo.undervoltagein120V; + } + } else { + if (!min_input_power) { + min_input_power = 96; + upsdebugx(4, "I'm on unknown input!. My undervoltage is default %d", min_input_power); } } + + if (lastpktdata.s_battery_mode) { + /* ON BATTERY */ + upsdebugx(4, "UPS is on Battery Mode"); + dstate_setinfo("ups.status", "%s", "OB"); + if (lastpktdata.s_battery_low) { + /* If battery is LOW, warn user! */ + upsdebugx(4, "UPS is on Battery Mode and in Low Battery State"); + dstate_setinfo("ups.status", "%s", "LB"); + } /* end if */ + } /* end if */ else { - /* Clean all read buffers to avoid errors: - * To clean OUTPUT buffer is TCOFLUSH. - * To both is TCIOFLUSH. - */ - /* //tcflush(serial_fd, TCIFLUSH); */ - chr = '\0'; - while (read(serial_fd, &chr, 1) > 0) { - if (chr == 0xFF) { /* DataPacket start */ - datapacketstart = true; - } /* end for */ - if (datapacketstart) { - datapacket[datapacket_index] = chr; - datapacket_index++; - if (chr == 0xFE) { /* DataPacket */ - time_t now = time(NULL); - upsdebugx(4, "DATAPACKET INDEX IS %d", datapacket_index); - if (lastdp != 0) { - tempodecorrido = difftime(now, lastdp); - } - lastdp = now; - /* If size is 18 or 50, may be an answer packet. - * Then check if doesn't have already a packet processed. - * We don't need to read all times these information. - * Can be a corrupted packet too. */ - if (((datapacket_index == 18) || (datapacket_index == 50)) && (!lastpkthwinfo.checksum_ok)) { - lastpkthwinfo = mount_hwinfo(datapacket, datapacket_index); + /* Check if MAINS (power) is not preset. + * Well, we can check pkt_data.s_network_failure too... */ + if ((lastpktdata.vacinrms <= min_input_power) || (lastpktdata.s_network_failure)) { + upsdebugx(4, "UPS has power-in value %0.2f " + "and min_input_power is %d, " + "or network is in failure. Network failure is %d", + lastpktdata.vacinrms, + min_input_power, + lastpktdata.s_network_failure); + dstate_setinfo("ups.status", "%s", "DISCHRG"); + } /* end if */ + else { + /* MAINS is present. We need to check some situations. + * NHS only charge if have more than min_input_power. + * If MAINS is less than or equal to min_input_power, + * then the UPS goes to BATTERY + */ + if (lastpktdata.vacinrms > min_input_power) { + upsdebugx(4, "UPS is on MAINS"); + if (lastpktdata.s_charger_on) { + upsdebugx(4, "UPS Charging..."); + dstate_setinfo("ups.status", "%s", "CHRG"); + } + else { + if ((lastpktdata.s_network_failure) || (lastpktdata.s_fast_network_failure)) { + upsdebugx(4, "UPS is on battery mode because network failure or fast network failure"); + dstate_setinfo("ups.status", "%s", "OB"); } /* end if */ else { - if (datapacket_index == 21) - lastpktdata = mount_datapacket(datapacket, datapacket_index, tempodecorrido, lastpkthwinfo); + upsdebugx(4, "All is OK. UPS is on ONLINE!"); + dstate_setinfo("ups.status", "%s", "OL"); } /* end else */ - /* Clean datapacket structure to avoid problems */ - datapacket_index = 0; - memset(datapacket, 0, sizeof(datapacket)); - datapacketstart = false; - if (lastpktdata.checksum_ok) { - /* checksum is OK, then use it to set values */ - upsdebugx(4, "Data Packet seems be OK"); - if (lastpkthwinfo.size == 0) - upsdebugx(2, "Pkt HWINFO is not OK. See if will be requested next time!"); - else { - if (lastpkthwinfo.checksum_ok) { - upsdebugx(4, "Pkt HWINFO is OK. Model is %d, hwversion is %d and swversion is %d", lastpkthwinfo.model, lastpkthwinfo.hardwareversion, lastpkthwinfo.softwareversion); - /* We need to set data on NUT with data - * that I believe that I can calculate. - * Now setting data on NUT */ - ups = getupsinfo(lastpkthwinfo.model); - upsdebugx(4, "UPS Struct data: Code %d Model %s VA %d", ups.upscode, ups.upsdesc, ups.VA); - dstate_setinfo("device.model", "%s", ups.upsdesc); - dstate_setinfo("device.mfr", "%s", MANUFACTURER); - dstate_setinfo("device.serial", "%s", lastpkthwinfo.serial); - dstate_setinfo("device.type", "%s", "ups"); - - /* Setting UPS Status: - * OL -- On line (mains is present): Code below - * OB -- On battery (mains is not present) : Code below - * LB -- Low battery: Code below - * HB -- High battery: NHS doesn't have any variable with that information. Feel free to discover a way to set it - * RB -- The battery needs to be replaced: Well, as mentioned, we can write some infos on nobreak fw, on structures like pkt_hwinfo.year, pkt_hwinfo.month, etc. I never found any equipment with these values. - * CHRG -- The battery is charging: Code below - * DISCHRG -- The battery is discharging (inverter is providing load power): Code Below - * BYPASS -- UPS bypass circuit is active -- no battery protection is available: It's another PROBLEM, because NHS can work in bypass mode in some models, even if you have sealed batteries on it (without any external battery device). On the moment, i'll won't work with that. Feel free to discover how it work correctly. - * CAL -- UPS is currently performing runtime calibration (on battery) - * OFF -- UPS is offline and is not supplying power to the load - * OVER -- UPS is overloaded - * TRIM -- UPS is trimming incoming voltage (called "buck" in some hardware) - * BOOST -- UPS is boosting incoming voltage - * FSD -- Forced Shutdown (restricted use, see the note below) - */ - - /* Decision Chain commented below */ - - /* First we check if system is on battery or not */ - upsdebugx(4, "Set UPS status as OFF and start checking. s_battery_mode is %d", lastpktdata.s_battery_mode); - if (lastpkthwinfo.s_220V_in) { - upsdebugx(4, "I'm on 220v IN!. My overvoltage is %d", lastpkthwinfo.undervoltagein220V); - min_input_power = lastpkthwinfo.undervoltagein220V; - } - else { - upsdebugx(4, "I'm on 120v IN!. My overvoltage is %d", lastpkthwinfo.undervoltagein120V); - min_input_power = lastpkthwinfo.undervoltagein120V; - } - if (lastpktdata.s_battery_mode) { - /* ON BATTERY */ - upsdebugx(4, "UPS is on Battery Mode"); - dstate_setinfo("ups.status", "%s", "OB"); - if (lastpktdata.s_battery_low) { - /* If battery is LOW, warn user! */ - upsdebugx(4, "UPS is on Battery Mode and in Low Battery State"); - dstate_setinfo("ups.status", "%s", "LB"); - } /* end if */ - } /* end if */ - else { - /* Check if MAINS (power) is not preset. - * Well, we can check pkt_data.s_network_failure too... */ - if ((lastpktdata.vacinrms <= min_input_power) || (lastpktdata.s_network_failure)) { - upsdebugx(4, "UPS has power-in value %0.2f " - "and min_input_power is %d, " - "or network is in failure. Network failure is %d", - lastpktdata.vacinrms, - min_input_power, - lastpktdata.s_network_failure); - dstate_setinfo("ups.status", "%s", "DISCHRG"); - } /* end if */ - else { - /* MAINS is present. We need to check some situations. - * NHS only charge if have more than min_input_power. - * If MAINS is less than or equal to min_input_power, - * then the UPS goes to BATTERY */ - if (lastpktdata.vacinrms > min_input_power) { - upsdebugx(4, "UPS is on MAINS"); - if (lastpktdata.s_charger_on) { - upsdebugx(4, "UPS Charging..."); - dstate_setinfo("ups.status", "%s", "CHRG"); - } - else { - if ((lastpktdata.s_network_failure) || (lastpktdata.s_fast_network_failure)) { - upsdebugx(4, "UPS is on battery mode because network failure or fast network failure"); - dstate_setinfo("ups.status", "%s", "OB"); - } /* end if */ - else { - upsdebugx(4, "All is OK. UPS is on ONLINE!"); - dstate_setinfo("ups.status", "%s", "OL"); - } /* end else */ - } /* end else */ - } /* end if */ - else { - /* Energy is below limit. - * Nobreak is probably in battery mode... */ - if (lastpktdata.s_battery_low) - dstate_setinfo("ups.status", "%s", "LB"); - else { - /* ...or network failure */ - dstate_setinfo("ups.status", "%s", "OB"); - } /* end else */ - } /* end else */ - } /* end else */ - } /* end else */ - - numbat = get_numbat(); - if (numbat == 0) - numbat = lastpkthwinfo.numbatteries; - else - upsdebugx(4, "Number of batteries is set to %d", numbat); - vbat = get_vbat(); - ah = get_ah(); - - /* Set all alarms possible */ - alarm[0] = '\0'; - if (lastpktdata.s_battery_mode) - snprintf(alarm, sizeof(alarm), "%s", "|UPS IN BATTERY MODE|"); - if (lastpktdata.s_battery_low) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|UPS IN BATTERY MODE|"); - if (lastpktdata.s_network_failure) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|NETWORK FAILURE|"); - - /* FIXME: Really same criteria in these 3? */ - if (lastpktdata.s_fast_network_failure) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|FAST NETWORK FAILURE|"); - if (lastpktdata.s_fast_network_failure) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|220v IN|"); - if (lastpktdata.s_fast_network_failure) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|220v OUT|"); - - if (lastpktdata.s_bypass_on) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|BYPASS ON|"); - if (lastpktdata.s_charger_on) - snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", - "|CHARGER ON|"); - dstate_setinfo("ups.alarm", "%s", alarm); - dstate_setinfo("ups.model", "%s", ups.upsdesc); - dstate_setinfo("ups.mfr", "%s", MANUFACTURER); - dstate_setinfo("ups.serial", "%s", lastpkthwinfo.serial); - dstate_setinfo("ups.firmware", "%u", lastpkthwinfo.softwareversion); - /* Setting hardware version here. - * Did not find another place to do this. - * Feel free to correct it. - * FIXME: move to upsdrv_initinfo() or so - */ - dstate_setinfo("ups.firmware.aux", "%u", lastpkthwinfo.hardwareversion); - dstate_setinfo("ups.temperature", "%0.2f", lastpktdata.tempmed_real); - dstate_setinfo("ups.load", "%u", lastpktdata.potrms); - dstate_setinfo("ups.efficiency", "%0.2f", calculate_efficiency(lastpktdata.vacoutrms, lastpktdata.vacinrms)); - va = get_va(lastpkthwinfo.model); - pf = get_pf(); - /* vpower is the power in Watts */ - vpower = ((va * pf) * (lastpktdata.potrms / 100.0)); - /* abat is the battery's consumption in Amperes */ - abat = ((vpower / lastpktdata.vdcmed_real) / numbat); - if (vpower > maxpower) - maxpower = vpower; - if (vpower < minpower) - minpower = vpower; - dstate_setinfo("ups.power", "%0.2f", vpower); - dstate_setinfo("ups.power.nominal", "%u", va); - dstate_setinfo("ups.realpower", "%ld", lrint(round(vpower))); - dstate_setinfo("ups.realpower.nominal", "%ld", lrint(round((double)va * (double)pf))); - dstate_setinfo("ups.beeper.status", "%d", !lastpkthwinfo.c_buzzer_disable); - dstate_setinfo("input.voltage", "%0.2f", lastpktdata.vacinrms); - dstate_setinfo("input.voltage.maximum", "%0.2f", lastpktdata.vacinrmsmin); - dstate_setinfo("input.voltage.minimum", "%0.2f", lastpktdata.vacinrmsmax); - vin_underv = lastpkthwinfo.s_220V_in ? lastpkthwinfo.undervoltagein220V : lastpkthwinfo.undervoltagein120V; - vin_overv = lastpkthwinfo.s_220V_in ? lastpkthwinfo.overvoltagein220V : lastpkthwinfo.overvoltagein120V; - perc = f_equal(get_vin_perc("vin_low_warn_perc"), get_vin_perc("vin_low_crit_perc")) ? 2 : 1; - vin_low_warn = vin_underv + (vin_underv * ((get_vin_perc("vin_low_warn_perc") * perc) / 100.0)); - dstate_setinfo("input.voltage.low.warning", "%0.2f", calculated); - vin_low_crit = vin_underv + (vin_underv * (get_vin_perc("vin_low_crit_perc") / 100.0)); - dstate_setinfo("input.voltage.low.critical", "%0.2f", calculated); - vin_high_warn = vin_overv + (vin_overv * ((get_vin_perc("vin_high_warn_perc") * perc) / 100.0)); - dstate_setinfo("input.voltage.high.warning", "%0.2f", calculated); - vin_high_crit = vin_overv + (vin_overv * (get_vin_perc("vin_high_crit_perc") / 100.0)); - dstate_setinfo("input.voltage.high.critical", "%0.2f", calculated); - vin = lastpkthwinfo.s_220V_in ? lastpkthwinfo.tensionout220V : lastpkthwinfo.tensionout120V; - dstate_setinfo("input.voltage.nominal", "%u", vin); - dstate_setinfo("input.transfer.low", "%u", lastpkthwinfo.s_220V_in ? lastpkthwinfo.undervoltagein220V : lastpkthwinfo.undervoltagein120V); - dstate_setinfo("input.transfer.high", "%u", lastpkthwinfo.s_220V_in ? lastpkthwinfo.overvoltagein220V : lastpkthwinfo.overvoltagein120V); - dstate_setinfo("output.voltage", "%0.2f", lastpktdata.vacoutrms); - vout = lastpkthwinfo.s_220V_out ? lastpkthwinfo.tensionout220V : lastpkthwinfo.tensionout120V; - dstate_setinfo("output.voltage.nominal", "%u", vout); - dstate_setinfo("voltage", "%0.2f", lastpktdata.vacoutrms); - dstate_setinfo("voltage.nominal", "%u", vout); - dstate_setinfo("voltage.maximum", "%0.2f", lastpktdata.vacinrmsmax); - dstate_setinfo("voltage.minimum", "%0.2f", lastpktdata.vacinrmsmin); - dstate_setinfo("voltage.low.warning", "%0.2f", vin_low_warn); - dstate_setinfo("voltage.low.critical", "%0.2f", vin_low_crit); - dstate_setinfo("voltage.high.warning", "%0.2f", vin_high_warn); - dstate_setinfo("voltage.high.critical", "%0.2f", vin_high_crit); - dstate_setinfo("power", "%0.2f", vpower); - dstate_setinfo("power.maximum", "%0.2f", maxpower); - dstate_setinfo("power.minimum", "%0.2f", minpower); - dstate_setinfo("power.percent", "%u", lastpktdata.potrms); - if (lastpktdata.potrms > maxpowerperc) - maxpowerperc = lastpktdata.potrms; - if (lastpktdata.potrms < minpowerperc) - minpowerperc = lastpktdata.potrms; - dstate_setinfo("power.maximum.percent", "%u", maxpowerperc); - dstate_setinfo("power.minimum.percent", "%u", minpowerperc); - dstate_setinfo("realpower", "%ld", lrint(round(vpower))); - dstate_setinfo("power", "%ld", lrint(round(va * (lastpktdata.potrms / 100.0)))); - bcharge = lrint(round((lastpktdata.vdcmed_real * 100) / vbat)); - if (bcharge > 100) - bcharge = 100; - dstate_setinfo("battery.charge", "%ld", bcharge); - dstate_setinfo("battery.voltage", "%0.2f", lastpktdata.vdcmed_real); - dstate_setinfo("battery.voltage.nominal", "%u", vbat); - dstate_setinfo("battery.capacity", "%u", ah); - dstate_setinfo("battery.capacity.nominal", "%0.2f", (float)ah * pf); - dstate_setinfo("battery.current", "%0.2f", abat); - dstate_setinfo("battery.current.total", "%0.2f", (float)abat * numbat); - dstate_setinfo("battery.temperature", "%ld", lrint(round(lastpktdata.tempmed_real))); - dstate_setinfo("battery.packs", "%u", numbat); - /* We will calculate autonomy in seconds - * autonomy_secs = (ah / lastpktdata.vdcmed_real) * 3600; - * Maybe wrong, too. - * People say that the correct calculation is - * - * Battery Amp-Hour / (Power in Watts / battery voltage) - * - * Is that correct? I don't know. I'll use it for now. - */ - - /* That result is IN HOURS. We need to convert it to seconds */ - actual_current = vpower / vbat; /* Current consumption in A*/ - autonomy_secs = (ah / actual_current) * 3600; - - dstate_setinfo("battery.runtime", "%u", autonomy_secs); - dstate_setinfo("battery.runtime.low", "%u", 30); - if (lastpktdata.s_charger_on) - dstate_setinfo("battery.charger.status", "%s", "CHARGING"); - else { - if (lastpktdata.s_battery_mode) - dstate_setinfo("battery.charger.status", "%s", "DISCHARGING"); - else - dstate_setinfo("battery.charger.status", "%s", "RESTING"); - } - /* Now, creating a structure called NHS, */ - dstate_setinfo("experimental.nhs.hw.header", "%u", lastpkthwinfo.header); - dstate_setinfo("experimental.nhs.hw.size", "%u", lastpkthwinfo.size); - dstate_setinfo("experimental.nhs.hw.type", "%c", lastpkthwinfo.type); - dstate_setinfo("experimental.nhs.hw.model", "%u", lastpkthwinfo.model); - dstate_setinfo("experimental.nhs.hw.hardwareversion", "%u", lastpkthwinfo.hardwareversion); - dstate_setinfo("experimental.nhs.hw.softwareversion", "%u", lastpkthwinfo.softwareversion); - dstate_setinfo("experimental.nhs.hw.configuration", "%u", lastpkthwinfo.configuration); - for (i = 0; i < 5; i++) { - /* Reusing variable */ - snprintf(alarm, sizeof(alarm), "experimental.nhs.hw.configuration_array_p%d", i); - dstate_setinfo(alarm, "%u", lastpkthwinfo.configuration_array[i]); - } - dstate_setinfo("experimental.nhs.hw.c_oem_mode", "%s", lastpkthwinfo.c_oem_mode ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.c_buzzer_disable", "%s", lastpkthwinfo.c_buzzer_disable ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.c_potmin_disable", "%s", lastpkthwinfo.c_potmin_disable ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.c_rearm_enable", "%s", lastpkthwinfo.c_rearm_enable ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.c_bootloader_enable", "%s", lastpkthwinfo.c_bootloader_enable ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.numbatteries", "%u", lastpkthwinfo.numbatteries); - dstate_setinfo("experimental.nhs.hw.undervoltagein120V", "%u", lastpkthwinfo.undervoltagein120V); - dstate_setinfo("experimental.nhs.hw.overvoltagein120V", "%u", lastpkthwinfo.overvoltagein120V); - dstate_setinfo("experimental.nhs.hw.undervoltagein220V", "%u", lastpkthwinfo.undervoltagein220V); - dstate_setinfo("experimental.nhs.hw.overvoltagein220V", "%u", lastpkthwinfo.overvoltagein220V); - dstate_setinfo("experimental.nhs.hw.tensionout120V", "%u", lastpkthwinfo.tensionout120V); - dstate_setinfo("experimental.nhs.hw.tensionout220V", "%u", lastpkthwinfo.tensionout220V); - dstate_setinfo("experimental.nhs.hw.statusval", "%u", lastpkthwinfo.statusval); - for (i = 0; i < 6; i++) { - /* Reusing variable */ - snprintf(alarm, sizeof(alarm), "experimental.nhs.hw.status_p%d", i); - dstate_setinfo(alarm, "%u", lastpkthwinfo.status[i]); - } - dstate_setinfo("experimental.nhs.hw.s_220V_in", "%s", lastpkthwinfo.s_220V_in ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.s_220V_out", "%s", lastpkthwinfo.s_220V_out ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.s_sealed_battery", "%s", lastpkthwinfo.s_sealed_battery ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.s_show_out_tension", "%s", lastpkthwinfo.s_show_out_tension ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.s_show_temperature", "%s", lastpkthwinfo.s_show_temperature ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.s_show_charger_current", "%s", lastpkthwinfo.s_show_charger_current ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.chargercurrent", "%u", lastpkthwinfo.chargercurrent); - dstate_setinfo("experimental.nhs.hw.checksum", "%u", lastpkthwinfo.checksum); - dstate_setinfo("experimental.nhs.hw.checksum_calc", "%u", lastpkthwinfo.checksum_calc); - dstate_setinfo("experimental.nhs.hw.checksum_ok", "%s", lastpkthwinfo.checksum_ok ? "true" : "false"); - dstate_setinfo("experimental.nhs.hw.serial", "%s", lastpkthwinfo.serial); - dstate_setinfo("experimental.nhs.hw.year", "%u", lastpkthwinfo.year); - dstate_setinfo("experimental.nhs.hw.month", "%u", lastpkthwinfo.month); - dstate_setinfo("experimental.nhs.hw.wday", "%u", lastpkthwinfo.wday); - dstate_setinfo("experimental.nhs.hw.hour", "%u", lastpkthwinfo.hour); - dstate_setinfo("experimental.nhs.hw.minute", "%u", lastpkthwinfo.minute); - dstate_setinfo("experimental.nhs.hw.second", "%u", lastpkthwinfo.second); - dstate_setinfo("experimental.nhs.hw.alarmyear", "%u", lastpkthwinfo.alarmyear); - dstate_setinfo("experimental.nhs.hw.alarmmonth", "%u", lastpkthwinfo.alarmmonth); - dstate_setinfo("experimental.nhs.hw.alarmwday", "%u", lastpkthwinfo.alarmwday); - dstate_setinfo("experimental.nhs.hw.alarmday", "%u", lastpkthwinfo.alarmday); - dstate_setinfo("experimental.nhs.hw.alarmhour", "%u", lastpkthwinfo.alarmhour); - dstate_setinfo("experimental.nhs.hw.alarmminute", "%u", lastpkthwinfo.alarmminute); - dstate_setinfo("experimental.nhs.hw.alarmsecond", "%u", lastpkthwinfo.alarmsecond); - dstate_setinfo("experimental.nhs.hw.end_marker", "%u", lastpkthwinfo.end_marker); - - /* Data packet */ - dstate_setinfo("experimental.nhs.data.header", "%u", lastpktdata.header); - dstate_setinfo("experimental.nhs.data.length", "%u", lastpktdata.length); - dstate_setinfo("experimental.nhs.data.packet_type", "%c", lastpktdata.packet_type); - dstate_setinfo("experimental.nhs.data.vacinrms_high", "%u", lastpktdata.vacinrms_high); - dstate_setinfo("experimental.nhs.data.vacinrms_low", "%u", lastpktdata.vacinrms_low); - dstate_setinfo("experimental.nhs.data.vacinrms", "%0.2f", lastpktdata.vacinrms); - dstate_setinfo("experimental.nhs.data.vdcmed_high", "%u", lastpktdata.vdcmed_high); - dstate_setinfo("experimental.nhs.data.vdcmed_low", "%u", lastpktdata.vdcmed_low); - dstate_setinfo("experimental.nhs.data.vdcmed", "%0.2f", lastpktdata.vdcmed); - dstate_setinfo("experimental.nhs.data.vdcmed_real", "%0.2f", lastpktdata.vdcmed_real); - dstate_setinfo("experimental.nhs.data.potrms", "%u", lastpktdata.potrms); - dstate_setinfo("experimental.nhs.data.vacinrmsmin_high", "%u", lastpktdata.vacinrmsmin_high); - dstate_setinfo("experimental.nhs.data.vacinrmsmin_low", "%u", lastpktdata.vacinrmsmin_low); - dstate_setinfo("experimental.nhs.data.vacinrmsmin", "%0.2f", lastpktdata.vacinrmsmin); - dstate_setinfo("experimental.nhs.data.vacinrmsmax_high", "%u", lastpktdata.vacinrmsmax_high); - dstate_setinfo("experimental.nhs.data.vacinrmsmax_low", "%u", lastpktdata.vacinrmsmax_low); - dstate_setinfo("experimental.nhs.data.vacinrmsmax", "%0.2f", lastpktdata.vacinrmsmax); - dstate_setinfo("experimental.nhs.data.vacoutrms_high", "%u", lastpktdata.vacoutrms_high); - dstate_setinfo("experimental.nhs.data.vacoutrms_low", "%u", lastpktdata.vacoutrms_low); - dstate_setinfo("experimental.nhs.data.vacoutrms", "%0.2f", lastpktdata.vacoutrms); - dstate_setinfo("experimental.nhs.data.tempmed_high", "%u", lastpktdata.tempmed_high); - dstate_setinfo("experimental.nhs.data.tempmed_low", "%u", lastpktdata.tempmed_low); - dstate_setinfo("experimental.nhs.data.tempmed", "%0.2f", lastpktdata.tempmed); - dstate_setinfo("experimental.nhs.data.tempmed_real", "%0.2f", lastpktdata.tempmed_real); - dstate_setinfo("experimental.nhs.data.icarregrms", "%u", lastpktdata.icarregrms); - dstate_setinfo("experimental.nhs.data.icarregrms_real", "%u", lastpktdata.icarregrms_real); - dstate_setinfo("experimental.nhs.data.battery_tension", "%0.2f", lastpktdata.battery_tension); - dstate_setinfo("experimental.nhs.data.perc_output", "%u", lastpktdata.perc_output); - dstate_setinfo("experimental.nhs.data.statusval", "%u", lastpktdata.statusval); - for (i = 0; i < 8; i++) { - /* Reusing variable */ - snprintf(alarm, sizeof(alarm), "experimental.nhs.data.status_p%d", i); - dstate_setinfo(alarm, "%u", lastpktdata.status[i]); - } - dstate_setinfo("experimental.nhs.data.nominaltension", "%u", lastpktdata.nominaltension); - dstate_setinfo("experimental.nhs.data.timeremain", "%0.2f", lastpktdata.timeremain); - dstate_setinfo("experimental.nhs.data.s_battery_mode", "%s", lastpktdata.s_battery_mode ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_battery_low", "%s", lastpktdata.s_battery_low ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_network_failure", "%s", lastpktdata.s_network_failure ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_fast_network_failure", "%s", lastpktdata.s_fast_network_failure ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_220_in", "%s", lastpktdata.s_220_in ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_220_out", "%s", lastpktdata.s_220_out ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_bypass_on", "%s", lastpktdata.s_bypass_on ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.s_charger_on", "%s", lastpktdata.s_charger_on ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.checksum", "%u", lastpktdata.checksum); - dstate_setinfo("experimental.nhs.data.checksum_ok", "%s", lastpktdata.checksum_ok ? "true" : "false"); - dstate_setinfo("experimental.nhs.data.checksum_calc", "%u", lastpktdata.checksum_calc); - dstate_setinfo("experimental.nhs.data.end_marker", "%u", lastpktdata.end_marker); - dstate_setinfo("experimental.nhs.param.va", "%u", va); - dstate_setinfo("experimental.nhs.param.pf", "%0.2f", pf); - dstate_setinfo("experimental.nhs.param.ah", "%u", ah); - dstate_setinfo("experimental.nhs.param.vin_low_warn_perc", "%0.2f", get_vin_perc("vin_low_warn_perc")); - dstate_setinfo("experimental.nhs.param.vin_low_crit_perc", "%0.2f", get_vin_perc("vin_low_crit_perc")); - dstate_setinfo("experimental.nhs.param.vin_high_warn_perc", "%0.2f", get_vin_perc("vin_high_warn_perc")); - dstate_setinfo("experimental.nhs.param.vin_high_crit_perc", "%0.2f", get_vin_perc("vin_high_crit_perc")); - - dstate_dataok(); - } /* end if */ - else - upsdebugx(4, "Checksum of pkt_hwinfo is corrupted or not initialized. Waiting for new request..."); - } /* end else */ - } /* end if */ - } /* end if */ - } /* end if */ - } /* end if */ - - /* Now the nobreak read buffer is empty. - * We need a hw info packet to discover several variables, - * like number of batteries, to calculate some data - * FIXME: move (semi)static info discovery to upsdrv_initinfo() or so - */ - if (!lastpkthwinfo.checksum_ok) { - upsdebugx(4, "pkt_hwinfo loss -- Requesting"); - /* If size == 0, packet maybe not initizated, - * then send an initialization packet to obtain data. - * Send two times the extended initialization string, - * but, on fail, try randomly send extended or normal. - */ - if (send_extended < 6) { - upsdebugx(4, "Sending extended initialization packet. Try %d", send_extended+1); - bwritten = write_serial_int(serial_fd, string_initialization_long, 9); - send_extended++; + } /* end else */ } /* end if */ else { - /* randomly send */ - if (rand() % 2 == 0) { - upsdebugx(4, "Sending long initialization packet"); - bwritten = write_serial_int(serial_fd, string_initialization_long, 9); - } /* end if */ + /* Energy is below limit. + * Nobreak is probably in battery mode... */ + if (lastpktdata.s_battery_low) + dstate_setinfo("ups.status", "%s", "LB"); else { - upsdebugx(4, "Sending short initialization packet"); - bwritten = write_serial_int(serial_fd, string_initialization_short, 9); + /* ...or network failure */ + dstate_setinfo("ups.status", "%s", "OB"); } /* end else */ } /* end else */ - if (bwritten < 0) { - upsdebugx(1, "%s: Problem to write data to %s", __func__, porta); - if (bwritten == -1) { - upsdebugx(1, "%s: Data problem", __func__); - } - if (bwritten == -2) { - upsdebugx(1, "%s: Flush problem", __func__); - } - close(serial_fd); - serial_fd = -1; + } /* end else */ + } /* end else */ + + /* TODO: Report in NUT datapoints (battery.packs etc.), + * Perhaps set in upsdrv_initinfo() and refresh in + * interpret_pkt_hwinfo() if needed, but not here? + * NOTE: values are cached in static C vars so as to + * not re-getval on every loop. + */ + if (!vbat) + vbat = get_vbat(); + if (!ah) + ah = get_ah(); + if (va == 0 && got_hwinfo) + va = get_va(lastpkthwinfo.model); + if (!pf) + pf = get_pf(); + if (!numbat) { + numbat = get_numbat(); + if (numbat == 0 && got_hwinfo) + numbat = lastpkthwinfo.numbatteries; + else + upsdebugx(4, "Number of batteries is set to %d", numbat); + } + + /* Set all alarms possible */ + alarm[0] = '\0'; + if (lastpktdata.s_battery_mode) + snprintf(alarm, sizeof(alarm), "%s", "|UPS IN BATTERY MODE|"); + if (lastpktdata.s_battery_low) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|UPS IN BATTERY MODE|"); + if (lastpktdata.s_network_failure) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|NETWORK FAILURE|"); + + /* FIXME: Really same criteria in these 3? */ + if (lastpktdata.s_fast_network_failure) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|FAST NETWORK FAILURE|"); + if (lastpktdata.s_fast_network_failure) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|220v IN|"); + if (lastpktdata.s_fast_network_failure) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|220v OUT|"); + + if (lastpktdata.s_bypass_on) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|BYPASS ON|"); + if (lastpktdata.s_charger_on) + snprintfcat(alarm, sizeof(alarm), "%s%s", *alarm ? " " : "", + "|CHARGER ON|"); + dstate_setinfo("ups.alarm", "%s", alarm); + + dstate_setinfo("ups.temperature", "%0.2f", lastpktdata.tempmed_real); + dstate_setinfo("ups.load", "%u", lastpktdata.potrms); + dstate_setinfo("ups.efficiency", "%0.2f", calculate_efficiency(lastpktdata.vacoutrms, lastpktdata.vacinrms)); + + /* We've got the power? */ + if (va > 0 && pf > 0) { + /* vpower is the power in Watts */ + vpower = ((va * pf) * (lastpktdata.potrms / 100.0)); + /* abat is the battery's consumption in Amperes */ + abat = ((vpower / lastpktdata.vdcmed_real) / numbat); + if (vpower > maxpower) + maxpower = vpower; + if (vpower < minpower) + minpower = vpower; + dstate_setinfo("ups.power", "%0.2f", vpower); + dstate_setinfo("ups.realpower", "%ld", lrint(round(vpower))); + + /* FIXME: Move nominal settings to upsdrv_initinfo() + * or at worst to interpret_pkt_hwinfo() */ + dstate_setinfo("ups.power.nominal", "%u", va); + dstate_setinfo("ups.realpower.nominal", "%ld", lrint(round((double)va * (double)pf))); + + dstate_setinfo("output.realpower", "%ld", lrint(round(va * (lastpktdata.potrms / 100.0)))); + dstate_setinfo("output.power", "%0.2f", vpower); + dstate_setinfo("output.power.maximum", "%0.2f", maxpower); + dstate_setinfo("output.power.minimum", "%0.2f", minpower); + dstate_setinfo("output.power.percent", "%u", lastpktdata.potrms); + + if (lastpktdata.potrms > maxpowerperc) + maxpowerperc = lastpktdata.potrms; + if (lastpktdata.potrms < minpowerperc) + minpowerperc = lastpktdata.potrms; + dstate_setinfo("output.power.maximum.percent", "%u", maxpowerperc); + dstate_setinfo("output.power.minimum.percent", "%u", minpowerperc); + } + + dstate_setinfo("output.voltage", "%0.2f", lastpktdata.vacoutrms); + dstate_setinfo("input.voltage", "%0.2f", lastpktdata.vacinrms); + dstate_setinfo("input.voltage.maximum", "%0.2f", lastpktdata.vacinrmsmin); + dstate_setinfo("input.voltage.minimum", "%0.2f", lastpktdata.vacinrmsmax); + + if (got_hwinfo) { + dstate_setinfo("ups.beeper.status", "%d", !lastpkthwinfo.c_buzzer_disable); + + vin_underv = lastpkthwinfo.s_220V_in ? lastpkthwinfo.undervoltagein220V : lastpkthwinfo.undervoltagein120V; + vin_overv = lastpkthwinfo.s_220V_in ? lastpkthwinfo.overvoltagein220V : lastpkthwinfo.overvoltagein120V; + perc = f_equal(get_vin_perc("vin_low_warn_perc"), get_vin_perc("vin_low_crit_perc")) ? 2 : 1; + vin_low_warn = vin_underv + (vin_underv * ((get_vin_perc("vin_low_warn_perc") * perc) / 100.0)); + dstate_setinfo("input.voltage.low.warning", "%0.2f", vin_low_warn); + vin_low_crit = vin_underv + (vin_underv * (get_vin_perc("vin_low_crit_perc") / 100.0)); + dstate_setinfo("input.voltage.low.critical", "%0.2f", vin_low_crit); + vin_high_warn = vin_overv + (vin_overv * ((get_vin_perc("vin_high_warn_perc") * perc) / 100.0)); + dstate_setinfo("input.voltage.high.warning", "%0.2f", vin_high_warn); + vin_high_crit = vin_overv + (vin_overv * (get_vin_perc("vin_high_crit_perc") / 100.0)); + dstate_setinfo("input.voltage.high.critical", "%0.2f", vin_high_crit); + + dstate_setinfo("input.transfer.low", "%u", lastpkthwinfo.s_220V_in ? lastpkthwinfo.undervoltagein220V : lastpkthwinfo.undervoltagein120V); + dstate_setinfo("input.transfer.high", "%u", lastpkthwinfo.s_220V_in ? lastpkthwinfo.overvoltagein220V : lastpkthwinfo.overvoltagein120V); + + /* FIXME: Move nominal settings to upsdrv_initinfo() + * or at worst to interpret_pkt_hwinfo() */ + vin = lastpkthwinfo.s_220V_in ? lastpkthwinfo.tensionout220V : lastpkthwinfo.tensionout120V; + dstate_setinfo("input.voltage.nominal", "%u", vin); + vout = lastpkthwinfo.s_220V_out ? lastpkthwinfo.tensionout220V : lastpkthwinfo.tensionout120V; + dstate_setinfo("output.voltage.nominal", "%u", vout); + } + + /* Battery electric info */ + bcharge = lrint(round((lastpktdata.vdcmed_real * 100) / vbat)); + if (bcharge > 100) + bcharge = 100; + dstate_setinfo("battery.charge", "%ld", bcharge); + dstate_setinfo("battery.voltage", "%0.2f", lastpktdata.vdcmed_real); + dstate_setinfo("battery.current", "%0.2f", abat); + dstate_setinfo("battery.current.total", "%0.2f", (float)abat * numbat); + dstate_setinfo("battery.temperature", "%ld", lrint(round(lastpktdata.tempmed_real))); + + /* FIXME: Move nominal and other static settings to upsdrv_initinfo() + * or at worst to interpret_pkt_hwinfo() */ + dstate_setinfo("battery.packs", "%u", numbat); + dstate_setinfo("battery.voltage.nominal", "%u", vbat); + dstate_setinfo("battery.capacity", "%u", ah); + dstate_setinfo("battery.capacity.nominal", "%0.2f", (float)ah * pf); + dstate_setinfo("battery.runtime.low", "%u", 30); + + if (vpower > 0) { + /* We will calculate autonomy in seconds + * autonomy_secs = (ah / lastpktdata.vdcmed_real) * 3600; + * Maybe wrong, too. + * People say that the correct calculation is + * + * Battery Amp-Hour / (Power in Watts / battery voltage) + * + * Is that correct? I don't know. I'll use it for now. + */ + + /* That result is IN HOURS. We need to convert it to seconds */ + actual_current = vpower / vbat; /* Current consumption in A*/ + autonomy_secs = (ah / actual_current) * 3600; + + dstate_setinfo("battery.runtime", "%u", autonomy_secs); + } + + /* Battery charger status */ + if (lastpktdata.s_charger_on) { + dstate_setinfo("battery.charger.status", "%s", "CHARGING"); + } else { + if (lastpktdata.s_battery_mode) + dstate_setinfo("battery.charger.status", "%s", "DISCHARGING"); + else + dstate_setinfo("battery.charger.status", "%s", "RESTING"); + } + + if (debug_pkt_data) { + unsigned int i = 0; + + /* Now, creating a structure called NHS.DATA, for latest + * data packet contents and raw data points, including those + * that were sorted above into NUT standard variables - + * for debug. + */ + dstate_setinfo("experimental.nhs.data.header", "%u", lastpktdata.header); + dstate_setinfo("experimental.nhs.data.length", "%u", lastpktdata.length); + dstate_setinfo("experimental.nhs.data.packet_type", "%c", lastpktdata.packet_type); + dstate_setinfo("experimental.nhs.data.vacinrms_high", "%u", lastpktdata.vacinrms_high); + dstate_setinfo("experimental.nhs.data.vacinrms_low", "%u", lastpktdata.vacinrms_low); + dstate_setinfo("experimental.nhs.data.vacinrms", "%0.2f", lastpktdata.vacinrms); + dstate_setinfo("experimental.nhs.data.vdcmed_high", "%u", lastpktdata.vdcmed_high); + dstate_setinfo("experimental.nhs.data.vdcmed_low", "%u", lastpktdata.vdcmed_low); + dstate_setinfo("experimental.nhs.data.vdcmed", "%0.2f", lastpktdata.vdcmed); + dstate_setinfo("experimental.nhs.data.vdcmed_real", "%0.2f", lastpktdata.vdcmed_real); + dstate_setinfo("experimental.nhs.data.potrms", "%u", lastpktdata.potrms); + dstate_setinfo("experimental.nhs.data.vacinrmsmin_high", "%u", lastpktdata.vacinrmsmin_high); + dstate_setinfo("experimental.nhs.data.vacinrmsmin_low", "%u", lastpktdata.vacinrmsmin_low); + dstate_setinfo("experimental.nhs.data.vacinrmsmin", "%0.2f", lastpktdata.vacinrmsmin); + dstate_setinfo("experimental.nhs.data.vacinrmsmax_high", "%u", lastpktdata.vacinrmsmax_high); + dstate_setinfo("experimental.nhs.data.vacinrmsmax_low", "%u", lastpktdata.vacinrmsmax_low); + dstate_setinfo("experimental.nhs.data.vacinrmsmax", "%0.2f", lastpktdata.vacinrmsmax); + dstate_setinfo("experimental.nhs.data.vacoutrms_high", "%u", lastpktdata.vacoutrms_high); + dstate_setinfo("experimental.nhs.data.vacoutrms_low", "%u", lastpktdata.vacoutrms_low); + dstate_setinfo("experimental.nhs.data.vacoutrms", "%0.2f", lastpktdata.vacoutrms); + dstate_setinfo("experimental.nhs.data.tempmed_high", "%u", lastpktdata.tempmed_high); + dstate_setinfo("experimental.nhs.data.tempmed_low", "%u", lastpktdata.tempmed_low); + dstate_setinfo("experimental.nhs.data.tempmed", "%0.2f", lastpktdata.tempmed); + dstate_setinfo("experimental.nhs.data.tempmed_real", "%0.2f", lastpktdata.tempmed_real); + dstate_setinfo("experimental.nhs.data.icarregrms", "%u", lastpktdata.icarregrms); + dstate_setinfo("experimental.nhs.data.icarregrms_real", "%u", lastpktdata.icarregrms_real); + dstate_setinfo("experimental.nhs.data.battery_tension", "%0.2f", lastpktdata.battery_tension); + dstate_setinfo("experimental.nhs.data.perc_output", "%u", lastpktdata.perc_output); + dstate_setinfo("experimental.nhs.data.statusval", "%u", lastpktdata.statusval); + for (i = 0; i < 8; i++) { + /* Reusing variable */ + snprintf(alarm, sizeof(alarm), "experimental.nhs.data.status_p%d", i); + dstate_setinfo(alarm, "%u", lastpktdata.status[i]); + } + dstate_setinfo("experimental.nhs.data.nominaltension", "%u", lastpktdata.nominaltension); + dstate_setinfo("experimental.nhs.data.timeremain", "%0.2f", lastpktdata.timeremain); + dstate_setinfo("experimental.nhs.data.s_battery_mode", "%s", lastpktdata.s_battery_mode ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_battery_low", "%s", lastpktdata.s_battery_low ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_network_failure", "%s", lastpktdata.s_network_failure ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_fast_network_failure", "%s", lastpktdata.s_fast_network_failure ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_220_in", "%s", lastpktdata.s_220_in ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_220_out", "%s", lastpktdata.s_220_out ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_bypass_on", "%s", lastpktdata.s_bypass_on ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.s_charger_on", "%s", lastpktdata.s_charger_on ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.checksum", "%u", lastpktdata.checksum); + dstate_setinfo("experimental.nhs.data.checksum_ok", "%s", lastpktdata.checksum_ok ? "true" : "false"); + dstate_setinfo("experimental.nhs.data.checksum_calc", "%u", lastpktdata.checksum_calc); + dstate_setinfo("experimental.nhs.data.end_marker", "%u", lastpktdata.end_marker); + dstate_setinfo("experimental.nhs.param.va", "%u", va); + dstate_setinfo("experimental.nhs.param.pf", "%0.2f", pf); + dstate_setinfo("experimental.nhs.param.ah", "%u", ah); + dstate_setinfo("experimental.nhs.param.vin_low_warn_perc", "%0.2f", get_vin_perc("vin_low_warn_perc")); + dstate_setinfo("experimental.nhs.param.vin_low_crit_perc", "%0.2f", get_vin_perc("vin_low_crit_perc")); + dstate_setinfo("experimental.nhs.param.vin_high_warn_perc", "%0.2f", get_vin_perc("vin_high_warn_perc")); + dstate_setinfo("experimental.nhs.param.vin_high_crit_perc", "%0.2f", get_vin_perc("vin_high_crit_perc")); + } +} + +void upsdrv_updateinfo(void) { + double tempodecorrido = 0.0; + time_t now; + + upsdebugx(3, "%s: starting...", __func__); + + /* If comms failed earlier, try to resuscitate */ + if (reconnect_ups_if_needed() <= 0) + return; + + /* Clean all read buffers to avoid errors: + * To clean OUTPUT buffer is TCOFLUSH. + * To both is TCIOFLUSH. + * //tcflush(serial_fd, TCIFLUSH); + * + * Alternative implemented below - we might + * potentially break off in the middle of + * the read() loop and continue in the next + * call to the method. In reality this is + * unlikely. + */ + chr = '\0'; + while (read(serial_fd, &chr, 1) > 0) { + if (chr == 0xFF) { /* DataPacket start */ + datapacketstart = true; + memset(datapacket, 0, sizeof(datapacket)); + datapacket_index = 0; + } /* end for */ + if (datapacketstart) { + datapacket[datapacket_index] = chr; + datapacket_index++; + if (chr == 0xFE) { /* DataPacket */ + break; + } + if (datapacket_index >= sizeof(datapacket)) { + upslogx(LOG_WARNING, "Incoming packet does not seem to end, discarding!"); + datapacketstart = false; + /* // datapacket_index = 0; */ + break; + } + } + } /* end while read */ + + if (chr != 0xFE || !datapacketstart) { + upsdebugx(2, "%s: packet reading did not finish, not interpreting yet", __func__); + return; + } + + /* Interpret the just-finished packet buffer */ + now = time(NULL); + upsdebugx(4, "DATAPACKET INDEX IS %" PRIuSIZE, datapacket_index); + if (lastdp != 0) { + tempodecorrido = difftime(now, lastdp); + } + lastdp = now; + + /* Parse the bytes into a structure to handle below: + * If size is 18 or 50, may be an answer packet. + * Then check if doesn't have already a packet processed. + * We don't need to read all times these information. + * Can be a corrupted packet too. + */ + if (((datapacket_index == 18) || (datapacket_index == 50)) && (!lastpkthwinfo.checksum_ok)) { + /* Re-read HW info only if the old one is broken */ + lastpkthwinfo = mount_hwinfo(datapacket, datapacket_index); + if (lastpkthwinfo.checksum_ok) { + interpret_pkt_hwinfo(); + /* Refresh the healthy timer */ + dstate_dataok(); + } + } /* end if */ + else if (datapacket_index == 21) { + lastpktdata = mount_datapacket(datapacket, datapacket_index, tempodecorrido, lastpkthwinfo); + if (lastpktdata.checksum_ok) { + interpret_pkt_data(); + /* Refresh the healthy timer */ + dstate_dataok(); + } + } /* end else-if */ + else { + upslogx(LOG_WARNING, "Incoming packet size not recognized, discarding!"); + } /* end else */ + + /* Clean datapacket structure to avoid problems for next parse */ + datapacket_index = 0; + memset(datapacket, 0, sizeof(datapacket)); + datapacketstart = false; + + /* Now the nobreak read buffer is empty. + * We need a hw info packet to discover several variables, + * like number of batteries, to calculate some data + * FIXME: move (semi)static info discovery to upsdrv_initinfo() or so + */ + if (!lastpkthwinfo.checksum_ok) { + upsdebugx(4, "pkt_hwinfo loss -- Requesting"); + /* If size == 0, packet maybe not initizated, + * then send an initialization packet to obtain data. + * Send two times the extended initialization string, + * but, on fail, try randomly send extended or normal. + */ + if (send_extended < 6) { + upsdebugx(4, "Sending extended initialization packet. Try %d", send_extended+1); + bwritten = write_serial_int(serial_fd, string_initialization_long, 9); + send_extended++; + } /* end if */ + else { + /* randomly send */ + if (rand() % 2 == 0) { + upsdebugx(4, "Sending long initialization packet"); + bwritten = write_serial_int(serial_fd, string_initialization_long, 9); } /* end if */ else { - if (checktime > max_checktime) - checktime = max_checktime; - else { - upsdebugx(3, "Increase checktime to %d", checktime + 100000); - checktime = checktime + 100000; - } - usleep(checktime); + upsdebugx(4, "Sending short initialization packet"); + bwritten = write_serial_int(serial_fd, string_initialization_short, 9); } /* end else */ + } /* end else */ + if (bwritten < 0) { + upsdebugx(1, "%s: Problem to write data to %s", __func__, porta); + if (bwritten == -1) { + upsdebugx(1, "%s: Data problem", __func__); + } + if (bwritten == -2) { + upsdebugx(1, "%s: Flush problem", __func__); + } + close(serial_fd); + serial_fd = -1; } /* end if */ - } /* end else */ + else { + if (checktime > max_checktime) + checktime = max_checktime; + else { + upsdebugx(3, "Increase checktime to %d", checktime + 100000); + checktime = checktime + 100000; + } + usleep(checktime); + } /* end else */ + } /* end if lastpkthwinfo good/bad checksum */ + upsdebugx(3, "%s: finished", __func__); } @@ -2217,9 +2382,22 @@ void upsdrv_cleanup(void) { } void upsdrv_initups(void) { + /* From docs/new-drivers.txt: + * Open the port (`device_path`) and do any low-level + * things that it may need to start using that port. + * If you have to set DTR or RTS on a serial port, + * do it here. + * Don't do any sort of hardware detection here, since + * you may be quickly going into upsdrv_shutdown next. + */ + + char *b = getval("baud"); + upsdebugx(3, "%s: starting...", __func__); - /* Process optional configuration flags */ + /* Process optional configuration flags that may + * impact HW init methods (debug them or not) + */ if (getval("debug_pkt_raw")) debug_pkt_raw = 1; if (getval("debug_pkt_data")) @@ -2227,7 +2405,34 @@ void upsdrv_initups(void) { if (getval("debug_pkt_hwinfo")) debug_pkt_hwinfo = 1; - upsdrv_initinfo(); + baudrate = DEFAULTBAUD; + + upsdebugx(1, "%s: Port is %s and baud_rate is %s", + __func__, device_path, NUT_STRARG(b)); + + if (b) + baudrate = atoi(b); + if (device_path) { + if (strcasecmp(device_path, "auto") == 0) + strncpy(porta, DEFAULTPORT, sizeof(porta) - 1); + else + strncpy(porta, device_path, sizeof(porta) - 1); + serial_fd = openfd(porta, baudrate); + if (serial_fd == -1) + fatalx(EXIT_FAILURE, "Unable to open port %s with baud %d", + porta, baudrate); + else { + upsdebugx(1, "%s: Communication started on port %s, baud rate %d", + __func__, porta, baudrate); + } + } + else + fatalx(EXIT_FAILURE, "Unable to define port and baud"); + + /* If we got here, the port is opened with desired baud rate. + * If not shutting down ASAP, soon we will call upsdrv_initinfo() + * and "infinitely" loop calling upsdrv_updateinfo() afterwards. + */ upsdebugx(3, "%s: finished", __func__); } diff --git a/drivers/nut-ipmipsu.c b/drivers/nut-ipmipsu.c index 95845285a3..9e978ee845 100644 --- a/drivers/nut-ipmipsu.c +++ b/drivers/nut-ipmipsu.c @@ -27,7 +27,7 @@ #include "nut-ipmi.h" #define DRIVER_NAME "IPMI PSU driver" -#define DRIVER_VERSION "0.33" +#define DRIVER_VERSION "0.34" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -145,9 +145,26 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* + * WARNING: + * This driver will probably never support this properly: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } /* diff --git a/drivers/nutdrv_atcl_usb.c b/drivers/nutdrv_atcl_usb.c index 5c52057a33..2bb69a60fb 100644 --- a/drivers/nutdrv_atcl_usb.c +++ b/drivers/nutdrv_atcl_usb.c @@ -28,7 +28,7 @@ /* driver version */ #define DRIVER_NAME "'ATCL FOR UPS' USB driver" -#define DRIVER_VERSION "1.18" +#define DRIVER_VERSION "1.19" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -65,6 +65,9 @@ static usb_dev_handle *udev = NULL; static USBDevice_t usbdevice; static unsigned int comm_failures = 0; +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + static int device_match_func(USBDevice_t *device, void *privdata) { char *requested_vendor; @@ -582,6 +585,15 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID); dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID); + + /* commands ----------------------------------------------- */ + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("shutdown.stayoff"); + + /* install handlers */ + upsh.instcmd = instcmd; } void upsdrv_updateinfo(void) @@ -641,44 +653,75 @@ void upsdrv_updateinfo(void) status_commit(); } -/* If the UPS is on battery, it should shut down about 30 seconds after - * receiving this packet. - */ -void upsdrv_shutdown(void) +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) { - /* Not "const" because this mismatches arg type of usb_interrupt_write() */ - char shutdown_packet[SHUTDOWN_PACKETSIZE] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - int ret; + NUT_UNUSED_VARIABLE(extra); + + /* FIXME: Which one is this - "load.off", + * "shutdown.stayoff" or "shutdown.return"? */ + + /* Shutdown UPS */ + if (!strcasecmp(cmdname, "shutdown.stayoff")) { + /* If the UPS is on battery, it should shut down + * about 30 seconds after receiving this packet. + */ + + /* Not "const" because this mismatches arg type + * of usb_interrupt_write() */ + char shutdown_packet[SHUTDOWN_PACKETSIZE] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + int ret; + + upslogx(LOG_DEBUG, + "%s: attempting to call usb_interrupt_write(01 00 00 00 00 00 00 00)", + __func__); + + ret = usb_interrupt_write(udev, + SHUTDOWN_ENDPOINT, (usb_ctrl_charbuf)shutdown_packet, + SHUTDOWN_PACKETSIZE, ATCL_USB_TIMEOUT); + + if (ret <= 0) { + upslogx(LOG_NOTICE, + "%s: first usb_interrupt_write() failed: %s", + __func__, + ret ? nut_usb_strerror(ret) : "timeout"); + } - upslogx(LOG_DEBUG, - "%s: attempting to call usb_interrupt_write(01 00 00 00 00 00 00 00)", - __func__); + /* Totally guessing from the .pcap file here. + * TODO: configurable delay? + */ + usleep(170*1000); - ret = usb_interrupt_write(udev, - SHUTDOWN_ENDPOINT, (usb_ctrl_charbuf)shutdown_packet, - SHUTDOWN_PACKETSIZE, ATCL_USB_TIMEOUT); + ret = usb_interrupt_write(udev, + SHUTDOWN_ENDPOINT, (usb_ctrl_charbuf)shutdown_packet, + SHUTDOWN_PACKETSIZE, ATCL_USB_TIMEOUT); - if (ret <= 0) { - upslogx(LOG_NOTICE, - "%s: first usb_interrupt_write() failed: %s", - __func__, - ret ? nut_usb_strerror(ret) : "timeout"); - } + if (ret <= 0) { + upslogx(LOG_ERR, + "%s: second usb_interrupt_write() failed: %s", + __func__, + ret ? nut_usb_strerror(ret) : "timeout"); + } - /* Totally guessing from the .pcap file here. TODO: configurable delay? */ - usleep(170*1000); + return STAT_INSTCMD_HANDLED; + } - ret = usb_interrupt_write(udev, - SHUTDOWN_ENDPOINT, (usb_ctrl_charbuf)shutdown_packet, - SHUTDOWN_PACKETSIZE, ATCL_USB_TIMEOUT); + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} - if (ret <= 0) { - upslogx(LOG_ERR, - "%s: second usb_interrupt_write() failed: %s", - __func__, - ret ? nut_usb_strerror(ret) : "timeout"); - } +void upsdrv_shutdown(void) +{ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + int ret = do_loop_shutdown_commands("shutdown.stayoff", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index 3bc1d8675a..46ee43b725 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -58,7 +58,7 @@ # define DRIVER_NAME "Generic Q* Serial driver" #endif /* QX_USB */ -#define DRIVER_VERSION "0.37" +#define DRIVER_VERSION "0.38" #ifdef QX_SERIAL # include "serial.h" @@ -2769,12 +2769,19 @@ int setvar(const char *varname, const char *val) /* Try to shutdown the UPS */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + int retry; item_t *item; const char *val; upsdebugx(1, "%s...", __func__); + /* FIXME: Use common "sdcommands" feature to + * replace tunables used below ("stayoff" etc). + */ + /* Get user-defined delays */ /* Start delay */ @@ -2783,7 +2790,8 @@ void upsdrv_shutdown(void) /* Don't know what happened */ if (!item) { upslogx(LOG_ERR, "Unable to set start delay"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } @@ -2798,7 +2806,8 @@ void upsdrv_shutdown(void) if (val && setvar(item->info_type, val) != STAT_SET_HANDLED) { upslogx(LOG_ERR, "Start delay '%s' out of range", val); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } @@ -2808,7 +2817,8 @@ void upsdrv_shutdown(void) /* Don't know what happened */ if (!item) { upslogx(LOG_ERR, "Unable to set shutdown delay"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } @@ -2823,13 +2833,13 @@ void upsdrv_shutdown(void) if (val && setvar(item->info_type, val) != STAT_SET_HANDLED) { upslogx(LOG_ERR, "Shutdown delay '%s' out of range", val); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } /* Stop pending shutdowns */ if (find_nut_info("shutdown.stop", QX_FLAG_CMD, QX_FLAG_SKIP)) { - for (retry = 1; retry <= MAXTRIES; retry++) { if (instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { @@ -2843,34 +2853,30 @@ void upsdrv_shutdown(void) if (retry > MAXTRIES) { upslogx(LOG_NOTICE, "No shutdown pending"); } - } /* Shutdown */ for (retry = 1; retry <= MAXTRIES; retry++) { - if (testvar("stayoff")) { - if (instcmd("shutdown.stayoff", NULL) != STAT_INSTCMD_HANDLED) { continue; } - } else { - if (instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) { continue; } - } upslogx(LOG_ERR, "Shutting down in %s seconds", dstate_getinfo("ups.delay.shutdown")); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } #ifdef QX_USB diff --git a/drivers/nutdrv_siemens_sitop.c b/drivers/nutdrv_siemens_sitop.c index f45bbc6dd4..0029ed0eb0 100644 --- a/drivers/nutdrv_siemens_sitop.c +++ b/drivers/nutdrv_siemens_sitop.c @@ -56,7 +56,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Siemens SITOP UPS500 series driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" #define RX_BUFFER_SIZE 100 @@ -248,8 +248,14 @@ void upsdrv_updateinfo(void) { } void upsdrv_shutdown(void) { - /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ - instcmd("shutdown.return", NULL); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* by default, tell the UPS to shut down, + * then return - DO NOT SLEEP HERE */ + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } diff --git a/drivers/oneac.c b/drivers/oneac.c index dcbac8e744..a534ddbff9 100644 --- a/drivers/oneac.c +++ b/drivers/oneac.c @@ -48,7 +48,7 @@ int setcmd(const char* varname, const char* setvalue); int instcmd(const char *cmdname, const char *extra); #define DRIVER_NAME "Oneac EG/ON/OZ/OB UPS driver" -#define DRIVER_VERSION "0.83" +#define DRIVER_VERSION "0.84" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -814,7 +814,19 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - ser_send(upsfd,"%s",SHUTDOWN); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* FIXME: before loop_shutdown_commands(), code directly called + * here was identical to "shutdown.reboot", while the driver + * shutdown should more reasonably be "shutdown.return" (when + * wall power is back) or "shutdown.stayoff". All of these are + * implemented in instcmd() here nominally (not sure if named + * correctly - better re-check on hardware). + */ + int ret = do_loop_shutdown_commands("shutdown.reboot", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/optiups.c b/drivers/optiups.c index 9f4344d53c..4fe2cbb1ec 100644 --- a/drivers/optiups.c +++ b/drivers/optiups.c @@ -28,7 +28,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Opti-UPS driver" -#define DRIVER_VERSION "1.05" +#define DRIVER_VERSION "1.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -300,7 +300,7 @@ static int instcmd(const char *cmdname, const char *extra) } else if (!strcasecmp(cmdname, "shutdown.stop")) { - /* Aborts a shutdown that is couting down via the Cs command */ + /* Aborts a shutdown that is counting down via the Cs command */ optiquery( "Cs-0000001" ); return STAT_INSTCMD_HANDLED; } @@ -537,13 +537,16 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* OL: this must power cycle the load if possible */ /* OB: the load must remain off until the power returns */ /* If get no response, assume on battery & battery low */ - long s = OPTISBIT_ON_BATTERY_POWER | OPTISBIT_LOW_BATTERY; + long s = OPTISBIT_ON_BATTERY_POWER | OPTISBIT_LOW_BATTERY; - ssize_t r = optiquery( "AG" ); + ssize_t r = optiquery( "AG" ); if ( r < 1 ) { upslogx(LOG_ERR, "can't retrieve ups status during shutdown" ); diff --git a/drivers/phoenixcontact_modbus.c b/drivers/phoenixcontact_modbus.c index 3e82fe69ec..25342fd59f 100644 --- a/drivers/phoenixcontact_modbus.c +++ b/drivers/phoenixcontact_modbus.c @@ -517,9 +517,26 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* + * WARNING: When using RTU TCP, this driver will probably + * never support shutdowns properly, except on some systems: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/pijuice.c b/drivers/pijuice.c index 09499f099a..0ac56c8fb6 100644 --- a/drivers/pijuice.c +++ b/drivers/pijuice.c @@ -23,7 +23,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "PiJuice UPS driver" -#define DRIVER_VERSION "0.12" +#define DRIVER_VERSION "0.13" /* * Linux I2C userland is a bit of a mess until distros refresh to @@ -46,6 +46,9 @@ # endif #endif +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + /* * i2c-tools pre-4.0 has a userspace header with a name that conflicts * with a kernel header, so it may be ignored/removed by distributions @@ -798,6 +801,15 @@ void upsdrv_initinfo(void) get_i2c_address(); get_battery_profile(); get_battery_profile_ext(); + + /* commands ----------------------------------------------- */ + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("shutdown.stayoff"); + + /* install handlers */ + upsh.instcmd = instcmd; } void upsdrv_updateinfo(void) @@ -817,9 +829,37 @@ void upsdrv_updateinfo(void) dstate_dataok(); } +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) +{ + NUT_UNUSED_VARIABLE(extra); + + /* FIXME: Which one is this - "load.off", + * "shutdown.stayoff" or "shutdown.return"? */ + + /* Shutdown UPS */ + if (!strcasecmp(cmdname, "shutdown.stayoff")) { + set_power_off(); + + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} + void upsdrv_shutdown(void) { - set_power_off(); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + int ret = do_loop_shutdown_commands("shutdown.stayoff", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/powercom.c b/drivers/powercom.c index d31e730b7c..752a2a2198 100644 --- a/drivers/powercom.c +++ b/drivers/powercom.c @@ -86,7 +86,7 @@ #include "nut_float.h" #define DRIVER_NAME "PowerCom protocol UPS driver" -#define DRIVER_VERSION "0.23" +#define DRIVER_VERSION "0.24" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -297,7 +297,8 @@ static void shutdown_halt(void) ser_send_char (upsfd, types[type].shutdown_arguments.delay[1]); upslogx(LOG_INFO, "Shutdown (stayoff) initiated."); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); } static void shutdown_ret(void) @@ -309,7 +310,8 @@ static void shutdown_ret(void) ser_send_char (upsfd, types[type].shutdown_arguments.delay[1]); upslogx(LOG_INFO, "Shutdown (return) initiated."); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); } /* registered instant commands */ @@ -324,15 +326,11 @@ static int instcmd (const char *cmdname, const char *extra) * wall-power gets restored. The routine exits the driver anyway. */ shutdown_ret(); -#ifndef HAVE___ATTRIBUTE__NORETURN return STAT_INSTCMD_HANDLED; -#endif } if (!strcasecmp(cmdname, "shutdown.stayoff")) { shutdown_halt(); -#ifndef HAVE___ATTRIBUTE__NORETURN return STAT_INSTCMD_HANDLED; -#endif } upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); @@ -842,9 +840,19 @@ void upsdrv_updateinfo(void) /* shutdown UPS */ void upsdrv_shutdown(void) { - /* power down the attached load immediately */ - printf("Forced UPS shutdown (and wait for power)...\n"); - shutdown_ret(); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = -1; + + if (!device_sdcommands) { + /* default: power down the attached load immediately */ + printf("Forced UPS shutdown (and wait for power)...\n"); + } + + ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } /* initialize UPS */ diff --git a/drivers/powerman-pdu.c b/drivers/powerman-pdu.c index 277bd4d347..348c67a20c 100644 --- a/drivers/powerman-pdu.c +++ b/drivers/powerman-pdu.c @@ -23,7 +23,7 @@ #include /* pm_err_t and other beasts */ #define DRIVER_NAME "Powerman PDU client driver" -#define DRIVER_VERSION "0.14" +#define DRIVER_VERSION "0.15" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -135,12 +135,29 @@ void upsdrv_initinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* + * WARNING: + * This driver will probably never support this properly: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + + /* replace with a proper shutdown function */ /* FIXME: shutdown all outlets? */ - upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); - /* OL: this must power cycle the load if possible */ /* OB: the load must remain off until the power returns */ + upslogx(LOG_ERR, "shutdown not supported"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } /* diff --git a/drivers/powerp-bin.c b/drivers/powerp-bin.c index 3c6d74f00b..4c3076040e 100644 --- a/drivers/powerp-bin.c +++ b/drivers/powerp-bin.c @@ -34,7 +34,7 @@ #include "nut_stdint.h" #include "nut_float.h" -#define POWERPANEL_BIN_VERSION "Powerpanel-Binary 0.60" +#define POWERPANEL_BIN_VERSION "Powerpanel-Binary 0.61" typedef struct { unsigned char start; diff --git a/drivers/powerp-txt.c b/drivers/powerp-txt.c index d13e8899a0..a5d58135d4 100644 --- a/drivers/powerp-txt.c +++ b/drivers/powerp-txt.c @@ -36,7 +36,7 @@ #include -#define POWERPANEL_TEXT_VERSION "Powerpanel-Text 0.60" +#define POWERPANEL_TEXT_VERSION "Powerpanel-Text 0.61" typedef struct { float i_volt; diff --git a/drivers/powerpanel.c b/drivers/powerpanel.c index 5600ad4f7c..2d8c212781 100644 --- a/drivers/powerpanel.c +++ b/drivers/powerpanel.c @@ -102,6 +102,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + int i, ret = -1; /* @@ -118,7 +121,6 @@ void upsdrv_shutdown(void) * we can't read status or it is telling us we're on battery. */ for (i = 0; i < MAXTRIES; i++) { - ret = subdriver[mode]->updateinfo(); if (ret >= 0) { break; diff --git a/drivers/rhino.c b/drivers/rhino.c index f312fab798..60346ed806 100644 --- a/drivers/rhino.c +++ b/drivers/rhino.c @@ -38,7 +38,7 @@ #include "timehead.h" #define DRIVER_NAME "Microsol Rhino UPS driver" -#define DRIVER_VERSION "0.54" +#define DRIVER_VERSION "0.55" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -749,13 +749,17 @@ void upsdrv_updateinfo(void) /* power down the attached load immediately */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* basic idea: find out line status and send appropriate command */ /* on line: send normal shutdown, ups will return by itself on utility */ /* on battery: send shutdown+return, ups will cycle and return soon */ if (!SourceFail) /* on line */ { - /* FIXME: Both legs of the if-clause send CMD_SHUT, where is the "forcing"? */ + /* FIXME: Both legs of the if-clause send CMD_SHUT, + * where is the "forcing"? */ printf("On line, forcing shutdown command...\n"); /* send_command( CMD_SHUT ); */ sendshut(); diff --git a/drivers/richcomm_usb.c b/drivers/richcomm_usb.c index 766d3bba60..77b3ceeab8 100644 --- a/drivers/richcomm_usb.c +++ b/drivers/richcomm_usb.c @@ -30,7 +30,7 @@ /* driver version */ #define DRIVER_NAME "Richcomm dry-contact to USB driver" -#define DRIVER_VERSION "0.13" +#define DRIVER_VERSION "0.14" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -66,6 +66,9 @@ static usb_dev_handle *udev = NULL; static USBDevice_t usbdevice; static unsigned int comm_failures = 0; +/* Forward decls */ +static int instcmd(const char *cmdname, const char *extra); + static int device_match_func(USBDevice_t *device, void *privdata) { NUT_UNUSED_VARIABLE(privdata); @@ -636,6 +639,15 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID); dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID); + + /* commands ----------------------------------------------- */ + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + dstate_addcmd("shutdown.return"); + + /* install handlers */ + upsh.instcmd = instcmd; } void upsdrv_updateinfo(void) @@ -696,46 +708,82 @@ void upsdrv_updateinfo(void) status_commit(); } -/* - * The shutdown feature is a bit strange on this UPS IMHO, it - * switches the polarity of the 'Shutdown UPS' signal, at which - * point it will automatically power down once it loses power. - * - * It will still, however, be possible to poll the UPS and - * reverse the polarity _again_, at which point it will - * start back up once power comes back. - * - * Maybe this is the normal way, it just seems a bit strange. - * - * Please note, this function doesn't power the UPS off if - * line power is connected. - */ -void upsdrv_shutdown(void) +/* handler for commands to be sent to UPS */ +static +int instcmd(const char *cmdname, const char *extra) { - /* - * This packet shuts down the UPS, that is, - * if it is not currently on line power - */ - char prepare[QUERY_PACKETSIZE] = { 0x02, 0x00, 0x00, 0x00 }; + NUT_UNUSED_VARIABLE(extra); - /* - * This should make the UPS turn itself back on once the - * power comes back on; which is probably what we want - */ - char restart[QUERY_PACKETSIZE] = { 0x02, 0x01, 0x00, 0x00 }; - char reply[REPLY_PACKETSIZE]; + /* Shutdown UPS */ + if (!strcasecmp(cmdname, "shutdown.return")) + { + /* FIXME: Which one is this - "load.off", + * "shutdown.stayoff" or "shutdown.return"? + * Per legacy comments below it seems to + * best fit "load.off", and then we would + * want a "load.on" as well (is it different + * given the talk of polarity inversion?), + * except that "load.*" are to be immediate + * and here it depends on line power state... + */ + + /* + * The shutdown feature is a bit strange on this UPS IMHO, it + * switches the polarity of the 'Shutdown UPS' signal, at which + * point it will automatically power down once it loses power. + * + * It will still, however, be possible to poll the UPS and + * reverse the polarity _again_, at which point it will + * start back up once power comes back. + * + * Maybe this is the normal way, it just seems a bit strange. + * + * Please note, this function doesn't power the UPS off if + * line power is connected. + */ + + /* + * This packet shuts down the UPS, that is, + * if it is not currently on line power + */ + char prepare[QUERY_PACKETSIZE] = { 0x02, 0x00, 0x00, 0x00 }; + + /* + * This should make the UPS turn itself back on once the + * power comes back on; which is probably what we want + */ + char restart[QUERY_PACKETSIZE] = { 0x02, 0x01, 0x00, 0x00 }; + char reply[REPLY_PACKETSIZE]; + + execute_and_retrieve_query(prepare, reply); + + /* + * have to wait a bit, the previous command seems + * to be ignored if the second command comes right + * behind it + */ + sleep(1); - execute_and_retrieve_query(prepare, reply); + execute_and_retrieve_query(restart, reply); - /* - * have to, the previous command seems to be - * ignored if the second command comes right - * behind it - */ - sleep(1); + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} +void upsdrv_shutdown(void) +{ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - execute_and_retrieve_query(restart, reply); + /* FIXME: Check with the device what our instcmd + * (nee upsdrv_shutdown() contents) actually does! + */ + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/riello_ser.c b/drivers/riello_ser.c index f15c785ef5..59bae583cd 100644 --- a/drivers/riello_ser.c +++ b/drivers/riello_ser.c @@ -48,7 +48,7 @@ #include "riello.h" #define DRIVER_NAME "Riello serial driver" -#define DRIVER_VERSION "0.11" +#define DRIVER_VERSION "0.12" #define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ #define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ @@ -1163,6 +1163,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ int retry; @@ -1181,7 +1184,8 @@ void upsdrv_shutdown(void) upsdebugx(2, "upsdrv Shutdown execute"); for (retry = 1; retry <= MAXTRIES; retry++) { - + /* By default, abort a previously requested shutdown + * (if any) and schedule a new one from this moment. */ if (riello_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { continue; } @@ -1191,12 +1195,14 @@ void upsdrv_shutdown(void) } upslogx(LOG_ERR, "Shutting down"); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } diff --git a/drivers/riello_usb.c b/drivers/riello_usb.c index cde20fbae9..a56f27757b 100644 --- a/drivers/riello_usb.c +++ b/drivers/riello_usb.c @@ -36,7 +36,7 @@ #include "riello.h" #define DRIVER_NAME "Riello USB driver" -#define DRIVER_VERSION "0.13" +#define DRIVER_VERSION "0.14" #define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ #define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ @@ -1134,6 +1134,9 @@ void upsdrv_initinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ int retry; @@ -1151,7 +1154,8 @@ void upsdrv_shutdown(void) /* OB: the load must remain off until the power returns */ for (retry = 1; retry <= MAXTRIES; retry++) { - + /* By default, abort a previously requested shutdown + * (if any) and schedule a new one from this moment. */ if (riello_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { continue; } @@ -1161,12 +1165,14 @@ void upsdrv_shutdown(void) } upslogx(LOG_ERR, "Shutting down"); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_updateinfo(void) diff --git a/drivers/safenet.c b/drivers/safenet.c index 351187419e..ac5a6b0e14 100644 --- a/drivers/safenet.c +++ b/drivers/safenet.c @@ -41,7 +41,7 @@ #include "safenet.h" #define DRIVER_NAME "Generic SafeNet UPS driver" -#define DRIVER_VERSION "1.81" +#define DRIVER_VERSION "1.82" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -419,6 +419,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + int retry = 3; /* @@ -435,7 +438,8 @@ void upsdrv_shutdown(void) } upslogx(LOG_ERR, "SafeNet protocol compatible UPS not found on %s", device_path); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); return; } diff --git a/drivers/skel.c b/drivers/skel.c index 9b45023832..3cb620ab1c 100644 --- a/drivers/skel.c +++ b/drivers/skel.c @@ -22,7 +22,7 @@ /* #define IGNCHARS "" */ #define DRIVER_NAME "Skeleton UPS driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -33,6 +33,9 @@ upsdrv_info_t upsdrv_info = { { NULL } }; +/* Forward decls */ +/*static int instcmd(const char *cmdname, const char *extra);*/ + void upsdrv_initinfo(void) { /* try to detect the UPS here - call fatal_with_errno(EXIT_FAILURE, ...) @@ -44,6 +47,9 @@ void upsdrv_initinfo(void) /* dstate_setinfo("device.mfr", "skel manufacturer"); */ /* dstate_setinfo("device.model", "longrun 15000"); */ + /* commands ----------------------------------------------- */ + /*dstate_addcmd("shutdown.return");*/ + /*dstate_addcmd("test.battery.stop);*/ /* upsh.instcmd = instcmd; */ } @@ -96,14 +102,19 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ /* maybe try to detect the UPS here, but try a shutdown even if it doesn't respond at first if possible */ /* replace with a proper shutdown function */ + upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); /* you may have to check the line status since the commands for toggling power are frequently different for OL vs. OB */ @@ -121,7 +132,12 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } - upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname); + if (!strcasecmp(cmdname, "shutdown.stayoff")) { + ser_send_buf(upsfd, ...); + return STAT_INSTCMD_HANDLED; + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); return STAT_INSTCMD_UNKNOWN; } */ diff --git a/drivers/sms_ser.c b/drivers/sms_ser.c index 7b0f840896..9a96b6f294 100644 --- a/drivers/sms_ser.c +++ b/drivers/sms_ser.c @@ -31,7 +31,7 @@ #define ENDCHAR '\r' #define DRIVER_NAME "SMS Brazil UPS driver" -#define DRIVER_VERSION "1.01" +#define DRIVER_VERSION "1.02" #define QUERY_SIZE 7 #define BUFFER_SIZE 18 @@ -514,6 +514,9 @@ void upsdrv_updateinfo(void) { } void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ int retry; @@ -531,6 +534,8 @@ void upsdrv_shutdown(void) { upsdebugx(2, "upsdrv Shutdown execute"); for (retry = 1; retry <= MAXTRIES; retry++) { + /* By default, abort a previously requested shutdown + * (if any) and schedule a new one from this moment. */ if (sms_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { continue; } @@ -540,12 +545,14 @@ void upsdrv_shutdown(void) { } upslogx(LOG_ERR, "Shutting down"); - set_exit_flag(-2); /* EXIT_SUCCESS */ + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) { diff --git a/drivers/snmp-ups.c b/drivers/snmp-ups.c index d6e2f6a89b..a4a4090ff3 100644 --- a/drivers/snmp-ups.c +++ b/drivers/snmp-ups.c @@ -174,7 +174,7 @@ static const char *mibname; static const char *mibvers; #define DRIVER_NAME "Generic SNMP UPS driver" -#define DRIVER_VERSION "1.31" +#define DRIVER_VERSION "1.32" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -341,40 +341,44 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* - This driver will probably never support this. In order to - be any use, the driver should be called near the end of - the system halt script. By that time we in all likelyhood - we won't have network capabilities anymore, so we could - never send this command to the UPS. This is not an error, - but a limitation of the interface used. - */ + * WARNING: + * This driver will probably never support this properly: + * In order to be of any use, the driver should be called + * near the end of the system halt script (or a service + * management framework's equivalent, if any). By that + * time we, in all likelyhood, won't have basic network + * capabilities anymore, so we could never send this + * command to the UPS. This is not an error, but rather + * a limitation (on some platforms) of the interface/media + * used for these devices. + */ + char *cmd_used = NULL; upsdebugx(1, "%s...", __func__); /* set shutdown and autostart delay */ set_delays(); - /* Try to shutdown with delay */ - if (su_instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ - return; - } - - /* If the above doesn't work, try shutdown.reboot */ - if (su_instcmd("shutdown.reboot", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ - return; - } - - /* If the above doesn't work, try load.off.delay */ - if (su_instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ + /* By default: + * - Try to shutdown with delay + * - If the above doesn't work, try shutdown.reboot + * - If the above doesn't work, try load.off.delay + * - Finally, try shutdown.stayoff + */ + if (do_loop_shutdown_commands("shutdown.return,shutdown.reboot,load.off.delay,shutdown.stayoff", &cmd_used) == STAT_INSTCMD_HANDLED) { + upslogx(LOG_INFO, "Shutdown successful with '%s'", NUT_STRARG(cmd_used)); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/socomec_jbus.c b/drivers/socomec_jbus.c index 76534512be..075283449e 100644 --- a/drivers/socomec_jbus.c +++ b/drivers/socomec_jbus.c @@ -31,7 +31,7 @@ #include #define DRIVER_NAME "Socomec jbus driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 @@ -428,9 +428,13 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) diff --git a/drivers/solis.c b/drivers/solis.c index 11120580bb..e4f92acbf4 100644 --- a/drivers/solis.c +++ b/drivers/solis.c @@ -48,7 +48,7 @@ #include "timehead.h" #define DRIVER_NAME "Microsol Solis UPS driver" -#define DRIVER_VERSION "0.70" +#define DRIVER_VERSION "0.71" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -897,6 +897,8 @@ static void get_update_info(void) { static int instcmd(const char *cmdname, const char *extra) { if (!strcasecmp(cmdname, "shutdown.return")) { /* shutdown and restart */ + /* FIXME: check with HW if this is not + * a "shutdown.reboot" instead (or also)? */ ser_send_char(upsfd, CMD_SHUTRET); /* 0xDE */ /* ser_send_char(upsfd, ENDCHAR); */ return STAT_INSTCMD_HANDLED; @@ -960,12 +962,17 @@ void upsdrv_updateinfo(void) { * - on line: send shutdown+return, UPS will cycle and return soon. */ void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + if (!SourceFail) { /* on line */ upslogx(LOG_NOTICE, "On line, sending shutdown+return command...\n"); ser_send_char(upsfd, CMD_SHUTRET ); + /* Seems AKA: instcmd("shutdown.return", NULL); */ } else { upslogx(LOG_NOTICE, "On battery, sending normal shutdown command...\n"); ser_send_char(upsfd, CMD_SHUT); + /* Seems AKA: instcmd("shutdown.stayoff", NULL); */ } } diff --git a/drivers/tripplite.c b/drivers/tripplite.c index cfb4d2230f..299fb5fd7d 100644 --- a/drivers/tripplite.c +++ b/drivers/tripplite.c @@ -117,7 +117,7 @@ #include #define DRIVER_NAME "Tripp-Lite SmartUPS driver" -#define DRIVER_VERSION "0.96" +#define DRIVER_VERSION "0.97" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -378,7 +378,12 @@ void upsdrv_initinfo(void) void upsdrv_shutdown(void) { - soft_shutdown(); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_updateinfo(void) diff --git a/drivers/tripplite_usb.c b/drivers/tripplite_usb.c index d441e783a4..d886c91e20 100644 --- a/drivers/tripplite_usb.c +++ b/drivers/tripplite_usb.c @@ -137,7 +137,7 @@ #include "usb-common.h" #define DRIVER_NAME "Tripp Lite OMNIVS / SMARTPRO driver" -#define DRIVER_VERSION "0.38" +#define DRIVER_VERSION "0.39" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1261,7 +1261,12 @@ void upsdrv_initinfo(void) void upsdrv_shutdown(void) { - soft_shutdown(); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } void upsdrv_updateinfo(void) diff --git a/drivers/tripplitesu.c b/drivers/tripplitesu.c index fb6f42076a..d849df2b99 100644 --- a/drivers/tripplitesu.c +++ b/drivers/tripplitesu.c @@ -126,7 +126,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Tripp Lite SmartOnline driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -846,7 +846,10 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - char parm[20]; + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + char parm[20]; if (!init_comm()) printf("Status failed. Assuming it's on battery and trying a shutdown anyway.\n"); diff --git a/drivers/upscode2.c b/drivers/upscode2.c index ee88ba74f8..f1d4ae16e3 100644 --- a/drivers/upscode2.c +++ b/drivers/upscode2.c @@ -43,7 +43,7 @@ #include "nut_float.h" #define DRIVER_NAME "UPScode II UPS driver" -#define DRIVER_VERSION "0.92" +#define DRIVER_VERSION "0.93" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -876,6 +876,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + upslogx(LOG_EMERG, "Shutting down..."); /* send shutdown command twice, just to be sure */ diff --git a/drivers/upsdrvquery.c b/drivers/upsdrvquery.c index 31bb8e94e1..2c4a767bbb 100644 --- a/drivers/upsdrvquery.c +++ b/drivers/upsdrvquery.c @@ -238,6 +238,11 @@ ssize_t upsdrvquery_read_timeout(udq_pipe_conn_t *conn, struct timeval tv) { struct timeval start, now, presleep; #endif + upsdebugx(5, "%s: tv={sec=%" PRIiMAX ", usec=%06" PRIiMAX "}%s", + __func__, (intmax_t)tv.tv_sec, (intmax_t)tv.tv_usec, + tv.tv_sec < 0 || tv.tv_usec < 0 ? " (unlimited timeout)" : "" + ); + if (!conn || INVALID_FD(conn->sockfd)) { if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) upslog_with_errno(LOG_ERR, "socket not initialized"); @@ -248,7 +253,7 @@ ssize_t upsdrvquery_read_timeout(udq_pipe_conn_t *conn, struct timeval tv) { FD_ZERO(&rfds); FD_SET(conn->sockfd, &rfds); - if (select(conn->sockfd + 1, &rfds, NULL, NULL, &tv) < 0) { + if (select(conn->sockfd + 1, &rfds, NULL, NULL, tv.tv_sec < 0 || tv.tv_usec < 0 ? NULL : &tv) < 0) { if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_DIALOG) upslog_with_errno(LOG_ERR, "select with socket"); /* upsdrvquery_close(conn); */ diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 895e3891ef..8fba2b67fd 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -29,7 +29,7 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.58" +#define DRIVER_VERSION "0.59" #define HU_VAR_WAITBEFORERECONNECT "waitbeforereconnect" @@ -988,28 +988,29 @@ int setvar(const char *varname, const char *val) void upsdrv_shutdown(void) { - upsdebugx(1, "upsdrv_shutdown..."); + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ - /* Try to shutdown with delay */ - if (instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ - return; - } + char *cmd_used = NULL; - /* If the above doesn't work, try shutdown.reboot */ - if (instcmd("shutdown.reboot", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ - return; - } + upsdebugx(1, "%s...", __func__); - /* If the above doesn't work, try load.off.delay */ - if (instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) { - /* Shutdown successful */ + /* By default: + * - Try to shutdown with delay + * - If the above doesn't work, try shutdown.reboot + * - If the above doesn't work, try load.off.delay + * - Finally, try shutdown.stayoff + */ + if (do_loop_shutdown_commands("shutdown.return,shutdown.reboot,load.off.delay,shutdown.stayoff", &cmd_used) == STAT_INSTCMD_HANDLED) { + upslogx(LOG_INFO, "Shutdown successful with '%s'", NUT_STRARG(cmd_used)); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); return; } upslogx(LOG_ERR, "Shutdown failed!"); - set_exit_flag(-1); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); } void upsdrv_help(void) @@ -2409,7 +2410,7 @@ static void ups_status_set(void) if (ups_status & STATUS(OVERLOAD)) { status_set("OVER"); /* overload */ } - if (ups_status & STATUS(REPLACEBATT)) { + if ((ups_status & STATUS(REPLACEBATT)) || (ups_status & STATUS(NOBATTERY))) { if (lbrb_log_delay_sec < 1 || (!isCalibrating && !lbrb_log_delay_without_calibrating) || !last_lb_start /* Calibration ended (not LB anymore) */ diff --git a/drivers/victronups.c b/drivers/victronups.c index ad11b4b3cb..10425d3fb8 100644 --- a/drivers/victronups.c +++ b/drivers/victronups.c @@ -32,7 +32,7 @@ #include "serial.h" #define DRIVER_NAME "GE/IMV/Victron UPS driver" -#define DRIVER_VERSION "0.23" +#define DRIVER_VERSION "0.24" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -498,6 +498,9 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + ser_send(upsfd, "vCc0!%c", ENDCHAR); usleep(UPS_DELAY); diff --git a/include/common.h b/include/common.h index bc171f7ce8..77b81934c3 100644 --- a/include/common.h +++ b/include/common.h @@ -560,6 +560,15 @@ extern int optind; # define setegid(x) setresgid(-1,x,-1) /* Works for HP-UX 10.20 */ #endif +/* Several NUT programs define their set_exit_flag(int) methods + * which accept a signal code or similar parameter. Commonly they + * also accept a few negative values summarized below, to just + * exit (typically after completing a processing loop) with one + * of C standard exit codes. + */ +#define EF_EXIT_FAILURE -1 /* eventually exit(EXIT_FAILURE); */ +#define EF_EXIT_SUCCESS -2 /* eventually exit(EXIT_SUCCESS); */ + #ifdef WIN32 /* FIXME : this might not be the optimal mapping between syslog and ReportEvent*/ #define LOG_ERR EVENTLOG_ERROR_TYPE diff --git a/include/nutconf.hpp b/include/nutconf.hpp index a97298ce82..a3bdf7fdaf 100644 --- a/include/nutconf.hpp +++ b/include/nutconf.hpp @@ -1475,6 +1475,9 @@ class UpsmonConfiguration : public Serialisable NOTIFY_ALARM, NOTIFY_NOTALARM, + NOTIFY_OTHER = 28, + NOTIFY_NOTOTHER, + NOTIFY_SUSPEND_STARTING = 30, NOTIFY_SUSPEND_FINISHED, diff --git a/scripts/augeas/nutupsmonconf.aug.in b/scripts/augeas/nutupsmonconf.aug.in index e972a406d7..7e7f219433 100644 --- a/scripts/augeas/nutupsmonconf.aug.in +++ b/scripts/augeas/nutupsmonconf.aug.in @@ -127,6 +127,8 @@ let upsmon_notify_type = "ONLINE" | "NOTECO" | "ALARM" | "NOTALARM" + | "OTHER" + | "NOTOTHER" | "SUSPEND_STARTING" | "SUSPEND_FINISHED" diff --git a/scripts/misc/notifyme-debug b/scripts/misc/notifyme-debug index fb1620aa5c..22e2f79e76 100755 --- a/scripts/misc/notifyme-debug +++ b/scripts/misc/notifyme-debug @@ -1,12 +1,17 @@ #!/bin/sh -# This is a sample NUT notification script aimed to aid debugging of upsmon -# behavior. It creates or appends to a log file named per user ID in the -# temporary location with an entry per line like this: +# This is a sample NUT notification script aimed to aid debugging of +# `upsmon` behavior, as its NOTIFYCMD (as specified in upsmon.conf). +# This script creates or appends to a log file named per user ID in +# the specified or default temporary location with an entry per line +# like this: # Sat Apr 6 17:23:06 UTC 2024 [uid=77(nut) gid=77(nut) groups=77(nut)] COMMBAD [eco650]: Communications with UPS eco650 lost (1 tokens) # Sat Apr 6 17:28:01 UTC 2024 [uid=77(nut) gid=77(nut) groups=77(nut)] NOCOMM [eco650]: UPS eco650 is unavailable (1 tokens) # # Copyright (C) 2023-2024 by Jim Klimov # Licensed under the terms of the Network UPS Tools source license (GPLv2+) -printf '%s\t[%s]\t%s\t[%s]:\t%s\t(%s tokens)\n' "`date -u`" "`id`" "${NOTIFYTYPE-}" "${UPSNAME-}" "$*" "$#" >> "/tmp/notifyme-`id -u`.log" +[ -n "${TEMPDIR-}" ] || TEMPDIR="${TMPDIR-}" +[ -n "${TEMPDIR-}" ] || { [ -d "/dev/shm" && TEMPDIR="/dev/shm" || TEMPDIR="/tmp" ; } + +printf '%s\t[%s]\t%s\t[%s]:\t%s\t(%s tokens)\n' "`date -u`" "`id`" "${NOTIFYTYPE-}" "${UPSNAME-}" "$*" "$#" >> "${TEMPDIR}/notifyme-`id -u`.log" diff --git a/tests/NIT/Makefile.am b/tests/NIT/Makefile.am index 069825355f..825df6bd92 100644 --- a/tests/NIT/Makefile.am +++ b/tests/NIT/Makefile.am @@ -31,16 +31,20 @@ check-NIT-devel: $(abs_srcdir)/nit.sh +@cd "$(top_builddir)/drivers" && $(MAKE) $(AM_MAKEFLAGS) -s dummy-ups$(EXEEXT) upsdrvctl$(EXEEXT) +@$(MAKE) $(AM_MAKEFLAGS) check-NIT +# Allow to override with make/env vars; provide sensible defaults (see nit.sh): +#NIT_CASE = testcase_sandbox_start_drivers_after_upsd +NIT_CASE = testgroup_sandbox_upsmon_master +NUT_PORT = 12345 check-NIT-sandbox: $(abs_srcdir)/nit.sh [ -n "$${DEBUG_SLEEP-}" ] && [ "$${DEBUG_SLEEP-}" -gt 0 ] || DEBUG_SLEEP=600 ; export DEBUG_SLEEP ; \ LANG=C LC_ALL=C TZ=UTC \ - NUT_PORT=12345 NIT_CASE=testcase_sandbox_start_drivers_after_upsd NUT_FOREGROUND_WITH_PID=true \ + NUT_PORT=$(NUT_PORT) NIT_CASE="$(NIT_CASE)" NUT_FOREGROUND_WITH_PID=true \ "$(abs_srcdir)/nit.sh" check-NIT-sandbox-devel: $(abs_srcdir)/nit.sh +[ -n "$${DEBUG_SLEEP-}" ] && [ "$${DEBUG_SLEEP-}" -gt 0 ] || DEBUG_SLEEP=600 ; export DEBUG_SLEEP ; \ LANG=C LC_ALL=C TZ=UTC \ - NUT_PORT=12345 NIT_CASE=testcase_sandbox_start_drivers_after_upsd NUT_FOREGROUND_WITH_PID=true \ + NUT_PORT=$(NUT_PORT) NIT_CASE="$(NIT_CASE)" NUT_FOREGROUND_WITH_PID=true \ $(MAKE) $(AM_MAKEFLAGS) check-NIT-devel SPELLCHECK_SRC = README.adoc diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 0d04dd54e3..2da6495092 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -23,6 +23,8 @@ # TESTDIR=/tmp/nut-NIT to propose a location for "etc" and "run" # mock locations (under some circumstances, the # script can anyway choose to `mktemp` another) +# NUT_DEBUG_UPSMON_NOTIFY_SYSLOG=false To *disable* upsmon +# notifications spilling to the script's console. # # Common sandbox run for testing goes from NUT root build directory like: # DEBUG_SLEEP=600 NUT_PORT=12345 NIT_CASE=testcase_sandbox_start_drivers_after_upsd NUT_FOREGROUND_WITH_PID=true make check-NIT & @@ -571,12 +573,33 @@ updatecfg_upsmon_supplies() { generatecfg_upsmon_trivial() { # Populate the configs for the run rm -f "$NUT_STATEPATH/upsmon.sd.log" - ( echo 'MINSUPPLIES 0' > "$NUT_CONFPATH/upsmon.conf" || exit - echo 'SHUTDOWNCMD "echo \"`date`: TESTING_DUMMY_SHUTDOWN_NOW\" | tee \"'"$NUT_STATEPATH"'/upsmon.sd.log\" "' >> "$NUT_CONFPATH/upsmon.conf" || exit + ( echo 'MINSUPPLIES 0' > "$NUT_CONFPATH/upsmon.conf" || exit + echo 'SHUTDOWNCMD "echo \"`date`: TESTING_DUMMY_SHUTDOWN_NOW\" | tee \"'"$NUT_STATEPATH"'/upsmon.sd.log\" "' >> "$NUT_CONFPATH/upsmon.conf" || exit - if [ -n "${NUT_DEBUG_MIN-}" ] ; then - echo "DEBUG_MIN ${NUT_DEBUG_MIN}" >> "$NUT_CONFPATH/upsmon.conf" || exit - fi + NOTIFYTGT="" + if [ -x "${TOP_SRCDIR-}/scripts/misc/notifyme-debug" ] ; then + echo "NOTIFYCMD \"TEMPDIR='${NUT_STATEPATH}' ${TOP_SRCDIR-}/scripts/misc/notifyme-debug\"" >> "$NUT_CONFPATH/upsmon.conf" || exit + + # NOTE: "SYSLOG" typically ends up in console log of the NIT run and + # "EXEC" goes to a log file like tests/NIT/tmp/run/notifyme-399.log + NOTIFYTGT="EXEC" + fi + if [ x"$NUT_DEBUG_UPSMON_NOTIFY_SYSLOG" != xfalse ] ; then + [ -n "${NOTIFYTGT}" ] && NOTIFYTGT="${NOTIFYTGT}+SYSLOG" || NOTIFYTGT="SYSLOG" + fi + + if [ -n "${NOTIFYTGT}" ]; then + if [ -s "${TOP_SRCDIR-}/conf/upsmon.conf.sample.in" ] ; then + grep -E '# NOTIFYFLAG .*SYSLOG\+WALL$' \ + < "${TOP_SRCDIR-}/conf/upsmon.conf.sample.in" \ + | sed 's,^# \(NOTIFYFLAG[^A-Z_]*[A-Z_]*\)[^A-Z_]*SYSLOG.*$,\1\t'"${NOTIFYTGT}"',' \ + >> "$NUT_CONFPATH/upsmon.conf" || exit + fi + fi + + if [ -n "${NUT_DEBUG_MIN-}" ] ; then + echo "DEBUG_MIN ${NUT_DEBUG_MIN}" >> "$NUT_CONFPATH/upsmon.conf" || exit + fi ) || die "Failed to populate temporary FS structure for the NIT: upsmon.conf" if [ "`id -u`" = 0 ]; then @@ -1172,15 +1195,23 @@ testcase_sandbox_upsc_query_timer() { OUT2="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT2" OUT3="" OUT4="" + OUT5="" + + # The SUT may be busy, or dummy-ups can have a loop delay + # (pollfreq) after reading the file before wrapping around if [ x"$OUT1" = x"$OUT2" ]; then sleep 3 OUT3="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT3" if [ x"$OUT2" = x"$OUT3" ]; then sleep 3 OUT4="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT4" + if [ x"$OUT3" = x"$OUT4" ]; then + sleep 8 + OUT5="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT4" + fi fi fi - if echo "$OUT1$OUT2$OUT3$OUT4" | grep "OB" && echo "$OUT1$OUT2$OUT3$OUT4" | grep "OL" ; then + if echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OB" && echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OL" ; then log_info "[testcase_sandbox_upsc_query_timer] PASSED: ups.status flips over time" PASSED="`expr $PASSED + 1`" else @@ -1667,6 +1698,7 @@ if [ -n "${DEBUG_SLEEP-}" ] ; then log_info "You may want to press Ctrl+Z now and command 'bg' to the shell, if you did not start '$0 &' backgrounded already" log_info "To kill the script early, return it to foreground with 'fg' and press Ctrl+C, or 'kill -2 \$PID_NIT_SCRIPT' (kill -2 $$)" + log_info "To trace built programs, check scripts/valgrind/README.adoc and LD_LIBRARY_PATH for this build" log_info "Remember that in shell/scripting you can probe for '${NUT_CONFPATH}/NIT.env-sandbox-ready' which only appears at this moment" touch "${NUT_CONFPATH}/NIT.env-sandbox-ready" diff --git a/tools/nutconf/nutconf-cli.cpp b/tools/nutconf/nutconf-cli.cpp index a12b071eb5..f72aa7a7df 100644 --- a/tools/nutconf/nutconf-cli.cpp +++ b/tools/nutconf/nutconf-cli.cpp @@ -145,7 +145,7 @@ const char * Usage::s_text[] = { "Notification types:", " ONLINE, ONBATT, LOWBATT, FSD, COMMOK, COMMBAD, SHUTDOWN, REPLBATT, NOCOMM, NOPARENT,", " CAL, NOTCAL, OFF, NOTOFF, BYPASS, NOTBYPASS, ECO, NOTECO, ALARM, NOTALARM,", - " SUSPEND_STARTING, SUSPEND_FINISHED", + " OTHER, NOTOTHER, SUSPEND_STARTING, SUSPEND_FINISHED", "Notification flags:", " SYSLOG, WALL, EXEC, IGNORE", "User specification:",