2 apcsmart.c - driver for APC smart protocol units (originally "newapc")
4 Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
5 (C) 2000 Nigel Metheringham <Nigel.Metheringham@Intechnology.co.uk>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include "apcsmart-old.h"
26 #define DRIVER_NAME "APC Smart protocol driver"
27 #define DRIVER_VERSION "2.1"
29 static upsdrv_info_t table_info
= {
37 /* driver description structure */
38 upsdrv_info_t upsdrv_info
= {
41 "Russell Kroll <rkroll@exploits.org>\n"
42 "Nigel Metheringham <Nigel.Metheringham@Intechnology.co.uk>\n"
43 "Michal Soltys <soltys@ziu.info>",
48 #define ALT_CABLE_1 "940-0095B"
50 static int ups_status
= 0, quirk_capability_overflow
= 0;
52 static apc_vartab_t
*vartab_lookup_char(char cmdchar
)
56 for (i
= 0; apc_vartab
[i
].name
!= NULL
; i
++)
57 if (apc_vartab
[i
].cmd
== cmdchar
)
58 return &apc_vartab
[i
];
63 static apc_vartab_t
*vartab_lookup_name(const char *var
)
67 for (i
= 0; apc_vartab
[i
].name
!= NULL
; i
++)
68 if (!strcasecmp(apc_vartab
[i
].name
, var
))
69 return &apc_vartab
[i
];
74 /* FUTURE: change to use function pointers */
76 /* convert APC formatting to NUT formatting */
77 static const char *convert_data(apc_vartab_t
*cmd_entry
, const char *upsval
)
82 switch(cmd_entry
->flags
& APC_FORMATMASK
) {
92 /* no conversion for any of these */
96 /* convert to seconds */
98 tval
= 60 * 60 * strtol(upsval
, NULL
, 10);
100 snprintf(tmp
, sizeof(tmp
), "%d", tval
);
104 /* Convert to seconds - NUT standard time measurement */
105 tval
= 60 * strtol(upsval
, NULL
, 10);
106 /* Ignore errors - Theres not much we can do */
107 snprintf(tmp
, sizeof(tmp
), "%d", tval
);
112 case 'R': return "unacceptable utility voltage rate of change";
113 case 'H': return "high utility voltage";
114 case 'L': return "low utility voltage";
115 case 'T': return "line voltage notch or spike";
116 case 'O': return "no transfers yet since turnon";
117 case 'S': return "simulated power failure or UPS test";
118 default: return upsval
;
122 upslogx(LOG_NOTICE
, "Unable to handle conversion of %s", cmd_entry
->name
);
126 static void ups_status_set(void)
129 if (ups_status
& APC_STAT_CAL
)
130 status_set("CAL"); /* calibration */
131 if (ups_status
& APC_STAT_TRIM
)
132 status_set("TRIM"); /* SmartTrim */
133 if (ups_status
& APC_STAT_BOOST
)
134 status_set("BOOST"); /* SmartBoost */
135 if (ups_status
& APC_STAT_OL
)
136 status_set("OL"); /* on line */
137 if (ups_status
& APC_STAT_OB
)
138 status_set("OB"); /* on battery */
139 if (ups_status
& APC_STAT_OVER
)
140 status_set("OVER"); /* overload */
141 if (ups_status
& APC_STAT_LB
)
142 status_set("LB"); /* low battery */
143 if (ups_status
& APC_STAT_RB
)
144 status_set("RB"); /* replace batt */
152 static void alert_handler(char ch
)
155 case '!': /* clear OL, set OB */
156 upsdebugx(4, "alert_handler: OB");
157 ups_status
&= ~APC_STAT_OL
;
158 ups_status
|= APC_STAT_OB
;
161 case '$': /* clear OB, set OL */
162 upsdebugx(4, "alert_handler: OL");
163 ups_status
&= ~APC_STAT_OB
;
164 ups_status
|= APC_STAT_OL
;
167 case '%': /* set LB */
168 upsdebugx(4, "alert_handler: LB");
169 ups_status
|= APC_STAT_LB
;
172 case '+': /* clear LB */
173 upsdebugx(4, "alert_handler: not LB");
174 ups_status
&= ~APC_STAT_LB
;
177 case '#': /* set RB */
178 upsdebugx(4, "alert_handler: RB");
179 ups_status
|= APC_STAT_RB
;
182 case '?': /* set OVER */
183 upsdebugx(4, "alert_handler: OVER");
184 ups_status
|= APC_STAT_OVER
;
187 case '=': /* clear OVER */
188 upsdebugx(4, "alert_handler: not OVER");
189 ups_status
&= ~APC_STAT_OVER
;
193 upsdebugx(4, "alert_handler got 0x%02x (unhandled)", ch
);
200 static int read_buf(char *buf
, size_t buflen
)
204 ret
= ser_get_line_alert(upsfd
, buf
, buflen
, ENDCHAR
, POLL_IGNORE
,
205 POLL_ALERT
, alert_handler
, SER_WAIT_SEC
, SER_WAIT_USEC
);
208 ser_comm_fail("%s", ret
? strerror(errno
) : "timeout");
216 static int poll_data(apc_vartab_t
*vt
)
221 if ((vt
->flags
& APC_PRESENT
) == 0)
224 upsdebugx(4, "poll_data: %s", vt
->name
);
226 ret
= ser_send_char(upsfd
, vt
->cmd
);
229 upslogx(LOG_ERR
, "poll_data: ser_send_char failed");
234 if (read_buf(tmp
, sizeof(tmp
)) < 1) {
239 /* no longer supported by the hardware somehow */
240 if (!strcmp(tmp
, "NA")) {
241 dstate_delinfo(vt
->name
);
245 dstate_setinfo(vt
->name
, "%s", convert_data(vt
, tmp
));
251 /* check for support or just update a named variable */
252 static int query_ups(const char *var
, int first
)
259 vt
= vartab_lookup_name(var
);
262 upsdebugx(1, "query_ups: unknown variable %s", var
);
267 * not first run and already known to not be supported ?
269 if (!first
&& !(vt
->flags
& APC_PRESENT
))
272 /* empty the input buffer (while allowing the alert handler to run) */
273 ret
= ser_get_line_alert(upsfd
, temp
, sizeof(temp
), ENDCHAR
,
274 POLL_IGNORE
, POLL_ALERT
, alert_handler
, 0, 0);
276 ret
= ser_send_char(upsfd
, vt
->cmd
);
279 upslog_with_errno(LOG_ERR
, "query_ups: ser_send_char failed");
283 ret
= ser_get_line_alert(upsfd
, temp
, sizeof(temp
), ENDCHAR
,
284 POLL_IGNORE
, POLL_ALERT
, alert_handler
, SER_WAIT_SEC
,
287 if ((ret
< 1) && (first
== 0)) {
288 ser_comm_fail("%s", ret
? strerror(errno
) : "timeout");
294 if ((ret
< 1) || (!strcmp(temp
, "NA"))) /* not supported */
297 vt
->flags
|= APC_PRESENT
;
298 ptr
= convert_data(vt
, temp
);
299 dstate_setinfo(vt
->name
, "%s", ptr
);
301 return 1; /* success */
304 static void do_capabilities(void)
306 const char *ptr
, *entptr
;
307 char upsloc
, temp
[512], cmd
, loc
, etmp
[16], *endtemp
;
308 int nument
, entlen
, i
, matrix
, ret
, valid
;
311 upsdebugx(1, "APC - About to get capabilities string");
312 /* If we can do caps, then we need the Firmware revision which has
313 the locale descriptor as the last character (ugh)
315 ptr
= dstate_getinfo("ups.firmware");
317 upsloc
= ptr
[strlen(ptr
) - 1];
321 /* get capability string */
322 ret
= ser_send_char(upsfd
, APC_CAPABILITY
); /* ^Z */
325 upslog_with_errno(LOG_ERR
, "do_capabilities: ser_send_char failed");
329 /* note different IGN set since ^Z returns things like # */
330 ret
= ser_get_line(upsfd
, temp
, sizeof(temp
), ENDCHAR
,
331 MINIGNCHARS
, SER_WAIT_SEC
, SER_WAIT_USEC
);
333 if ((ret
< 1) || (!strcmp(temp
, "NA"))) {
335 /* Early Smart-UPS, not as smart as later ones */
336 /* This should never happen since we only call
337 this if the REQ_CAPABILITIES command is supported
339 upslogx(LOG_ERR
, "ERROR: APC cannot do capabilities but said it could!");
343 /* recv always puts a \0 at the end, so this is safe */
344 /* however it assumes a zero byte cannot be embedded */
345 endtemp
= &temp
[0] + strlen(temp
);
347 if (temp
[0] != '#') {
348 upsdebugx(1, "Unrecognized capability start char %c", temp
[0]);
349 upsdebugx(1, "Please report this error [%s]", temp
);
350 upslogx(LOG_ERR
, "ERROR: unknown capability start char %c!",
356 if (temp
[1] == '#') { /* Matrix-UPS */
365 /* command char, location, # of entries, entry length */
367 while (ptr
[0] != '\0') {
369 ptr
+= 2; /* jump over repeating ## */
371 /* check for idiocy */
372 if (ptr
>= endtemp
) {
374 /* if we expected this, just ignore it */
375 if (quirk_capability_overflow
)
379 "Capability string has overflowed\n"
380 "Please report this error\n"
381 "ERROR: capability overflow!"
387 nument
= ptr
[2] - 48;
388 entlen
= ptr
[3] - 48;
391 vt
= vartab_lookup_char(cmd
);
392 valid
= vt
&& ((loc
== upsloc
) || (loc
== '4'));
394 /* mark this as writable */
396 upsdebugx(1, "Supported capability: %02x (%c) - %s",
399 dstate_setflags(vt
->name
, ST_FLAG_RW
);
401 /* make sure setvar knows what this is */
402 vt
->flags
|= APC_RW
| APC_ENUM
;
405 for (i
= 0; i
< nument
; i
++) {
407 snprintf(etmp
, entlen
+ 1, "%s", entptr
);
408 dstate_addenum(vt
->name
, "%s", convert_data(vt
, etmp
));
418 static int update_status(void)
423 upsdebugx(4, "update_status");
425 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
427 ret
= ser_send_char(upsfd
, APC_STATUS
);
430 upslog_with_errno(LOG_ERR
, "update_status: ser_send_char failed");
435 ret
= read_buf(buf
, sizeof(buf
));
437 if ((ret
< 1) || (!strcmp(buf
, "NA"))) {
442 ups_status
= strtol(buf
, 0, 16) & 0xff;
450 static void oldapcsetup(void)
454 /* really old models ignore REQ_MODEL, so find them first */
455 ret
= query_ups("ups.model", 1);
458 /* force the model name */
459 dstate_setinfo("ups.model", "Smart-UPS");
462 /* see if this might be an old Matrix-UPS instead */
463 if (query_ups("output.current", 1))
464 dstate_setinfo("ups.model", "Matrix-UPS");
466 query_ups("ups.serial", 1);
467 query_ups("input.voltage", 1); /* This one may fail... no problem */
471 /* If we have come down this path then we dont do capabilities and
476 static void protocol_verify(unsigned char cmd
)
480 /* see if it's a variable */
481 for (i
= 0; apc_vartab
[i
].name
!= NULL
; i
++) {
483 /* 1:1 here, so the first match is the only match */
485 if (apc_vartab
[i
].cmd
== cmd
) {
486 upsdebugx(3, "UPS supports variable [%s]",
489 /* load initial data */
490 apc_vartab
[i
].flags
|= APC_PRESENT
;
491 poll_data(&apc_vartab
[i
]);
493 /* handle special data for our two strings */
494 if (apc_vartab
[i
].flags
& APC_STRING
) {
495 dstate_setflags(apc_vartab
[i
].name
,
496 ST_FLAG_RW
| ST_FLAG_STRING
);
497 dstate_setaux(apc_vartab
[i
].name
, APC_STRLEN
);
499 apc_vartab
[i
].flags
|= APC_RW
;
506 /* check the command list */
508 /* some cmdchars map onto multiple commands (start and stop) */
512 for (i
= 0; apc_cmdtab
[i
].name
!= NULL
; i
++) {
513 if (apc_cmdtab
[i
].cmd
== cmd
) {
514 upsdebugx(2, "UPS supports command [%s]",
517 dstate_addcmd(apc_cmdtab
[i
].name
);
519 apc_cmdtab
[i
].flags
|= APC_PRESENT
;
528 upsdebugx(1, "protocol_verify: 0x%02x [%c] unrecognized",
531 upsdebugx(1, "protocol_verify: 0x%02x unrecognized", cmd
);
534 /* some hardware is a special case - hotwire the list of cmdchars */
535 static int firmware_table_lookup(void)
541 upsdebugx(1, "Attempting firmware lookup using command 'V'");
543 ret
= ser_send_char(upsfd
, 'V');
546 upslog_with_errno(LOG_ERR
, "firmware_table_lookup: ser_send_char failed");
550 ret
= ser_get_line(upsfd
, buf
, sizeof(buf
), ENDCHAR
, IGNCHARS
,
551 SER_WAIT_SEC
, SER_WAIT_USEC
);
554 * Some UPSes support both 'V' and 'b'. As 'b' doesn't always return
555 * firmware version, we attempt that only if 'V' doesn't work.
557 if ((ret
< 1) || (!strcmp(buf
, "NA"))) {
558 upsdebugx(1, "Attempting firmware lookup using command 'b'");
559 ret
= ser_send_char(upsfd
, 'b');
562 upslog_with_errno(LOG_ERR
, "firmware_table_lookup: ser_send_char failed");
566 ret
= ser_get_line(upsfd
, buf
, sizeof(buf
), ENDCHAR
, IGNCHARS
,
567 SER_WAIT_SEC
, SER_WAIT_USEC
);
570 upslog_with_errno(LOG_ERR
, "firmware_table_lookup: ser_get_line failed");
575 upsdebugx(2, "Firmware: [%s]", buf
);
577 /* this will be reworked if we get a lot of these things */
578 if (!strcmp(buf
, "451.2.I")) {
579 quirk_capability_overflow
= 1;
583 for (i
= 0; compat_tab
[i
].firmware
!= NULL
; i
++) {
584 if (!strcmp(compat_tab
[i
].firmware
, buf
)) {
586 upsdebugx(2, "Matched - cmdchars: %s",
587 compat_tab
[i
].cmdchars
);
589 if (strspn(compat_tab
[i
].firmware
, "05")) {
590 dstate_setinfo("ups.model", "Matrix-UPS");
592 dstate_setinfo("ups.model", "Smart-UPS");
595 /* matched - run the cmdchars from the table */
596 for (j
= 0; j
< strlen(compat_tab
[i
].cmdchars
); j
++)
597 protocol_verify(compat_tab
[i
].cmdchars
[j
]);
599 return 1; /* matched */
603 upsdebugx(2, "Not found in table - trying normal method");
607 static void getbaseinfo(void)
611 char *alrts
, *cmds
, temp
[512];
614 * try firmware lookup first; we could start with 'a', but older models
615 * sometimes return other things than a command set
617 if (firmware_table_lookup() == 1)
620 upsdebugx(1, "APC - Attempting to find command set");
621 /* Initially we ask the UPS what commands it takes
622 If this fails we are going to need an alternate
623 strategy - we can deal with that if it happens
626 ret
= ser_send_char(upsfd
, APC_CMDSET
);
629 upslog_with_errno(LOG_ERR
, "getbaseinfo: ser_send_char failed");
633 ret
= ser_get_line(upsfd
, temp
, sizeof(temp
), ENDCHAR
, IGNCHARS
,
634 SER_WAIT_SEC
, SER_WAIT_USEC
);
636 if ((ret
< 1) || (!strcmp(temp
, "NA"))) {
637 /* We have an old dumb UPS - go to specific code for old stuff */
642 upsdebugx(1, "APC - Parsing out command set");
643 /* We have the version.alert.cmdchars string
644 NB the alert chars are normally in IGNCHARS
645 so will have been pretty much edited out.
646 You will need to change the ser_get_line above if
647 you want to check those out too....
649 alrts
= strchr(temp
, '.');
651 fatalx(EXIT_FAILURE
, "Unable to split APC version string");
655 cmds
= strchr(alrts
, '.');
657 fatalx(EXIT_FAILURE
, "Unable to find APC command string");
661 for (i
= 0; i
< strlen(cmds
); i
++)
662 protocol_verify(cmds
[i
]);
664 /* if capabilities are supported, add them here */
665 if (strchr(cmds
, APC_CAPABILITY
))
668 upsdebugx(1, "APC - UPS capabilities determined");
671 /* check for calibration status and either start or stop */
672 static int do_cal(int start
)
677 ret
= ser_send_char(upsfd
, APC_STATUS
);
680 upslog_with_errno(LOG_ERR
, "do_cal: ser_send_char failed");
681 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
684 ret
= read_buf(temp
, sizeof(temp
));
686 /* if we can't check the current calibration status, bail out */
687 if ((ret
< 1) || (!strcmp(temp
, "NA")))
688 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
690 tval
= strtol(temp
, 0, 16);
692 if (tval
& APC_STAT_CAL
) { /* calibration currently happening */
694 /* requested start while calibration still running */
695 upslogx(LOG_INFO
, "Runtime calibration already in progress");
696 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
701 upslogx(LOG_INFO
, "Stopping runtime calibration");
703 ret
= ser_send_char(upsfd
, APC_CMD_CALTOGGLE
);
706 upslog_with_errno(LOG_ERR
, "do_cal: ser_send_char failed");
707 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
710 ret
= read_buf(temp
, sizeof(temp
));
712 if ((ret
< 1) || (!strcmp(temp
, "NA")) || (!strcmp(temp
, "NO"))) {
713 upslogx(LOG_WARNING
, "Stop calibration failed: %s",
715 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
718 return STAT_INSTCMD_HANDLED
; /* FUTURE: success */
721 /* calibration not happening */
723 if (start
== 0) { /* stop requested */
724 upslogx(LOG_INFO
, "Runtime calibration not occurring");
725 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
728 upslogx(LOG_INFO
, "Starting runtime calibration");
730 ret
= ser_send_char(upsfd
, APC_CMD_CALTOGGLE
);
733 upslog_with_errno(LOG_ERR
, "do_cal: ser_send_char failed");
734 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
737 ret
= read_buf(temp
, sizeof(temp
));
739 if ((ret
< 1) || (!strcmp(temp
, "NA")) || (!strcmp(temp
, "NO"))) {
740 upslogx(LOG_WARNING
, "Start calibration failed: %s", temp
);
741 return STAT_INSTCMD_HANDLED
; /* FUTURE: failure */
744 return STAT_INSTCMD_HANDLED
; /* FUTURE: success */
747 /* get the UPS talking to us in smart mode */
748 static int smartmode(void)
753 for (tries
= 0; tries
< 5; tries
++) {
755 ret
= ser_send_char(upsfd
, APC_GOSMART
);
758 upslog_with_errno(LOG_ERR
, "smartmode: ser_send_char failed");
762 ret
= ser_get_line(upsfd
, temp
, sizeof(temp
), ENDCHAR
,
763 IGNCHARS
, SER_WAIT_SEC
, SER_WAIT_USEC
);
766 if (!strcmp(temp
, "SM"))
767 return 1; /* success */
769 sleep(1); /* wait before trying again */
771 /* it failed, so try to bail out of menus on newer units */
773 ret
= ser_send_char(upsfd
, 27); /* ESC */
776 upslog_with_errno(LOG_ERR
, "smartmode: ser_send_char failed");
780 /* eat the response (might be NA, might be something else) */
781 ret
= ser_get_line(upsfd
, temp
, sizeof(temp
), ENDCHAR
,
782 IGNCHARS
, SER_WAIT_SEC
, SER_WAIT_USEC
);
785 return 0; /* failure */
789 * all shutdown commands should respond with 'OK' or '*'
791 static int sdok(void)
795 ser_get_line(upsfd
, temp
, sizeof(temp
), ENDCHAR
, IGNCHARS
, SER_WAIT_SEC
, SER_WAIT_USEC
);
796 upsdebugx(4, "sdok: got \"%s\"", temp
);
798 if (!strcmp(temp
, "*") || !strcmp(temp
, "OK")) {
799 upsdebugx(4, "Last issued shutdown command succeeded");
803 upsdebugx(1, "Last issued shutdown command failed");
807 /* soft hibernate: S - working only when OB, otherwise ignored */
808 static int sdcmd_S(int dummy
)
810 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
812 upsdebugx(1, "Issuing soft hibernate");
813 ser_send_char(upsfd
, APC_CMD_SOFTDOWN
);
818 /* soft hibernate, hack version for CS 350 */
819 static int sdcmd_CS(int tval
)
821 upsdebugx(1, "Using CS 350 'force OB' shutdown method");
822 if (tval
& APC_STAT_OL
) {
823 upsdebugx(1, "On-line - forcing OB temporarily");
824 ser_send_char(upsfd
, 'U');
827 return sdcmd_S(tval
);
831 * hard hibernate: @nnn / @nn
832 * note: works differently for older and new models, see help function for
835 static int sdcmd_ATn(int cnt
)
837 int n
= 0, mmax
, ret
;
841 mmax
= cnt
== 2 ? 99 : 999;
843 if ((strval
= getval("wugrace"))) {
845 n
= strtol(strval
, NULL
, 10);
846 if (errno
|| n
< 0 || n
> mmax
)
850 snprintf(timer
, sizeof(timer
), "%.*d", cnt
, n
);
852 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
853 upsdebugx(1, "Issuing hard hibernate with %d minutes additional wakeup delay", n
*6);
855 ser_send_char(upsfd
, APC_CMD_GRACEDOWN
);
856 usleep(CMDLONGDELAY
);
857 ser_send_pace(upsfd
, UPSDELAY
, "%s", timer
);
864 * "tricky" part - we tried @nn variation and it (unsurprisingly)
865 * failed; we have to abort the sequence with something bogus to have
866 * the clean state; newer upses will respond with 'NO', older will be
869 ser_send_char(upsfd
, APC_CMD_GRACEDOWN
);
871 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
876 /* shutdown: K - delayed poweroff */
877 static int sdcmd_K(int dummy
)
879 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
880 upsdebugx(1, "Issuing delayed poweroff");
882 ser_send_char(upsfd
, APC_CMD_SHUTDOWN
);
883 usleep(CMDLONGDELAY
);
884 ser_send_char(upsfd
, APC_CMD_SHUTDOWN
);
889 /* shutdown: Z - immediate poweroff */
890 static int sdcmd_Z(int dummy
)
892 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
893 upsdebugx(1, "Issuing immediate poweroff");
895 ser_send_char(upsfd
, APC_CMD_OFF
);
896 usleep(CMDLONGDELAY
);
897 ser_send_char(upsfd
, APC_CMD_OFF
);
902 static int (*sdlist
[])(int) = {
904 sdcmd_ATn
, /* for @nnn version */
908 sdcmd_ATn
, /* for @nn version */
920 static void upsdrv_shutdown_simple(int status
)
922 unsigned int sdtype
= 0;
925 if ((strval
= getval("sdtype"))) {
927 sdtype
= strtol(strval
, NULL
, 10);
928 if (errno
|| sdtype
< 0 || sdtype
> 6)
934 case 6: /* hard hibernate */
937 case 5: /* "hack nn" hard hibernate */
940 case 4: /* special hack for CS 350 and similar models */
944 case 3: /* delayed poweroff */
948 case 2: /* instant poweroff */
953 * Send a combined set of shutdown commands which can work
954 * better if the UPS gets power during shutdown process
955 * Specifically it sends both the soft shutdown 'S' and the
956 * hard hibernate '@nnn' commands
958 upsdebugx(1, "UPS - currently %s - sending soft/hard hibernate commands",
959 (status
& APC_STAT_OL
) ? "on-line" : "on battery");
961 /* S works only when OB */
962 if ((status
& APC_STAT_OB
) && sdcmd_S(0))
969 * Send @nnn or S, depending on OB / OL status
971 if (status
& APC_STAT_OL
) /* on line */
978 static void upsdrv_shutdown_advanced(int status
)
981 const char deforder
[] = {48 + SDIDX_S
,
989 strval
= getval("advorder");
991 /* sanitize advorder */
993 if (!strval
|| !strlen(strval
) || strlen(strval
) > SDCNT
)
995 for (i
= 0; i
< strlen(strval
); i
++) {
996 if (strval
[i
] - 48 < 0 || strval
[i
] - 48 >= SDCNT
) {
1003 * try each method in the list with a little bit of handling in certain
1007 for (i
= 0; i
< strlen(strval
); i
++) {
1008 switch (strval
[i
] - 48) {
1020 if (sdlist
[strval
[i
] - 48](n
))
1021 break; /* finish if command succeeded */
1025 /* power down the attached load immediately */
1026 void upsdrv_shutdown(void)
1032 upsdebugx(1, "SM detection failed. Trying a shutdown command anyway");
1034 /* check the line status */
1036 ret
= ser_send_char(upsfd
, APC_STATUS
);
1039 ret
= ser_get_line(upsfd
, temp
, sizeof(temp
), ENDCHAR
,
1040 IGNCHARS
, SER_WAIT_SEC
, SER_WAIT_USEC
);
1043 upsdebugx(1, "Status read failed ! Assuming on battery state");
1044 status
= APC_STAT_LB
| APC_STAT_OB
;
1046 status
= strtol(temp
, 0, 16);
1050 upsdebugx(1, "Status request failed; assuming on battery state");
1051 status
= APC_STAT_LB
| APC_STAT_OB
;
1054 if (testvar("advorder") && strcasecmp(getval("advorder"), "no"))
1055 upsdrv_shutdown_advanced(status
);
1057 upsdrv_shutdown_simple(status
);
1060 /* 940-0095B support: set DTR, lower RTS */
1061 static void init_serial_0095B(void)
1063 ser_set_dtr(upsfd
, 1);
1064 ser_set_rts(upsfd
, 0);
1067 static void update_info_normal(void)
1071 upsdebugx(3, "update_info_normal: starting");
1073 for (i
= 0; apc_vartab
[i
].name
!= NULL
; i
++) {
1074 if ((apc_vartab
[i
].flags
& APC_POLL
) == 0)
1077 if (!poll_data(&apc_vartab
[i
])) {
1078 upsdebugx(3, "update_info_normal: poll_data (%s) failed - "
1079 "aborting scan", apc_vartab
[i
].name
);
1084 upsdebugx(3, "update_info_normal: done");
1087 static void update_info_all(void)
1091 upsdebugx(3, "update_info_all: starting");
1093 for (i
= 0; apc_vartab
[i
].name
!= NULL
; i
++) {
1094 if (!poll_data(&apc_vartab
[i
])) {
1095 upsdebugx(3, "update_info_all: poll_data (%s) failed - "
1096 "aborting scan", apc_vartab
[i
].name
);
1101 upsdebugx(3, "update_info_all: done");
1104 static int setvar_enum(apc_vartab_t
*vt
, const char *val
)
1107 char orig
[256], temp
[256];
1110 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
1111 ret
= ser_send_char(upsfd
, vt
->cmd
);
1114 upslog_with_errno(LOG_ERR
, "setvar_enum: ser_send_char failed");
1115 return STAT_SET_HANDLED
; /* FUTURE: failed */
1118 ret
= read_buf(orig
, sizeof(orig
));
1120 if ((ret
< 1) || (!strcmp(orig
, "NA")))
1121 return STAT_SET_HANDLED
; /* FUTURE: failed */
1123 ptr
= convert_data(vt
, orig
);
1125 /* suppress redundant changes - easier on the eeprom */
1126 if (!strcmp(ptr
, val
)) {
1127 upslogx(LOG_INFO
, "Ignoring enum SET %s='%s' (unchanged value)",
1130 return STAT_SET_HANDLED
; /* FUTURE: no change */
1133 for (i
= 0; i
< 6; i
++) {
1134 ret
= ser_send_char(upsfd
, APC_NEXTVAL
);
1137 upslog_with_errno(LOG_ERR
, "setvar_enum: ser_send_char failed");
1138 return STAT_SET_HANDLED
; /* FUTURE: failed */
1141 /* this should return either OK (if rotated) or NO (if not) */
1142 ret
= read_buf(temp
, sizeof(temp
));
1144 if ((ret
< 1) || (!strcmp(temp
, "NA")))
1145 return STAT_SET_HANDLED
; /* FUTURE: failed */
1148 if (!strcmp(temp
, "NO"))
1149 return STAT_SET_HANDLED
; /* FUTURE: failed */
1150 if (strcmp(temp
, "OK") != 0)
1151 return STAT_SET_HANDLED
; /* FUTURE: failed */
1153 /* see what it rotated onto */
1154 ret
= ser_send_char(upsfd
, vt
->cmd
);
1157 upslog_with_errno(LOG_ERR
, "setvar_enum: ser_send_char failed");
1158 return STAT_SET_HANDLED
; /* FUTURE: failed */
1161 ret
= read_buf(temp
, sizeof(temp
));
1163 if ((ret
< 1) || (!strcmp(temp
, "NA")))
1164 return STAT_SET_HANDLED
; /* FUTURE: failed */
1166 ptr
= convert_data(vt
, temp
);
1168 upsdebugx(1, "Rotate value: got [%s], want [%s]",
1171 if (!strcmp(ptr
, val
)) { /* got it */
1172 upslogx(LOG_INFO
, "SET %s='%s'", vt
->name
, val
);
1174 /* refresh data from the hardware */
1175 query_ups(vt
->name
, 0);
1177 return STAT_SET_HANDLED
; /* FUTURE: success */
1180 /* check for wraparound */
1181 if (!strcmp(ptr
, orig
)) {
1182 upslogx(LOG_ERR
, "setvar: variable %s wrapped",
1185 return STAT_SET_HANDLED
; /* FUTURE: failed */
1189 upslogx(LOG_ERR
, "setvar: gave up after 6 tries for %s",
1192 /* refresh data from the hardware */
1193 query_ups(vt
->name
, 0);
1195 return STAT_SET_HANDLED
;
1198 static int setvar_string(apc_vartab_t
*vt
, const char *val
)
1204 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
1205 ret
= ser_send_char(upsfd
, vt
->cmd
);
1208 upslog_with_errno(LOG_ERR
, "setvar_string: ser_send_char failed");
1209 return STAT_SET_HANDLED
; /* FUTURE: failed */
1212 ret
= read_buf(temp
, sizeof(temp
));
1214 if ((ret
< 1) || (!strcmp(temp
, "NA")))
1215 return STAT_SET_HANDLED
; /* FUTURE: failed */
1217 /* suppress redundant changes - easier on the eeprom */
1218 if (!strcmp(temp
, val
)) {
1219 upslogx(LOG_INFO
, "Ignoring string SET %s='%s' (unchanged value)",
1222 return STAT_SET_HANDLED
; /* FUTURE: no change */
1225 ret
= ser_send_char(upsfd
, APC_NEXTVAL
);
1228 upslog_with_errno(LOG_ERR
, "setvar_string: ser_send_char failed");
1229 return STAT_SET_HANDLED
; /* FUTURE: failed */
1234 for (i
= 0; i
< strlen(val
); i
++) {
1235 ret
= ser_send_char(upsfd
, val
[i
]);
1238 upslog_with_errno(LOG_ERR
, "setvar_string: ser_send_char failed");
1239 return STAT_SET_HANDLED
; /* FUTURE: failed */
1245 /* pad to 8 chars with CRs */
1246 for (i
= strlen(val
); i
< APC_STRLEN
; i
++) {
1247 ret
= ser_send_char(upsfd
, 13);
1250 upslog_with_errno(LOG_ERR
, "setvar_string: ser_send_char failed");
1251 return STAT_SET_HANDLED
; /* FUTURE: failed */
1257 ret
= read_buf(temp
, sizeof(temp
));
1260 upslogx(LOG_ERR
, "setvar_string: short final read");
1261 return STAT_SET_HANDLED
; /* FUTURE: failed */
1264 if (!strcmp(temp
, "NO")) {
1265 upslogx(LOG_ERR
, "setvar_string: got NO at final read");
1266 return STAT_SET_HANDLED
; /* FUTURE: failed */
1269 /* refresh data from the hardware */
1270 query_ups(vt
->name
, 0);
1272 upslogx(LOG_INFO
, "SET %s='%s'", vt
->name
, val
);
1274 return STAT_SET_HANDLED
; /* FUTURE: failed */
1277 static int setvar(const char *varname
, const char *val
)
1281 vt
= vartab_lookup_name(varname
);
1284 return STAT_SET_UNKNOWN
;
1286 if ((vt
->flags
& APC_RW
) == 0) {
1287 upslogx(LOG_WARNING
, "setvar: [%s] is not writable", varname
);
1288 return STAT_SET_UNKNOWN
;
1291 if (vt
->flags
& APC_ENUM
)
1292 return setvar_enum(vt
, val
);
1294 if (vt
->flags
& APC_STRING
)
1295 return setvar_string(vt
, val
);
1297 upslogx(LOG_WARNING
, "setvar: Unknown type for [%s]", varname
);
1298 return STAT_SET_UNKNOWN
;
1301 /* actually send the instcmd's char to the ups */
1302 static int do_cmd(apc_cmdtab_t
*ct
)
1307 ser_flush_in(upsfd
, IGNCHARS
, nut_debug_level
);
1308 ret
= ser_send_char(upsfd
, ct
->cmd
);
1311 upslog_with_errno(LOG_ERR
, "do_cmd: ser_send_char failed");
1312 return STAT_INSTCMD_HANDLED
; /* FUTURE: failed */
1315 /* some commands have to be sent twice with a 1.5s gap */
1316 if (ct
->flags
& APC_REPEAT
) {
1317 usleep(CMDLONGDELAY
);
1319 ret
= ser_send_char(upsfd
, ct
->cmd
);
1322 upslog_with_errno(LOG_ERR
, "do_cmd: ser_send_char failed");
1323 return STAT_INSTCMD_HANDLED
; /* FUTURE: failed */
1327 ret
= read_buf(buf
, sizeof(buf
));
1330 return STAT_INSTCMD_HANDLED
; /* FUTURE: failed */
1332 if (strcmp(buf
, "OK") != 0) {
1333 upslogx(LOG_WARNING
, "Got [%s] after command [%s]",
1336 return STAT_INSTCMD_HANDLED
; /* FUTURE: failed */
1339 upslogx(LOG_INFO
, "Command: %s", ct
->name
);
1340 return STAT_INSTCMD_HANDLED
; /* FUTURE: success */
1343 /* some commands must be repeated in a window to execute */
1344 static int instcmd_chktime(apc_cmdtab_t
*ct
)
1348 static time_t last
= 0;
1352 elapsed
= difftime(now
, last
);
1355 /* you have to hit this in a small window or it fails */
1356 if ((elapsed
< MINCMDTIME
) || (elapsed
> MAXCMDTIME
)) {
1357 upsdebugx(1, "instcmd_chktime: outside window for %s (%2.0f)",
1359 return STAT_INSTCMD_HANDLED
; /* FUTURE: again */
1365 static int instcmd(const char *cmdname
, const char *extra
)
1372 for (i
= 0; apc_cmdtab
[i
].name
!= NULL
; i
++)
1373 if (!strcasecmp(apc_cmdtab
[i
].name
, cmdname
))
1374 ct
= &apc_cmdtab
[i
];
1377 upslogx(LOG_WARNING
, "instcmd: unknown command [%s]", cmdname
);
1378 return STAT_INSTCMD_UNKNOWN
;
1381 if ((ct
->flags
& APC_PRESENT
) == 0) {
1382 upslogx(LOG_WARNING
, "instcmd: command [%s] is not supported",
1384 return STAT_INSTCMD_UNKNOWN
;
1387 if (!strcasecmp(cmdname
, "calibrate.start"))
1390 if (!strcasecmp(cmdname
, "calibrate.stop"))
1393 if (ct
->flags
& APC_NASTY
)
1394 return instcmd_chktime(ct
);
1396 /* nothing special here */
1400 /* install pointers to functions for msg handlers called from msgparse */
1401 static void setuphandlers(void)
1403 upsh
.setvar
= setvar
;
1404 upsh
.instcmd
= instcmd
;
1407 /* functions that interface with main.c */
1409 void upsdrv_makevartable(void)
1411 addvar(VAR_VALUE
, "cable", "Specify alternate cable (940-0095B)");
1412 addvar(VAR_VALUE
, "wugrace", "Hard hibernate's wakeup grace");
1413 addvar(VAR_VALUE
, "sdtype", "Specify simple shutdown method (0-6)");
1414 addvar(VAR_VALUE
, "advorder", "Enable advanced shutdown control");
1417 void upsdrv_initups(void)
1421 upsfd
= ser_open(device_path
);
1422 ser_set_speed(upsfd
, device_path
, B2400
);
1424 cable
= getval("cable");
1426 if (cable
&& !strcasecmp(cable
, ALT_CABLE_1
))
1427 init_serial_0095B();
1429 /* make sure we wake up if the UPS sends alert chars to us */
1433 void upsdrv_help(void)
1437 void upsdrv_initinfo(void)
1439 const char *pmod
, *pser
;
1442 fatalx(EXIT_FAILURE
,
1443 "Unable to detect an APC Smart protocol UPS on port %s\n"
1444 "Check the cabling, port name or model name and try again", device_path
1448 /* manufacturer ID - hardcoded in this particular module */
1449 dstate_setinfo("ups.mfr", "APC");
1453 if (!(pmod
= dstate_getinfo("ups.model")))
1454 pmod
= "\"unknown model\"";
1455 if (!(pser
= dstate_getinfo("ups.serial")))
1456 pser
= "unknown serial";
1458 upsdebugx(1, "Detected %s [%s] on %s", pmod
, pser
, device_path
);
1463 void upsdrv_updateinfo(void)
1465 static time_t last_full
= 0;
1468 /* try to wake up a dead ups once in awhile */
1469 if ((dstate_is_stale()) && (!smartmode())) {
1470 ser_comm_fail("Communications with UPS lost - check cabling");
1472 /* reset this so a full update runs when the UPS returns */
1479 if (!update_status())
1484 /* refresh all variables hourly */
1485 /* does not catch measure-ups II insertion/removal */
1486 if (difftime(now
, last_full
) > 3600) {
1492 update_info_normal();
1495 void upsdrv_cleanup(void)
1497 /* try to bring the UPS out of smart mode */
1498 ser_send_char(upsfd
, APC_GODUMB
);
1500 ser_close(upsfd
, device_path
);