diff --git a/NEWS.adoc b/NEWS.adoc index 2808324767..48487ee2c1 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -129,6 +129,10 @@ https://github.com/networkupstools/nut/milestone/11 as known supported by `nutdrv_qx` (Megatec protocol) since at least NUT v2.7.4 release [#2395] + - bicker_ser: added new driver for Bicker 12/24Vdc UPS via RS-232 serial + communication protocol, which supports any UPS shipped with the PSZ-1053 + extension module. [PR #2448] + - 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] @@ -150,10 +154,6 @@ https://github.com/networkupstools/nut/milestone/11 * `cps-hid` subdriver now supports more variables, as available on e.g. CP1350EPFCLCD model. [PR #2540] - - bicker_ser: added new driver for Bicker 12/24Vdc UPS via RS-232 serial - communication protocol, which supports any UPS shipped with the PSZ-1053 - extension module. [PR #2448] - - USB drivers could log `(nut_)libusb_get_string: Success` due to either reading an empty string or getting a success code `0` from libusb. This difference should now be better logged, and not into syslog. [#2399] diff --git a/docs/Makefile.am b/docs/Makefile.am index 9c3d6dfd5e..d8b18a382b 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -653,6 +653,10 @@ ASPELL_OUT_NOTERRORS = (^[ \t]*[\*\@]|^$$) SPELLCHECK_RECIPE_DEBUG_STREAM = /dev/null #SPELLCHECK_RECIPE_DEBUG_STREAM = &2 +# Note: if we do an interactive spell-check, it updates "nut.dict" +# timestamp even if contents remain. If the caller left a copy of +# the file as "$(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting", +# and the dictionary was not in fact modified, restore the timestamp. $(SPELLCHECK_BUILDDIR)/$(SPELLCHECK_SRC_ONE)-spellchecked: $(SPELLCHECK_SRCDIR)/$(SPELLCHECK_SRC_ONE) $(abs_top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) @LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ if test x"$(SPELLCHECK_SRC_ONE)" = x ; then echo "SKIP: Bogus spellcheck call for empty target filename (with make target $@ from `pwd`)" >&2 ; exit 0; fi; \ @@ -686,7 +690,15 @@ $(SPELLCHECK_BUILDDIR)/$(SPELLCHECK_SRC_ONE)-spellchecked: $(SPELLCHECK_SRCDIR)/ */) ;; \ *) REPORT_SRCDIR="$${REPORT_SRCDIR}/" ;; \ esac ; \ - echo " ASPELL Spell checking on $${REPORT_PREFIX}$${REPORT_SRCDIR}$${REPORT_SRC_ONE}"; \ + if [ x"$(SPELLCHECK_INTERACTIVE)" = xtrue ] ; then \ + echo " ASPELL Spell checking (interactively) on $${REPORT_PREFIX}$${REPORT_SRCDIR}$${REPORT_SRC_ONE}"; \ + LANG=$(ASPELL_ENV_LANG) LC_ALL=$(ASPELL_ENV_LANG) $(ASPELL) check $(ASPELL_NUT_COMMON_ARGS) '$(SPELLCHECK_SRCDIR)/$(SPELLCHECK_SRC_ONE)' || exit ; \ + if [ -s $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting ] && [ -s $(abs_srcdir)/$(NUT_SPELL_DICT) ] && diff $(abs_srcdir)/$(NUT_SPELL_DICT) $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting >/dev/null ; then \ + touch -r $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting $(abs_srcdir)/$(NUT_SPELL_DICT) ; \ + fi ; \ + else \ + echo " ASPELL Spell checking on $${REPORT_PREFIX}$${REPORT_SRCDIR}$${REPORT_SRC_ONE}"; \ + fi ; \ OUT="`(sed 's,^\(.*\)$$, \1,' | $(ASPELL) -a $(ASPELL_NUT_TEXMODE_ARGS) $(ASPELL_NUT_COMMON_ARGS) 2>&1) < '$(SPELLCHECK_SRCDIR)/$(SPELLCHECK_SRC_ONE)'`" \ && { if test -n "$$OUT" ; then OUT="`echo "$$OUT" | $(EGREP) -b -v '$(ASPELL_OUT_NOTERRORS)' `" ; fi; \ test -z "$$OUT" ; } \ @@ -757,17 +769,33 @@ $(abs_builddir)/$(NUT_SPELL_DICT).sorted: $(abs_srcdir)/$(NUT_SPELL_DICT) DISTCLEANFILES = $(NUT_SPELL_DICT).bak-pre-sorting .$(NUT_SPELL_DICT).sorted $(NUT_SPELL_DICT).sorted +# NOTE: In "make SPELLCHECK_INTERACTIVE=true ${docsrc}-spellchecked", +# after an interactive "aspell check" we follow-up by a run of usual +# non-interactive spell-checker to verify that the developer actually +# has fixed all of the files that the tool had concerns about, and +# that the touch-file is updated if the file is okay (to speed up +# any future re-runs). We also must update all relevant *-spellchecked +# touch-files after "make spellcheck-sortdict" which updates "nut.dict" +# file which is a prerequisite for docs checks. spellcheck-interactive: - @FAILED="" ; for docsrc in $(SPELLCHECK_SRC); do \ - echo "Spell checking on $(SPELLCHECK_SRCDIR)/$$docsrc"; \ - LANG=$(ASPELL_ENV_LANG) LC_ALL=$(ASPELL_ENV_LANG) $(ASPELL) check $(ASPELL_NUT_COMMON_ARGS) $(SPELLCHECK_SRCDIR)/$$docsrc || \ - FAILED="$$FAILED $(SPELLCHECK_SRCDIR)/$$docsrc"; \ - done ; \ - if test -n "$$FAILED" ; then \ + @cp -pf $(abs_srcdir)/$(NUT_SPELL_DICT) $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting + +@FAILED="" ; for docsrc in $(SPELLCHECK_SRC); do \ + if test "$(SPELLCHECK_ENV_DEBUG)" != no ; then \ + echo "ASPELL (INTERACTIVE) MAKEFILE DEBUG: Will see from `pwd` if '$(SPELLCHECK_SRCDIR)/$${docsrc}-spellchecked' is up to date" >&2; \ + else true ; fi ; \ + $(MAKE) $(AM_MAKEFLAGS) -s -f "$(abs_top_builddir)/docs/Makefile" SPELLCHECK_INTERACTIVE="true" SPELLCHECK_SRC="" SPELLCHECK_SRC_ONE="$${docsrc}" SPELLCHECK_BUILDDIR="$(SPELLCHECK_BUILDDIR)" SPELLCHECK_SRCDIR="$(SPELLCHECK_SRCDIR)" "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" \ + || FAILED="$$FAILED $(SPELLCHECK_SRCDIR)/$$docsrc"; \ + done ; \ + if test -n "$$FAILED" ; then \ echo "FAILED interactive spellcheck for the following sources (relative to `pwd`) using custom dictionary file '$(NUT_SPELL_DICT)': $$FAILED" >&2 ; \ exit 1; \ - fi ; exit 0 - +$(MAKE) $(AM_MAKEFLAGS) spellcheck-sortdict + fi ; \ + $(MAKE) $(AM_MAKEFLAGS) spellcheck-sortdict || exit ; \ + for docsrc in $(SPELLCHECK_SRC); do \ + if test -e "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" ; then \ + touch "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" ; \ + fi ; \ + done @echo "------------------------------------------------------------------------"; \ echo "Custom dictionary file $(NUT_SPELL_DICT) may have been updated now."; \ echo "Use e.g. 'git add -p docs/$(NUT_SPELL_DICT) && git checkout -- docs/$(NUT_SPELL_DICT) && make spellcheck-sortdict && git add -p docs/$(NUT_SPELL_DICT)'"; \ diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 3b62b95261..a7b5d3781a 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -487,7 +487,8 @@ SRC_SERIAL_PAGES = \ bicker_ser.txt \ blazer-common.txt \ blazer_ser.txt \ - clone.txt \ + clone.txt \ + clone-outlet.txt \ dummy-ups.txt \ etapro.txt \ everups.txt \ @@ -534,7 +535,8 @@ MAN_SERIAL_PAGES = \ bestfcom.8 \ bicker_ser.8 \ blazer_ser.8 \ - clone.8 \ + clone.8 \ + clone-outlet.8 \ dummy-ups.8 \ etapro.8 \ everups.8 \ @@ -584,7 +586,8 @@ HTML_SERIAL_MANS = \ bestfcom.html \ bicker_ser.html \ blazer_ser.html \ - clone.html \ + clone.html \ + clone-outlet.html \ dummy-ups.html \ etapro.html \ everups.html \ diff --git a/docs/man/clone-outlet.txt b/docs/man/clone-outlet.txt new file mode 100644 index 0000000000..b26d778f34 --- /dev/null +++ b/docs/man/clone-outlet.txt @@ -0,0 +1,197 @@ +CLONE-OUTLET(8) +=============== + +NAME +---- + +clone-outlet - clone an UPS, treating its outlet as if it were an UPS (monitoring only) + +SYNOPSIS +-------- + +*clone-outlet* -h + +*clone-outlet* -a 'UPS_NAME' ['OPTIONS'] + +NOTE: This man page only documents the specific features of the +clone-outlet driver. For information about the core driver, see +linkman:nutupsdrv[8]. + +DESCRIPTION +----------- + +This driver, which sits on top of another driver socket, allows users to group +clients to a particular outlet of a device and deal with this output as if it +were a normal UPS. Unlike the linkman:clone[8] driver or linkman:dummy-ups[8] +in repeater mode, this driver represents a read-only device for monitoring and +shutdowns (it does not accept setting any values or sending instant commands +during run time). + +Unlike linkman:dummy-ups[8], this driver does not require a running `upsd` +data server nor use the networked NUT protocol to talk to the "real" driver +(which may be remote in case of `dummy-ups` repeater mode). + +This driver does not create a completely new virtual device, but replaces or +extends some of the original readings reported by the "real" driver using +information from the specified outlet, and relays all other readings as they +were. + +Remote clients like `upsmon` can `MONITOR` the device entry presented by the +data server with this driver (and the "real" driver) running and published. + +A larger deployment with one or more lower-priority devices collected on a +manageable outlet of an UPS or ePDU would likely see several drivers set +up on the system actually capable of interactions with the UPS and running +the NUT data server linkman:upsd[8] (and likely powered by another outlet): + +* a "real" driver talking to the UPS; +* a `clone` driver talking to the "real" driver and issuing outlet power-off + (or power-cycle) based on relatively high thresholds for remaining battery + charge and/or runtime of the actual UPS (or explicit instant commands), + with such operations first setting the respective timers for the outlet + on the "real" driver, and the "FSD" flag among states of the virtual UPS + status; +* possibly a `clone-outlet` driver which is read-only and interprets the + outlet timer values as triggers for "FSD" or "OFF" flags reported among + states of the virtual UPS status. + +With this approach, the lower-priority systems collected on such outlet +would run the NUT linkman:upsmon[8] client to `MONITOR` the virtual UPS +presented by the read-only `clone-outlet` driver and shut down as soon as +the "FSD" flag is raised (fairly early, based on charge and/or runtime +thresholds configured for that driver) allowing the higher-priority devices +(likely including the NUT server) to enjoy a longer on-battery life. + +The `clone` driver responsible for outlet power state changes would not +normally be monitored directly (e.g. to avoid unfortunate direct shutdown +requests from those clients), although it can be (instead of `clone-outlet`) +in sufficiently trusted networks. + +EXTRA ARGUMENTS +--------------- + +This driver supports the following settings: + +*port*='drivername-devicename':: +Required. The standard NUT driver `port` setting, here it provides the name +of the local Unix socket (or named Windows pipe) for connection to the "real" +driver. + +*prefix*='outlet.N':: +Required. Specify the outlet prefix as known on the original driver. +The subset of data points reported by the "real" UPS driver for the actual device +on this prefix would be reported as data points of the virtual UPS maintained by +this driver. + +IMPLEMENTATION +-------------- + +The port specification in the linkman:ups.conf[5] should reference the +local driver socket (or Windows named pipe) that the "real" UPS driver +is using. For example: + +------ + [realups] + driver = usbhid-ups + port = auto + + [clone-outlet-1] + driver = clone-outlet + port = usbhid-ups-realups + prefix = outlet.1 + desc = "Outlet 1 of the Real UPS" + [...] +------ + +The driver internally interprets "real" driver's information about shutdown +delay and shutdown timer, whole UPS status and this outlet's status, and +relays other data points as they were. + +If the outlet supports and reports a delayed power-off, the virtual UPS would +issue an FSD via `ups.status` for its clients to shut down safely. + +In more detail: + +Given the (required) `prefix` value such as `outlet.1`, the driver would +specifically keep track of `.status`, `.delay.shutdown`, +and `.timer.shutdown` data points reported by the "real" driver. +Numeric values of the `*.shutdown` readings would be noted by this driver, +and the boolean outlet status ("off" or otherwise) will be remembered. +These values will also be re-published by this driver "as is". + +The `ups.status` from the "real" driver would be remembered, but not +re-published by this driver immediately. Instead, it would be published +during regular "update info" loop cycles either: + +* with the "OFF" state added (if `.status` indicates the outlet + is "off"), +* or with the "FSD" state prepended (if `.timer.shutdown` is + non-negative and does not exceed the `.delay.shutdown` value), +* or "as is" if the outlet power state is "not critical". + + +IMPORTANT +--------- + +Unlike a real UPS, you should *not* configure a upsmon primary mode for this +driver. When a upsmon primary sees the OB LB flags and tells the upsd server +it is OK to initiate the shutdown sequence, the server will latch the FSD +status and it will not be possible to restart the systems connected without +restarting the upsd server. + +This will be a problem if the power returns after the clone UPS initiated +the shutdown sequence on it's outlet, but returns before the real UPS begins +shutting down. The solution is in the clone driver, that will insert the +FSD flag if needed without the help of a upsmon primary. + +CAVEATS +------- + +The clone UPS will follow the status on the real UPS driver. You can only +make the clone UPS shutdown earlier than the real UPS driver, not later. +If the real UPS driver initiates a shutdown, the clone-outlet UPS driver +will immediately follow. + +Be aware that the commands to shutdown/restart an outlet on the real UPS +drivers are not affected, so if you tell the real UPS driver to shutdown +the outlet of the clone UPS driver immediately, your clients will lose +power without warning. A delayed outlet power-off should propagate as FSD, +and the delay should be sufficiently long to allow for client shutdowns. + +If you use service management frameworks like systemd or SMF to manage the +dependencies between driver instances and other units, then you may have +to set up special dependencies (e.g. with systemd "drop-in" snippet files) +to queue your `clone` drivers to start after the "real" device drivers. + +////////////////////////////////////// +TODO later: declare the driver as "optional", see +https://github.com/networkupstools/nut/issues/1389 +////////////////////////////////////// + +AUTHOR +------ + +Arjen de Korte + +SEE ALSO +-------- + +linkman:upscmd[1], +linkman:upsrw[1], +linkman:ups.conf[5], +linkman:clone[8], +linkman:nutupsdrv[8] + +Dummy driver: +~~~~~~~~~~~~~ + +The "repeater" mode of 'dummy-ups' driver is in some ways similar to the +'clone' and 'clone-outlet' drivers, by relaying information from a locally +or remotely running "real" device driver (and NUT data server). + +linkman:dummy-ups[8] + +Internet Resources: +~~~~~~~~~~~~~~~~~~~ + +The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ diff --git a/docs/man/clone.txt b/docs/man/clone.txt index d5ee2e4a8c..bd27128057 100644 --- a/docs/man/clone.txt +++ b/docs/man/clone.txt @@ -4,7 +4,7 @@ CLONE(8) NAME ---- -clone - UPS driver clone +clone - clone an UPS, treating its outlet as if it were an UPS (with shutdown INSTCMD support) SYNOPSIS -------- @@ -22,13 +22,61 @@ DESCRIPTION This driver, which sits on top of another driver socket, allows users to group clients to a particular outlet of a device and deal with this output as if it -was a normal UPS. +were a normal UPS. Unlike the linkman:clone-outlet[8] driver, this driver +represents a manageable device for that can be used both for monitoring and +for client computer and UPS/ePDU outlet shutdowns (it supports sending +relevant instant commands during run time). + +Unlike linkman:dummy-ups[8], this driver does not require a running `upsd` +data server nor use the networked NUT protocol to talk to the "real" driver +(which may be remote in case of `dummy-ups` repeater mode). + +This driver does not create a completely new virtual device, but replaces or +extends some of the original readings reported by the "real" driver using +information from the specified outlet, and relays all other readings as they +were. + +Remote clients like `upsmon` can `MONITOR` the device entry presented by the +data server with this driver (and the "real" driver) running and published. + +A larger deployment with one or more lower-priority devices collected on a +manageable outlet of an UPS or ePDU would likely see several drivers set +up on the system actually capable of interactions with the UPS and running +the NUT data server linkman:upsd[8] (and likely powered by another outlet): + +* a "real" driver talking to the UPS; +* a `clone` driver talking to the "real" driver and issuing outlet power-off + (or power-cycle) based on relatively high thresholds for remaining battery + charge and/or runtime of the actual UPS (or explicit instant commands), + with such operations first setting the respective timers for the outlet + on the "real" driver, and the "FSD" flag among states of the virtual UPS + status; +* possibly a `clone-outlet` driver which is read-only and interprets the + outlet timer values as triggers for "FSD" or "OFF" flags reported among + states of the virtual UPS status. + +With this approach, the lower-priority systems collected on such outlet +would run the NUT linkman:upsmon[8] client to `MONITOR` the virtual UPS +presented by the read-only `clone-outlet` driver and shut down as soon as +the "FSD" flag is raised (fairly early, based on charge and/or runtime +thresholds configured for that driver) allowing the higher-priority devices +(likely including the NUT server) to enjoy a longer on-battery life. + +The `clone` driver responsible for outlet power state changes would not +normally be monitored directly (e.g. to avoid unfortunate direct shutdown +requests from those clients), although it can be (instead of `clone-outlet`) +in sufficiently trusted networks. EXTRA ARGUMENTS --------------- This driver supports the following settings: +*port*='drivername-devicename':: +Required. The standard NUT driver `port` setting, here it provides the name +of the local Unix socket (or named Windows pipe) for connection to the "real" +driver. + *load.off*='command':: Recommended. Set the command on the "real" UPS driver that will be used to switch off the outlet. You need both *load.off* and *load.on* in @@ -67,8 +115,9 @@ Set the remaining battery runtime when the clone UPS switches to LB IMPLEMENTATION -------------- -The port specification in the linkman:ups.conf[5] reference the driver -socket that the "real" UPS driver is using. For example: +The port specification in the linkman:ups.conf[5] should reference the +local driver socket (or Windows named pipe) that the "real" UPS driver +is using. For example: ------ [realups] @@ -81,9 +130,44 @@ socket that the "real" UPS driver is using. For example: load.on = outlet.1.load.on load.off = outlet.1.load.off load.status = outlet.1.status + desc = "Outlet 1 of the Real UPS" [...] ------ +This driver supports instant commands to initiate a forced shutdown for +`upsmon` or similar clients which `MONITOR` this virtual UPS device, if +the outlet status is currently 'on' and no other shutdown was initiated +yet (setting the virtual UPS shutdown delay timer to `offdelay` and +issuing an FSD via `ups.status`): + +* `shutdown.return` -- power the outlet back on after `ondelay`; +* `shutdown.stayoff` -- keep the outlet 'off'. + +Such commands are propagated to the "real" driver using the NUT socket +protocol (using command names specified in the `load.off` and `load.on` +driver configuration options), if the shutdown or start timers are set +at the moment, or if the "real" device is not "online" and its known +battery charge or runtime are below the configured "low" thresholds. + +The outlet status is determined using the name specified by the +`load.status` driver option if set, or is just assumed by latest +completed shutdown/start operation (using unknown outlet number). + +The driver does not support a common NUT device shutdown operation as +such (`clone -k` just prints an error and bails out). + +This driver also supports setting certain NUT variables at run-time: + +* `battery.charge.low` -- see `mincharge` in driver options; +* `battery.runtime.low` -- see `minruntime` in driver options. + +Compared to the "real" driver's readings, this driver also adds +(or overrides) the following data points: `ups.delay.shutdown`, +`ups.delay.start`, `ups.timer.shutdown` and `ups.timer.start`. +It keeps track of "real" driver's values of `battery.charge` and +`battery.runtime` (actual current readings) to decide on automated +outlet shutdown later on. + IMPORTANT --------- @@ -132,14 +216,15 @@ SEE ALSO linkman:upscmd[1], linkman:upsrw[1], linkman:ups.conf[5], +linkman:clone-outlet[8], linkman:nutupsdrv[8] Dummy driver: ~~~~~~~~~~~~~ The "repeater" mode of 'dummy-ups' driver is in some ways similar to the -'clone' driver, by relaying information from a locally or remotely running -"real" device driver (and NUT data server). +'clone' and 'clone-outlet' drivers, by relaying information from a locally +or remotely running "real" device driver (and NUT data server). linkman:dummy-ups[8] diff --git a/docs/man/dummy-ups.txt b/docs/man/dummy-ups.txt index b45de7d85d..d51f42e56e 100644 --- a/docs/man/dummy-ups.txt +++ b/docs/man/dummy-ups.txt @@ -282,11 +282,16 @@ Clone driver: ~~~~~~~~~~~~~ The "repeater" mode of 'dummy-ups' driver is in some ways similar to the -'clone' driver, which sits on top of another driver socket, and allows -users to group clients to a particular outlet of a device and deal with -this output as if it were a normal UPS. - -linkman:clone[8] +'clone' and 'clone-outlet' drivers, which sit on top of another driver +socket (or named Windows pipe) locally, and allow users to group clients +to a particular outlet of a device and deal with this output as if it +were a normal UPS. Notably, in this mode the 'dummy-ups' driver is a +client to the networked NUT protocol and can relay information of local +or remotely served devices, and requires a running NUT data server 'upsd' +to represent the "real" device for this to work. + +linkman:clone[8], +linkman:clone-outlet[8] Internet Resources: ~~~~~~~~~~~~~~~~~~~ diff --git a/drivers/clone-outlet.c b/drivers/clone-outlet.c index 5be8c8c4e9..791a9648c9 100644 --- a/drivers/clone-outlet.c +++ b/drivers/clone-outlet.c @@ -1,7 +1,9 @@ /* -* clone-outlet.c: clone outlet UPS driver +* clone-outlet.c: clone an UPS, treating its outlet as if it were an UPS +* (monitoring only) * * Copyright (C) 2009 - Arjen de Korte +* Copyright (C) 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 @@ -28,8 +30,8 @@ #include #endif -#define DRIVER_NAME "clone outlet UPS Driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_NAME "Clone outlet UPS driver" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -67,20 +69,20 @@ static struct { static int dumpdone = 0; static PCONF_CTX_t sock_ctx; -static time_t last_heard = 0, last_ping = 0; +static time_t last_poll = 0, last_heard = 0, last_ping = 0; -#ifdef WIN32 -static char read_buf[SMALLBUF]; -static OVERLAPPED read_overlapped; -#else +#ifndef WIN32 /* TODO: Why not built in WIN32? */ static time_t last_connfail = 0; +#else +static char read_buf[SMALLBUF]; +static OVERLAPPED read_overlapped; #endif static int parse_args(size_t numargs, char **arg) { if (numargs < 1) { - return 0; + goto skip_out; } if (!strcasecmp(arg[0], "PONG")) { @@ -105,7 +107,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 2) { - return 0; + goto skip_out; } /* DELINFO */ @@ -115,7 +117,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 3) { - return 0; + goto skip_out; } /* SETINFO */ @@ -147,6 +149,24 @@ static int parse_args(size_t numargs, char **arg) return 1; } +skip_out: + if (nut_debug_level > 0) { + char buf[LARGEBUF]; + size_t i; + int len = -1; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < numargs; i++) { + len = snprintfcat(buf, sizeof(buf), "[%s] ", arg[i]); + } + if (len > 0) { + buf[len - 1] = '\0'; + } + + upsdebugx(3, "%s: ignored protocol line with %" PRIuSIZE " keyword(s): %s", + __func__, numargs, numargs < 1 ? "" : buf); + } + return 0; } @@ -154,11 +174,11 @@ static int parse_args(size_t numargs, char **arg) static TYPE_FD sstate_connect(void) { TYPE_FD fd; + const char *dumpcmd = "DUMPALL\n"; #ifndef WIN32 ssize_t ret; int len; - const char *dumpcmd = "DUMPALL\n"; struct sockaddr_un sa; memset(&sa, '\0', sizeof(sa)); @@ -234,7 +254,6 @@ static TYPE_FD sstate_connect(void) /* continued below... */ #else /* WIN32 */ char pipename[SMALLBUF]; - const char *dumpcmd = "DUMPALL\n"; BOOL result = FALSE; snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\%s/%s", dflt_statepath(), device_path); @@ -324,12 +343,12 @@ static int sstate_sendline(const char *buf) DWORD bytesWritten = 0; BOOL result = FALSE; - result = WriteFile (upsfd,buf,strlen(buf),&bytesWritten,NULL); + result = WriteFile (upsfd, buf, strlen(buf), &bytesWritten, NULL); - if( result == 0 ) { + if (result == 0) { ret = 0; } - else { + else { ret = (int)bytesWritten; } #endif @@ -449,6 +468,18 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { + time_t now = time(NULL); + double d; + + /* Throttle tight loops to avoid CPU burn, e.g. when the socket to driver + * is not in fact connected, so a select() somewhere is not waiting much */ + if (last_poll > 0 && (d = difftime(now, last_poll)) < 1.0) { + upsdebugx(5, "%s: too little time (%g sec) has passed since last cycle, throttling", + __func__, d); + usleep(500000); + now = time(NULL); + } + if (sstate_dead(15)) { sstate_disconnect(); extrafd = upsfd = sstate_connect(); @@ -474,6 +505,8 @@ void upsdrv_updateinfo(void) upsdebugx(3, "%s: power state not critical", getval("prefix")); dstate_setinfo("ups.status", "%s", ups.status); + + last_poll = now; } diff --git a/drivers/clone.c b/drivers/clone.c index d2fd97b1a4..b15819eb78 100644 --- a/drivers/clone.c +++ b/drivers/clone.c @@ -1,7 +1,9 @@ /* -* clone.c: UPS driver clone +* clone.c: clone an UPS, treating its outlet as if it were an UPS +* (with shutdown INSTCMD support) * * Copyright (C) 2009 - Arjen de Korte +* Copyright (C) 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 @@ -31,7 +33,7 @@ #endif #define DRIVER_NAME "Clone UPS driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -65,13 +67,14 @@ static int dumpdone = 0, online = 1, outlet = 1; static long offdelay = 120, ondelay = 30; static PCONF_CTX_t sock_ctx; -static time_t last_poll = 0, last_heard = 0, - last_ping = 0; +static time_t last_poll = 0, last_heard = 0, last_ping = 0; + #ifndef WIN32 -static time_t last_connfail = 0; +/* TODO: Why not built in WIN32? */ +static time_t last_connfail = 0; #else -static char read_buf[SMALLBUF]; -static OVERLAPPED read_overlapped; +static char read_buf[SMALLBUF]; +static OVERLAPPED read_overlapped; #endif static int instcmd(const char *cmdname, const char *extra); @@ -80,7 +83,7 @@ static int instcmd(const char *cmdname, const char *extra); static int parse_args(size_t numargs, char **arg) { if (numargs < 1) { - return 0; + goto skip_out; } if (!strcasecmp(arg[0], "PONG")) { @@ -105,7 +108,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 2) { - return 0; + goto skip_out; } /* DELINFO */ @@ -115,7 +118,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 3) { - return 0; + goto skip_out; } /* SETINFO */ @@ -161,6 +164,24 @@ static int parse_args(size_t numargs, char **arg) return 1; } +skip_out: + if (nut_debug_level > 0) { + char buf[LARGEBUF]; + size_t i; + int len = -1; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < numargs; i++) { + len = snprintfcat(buf, sizeof(buf), "[%s] ", arg[i]); + } + if (len > 0) { + buf[len - 1] = '\0'; + } + + upsdebugx(3, "%s: ignored protocol line with %" PRIuSIZE " keyword(s): %s", + __func__, numargs, numargs < 1 ? "" : buf); + } + return 0; } @@ -168,11 +189,11 @@ static int parse_args(size_t numargs, char **arg) static TYPE_FD sstate_connect(void) { TYPE_FD fd; + const char *dumpcmd = "DUMPALL\n"; #ifndef WIN32 ssize_t ret; int len; - const char *dumpcmd = "DUMPALL\n"; struct sockaddr_un sa; memset(&sa, '\0', sizeof(sa)); @@ -248,7 +269,6 @@ static TYPE_FD sstate_connect(void) /* continued below... */ #else /* WIN32 */ char pipename[SMALLBUF]; - const char *dumpcmd = "DUMPALL\n"; BOOL result = FALSE; snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\%s/%s", dflt_statepath(), device_path); @@ -338,7 +358,7 @@ static int sstate_sendline(const char *buf) DWORD bytesWritten = 0; BOOL result = FALSE; - result = WriteFile (upsfd,buf,strlen(buf),&bytesWritten,NULL); + result = WriteFile (upsfd, buf, strlen(buf), &bytesWritten, NULL); if (result == 0) { ret = 0; @@ -373,13 +393,13 @@ static int sstate_readline(void) if (ret < 0) { switch(errno) { - case EINTR: - case EAGAIN: - return 0; + case EINTR: + case EAGAIN: + return 0; - default: - upslog_with_errno(LOG_WARNING, "Read from UPS [%s] failed", device_path); - return -1; + default: + upslog_with_errno(LOG_WARNING, "Read from UPS [%s] failed", device_path); + return -1; } } #else @@ -398,19 +418,19 @@ static int sstate_readline(void) switch (pconf_char(&sock_ctx, buf[i])) { - case 1: - if (parse_args(sock_ctx.numargs, sock_ctx.arglist)) { - time(&last_heard); - } - continue; - - case 0: - continue; /* haven't gotten a line yet */ - - default: - /* parse error */ - upslogx(LOG_NOTICE, "Parse error on sock: %s", sock_ctx.errmsg); - return -1; + case 1: + if (parse_args(sock_ctx.numargs, sock_ctx.arglist)) { + time(&last_heard); + } + continue; + + case 0: + continue; /* haven't gotten a line yet */ + + default: + /* parse error */ + upslogx(LOG_NOTICE, "Parse error on sock: %s", sock_ctx.errmsg); + return -1; } } @@ -556,6 +576,16 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { time_t now = time(NULL); + double d; + + /* Throttle tight loops to avoid CPU burn, e.g. when the socket to driver + * is not in fact connected, so a select() somewhere is not waiting much */ + if (last_poll > 0 && (d = difftime(now, last_poll)) < 1.0) { + upsdebugx(5, "%s: too little time (%g sec) has passed since last cycle, throttling", + __func__, d); + usleep(500000); + now = time(NULL); + } if (sstate_dead(15)) { sstate_disconnect();