2
newapc.c - model specific routines for APC smart protocol units
4
$Id: newapc.c,v 1.2 2002/03/04 13:31:32 cvs Exp $
6
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
7
(C) 2000 Nigel Metheringham <Nigel.Metheringham@Intechnology.co.uk>
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
GNU General Public License for more details.
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
#define APC_DRIVER_VERSION "1.99.1a"
29
#define ALT_CABLE_1 "940-0095B"
31
static struct apc_vartab_t *vartab_lookup_char(char cmdchar)
35
for (i = 0; apc_vartab[i].name != NULL; i++)
36
if (apc_vartab[i].cmd == cmdchar)
37
return &apc_vartab[i];
42
static struct apc_vartab_t *vartab_lookup_name(const char *var)
46
for (i = 0; apc_vartab[i].name != NULL; i++)
47
if (!strcasecmp(apc_vartab[i].name, var))
48
return &apc_vartab[i];
53
/* FUTURE: change to use function pointers */
55
/* convert APC formatting to NUT formatting */
56
static char *convert_data(struct apc_vartab_t *cmd_entry, char *upsval)
61
switch(cmd_entry->flags & APC_FORMATMASK) {
71
/* no conversion for any of these */
75
/* convert to seconds */
77
tval = 60 * 60 * strtol(upsval, NULL, 10);
79
snprintf(tmp, sizeof(tmp), "%d", tval);
83
/* Convert to seconds - NUT standard time measurement */
84
tval = 60 * strtol(upsval, NULL, 10);
85
/* Ignore errors - Theres not much we can do */
86
snprintf(tmp, sizeof(tmp), "%d", tval);
90
upslogx(LOG_NOTICE, "Unable to handle conversion of %s",
99
static void poll_data(struct apc_vartab_t *vt)
104
if ((vt->flags & APC_PRESENT) == 0)
107
upsdebugx(4, "poll_data: %s", vt->name);
109
upssendchar(vt->cmd);
111
ret = upsrecv(tmp, sizeof(tmp), ENDCHAR, IGNCHARS);
113
if (ret < 1) { /* comm failure */
118
/* no longer supported by the hardware somehow */
119
if (!strcmp(tmp, "NA")) {
120
dstate_delinfo(vt->name);
124
dstate_setinfo(vt->name, "%s", convert_data(vt, tmp));
128
/* check for support or just update a named variable */
129
static int query_ups(const char *var, int first)
132
char temp[256], *ptr;
133
struct apc_vartab_t *vt;
135
vt = vartab_lookup_name(var);
138
upsdebugx(1, "query_ups: unknown variable %s", var);
142
/* already known to not be supported? */
143
if (vt->flags & APC_IGNORE)
146
upsflushin(0, nut_debug_level, IGNCHARS);
148
upssendchar(vt->cmd);
151
flag_timeoutfailure = -1;
153
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
155
if ((ret < 1) || (!strcmp(temp, "NA"))) { /* not supported */
156
vt->flags |= APC_IGNORE;
160
ptr = convert_data(vt, temp);
161
dstate_setinfo(vt->name, "%s", ptr);
163
return 1; /* success */
166
static void do_capabilities(void)
168
const char *ptr, *entptr;
169
char upsloc, temp[512], cmd, loc, etmp[16], *endtemp;
170
int nument, entlen, i, matrix, ret;
171
struct apc_vartab_t *vt;
173
upsdebugx(1, "APC - About to get capabilities string");
174
/* If we can do caps, then we need the Firmware revision which has
175
the locale descriptor as the last character (ugh)
177
ptr = dstate_getinfo("ups.firmware");
179
upsloc = ptr[strlen(ptr) - 1];
183
/* get capability string */
184
upssendchar(APC_CAPABILITY); /* ^Z */
186
ret = upsrecv(temp, sizeof(temp), ENDCHAR, MINIGNCHARS);
188
if ((ret < 1) || (!strcmp(temp, "NA"))) {
190
/* Early Smart-UPS, not as smart as later ones */
191
/* This should never happen since we only call
192
this if the REQ_CAPABILITIES command is supported
194
upslogx(LOG_ERR, "ERROR: APC cannot do capabilites but said it could!");
198
/* recv always puts a \0 at the end, so this is safe */
199
/* however it assumes a zero byte cannot be embedded */
200
endtemp = &temp[0] + strlen(temp);
202
if (temp[0] != '#') {
203
printf("Unrecognized capability start char %c\n", temp[0]);
204
printf("Please report this error [%s]\n", temp);
205
upslogx(LOG_ERR, "ERROR: unknown capability start char %c!",
211
if (temp[1] == '#') { /* Matrix-UPS */
220
/* command char, location, # of entries, entry length */
222
while (ptr[0] != '\0') {
224
ptr += 2; /* jump over repeating ## */
226
/* check for idiocy */
227
if (ptr >= endtemp) {
228
printf("Capability string has overflowed\n");
229
printf("Please report this error\n");
230
fatalx("ERROR: capability overflow!");
235
nument = ptr[2] - 48;
236
entlen = ptr[3] - 48;
239
vt = vartab_lookup_char(cmd);
241
/* mark this as writable */
242
if (vt && ((loc == upsloc) || (loc == '4'))) {
243
upsdebugx(1, "Supported capability: %02x (%c) - %s",
246
dstate_setflags(vt->name, ST_FLAG_RW);
248
/* make sure setvar knows what this is */
249
vt->flags |= APC_RW | APC_ENUM;
252
for (i = 0; i < nument; i++) {
253
snprintf(etmp, entlen + 1, "%s", entptr);
255
if (vt && ((loc == upsloc) || (loc == '4')))
256
dstate_addenum(vt->name, convert_data(vt, etmp));
265
static void update_status(void)
270
upsflushin(0, nut_debug_level, IGNCHARS);
272
upssendchar(APC_STATUS);
273
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
275
if ((ret < 1) || (!strcmp(buf, "NA"))) {
280
tval = strtol(buf, 0, 16);
285
status_set("CAL"); /* calibration */
287
status_set("TRIM"); /* SmartTrim */
289
status_set("BOOST"); /* SmartBoost */
291
status_set("OL"); /* on line */
293
status_set("OB"); /* on battery */
295
status_set("OVER"); /* overload */
297
status_set("LB"); /* low battery */
299
status_set("RB"); /* replace batt */
301
if (!strcmp(buf, "00"))
308
static void oldapcsetup(void)
312
/* really old models ignore REQ_MODEL, so find them first */
313
ret = query_ups("ups.model", 1);
316
/* force the model name */
317
dstate_setinfo("ups.model", "Smart-UPS");
320
/* see if this might be an old Matrix-UPS instead */
321
if (query_ups("output.current", 1))
322
dstate_setinfo("ups.model", "Matrix-UPS");
324
query_ups("ups.serial", 1);
325
query_ups("input.voltage", 1); /* This one may fail... no problem */
329
/* If we have come down this path then we dont do capabilities and
334
static void protocol_verify(unsigned char cmd)
338
/* we might not care about this one */
339
if (strchr(CMD_IGN_CHARS, cmd))
342
/* see if it's a variable */
343
for (i = 0; apc_vartab[i].name != NULL; i++) {
345
/* 1:1 here, so the first match is the only match */
347
if (apc_vartab[i].cmd == cmd) {
348
upsdebugx(3, "UPS supports variable [%s]",
351
/* load initial data */
352
apc_vartab[i].flags |= APC_PRESENT;
353
poll_data(&apc_vartab[i]);
355
/* handle special data for our two strings */
356
if (apc_vartab[i].flags & APC_STRING) {
357
dstate_setflags(apc_vartab[i].name,
358
ST_FLAG_RW | ST_FLAG_STRING);
359
dstate_setaux(apc_vartab[i].name, APC_STRLEN);
361
apc_vartab[i].flags |= APC_RW;
368
/* check the command list */
370
/* some cmdchars map onto multiple commands (start and stop) */
374
for (i = 0; apc_cmdtab[i].name != NULL; i++) {
375
if (apc_cmdtab[i].cmd == cmd) {
376
upsdebugx(2, "UPS supports command [%s]",
379
dstate_addcmd(apc_cmdtab[i].name);
381
apc_cmdtab[i].flags |= APC_PRESENT;
390
upsdebugx(1, "protocol_verify: 0x%02x [%c] unrecognized",
393
upsdebugx(1, "protocol_verify: 0x%02x unrecognized", cmd);
396
static void getbaseinfo(void)
399
char *alrts, *cmds, temp[512];
401
upsdebugx(1, "APC - Attempting to find command set");
402
/* Initially we ask the UPS what commands it takes
403
If this fails we are going to need an alternate
404
strategy - we can deal with that if it happens
407
upssendchar(APC_CMDSET);
409
/* disable timeout complaints temporarily */
410
flag_timeoutfailure = -1;
411
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
412
flag_timeoutfailure = 0;
414
if ((ret < 1) || (!strcmp(temp, "NA"))) {
416
/* We have an old dumb UPS - go to specific code for old stuff */
421
upsdebugx(1, "APC - Parsing out command set");
422
/* We have the version.alert.cmdchars string
423
NB the alert chars are normally in IGNCHARS
424
so will have been pretty much edited out.
425
You will need to change the upsrecv above if
426
you want to check those out too....
428
alrts = strchr(temp, '.');
430
printf("Unable to split APC version string\n");
431
printf("Bailing out\n");
436
cmds = strchr(alrts, '.');
438
printf("Unable to find APC command string\n");
439
printf("Bailing out\n");
444
for (i = 0; i < strlen(cmds); i++)
445
protocol_verify(cmds[i]);
447
/* if capabilities are supported, add them here */
448
if (strchr(cmds, APC_CAPABILITY))
451
upsdebugx(1, "APC - UPS capabilities determined");
454
/* TODO: roll this into upscommon ala bestups */
455
static void sendstring(char * string, int len, int delay)
459
upsflushin(0, nut_debug_level, IGNCHARS);
460
for (i=0; (i<len); i++) {
461
upssendchar(string[i]);
466
/* check for calibration status and either start or stop */
467
static int do_cal(int start)
472
upssendchar(APC_STATUS);
473
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
475
/* if we can't check the current calibration status, bail out */
476
if ((ret < 1) || (!strcmp(temp, "NA")))
477
return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
479
tval = strtol(temp, 0, 16);
481
if (tval & 1) { /* calibration currently happening */
483
/* requested start while calibration still running */
484
upslogx(LOG_INFO, "Runtime calibration already in progress");
485
return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
490
upslogx(LOG_INFO, "Stopping runtime calibration");
491
upssendchar(APC_CMD_CALTOGGLE);
493
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
495
if ((ret < 1) || (!strcmp(temp, "NA")) || (!strcmp(temp, "NO"))) {
496
upslogx(LOG_WARNING, "Stop calibration failed", temp);
497
return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
500
return STAT_INSTCMD_HANDLED; /* FUTURE: success */
503
/* calibration not happening */
505
if (start == 0) { /* stop requested */
506
upslogx(LOG_INFO, "Runtime calibration not occurring");
507
return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
510
upslogx(LOG_INFO, "Starting runtime calibration");
511
upssendchar(APC_CMD_CALTOGGLE);
513
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
515
if ((ret < 1) || (!strcmp(temp, "NA")) || (!strcmp(temp, "NO"))) {
516
upslogx(LOG_WARNING, "Start calibration failed", temp);
517
return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
520
return STAT_INSTCMD_HANDLED; /* FUTURE: success */
523
/* get the UPS talking to us in smart mode */
524
static int smartmode(void)
529
for (tries = 0; tries < 5; tries++) {
531
upssendchar(APC_GOSMART);
532
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
535
if (!strcmp(temp, "SM"))
536
return 1; /* success */
538
sleep(1); /* wait before trying again */
540
/* it failed, so try to bail out of menus on newer units */
542
upssendchar(27); /* ESC */
544
/* eat the response (might be NA, might be something else) */
545
upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
548
return 0; /* failure */
551
/* power down the attached load immediately */
552
void upsdrv_shutdown(void)
555
int ret, tval, sdtype = 0;
558
printf("Detection failed. Trying a shutdown command anyway.\n");
560
/* check the line status */
562
upssendchar(APC_STATUS);
563
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
566
printf("Status read failed! Assuming you're on battery...\n");
567
tval = 64 | 16; /* LB + OB */
570
tval = strtol(temp, 0, 16);
573
if (testvar("sdtype"))
574
sdtype = atoi(getval("sdtype"));
577
case 3: /* shutdown with grace period */
578
printf("Sending delayed power off command to UPS\n");
580
upssendchar(APC_CMD_SHUTDOWN);
582
upssendchar(APC_CMD_SHUTDOWN);
586
case 2: /* instant shutdown */
587
printf("Sending power off command to UPS\n");
589
upssendchar(APC_CMD_OFF);
591
upssendchar(APC_CMD_OFF);
597
/* Send a combined set of shutdown commands which can work better */
598
/* if the UPS gets power during shutdown process */
599
/* Specifically it sends both the soft shutdown 'S' */
600
/* and the powerdown after grace period - '@000' commands */
601
printf("UPS - currently %s - sending shutdown/powerdown\n",
602
(tval & 8) ? "on-line" : "on battery");
603
sendstring("S@000", 5, 50000);
608
/* @000 - shutdown after 'p' grace period */
609
/* - returns after 000 minutes (i.e. right away) */
611
/* S - shutdown after 'p' grace period, only on battery */
612
/* returns after 'e' charge % plus 'r' seconds */
614
if (tval & 8) { /* on line */
615
printf("On line, sending shutdown+return command...\n");
616
sendstring("@000", 4, 50000);
619
printf("On battery, sending normal shutdown command...\n");
620
upssendchar(APC_CMD_SOFTDOWN);
625
/* 940-0095B support: set DTR, lower RTS */
626
static void init_serial_0095B()
628
int dtr_bit = TIOCM_DTR;
629
int rts_bit = TIOCM_RTS;
631
ioctl(upsfd, TIOCMBIS, &dtr_bit);
632
ioctl(upsfd, TIOCMBIC, &rts_bit);
635
/* normal idle loop - keep up with the current state of the UPS */
636
/* If updateall is non-zero query all known UPS variables */
637
static void updateinfo(int updateall)
641
/* try to wake up a dead ups once in awhile */
642
if (flag_timeoutfailure == 1) {
644
upslogx(LOG_ERR, "Communication with UPS lost");
649
for (i = 0; apc_vartab[i].name != NULL; i++) {
651
/* "all" mode - do everything regardless */
653
poll_data(&apc_vartab[i]);
657
/* normal mode: check the reduced set */
658
if (apc_vartab[i].flags & APC_POLL)
659
poll_data(&apc_vartab[i]);
663
static int setvar_enum(struct apc_vartab_t *vt, const char *val)
666
char orig[256], temp[256], *ptr;
668
upssendchar(vt->cmd);
670
ret = upsrecv(orig, sizeof(orig), ENDCHAR, IGNCHARS);
672
if ((ret < 1) || (!strcmp(orig, "NA")))
673
return STAT_SET_HANDLED; /* FUTURE: failed */
675
ptr = convert_data(vt, orig);
677
/* suppress redundant changes - easier on the eeprom */
678
if (!strcmp(ptr, val)) {
679
upslogx(LOG_INFO, "Ignoring enum SET %s='%s' (unchanged value)",
682
return STAT_SET_HANDLED; /* FUTURE: no change */
685
for (i = 0; i < 6; i++) {
686
upssendchar(APC_NEXTVAL);
688
/* this should return either OK (if rotated) or NO (if not) */
689
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
691
if ((ret < 1) || (!strcmp(temp, "NA")))
692
return STAT_SET_HANDLED; /* FUTURE: failed */
695
if (!strcmp(temp, "NO"))
696
return STAT_SET_HANDLED; /* FUTURE: failed */
697
if (strcmp(temp, "OK") != 0)
698
return STAT_SET_HANDLED; /* FUTURE: failed */
700
/* see what it rotated onto */
701
upssendchar(vt->cmd);
702
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
704
if ((ret < 1) || (!strcmp(temp, "NA")))
705
return STAT_SET_HANDLED; /* FUTURE: failed */
707
ptr = convert_data(vt, temp);
709
upsdebugx(1, "Rotate value: got [%s], want [%s]",
712
if (!strcmp(ptr, val)) { /* got it */
713
upslogx(LOG_INFO, "SET %s='%s'", vt->name, val);
715
/* refresh data from the hardware */
716
query_ups(vt->name, 0);
718
return STAT_SET_HANDLED; /* FUTURE: success */
721
/* check for wraparound */
722
if (!strcmp(ptr, orig)) {
723
upslogx(LOG_ERR, "setvar: variable %s wrapped",
726
return STAT_SET_HANDLED; /* FUTURE: failed */
730
upslogx(LOG_ERR, "setvar: gave up after 6 tries for %s",
733
/* refresh data from the hardware */
734
query_ups(vt->name, 0);
736
return STAT_SET_HANDLED;
739
static int setvar_string(struct apc_vartab_t *vt, const char *val)
744
upsflushin(0, nut_debug_level, IGNCHARS);
746
upssendchar(vt->cmd);
748
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
750
if ((ret < 1) || (!strcmp(temp, "NA")))
751
return STAT_SET_HANDLED; /* FUTURE: failed */
753
/* suppress redundant changes - easier on the eeprom */
754
if (!strcmp(temp, val)) {
755
upslogx(LOG_INFO, "Ignoring string SET %s='%s' (unchanged value)",
758
return STAT_SET_HANDLED; /* FUTURE: no change */
761
upssendchar(APC_NEXTVAL);
764
for (i = 0; i < strlen(val); i++) {
769
/* pad to 8 chars with CRs */
770
for (i = strlen(val); i < APC_STRLEN; i++) {
775
ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
778
upslogx(LOG_ERR, "setvar_string: short final read");
779
return STAT_SET_HANDLED; /* FUTURE: failed */
782
if (!strcmp(temp, "NO")) {
783
upslogx(LOG_ERR, "setvar_string: got NO at final read");
784
return STAT_SET_HANDLED; /* FUTURE: failed */
787
/* refresh data from the hardware */
788
query_ups(vt->name, 0);
790
upslogx(LOG_INFO, "SET %s='%s'", vt->name, val);
792
return STAT_SET_HANDLED; /* FUTURE: failed */
795
int setvar(const char *varname, const char *val)
797
struct apc_vartab_t *vt;
799
vt = vartab_lookup_name(varname);
802
return STAT_SET_UNKNOWN;
804
if ((vt->flags & APC_RW) == 0) {
805
upslogx(LOG_WARNING, "setvar: [%s] is not writable", varname);
806
return STAT_SET_UNKNOWN;
809
if (vt->flags & APC_ENUM)
810
return setvar_enum(vt, val);
812
if (vt->flags & APC_STRING)
813
return setvar_string(vt, val);
815
upslogx(LOG_WARNING, "setvar: Unknown type for [%s]", varname);
816
return STAT_SET_UNKNOWN;
819
/* actually send the instcmd's char to the ups */
820
static int do_cmd(struct apc_cmdtab_t *ct)
825
upssendchar(ct->cmd);
827
/* some commands have to be sent twice with a 1.5s gap */
828
if (ct->flags & APC_REPEAT) {
830
upssendchar(ct->cmd);
833
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
836
return STAT_INSTCMD_HANDLED; /* FUTURE: failed */
838
if (strcmp(buf, "OK") != 0) {
839
upslogx(LOG_WARNING, "Got [%s] after command [%s]",
842
return STAT_INSTCMD_HANDLED; /* FUTURE: failed */
845
upslogx(LOG_INFO, "Command: %s", ct->name);
846
return STAT_INSTCMD_HANDLED; /* FUTURE: success */
849
/* some commands must be repeated in a window to execute */
850
static int instcmd_chktime(struct apc_cmdtab_t *ct)
854
static time_t last = 0;
858
elapsed = difftime(now, last);
861
/* you have to hit this in a small window or it fails */
862
if ((elapsed < MINCMDTIME) || (elapsed > MAXCMDTIME)) {
863
upsdebugx(1, "instcmd_chktime: outside window for %s (%2.0f)",
865
return STAT_INSTCMD_HANDLED; /* FUTURE: again */
871
int instcmd(const char *cmdname, const char *extra)
874
struct apc_cmdtab_t *ct;
878
for (i = 0; apc_cmdtab[i].name != NULL; i++)
879
if (!strcasecmp(apc_cmdtab[i].name, cmdname))
883
upslogx(LOG_WARNING, "instcmd: unknown command [%s]", cmdname);
884
return STAT_INSTCMD_UNKNOWN;
887
if ((ct->flags & APC_PRESENT) == 0) {
888
upslogx(LOG_WARNING, "instcmd: command [%s] is not supported",
890
return STAT_INSTCMD_UNKNOWN;
893
if (!strcasecmp(cmdname, "calibrate.start"))
896
if (!strcasecmp(cmdname, "calibrate.stop"))
899
if (ct->flags & APC_NASTY)
900
return instcmd_chktime(ct);
902
/* nothing special here */
906
/* install pointers to functions for msg handlers called from msgparse */
907
static void setuphandlers(void)
909
upsh.new_setvar = setvar;
910
upsh.new_instcmd = instcmd;
913
/* functions that interface with main.c */
915
void upsdrv_banner(void)
917
printf("Network UPS Tools (version %s) - APC Smart protocol driver\n",
919
printf("\tDriver version %s, command table %s\n",
924
void upsdrv_makevartable(void)
926
addvar(VAR_VALUE, "cable", "Specify alternate cable (940-0095B)");
927
addvar(VAR_VALUE, "sdtype", "Specify shutdown type (1-3)");
930
void upsdrv_initups(void)
934
open_serial(device_path, B2400);
936
cable = getval("cable");
939
if (!strcasecmp(cable, ALT_CABLE_1))
943
void upsdrv_help(void)
945
printf("\nShutdown types:\n");
946
printf(" 0: soft shutdown or powerdown, depending on battery status\n");
947
printf(" 1: soft shutdown followed by powerdown\n");
948
printf(" 2: instant power off\n");
949
printf(" 3: power off with grace period\n");
950
printf("Modes 0-1 will make the UPS come back when power returns\n");
951
printf("Modes 2-3 will make the UPS stay turned off when power returns\n");
954
void upsdrv_initinfo(void)
957
printf("Unable to detect an APC Smart protocol UPS on port %s\n",
959
printf("Check the cabling, port name or model name and try again\n");
963
/* manufacturer ID - hardcoded in this particular module */
964
dstate_setinfo("ups.mfr", "APC");
966
dstate_setinfo("driver.version.internal", "%s", APC_DRIVER_VERSION);
970
printf("Detected %s [%s] on %s\n", dstate_getinfo("ups.model"),
971
dstate_getinfo("ups.serial"), device_path);
976
void upsdrv_updateinfo(void)
978
static time_t last_full = 0;
985
/* refresh all variables hourly */
986
/* does not catch measure-ups II insertion/removal */
987
if (difftime(now, last_full) > 3600) {
1003
void upsdrv_cleanup(void)