4 * Driver for PowerChute Network Shutdown protocol.
8 * Copyright (C) 2006 Adam Kropelin
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU General
12 * Public License as published by the Free Software Foundation.
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 GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27 #include <sys/socket.h>
28 #include <netinet/in.h>
30 /* UPS broadcasts status packets to UDP port 3052 */
31 #define PCNET_DEFAULT_PORT 3052
34 * Number of seconds with no data before we declare COMMLOST.
35 * UPS should report in every 25 seconds. We allow 2 missing
36 * reports plus a fudge factor.
38 #define COMMLOST_TIMEOUT 55
40 /* Win32 needs a special close for sockets */
42 #define close(fd) closesocket(fd)
46 char device
[MAXSTRING
]; /* Copy of ups->device */
47 char *ipaddr
; /* IP address of UPS */
48 char *user
; /* Username */
49 char *pass
; /* Pass phrase */
50 bool auth
; /* Authenticate? */
51 unsigned long uptime
; /* UPS uptime counter */
52 unsigned long reboots
; /* UPS reboot counter */
53 time_t datatime
; /* Last time we got valid data */
56 /* Convert UPS response to enum and string */
57 static SelfTestResult
decode_testresult(const char* str
)
61 * "OK" - good battery,
62 * "BT" - failed due to insufficient capacity,
63 * "NG" - failed due to overload,
64 * "NO" - no results available (no test performed in last 5 minutes)
66 if (str
[0] == 'O' && str
[1] == 'K')
68 else if (str
[0] == 'B' && str
[1] == 'T')
70 else if (str
[0] == 'N' && str
[1] == 'G')
76 /* Convert UPS response to enum and string */
77 static LastXferCause
decode_lastxfer(const char *str
)
79 Dmsg1(80, "Transfer reason: %c\n", *str
);
89 return XFER_UNDERVOLT
;
91 return XFER_NOTCHSPIKE
;
103 static bool pcnet_process_data(UPSINFO
* ups
, const char *key
, const char *value
)
109 /* Make sure we have a value */
113 /* Detect remote shutdown command */
114 if (strcmp(key
, "SD") == 0)
116 cmd
= strtoul(value
, NULL
, 10);
120 Dmsg0(80, "SD: The UPS is NOT shutting down\n");
121 ups
->clear_shut_remote();
125 Dmsg0(80, "SD: The UPS is shutting down\n");
126 ups
->set_shut_remote();
130 Dmsg1(80, "Unrecognized SD value %s!\n", value
);
137 /* Key must be 2 hex digits */
138 if (!isxdigit(key
[0]) || !isxdigit(key
[1]))
141 /* Convert command to CI */
142 cmd
= strtoul(key
, NULL
, 16);
143 for (ci
=0; ci
<CI_MAXCI
; ci
++)
144 if (ups
->UPS_Cmd
[ci
] == cmd
)
151 /* Mark this CI as available */
152 ups
->UPS_Cap
[ci
] = true;
154 /* Handle the data */
161 Dmsg1(80, "Got CI_STATUS: %s\n", value
);
162 ups
->Status
&= ~0xFF; /* clear APC byte */
163 ups
->Status
|= strtoul(value
, NULL
, 16) & 0xFF; /* set APC byte */
166 Dmsg1(80, "Got CI_LQUAL: %s\n", value
);
167 astrncpy(ups
->linequal
, value
, sizeof(ups
->linequal
));
170 Dmsg1(80, "Got CI_WHY_BATT: %s\n", value
);
171 ups
->lastxfer
= decode_lastxfer(value
);
174 Dmsg1(80, "Got CI_ST_STAT: %s\n", value
);
175 ups
->testresult
= decode_testresult(value
);
178 Dmsg1(80, "Got CI_VLINE: %s\n", value
);
179 ups
->LineVoltage
= atof(value
);
182 Dmsg1(80, "Got CI_VMIN: %s\n", value
);
183 ups
->LineMin
= atof(value
);
186 Dmsg1(80, "Got CI_VMAX: %s\n", value
);
187 ups
->LineMax
= atof(value
);
190 Dmsg1(80, "Got CI_VOUT: %s\n", value
);
191 ups
->OutputVoltage
= atof(value
);
194 Dmsg1(80, "Got CI_BATTLEV: %s\n", value
);
195 ups
->BattChg
= atof(value
);
198 Dmsg1(80, "Got CI_VBATT: %s\n", value
);
199 ups
->BattVoltage
= atof(value
);
202 Dmsg1(80, "Got CI_LOAD: %s\n", value
);
203 ups
->UPSLoad
= atof(value
);
206 Dmsg1(80, "Got CI_FREQ: %s\n", value
);
207 ups
->LineFreq
= atof(value
);
210 Dmsg1(80, "Got CI_RUNTIM: %s\n", value
);
211 ups
->TimeLeft
= atof(value
);
214 Dmsg1(80, "Got CI_ITEMP: %s\n", value
);
215 ups
->UPSTemp
= atof(value
);
218 Dmsg1(80, "Got CI_DIPSW: %s\n", value
);
219 ups
->dipsw
= strtoul(value
, NULL
, 16);
222 Dmsg1(80, "Got CI_REG1: %s\n", value
);
223 ups
->reg1
= strtoul(value
, NULL
, 16);
226 ups
->reg2
= strtoul(value
, NULL
, 16);
227 ups
->set_battpresent(!(ups
->reg2
& 0x20));
230 Dmsg1(80, "Got CI_REG3: %s\n", value
);
231 ups
->reg3
= strtoul(value
, NULL
, 16);
234 Dmsg1(80, "Got CI_HUMID: %s\n", value
);
235 ups
->humidity
= atof(value
);
238 Dmsg1(80, "Got CI_ATEMP: %s\n", value
);
239 ups
->ambtemp
= atof(value
);
242 Dmsg1(80, "Got CI_ST_TIME: %s\n", value
);
243 ups
->LastSTTime
= atof(value
);
250 Dmsg1(80, "Got CI_SENS: %s\n", value
);
251 astrncpy(ups
->sensitivity
, value
, sizeof(ups
->sensitivity
));
254 Dmsg1(80, "Got CI_DWAKE: %s\n", value
);
255 ups
->dwake
= (int)atof(value
);
258 Dmsg1(80, "Got CI_DSHUTD: %s\n", value
);
259 ups
->dshutd
= (int)atof(value
);
262 Dmsg1(80, "Got CI_LTRANS: %s\n", value
);
263 ups
->lotrans
= (int)atof(value
);
266 Dmsg1(80, "Got CI_HTRANS: %s\n", value
);
267 ups
->hitrans
= (int)atof(value
);
270 Dmsg1(80, "Got CI_RETPCT: %s\n", value
);
271 ups
->rtnpct
= (int)atof(value
);
274 Dmsg1(80, "Got CI_DALARM: %s\n", value
);
275 astrncpy(ups
->beepstate
, value
, sizeof(ups
->beepstate
));
278 Dmsg1(80, "Got CI_DLBATT: %s\n", value
);
279 ups
->dlowbatt
= (int)atof(value
);
282 Dmsg1(80, "Got CI_IDEN: %s\n", value
);
283 if (ups
->upsname
[0] == 0)
284 astrncpy(ups
->upsname
, value
, sizeof(ups
->upsname
));
287 Dmsg1(80, "Got CI_STESTI: %s\n", value
);
288 astrncpy(ups
->selftest
, value
, sizeof(ups
->selftest
));
291 Dmsg1(80, "Got CI_MANDAT: %s\n", value
);
292 astrncpy(ups
->birth
, value
, sizeof(ups
->birth
));
295 Dmsg1(80, "Got CI_SERNO: %s\n", value
);
296 astrncpy(ups
->serial
, value
, sizeof(ups
->serial
));
299 Dmsg1(80, "Got CI_BATTDAT: %s\n", value
);
300 astrncpy(ups
->battdat
, value
, sizeof(ups
->battdat
));
303 Dmsg1(80, "Got CI_NOMOUTV: %s\n", value
);
304 ups
->NomOutputVoltage
= (int)atof(value
);
307 Dmsg1(80, "Got CI_NOMBATTV: %s\n", value
);
308 ups
->nombattv
= atof(value
);
311 Dmsg1(80, "Got CI_REVNO: %s\n", value
);
312 astrncpy(ups
->firmrev
, value
, sizeof(ups
->firmrev
));
315 Dmsg1(80, "Got CI_EXTBATTS: %s\n", value
);
316 ups
->extbatts
= (int)atof(value
);
319 Dmsg1(80, "Got CI_BADBATTS: %s\n", value
);
320 ups
->badbatts
= (int)atof(value
);
323 Dmsg1(80, "Got CI_UPSMODEL: %s\n", value
);
324 astrncpy(ups
->upsmodel
, value
, sizeof(ups
->upsmodel
));
327 Dmsg1(80, "Got CI_EPROM: %s\n", value
);
328 astrncpy(ups
->eprom
, value
, sizeof(ups
->eprom
));
331 Dmsg1(100, "Unknown CI (%d)\n", ci
);
339 static char *digest2ascii(md5_byte_t
*digest
)
341 static char ascii
[33];
345 /* Convert binary digest to ascii */
347 for (idx
=0; idx
<16; idx
++) {
348 sprintf(ptr
, "%02x", (unsigned char)digest
[idx
]);
360 #define MAX_PAIRS 256
362 static const char *lookup_key(const char *key
, struct pair table
[])
365 const char *ret
= NULL
;
367 for (idx
=0; table
[idx
].key
; idx
++) {
368 if (strcmp(key
, table
[idx
].key
) == 0) {
369 ret
= table
[idx
].value
;
377 static struct pair
*auth_and_map_packet(UPSINFO
* ups
, char *buf
, int len
)
379 PCNET_DATA
*my_data
= (PCNET_DATA
*)ups
->driver_internal_data
;
380 char *key
, *end
, *ptr
, *value
;
381 const char *val
, *hash
=NULL
;
382 static struct pair pairs
[MAX_PAIRS
+1];
384 md5_byte_t digest
[16];
386 unsigned long uptime
, reboots
;
388 /* If there's no MD= field, drop the packet */
389 if ((ptr
= strstr(buf
, "MD=")) == NULL
|| ptr
== buf
)
393 /* Calculate the MD5 of the packet before messing with it */
395 md5_append(&ms
, (md5_byte_t
*)buf
, ptr
-buf
);
396 md5_append(&ms
, (md5_byte_t
*)my_data
->user
, strlen(my_data
->user
));
397 md5_append(&ms
, (md5_byte_t
*)my_data
->pass
, strlen(my_data
->pass
));
398 md5_finish(&ms
, digest
);
400 /* Convert binary digest to ascii */
401 hash
= digest2ascii(digest
);
404 /* Build a table of pointers to key/value pairs */
405 memset(pairs
, 0, sizeof(pairs
));
408 while (*ptr
&& idx
< MAX_PAIRS
) {
409 /* Find the beginning of the line */
410 while (isspace(*ptr
))
414 /* Find the end of the line */
415 while (*ptr
&& *ptr
!= '\r' && *ptr
!= '\n')
421 /* Remove trailing whitespace */
424 } while (end
>= key
&& isspace(*end
));
426 Dmsg1(300, "process_packet: line='%s'\n", key
);
428 /* Split the string */
429 if ((value
= strchr(key
, '=')) == NULL
)
433 Dmsg2(300, "process_packet: key='%s' value='%s'\n",
436 /* Save key/value in table */
437 pairs
[idx
].key
= key
;
438 pairs
[idx
].value
= value
;
443 /* Check calculated hash vs received */
444 Dmsg1(200, "process_packet: calculated=%s\n", hash
);
445 val
= lookup_key("MD", pairs
);
446 if (!val
|| strcmp(hash
, val
)) {
447 Dmsg0(200, "process_packet: message hash failed\n");
450 Dmsg1(200, "process_packet: message hash passed\n", val
);
452 /* Check management card IP address */
453 val
= lookup_key("PC", pairs
);
455 Dmsg0(200, "process_packet: Missing PC field\n");
458 Dmsg1(200, "process_packet: Expected IP=%s\n", my_data
->ipaddr
);
459 Dmsg1(200, "process_packet: Received IP=%s\n", val
);
460 if (strcmp(val
, my_data
->ipaddr
)) {
461 Dmsg2(200, "process_packet: IP address mismatch\n",
462 my_data
->ipaddr
, val
);
468 * Check that uptime and/or reboots have advanced. If not,
469 * this packet could be out of order, or an attacker may
470 * be trying to replay an old packet.
472 val
= lookup_key("SR", pairs
);
474 Dmsg0(200, "process_packet: Missing SR field\n");
477 reboots
= strtoul(val
, NULL
, 16);
479 val
= lookup_key("SU", pairs
);
481 Dmsg0(200, "process_packet: Missing SU field\n");
484 uptime
= strtoul(val
, NULL
, 16);
486 Dmsg1(200, "process_packet: Our reboots=%d\n", my_data
->reboots
);
487 Dmsg1(200, "process_packet: UPS reboots=%d\n", reboots
);
488 Dmsg1(200, "process_packet: Our uptime=%d\n", my_data
->uptime
);
489 Dmsg1(200, "process_packet: UPS uptime=%d\n", uptime
);
491 if ((reboots
== my_data
->reboots
&& uptime
<= my_data
->uptime
) ||
492 (reboots
< my_data
->reboots
)) {
493 Dmsg0(200, "process_packet: Packet is out of order or replayed\n");
497 my_data
->reboots
= reboots
;
498 my_data
->uptime
= uptime
;
503 * Read UPS events. I.e. state changes.
505 int pcnet_ups_check_state(UPSINFO
*ups
)
507 PCNET_DATA
*my_data
= (PCNET_DATA
*)ups
->driver_internal_data
;
508 struct timeval tv
, now
, exit
;
511 struct sockaddr_in from
;
518 /* Figure out when we need to exit by */
519 gettimeofday(&exit
, NULL
);
520 exit
.tv_sec
+= ups
->wait_time
;
524 /* Figure out how long until we have to exit */
525 gettimeofday(&now
, NULL
);
527 if (now
.tv_sec
> exit
.tv_sec
||
528 (now
.tv_sec
== exit
.tv_sec
&&
529 now
.tv_usec
>= exit
.tv_usec
)) {
530 /* Done already? How time flies... */
534 tv
.tv_sec
= exit
.tv_sec
- now
.tv_sec
;
535 tv
.tv_usec
= exit
.tv_usec
- now
.tv_usec
;
536 if (tv
.tv_usec
< 0) {
537 tv
.tv_sec
--; /* Normalize */
538 tv
.tv_usec
+= 1000000;
541 Dmsg2(100, "Waiting for %d.%d\n", tv
.tv_sec
, tv
.tv_usec
);
543 FD_SET(ups
->fd
, &rfds
);
545 retval
= select(ups
->fd
+ 1, &rfds
, NULL
, NULL
, &tv
);
548 /* No chars available in TIMER seconds. */
550 } else if (retval
== -1) {
551 if (errno
== EINTR
|| errno
== EAGAIN
) /* assume SIGCHLD */
553 Dmsg1(200, "select error: ERR=%s\n", strerror(errno
));
558 fromlen
= sizeof(from
);
559 retval
= recvfrom(ups
->fd
, buf
, sizeof(buf
)-1, 0, (struct sockaddr
*)&from
, &fromlen
);
560 } while (retval
== -1 && (errno
== EAGAIN
|| errno
== EINTR
));
562 if (retval
< 0) { /* error */
563 Dmsg1(200, "recvfrom error: ERR=%s\n", strerror(errno
));
564 // usb_link_check(ups); /* notify that link is down, wait */
568 Dmsg4(200, "Packet from: %d.%d.%d.%d\n",
569 (ntohl(from
.sin_addr
.s_addr
) >> 24) & 0xff,
570 (ntohl(from
.sin_addr
.s_addr
) >> 16) & 0xff,
571 (ntohl(from
.sin_addr
.s_addr
) >> 8) & 0xff,
572 ntohl(from
.sin_addr
.s_addr
) & 0xff);
574 /* Ensure the packet is nul-terminated */
577 hex_dump(300, buf
, retval
);
579 map
= auth_and_map_packet(ups
, buf
, retval
);
585 for (idx
=0; map
[idx
].key
; idx
++)
586 done
|= pcnet_process_data(ups
, map
[idx
].key
, map
[idx
].value
);
591 /* If we successfully received a data packet, update timer. */
593 time(&my_data
->datatime
);
594 Dmsg1(100, "Valid data at time_t=%d\n", my_data
->datatime
);
600 int pcnet_ups_open(UPSINFO
*ups
)
602 struct sockaddr_in addr
;
603 PCNET_DATA
*my_data
= (PCNET_DATA
*)ups
->driver_internal_data
;
608 if (my_data
== NULL
) {
609 my_data
= (PCNET_DATA
*)malloc(sizeof(*my_data
));
610 memset(my_data
, 0, sizeof(*my_data
));
611 ups
->driver_internal_data
= my_data
;
614 unsigned short port
= PCNET_DEFAULT_PORT
;
615 if (ups
->device
[0] != '\0') {
616 my_data
->auth
= true;
618 astrncpy(my_data
->device
, ups
->device
, sizeof(my_data
->device
));
619 ptr
= my_data
->device
;
621 my_data
->ipaddr
= ptr
;
622 ptr
= strchr(ptr
, ':');
624 Error_abort0("Malformed DEVICE [ip:user:pass]\n");
628 ptr
= strchr(ptr
, ':');
630 Error_abort0("Malformed DEVICE [ip:user:pass]\n");
635 Error_abort0("Malformed DEVICE [ip:user:pass]\n");
637 // Last segment is optional port number
638 ptr
= strchr(ptr
, ':');
644 port
= PCNET_DEFAULT_PORT
;
648 ups
->fd
= socket(PF_INET
, SOCK_DGRAM
, 0);
650 Error_abort1("Cannot create socket (%d)\n", errno
);
653 setsockopt(ups
->fd
, SOL_SOCKET
, SO_BROADCAST
, (const char*)&enable
, sizeof(enable
));
655 memset(&addr
, 0, sizeof(addr
));
656 addr
.sin_family
= AF_INET
;
657 addr
.sin_port
= htons(port
);
658 addr
.sin_addr
.s_addr
= INADDR_ANY
;
659 if (bind(ups
->fd
, (struct sockaddr
*)&addr
, sizeof(addr
)) == -1) {
661 Error_abort1("Cannot bind socket (%d)\n", errno
);
664 /* Reset datatime to now */
665 time(&my_data
->datatime
);
671 int pcnet_ups_setup(UPSINFO
*ups
)
673 /* Seems that there is nothing to do. */
677 int pcnet_ups_close(UPSINFO
*ups
)
689 * Setup capabilities structure for UPS
691 int pcnet_ups_get_capabilities(UPSINFO
*ups
)
694 * Unfortunately, we don't know capabilities until we
695 * receive the first broadcast status message.
701 * Read UPS info that remains unchanged -- e.g. transfer
702 * voltages, shutdown delay, ...
704 * This routine is called once when apcupsd is starting
706 int pcnet_ups_read_static_data(UPSINFO
*ups
)
708 /* All our data gathering is done in pcnet_ups_check_state() */
713 * Read UPS info that changes -- e.g. Voltage, temperature, ...
715 * This routine is called once every N seconds to get
716 * a current idea of what the UPS is doing.
718 int pcnet_ups_read_volatile_data(UPSINFO
*ups
)
720 PCNET_DATA
*my_data
= (PCNET_DATA
*)ups
->driver_internal_data
;
724 * All our data gathering is done in pcnet_ups_check_state().
725 * But we do use this function to check our commlost state.
729 diff
= now
- my_data
->datatime
;
731 if (ups
->is_commlost()) {
732 if (diff
< COMMLOST_TIMEOUT
) {
733 generate_event(ups
, CMDCOMMOK
);
734 ups
->clear_commlost();
737 if (diff
>= COMMLOST_TIMEOUT
) {
738 generate_event(ups
, CMDCOMMFAILURE
);
746 int pcnet_ups_kill_power(UPSINFO
*ups
)
748 PCNET_DATA
*my_data
= (PCNET_DATA
*)ups
->driver_internal_data
;
749 struct sockaddr_in addr
;
751 int s
, len
=0, temp
=0;
753 const char *cs
, *hash
;
756 md5_byte_t digest
[16];
758 /* We cannot perform a killpower without authentication data */
759 if (!my_data
->auth
) {
760 Error_abort0("Cannot perform killpower without authentication "
761 "data. Please set ip:user:pass for DEVICE in "
765 /* Open a TCP stream to the UPS */
766 s
= socket(PF_INET
, SOCK_STREAM
, 0);
768 Dmsg1(100, "pcnet_ups_kill_power: Unable to open socket: %s\n",
773 memset(&addr
, 0, sizeof(addr
));
774 addr
.sin_family
= AF_INET
;
775 addr
.sin_port
= htons(80);
776 inet_pton(AF_INET
, my_data
->ipaddr
, &addr
.sin_addr
.s_addr
);
778 if (connect(s
, (sockaddr
*)&addr
, sizeof(addr
))) {
779 Dmsg3(100, "pcnet_ups_kill_power: Unable to connect to %s:%d: %s\n",
780 my_data
->ipaddr
, 80, strerror(errno
));
785 /* Send a simple HTTP request for "/macontrol.htm". */
786 asnprintf(data
, sizeof(data
),
787 "GET /macontrol.htm HTTP/1.1\r\n"
792 Dmsg1(200, "Request:\n---\n%s---\n", data
);
794 if (send(s
, data
, strlen(data
), 0) != (int)strlen(data
)) {
795 Dmsg1(100, "pcnet_ups_kill_power: send failed: %s\n", strerror(errno
));
801 * Clear buffer and read data until we find the 0-length
802 * chunk. We know that AP9617 uses chunked encoding, so we
803 * can count on the 0-length chunk at the end.
805 memset(data
, 0, sizeof(data
));
808 temp
= recv(s
, data
+len
, sizeof(data
)-len
, 0);
809 } while(temp
> 0 && strstr(data
, "\r\n0\r\n") == NULL
);
811 Dmsg1(200, "Response:\n---\n%s---\n", data
);
814 Dmsg1(100, "pcnet_ups_kill_power: recv failed: %s\n", strerror(errno
));
820 * Find "<html>" since that's where the real authenticated
821 * data begins. Everything before that is headers.
823 start
= strstr(data
, "<html>");
825 Dmsg0(100, "pcnet_ups_kill_power: Malformed data\n");
831 * Authenticate and map the packet contents. This will
832 * extract all key/value pairs and ensure the packet
833 * authentication hash is valid.
835 map
= auth_and_map_packet(ups
, start
, strlen(start
));
841 /* Check that we got a challenge string. */
842 cs
= lookup_key("CS", map
);
844 Dmsg0(200, "pcnet_ups_kill_power: Missing CS field\n");
850 * Now construct the hash of the packet we're about to
851 * send using the challenge string from the packet we
852 * just received, plus our username and passphrase.
855 md5_append(&ms
, (md5_byte_t
*)"macontrol1_control_shutdown_1=1,", 32);
856 md5_append(&ms
, (md5_byte_t
*)cs
, strlen(cs
));
857 md5_append(&ms
, (md5_byte_t
*)my_data
->user
, strlen(my_data
->user
));
858 md5_append(&ms
, (md5_byte_t
*)my_data
->pass
, strlen(my_data
->pass
));
859 md5_finish(&ms
, digest
);
860 hash
= digest2ascii(digest
);
862 /* Send the shutdown request */
863 asnprintf(data
, sizeof(data
),
864 "POST /Forms/macontrol1 HTTP/1.1\r\n"
866 "Content-Type: application/x-www-form-urlencoded\r\n"
867 "Content-Length: 72\r\n"
869 "macontrol1%%5fcontrol%%5fshutdown%%5f1=1%%2C%s",
870 my_data
->ipaddr
, hash
);
872 Dmsg2(200, "Request: (strlen=%d)\n---\n%s---\n", strlen(data
), data
);
874 if (send(s
, data
, strlen(data
), 0) != (int)strlen(data
)) {
875 Dmsg1(100, "pcnet_ups_kill_power: send failed: %s\n", strerror(errno
));
880 /* That's it, we're done. */
886 int pcnet_ups_program_eeprom(UPSINFO
*ups
, int command
, const char *data
)
892 int pcnet_ups_entry_point(UPSINFO
*ups
, int command
, void *data
)
897 case DEVICE_CMD_CHECK_SELFTEST
:
898 Dmsg0(80, "Checking self test.\n");
899 if (ups
->UPS_Cap
[CI_WHY_BATT
] && ups
->lastxfer
== XFER_SELFTEST
) {
901 * set Self Test start time
903 ups
->SelfTest
= time(NULL
);
904 Dmsg1(80, "Self Test time: %s", ctime(&ups
->SelfTest
));
908 case DEVICE_CMD_GET_SELFTEST_MSG
:
910 * This is a bit kludgy. The selftest result isn't available from
911 * the UPS for about 10 seconds after the selftest completes. So we
912 * invoke pcnet_ups_check_state() with a 12 second timeout,
913 * expecting that it should get a status report before then.
916 /* Save current ups->wait_time and set it to 12 seconds */
917 temp
= ups
->wait_time
;
920 /* Let check_status wait for the result */
922 pcnet_ups_check_state(ups
);
925 /* Restore ups->wait_time */
926 ups
->wait_time
= temp
;