apcupsd-ups: ignore generated files
[networkupstools/kirr.git] / drivers / apcsmart-old.c
blob5c06372ee5f488fd2e25b25a416e0606442b19e2
1 /*
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
22 #include "main.h"
23 #include "serial.h"
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 = {
30 "APC command table",
31 APC_TABLE_VERSION,
32 NULL,
34 { NULL }
37 /* driver description structure */
38 upsdrv_info_t upsdrv_info = {
39 DRIVER_NAME,
40 DRIVER_VERSION,
41 "Russell Kroll <rkroll@exploits.org>\n"
42 "Nigel Metheringham <Nigel.Metheringham@Intechnology.co.uk>\n"
43 "Michal Soltys <soltys@ziu.info>",
44 DRV_STABLE,
45 { &table_info, NULL }
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)
54 int i;
56 for (i = 0; apc_vartab[i].name != NULL; i++)
57 if (apc_vartab[i].cmd == cmdchar)
58 return &apc_vartab[i];
60 return NULL;
63 static apc_vartab_t *vartab_lookup_name(const char *var)
65 int i;
67 for (i = 0; apc_vartab[i].name != NULL; i++)
68 if (!strcasecmp(apc_vartab[i].name, var))
69 return &apc_vartab[i];
71 return NULL;
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)
79 static char tmp[128];
80 int tval;
82 switch(cmd_entry->flags & APC_FORMATMASK) {
83 case APC_F_PERCENT:
84 case APC_F_VOLT:
85 case APC_F_AMP:
86 case APC_F_CELSIUS:
87 case APC_F_HEX:
88 case APC_F_DEC:
89 case APC_F_SECONDS:
90 case APC_F_LEAVE:
92 /* no conversion for any of these */
93 return upsval;
95 case APC_F_HOURS:
96 /* convert to seconds */
98 tval = 60 * 60 * strtol(upsval, NULL, 10);
100 snprintf(tmp, sizeof(tmp), "%d", tval);
101 return tmp;
103 case APC_F_MINUTES:
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);
108 return tmp;
110 case APC_F_REASON:
111 switch (upsval[0]) {
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);
123 return upsval;
126 static void ups_status_set(void)
128 status_init();
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 */
146 if (ups_status == 0)
147 status_set("OFF");
149 status_commit();
152 static void alert_handler(char ch)
154 switch (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;
159 break;
161 case '$': /* clear OB, set OL */
162 upsdebugx(4, "alert_handler: OL");
163 ups_status &= ~APC_STAT_OB;
164 ups_status |= APC_STAT_OL;
165 break;
167 case '%': /* set LB */
168 upsdebugx(4, "alert_handler: LB");
169 ups_status |= APC_STAT_LB;
170 break;
172 case '+': /* clear LB */
173 upsdebugx(4, "alert_handler: not LB");
174 ups_status &= ~APC_STAT_LB;
175 break;
177 case '#': /* set RB */
178 upsdebugx(4, "alert_handler: RB");
179 ups_status |= APC_STAT_RB;
180 break;
182 case '?': /* set OVER */
183 upsdebugx(4, "alert_handler: OVER");
184 ups_status |= APC_STAT_OVER;
185 break;
187 case '=': /* clear OVER */
188 upsdebugx(4, "alert_handler: not OVER");
189 ups_status &= ~APC_STAT_OVER;
190 break;
192 default:
193 upsdebugx(4, "alert_handler got 0x%02x (unhandled)", ch);
194 break;
197 ups_status_set();
200 static int read_buf(char *buf, size_t buflen)
202 int ret;
204 ret = ser_get_line_alert(upsfd, buf, buflen, ENDCHAR, POLL_IGNORE,
205 POLL_ALERT, alert_handler, SER_WAIT_SEC, SER_WAIT_USEC);
207 if (ret < 1) {
208 ser_comm_fail("%s", ret ? strerror(errno) : "timeout");
209 return ret;
212 ser_comm_good();
213 return ret;
216 static int poll_data(apc_vartab_t *vt)
218 int ret;
219 char tmp[SMALLBUF];
221 if ((vt->flags & APC_PRESENT) == 0)
222 return 1;
224 upsdebugx(4, "poll_data: %s", vt->name);
226 ret = ser_send_char(upsfd, vt->cmd);
228 if (ret != 1) {
229 upslogx(LOG_ERR, "poll_data: ser_send_char failed");
230 dstate_datastale();
231 return 0;
234 if (read_buf(tmp, sizeof(tmp)) < 1) {
235 dstate_datastale();
236 return 0;
239 /* no longer supported by the hardware somehow */
240 if (!strcmp(tmp, "NA")) {
241 dstate_delinfo(vt->name);
242 return 1;
245 dstate_setinfo(vt->name, "%s", convert_data(vt, tmp));
246 dstate_dataok();
248 return 1;
251 /* check for support or just update a named variable */
252 static int query_ups(const char *var, int first)
254 int ret;
255 char temp[256];
256 const char *ptr;
257 apc_vartab_t *vt;
259 vt = vartab_lookup_name(var);
261 if (!vt) {
262 upsdebugx(1, "query_ups: unknown variable %s", var);
263 return 0;
267 * not first run and already known to not be supported ?
269 if (!first && !(vt->flags & APC_PRESENT))
270 return 0;
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);
278 if (ret != 1) {
279 upslog_with_errno(LOG_ERR, "query_ups: ser_send_char failed");
280 return 0;
283 ret = ser_get_line_alert(upsfd, temp, sizeof(temp), ENDCHAR,
284 POLL_IGNORE, POLL_ALERT, alert_handler, SER_WAIT_SEC,
285 SER_WAIT_USEC);
287 if ((ret < 1) && (first == 0)) {
288 ser_comm_fail("%s", ret ? strerror(errno) : "timeout");
289 return 0;
292 ser_comm_good();
294 if ((ret < 1) || (!strcmp(temp, "NA"))) /* not supported */
295 return 0;
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;
309 apc_vartab_t *vt;
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");
316 if (ptr)
317 upsloc = ptr[strlen(ptr) - 1];
318 else
319 upsloc = 0;
321 /* get capability string */
322 ret = ser_send_char(upsfd, APC_CAPABILITY); /* ^Z */
324 if (ret != 1) {
325 upslog_with_errno(LOG_ERR, "do_capabilities: ser_send_char failed");
326 return;
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!");
340 return;
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!",
351 temp[0]);
353 return;
356 if (temp[1] == '#') { /* Matrix-UPS */
357 matrix = 1;
358 ptr = &temp[0];
360 else {
361 ptr = &temp[1];
362 matrix = 0;
365 /* command char, location, # of entries, entry length */
367 while (ptr[0] != '\0') {
368 if (matrix)
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)
376 return;
378 fatalx(EXIT_FAILURE,
379 "Capability string has overflowed\n"
380 "Please report this error\n"
381 "ERROR: capability overflow!"
385 cmd = ptr[0];
386 loc = ptr[1];
387 nument = ptr[2] - 48;
388 entlen = ptr[3] - 48;
389 entptr = &ptr[4];
391 vt = vartab_lookup_char(cmd);
392 valid = vt && ((loc == upsloc) || (loc == '4'));
394 /* mark this as writable */
395 if (valid) {
396 upsdebugx(1, "Supported capability: %02x (%c) - %s",
397 cmd, loc, vt->name);
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++) {
406 if (valid) {
407 snprintf(etmp, entlen + 1, "%s", entptr);
408 dstate_addenum(vt->name, "%s", convert_data(vt, etmp));
411 entptr += entlen;
414 ptr = entptr;
418 static int update_status(void)
420 int ret;
421 char buf[SMALLBUF];
423 upsdebugx(4, "update_status");
425 ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
427 ret = ser_send_char(upsfd, APC_STATUS);
429 if (ret != 1) {
430 upslog_with_errno(LOG_ERR, "update_status: ser_send_char failed");
431 dstate_datastale();
432 return 0;
435 ret = read_buf(buf, sizeof(buf));
437 if ((ret < 1) || (!strcmp(buf, "NA"))) {
438 dstate_datastale();
439 return 0;
442 ups_status = strtol(buf, 0, 16) & 0xff;
443 ups_status_set();
445 dstate_dataok();
447 return 1;
450 static void oldapcsetup(void)
452 int ret = 0;
454 /* really old models ignore REQ_MODEL, so find them first */
455 ret = query_ups("ups.model", 1);
457 if (ret != 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 */
469 update_status();
471 /* If we have come down this path then we dont do capabilities and
472 other shiny features
476 static void protocol_verify(unsigned char cmd)
478 int i, found;
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]",
487 apc_vartab[i].name);
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;
502 return;
506 /* check the command list */
508 /* some cmdchars map onto multiple commands (start and stop) */
510 found = 0;
512 for (i = 0; apc_cmdtab[i].name != NULL; i++) {
513 if (apc_cmdtab[i].cmd == cmd) {
514 upsdebugx(2, "UPS supports command [%s]",
515 apc_cmdtab[i].name);
517 dstate_addcmd(apc_cmdtab[i].name);
519 apc_cmdtab[i].flags |= APC_PRESENT;
520 found = 1;
524 if (found)
525 return;
527 if (isprint(cmd))
528 upsdebugx(1, "protocol_verify: 0x%02x [%c] unrecognized",
529 cmd, cmd);
530 else
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)
537 int ret;
538 unsigned int i, j;
539 char buf[SMALLBUF];
541 upsdebugx(1, "Attempting firmware lookup using command 'V'");
543 ret = ser_send_char(upsfd, 'V');
545 if (ret != 1) {
546 upslog_with_errno(LOG_ERR, "firmware_table_lookup: ser_send_char failed");
547 return 0;
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');
561 if (ret != 1) {
562 upslog_with_errno(LOG_ERR, "firmware_table_lookup: ser_send_char failed");
563 return 0;
566 ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, IGNCHARS,
567 SER_WAIT_SEC, SER_WAIT_USEC);
569 if (ret < 1) {
570 upslog_with_errno(LOG_ERR, "firmware_table_lookup: ser_get_line failed");
571 return 0;
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;
580 return 0;
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");
591 } else {
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");
604 return 0;
607 static void getbaseinfo(void)
609 unsigned int i;
610 int ret = 0;
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)
618 return;
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);
628 if (ret != 1) {
629 upslog_with_errno(LOG_ERR, "getbaseinfo: ser_send_char failed");
630 return;
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 */
638 oldapcsetup();
639 return;
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, '.');
650 if (alrts == NULL) {
651 fatalx(EXIT_FAILURE, "Unable to split APC version string");
653 *alrts++ = 0;
655 cmds = strchr(alrts, '.');
656 if (cmds == NULL) {
657 fatalx(EXIT_FAILURE, "Unable to find APC command string");
659 *cmds++ = 0;
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))
666 do_capabilities();
668 upsdebugx(1, "APC - UPS capabilities determined");
671 /* check for calibration status and either start or stop */
672 static int do_cal(int start)
674 char temp[256];
675 int tval, ret;
677 ret = ser_send_char(upsfd, APC_STATUS);
679 if (ret != 1) {
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 */
693 if (start == 1) {
694 /* requested start while calibration still running */
695 upslogx(LOG_INFO, "Runtime calibration already in progress");
696 return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
699 /* stop requested */
701 upslogx(LOG_INFO, "Stopping runtime calibration");
703 ret = ser_send_char(upsfd, APC_CMD_CALTOGGLE);
705 if (ret != 1) {
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",
714 temp);
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);
732 if (ret != 1) {
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)
750 int ret, tries;
751 char temp[256];
753 for (tries = 0; tries < 5; tries++) {
755 ret = ser_send_char(upsfd, APC_GOSMART);
757 if (ret != 1) {
758 upslog_with_errno(LOG_ERR, "smartmode: ser_send_char failed");
759 return 0;
762 ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR,
763 IGNCHARS, SER_WAIT_SEC, SER_WAIT_USEC);
765 if (ret > 0)
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 */
775 if (ret != 1) {
776 upslog_with_errno(LOG_ERR, "smartmode: ser_send_char failed");
777 return 0;
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)
793 char temp[16];
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");
800 return 1;
803 upsdebugx(1, "Last issued shutdown command failed");
804 return 0;
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);
815 return sdok();
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');
825 usleep(UPSDELAY);
827 return sdcmd_S(tval);
831 * hard hibernate: @nnn / @nn
832 * note: works differently for older and new models, see help function for
833 * detailed info
835 static int sdcmd_ATn(int cnt)
837 int n = 0, mmax, ret;
838 const char *strval;
839 char timer[4];
841 mmax = cnt == 2 ? 99 : 999;
843 if ((strval = getval("wugrace"))) {
844 errno = 0;
845 n = strtol(strval, NULL, 10);
846 if (errno || n < 0 || n > mmax)
847 n = 0;
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);
859 ret = sdok();
860 if (ret || cnt == 3)
861 return ret;
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
867 * silent (YMMV);
869 ser_send_char(upsfd, APC_CMD_GRACEDOWN);
870 usleep(UPSDELAY);
871 ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
873 return 0;
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);
886 return sdok();
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);
899 return sdok();
902 static int (*sdlist[])(int) = {
903 sdcmd_S,
904 sdcmd_ATn, /* for @nnn version */
905 sdcmd_K,
906 sdcmd_Z,
907 sdcmd_CS,
908 sdcmd_ATn, /* for @nn version */
911 #define SDIDX_S 0
912 #define SDIDX_AT3N 1
913 #define SDIDX_K 2
914 #define SDIDX_Z 3
915 #define SDIDX_CS 4
916 #define SDIDX_AT2N 5
918 #define SDCNT 6
920 static void upsdrv_shutdown_simple(int status)
922 unsigned int sdtype = 0;
923 char *strval;
925 if ((strval = getval("sdtype"))) {
926 errno = 0;
927 sdtype = strtol(strval, NULL, 10);
928 if (errno || sdtype < 0 || sdtype > 6)
929 sdtype = 0;
932 switch (sdtype) {
934 case 6: /* hard hibernate */
935 sdcmd_ATn(3);
936 break;
937 case 5: /* "hack nn" hard hibernate */
938 sdcmd_ATn(2);
939 break;
940 case 4: /* special hack for CS 350 and similar models */
941 sdcmd_CS(status);
942 break;
944 case 3: /* delayed poweroff */
945 sdcmd_K(0);
946 break;
948 case 2: /* instant poweroff */
949 sdcmd_Z(0);
950 break;
951 case 1:
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))
963 break;
964 sdcmd_ATn(3);
965 break;
967 default:
969 * Send @nnn or S, depending on OB / OL status
971 if (status & APC_STAT_OL) /* on line */
972 sdcmd_ATn(3);
973 else
974 sdcmd_S(0);
978 static void upsdrv_shutdown_advanced(int status)
980 const char *strval;
981 const char deforder[] = {48 + SDIDX_S,
982 48 + SDIDX_AT3N,
983 48 + SDIDX_K,
984 48 + SDIDX_Z,
986 size_t i;
987 int n;
989 strval = getval("advorder");
991 /* sanitize advorder */
993 if (!strval || !strlen(strval) || strlen(strval) > SDCNT)
994 strval = deforder;
995 for (i = 0; i < strlen(strval); i++) {
996 if (strval[i] - 48 < 0 || strval[i] - 48 >= SDCNT) {
997 strval = deforder;
998 break;
1003 * try each method in the list with a little bit of handling in certain
1004 * cases
1007 for (i = 0; i < strlen(strval); i++) {
1008 switch (strval[i] - 48) {
1009 case SDIDX_CS:
1010 n = status;
1011 break;
1012 case SDIDX_AT3N:
1013 n = 3;
1014 break;
1015 case SDIDX_AT2N:
1016 default:
1017 n = 2;
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)
1028 char temp[32];
1029 int ret, status;
1031 if (!smartmode())
1032 upsdebugx(1, "SM detection failed. Trying a shutdown command anyway");
1034 /* check the line status */
1036 ret = ser_send_char(upsfd, APC_STATUS);
1038 if (ret == 1) {
1039 ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR,
1040 IGNCHARS, SER_WAIT_SEC, SER_WAIT_USEC);
1042 if (ret < 1) {
1043 upsdebugx(1, "Status read failed ! Assuming on battery state");
1044 status = APC_STAT_LB | APC_STAT_OB;
1045 } else {
1046 status = strtol(temp, 0, 16);
1049 } else {
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);
1056 else
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)
1069 int i;
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)
1075 continue;
1077 if (!poll_data(&apc_vartab[i])) {
1078 upsdebugx(3, "update_info_normal: poll_data (%s) failed - "
1079 "aborting scan", apc_vartab[i].name);
1080 return;
1084 upsdebugx(3, "update_info_normal: done");
1087 static void update_info_all(void)
1089 int i;
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);
1097 return;
1101 upsdebugx(3, "update_info_all: done");
1104 static int setvar_enum(apc_vartab_t *vt, const char *val)
1106 int i, ret;
1107 char orig[256], temp[256];
1108 const char *ptr;
1110 ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
1111 ret = ser_send_char(upsfd, vt->cmd);
1113 if (ret != 1) {
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)",
1128 vt->name, val);
1130 return STAT_SET_HANDLED; /* FUTURE: no change */
1133 for (i = 0; i < 6; i++) {
1134 ret = ser_send_char(upsfd, APC_NEXTVAL);
1136 if (ret != 1) {
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 */
1147 /* sanity checks */
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);
1156 if (ret != 1) {
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]",
1169 ptr, val);
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",
1183 vt->name);
1185 return STAT_SET_HANDLED; /* FUTURE: failed */
1189 upslogx(LOG_ERR, "setvar: gave up after 6 tries for %s",
1190 vt->name);
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)
1200 unsigned int i;
1201 int ret;
1202 char temp[256];
1204 ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
1205 ret = ser_send_char(upsfd, vt->cmd);
1207 if (ret != 1) {
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)",
1220 vt->name, val);
1222 return STAT_SET_HANDLED; /* FUTURE: no change */
1225 ret = ser_send_char(upsfd, APC_NEXTVAL);
1227 if (ret != 1) {
1228 upslog_with_errno(LOG_ERR, "setvar_string: ser_send_char failed");
1229 return STAT_SET_HANDLED; /* FUTURE: failed */
1232 usleep(UPSDELAY);
1234 for (i = 0; i < strlen(val); i++) {
1235 ret = ser_send_char(upsfd, val[i]);
1237 if (ret != 1) {
1238 upslog_with_errno(LOG_ERR, "setvar_string: ser_send_char failed");
1239 return STAT_SET_HANDLED; /* FUTURE: failed */
1242 usleep(UPSDELAY);
1245 /* pad to 8 chars with CRs */
1246 for (i = strlen(val); i < APC_STRLEN; i++) {
1247 ret = ser_send_char(upsfd, 13);
1249 if (ret != 1) {
1250 upslog_with_errno(LOG_ERR, "setvar_string: ser_send_char failed");
1251 return STAT_SET_HANDLED; /* FUTURE: failed */
1254 usleep(UPSDELAY);
1257 ret = read_buf(temp, sizeof(temp));
1259 if (ret < 1) {
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)
1279 apc_vartab_t *vt;
1281 vt = vartab_lookup_name(varname);
1283 if (!vt)
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)
1304 int ret;
1305 char buf[SMALLBUF];
1307 ser_flush_in(upsfd, IGNCHARS, nut_debug_level);
1308 ret = ser_send_char(upsfd, ct->cmd);
1310 if (ret != 1) {
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);
1321 if (ret != 1) {
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));
1329 if (ret < 1)
1330 return STAT_INSTCMD_HANDLED; /* FUTURE: failed */
1332 if (strcmp(buf, "OK") != 0) {
1333 upslogx(LOG_WARNING, "Got [%s] after command [%s]",
1334 buf, ct->name);
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)
1346 double elapsed;
1347 time_t now;
1348 static time_t last = 0;
1350 time(&now);
1352 elapsed = difftime(now, last);
1353 last = now;
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)",
1358 ct->name, elapsed);
1359 return STAT_INSTCMD_HANDLED; /* FUTURE: again */
1362 return do_cmd(ct);
1365 static int instcmd(const char *cmdname, const char *extra)
1367 int i;
1368 apc_cmdtab_t *ct;
1370 ct = NULL;
1372 for (i = 0; apc_cmdtab[i].name != NULL; i++)
1373 if (!strcasecmp(apc_cmdtab[i].name, cmdname))
1374 ct = &apc_cmdtab[i];
1376 if (!ct) {
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",
1383 cmdname);
1384 return STAT_INSTCMD_UNKNOWN;
1387 if (!strcasecmp(cmdname, "calibrate.start"))
1388 return do_cal(1);
1390 if (!strcasecmp(cmdname, "calibrate.stop"))
1391 return do_cal(0);
1393 if (ct->flags & APC_NASTY)
1394 return instcmd_chktime(ct);
1396 /* nothing special here */
1397 return do_cmd(ct);
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)
1419 char *cable;
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 */
1430 extrafd = upsfd;
1433 void upsdrv_help(void)
1437 void upsdrv_initinfo(void)
1439 const char *pmod, *pser;
1441 if (!smartmode()) {
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");
1451 getbaseinfo();
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);
1460 setuphandlers();
1463 void upsdrv_updateinfo(void)
1465 static time_t last_full = 0;
1466 time_t now;
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 */
1473 last_full = 0;
1474 return;
1477 ser_comm_good();
1479 if (!update_status())
1480 return;
1482 time(&now);
1484 /* refresh all variables hourly */
1485 /* does not catch measure-ups II insertion/removal */
1486 if (difftime(now, last_full) > 3600) {
1487 last_full = now;
1488 update_info_all();
1489 return;
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);