1 /* tripplitesu.c - model specific routines for
2 Tripp Lite SmartOnline (SU*) models
3
4 Copyright (C) 2003 Allan N. Hessenflow <allanh@kallisti.com>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 /* Notes:
22
23 The map for commands_available isn't clear. All of the information
24 I have on the Tripp Lite SmartOnline protocol comes from the files
25 TUN.tlp and TUNRT.tlp from their drivers, and some experimentation.
26 One of those files told me what one bit was for in the AVL response,
27 out of 18 that the SU1000RT2U sends or 21 that some other model
28 sends. Later I found a description of the Belkin protocol, which is
29 the same. Unfortunately it gives a definition of the AVL response
30 that conflicts with the one bit I found from TUNRT.tlp, and in fact
31 the response my SU1000RT2U gives, compared to the commands I have
32 found it supports, does not match the Belkin description. So I'm
33 treating the whole field as unknown. It would be nice to be able to
34 use it to determine what variables to make RW. As a workaround, I'm
35 assuming any value I query successfully which also can be set on at
36 least one model, can in fact be set on the one I'm talking to.
37
38 I didn't just add Tripp Lite support to the existing Belkin driver
39 because I didn't discover that they were the same protocol until
40 after I had put more features in this driver than the Belkin driver
41 has. I'm not calling this a unified Belkin/Tripp Lite driver for a
42 couple of reasons. One is that I don't have a Belkin to test with
43 (and so I explicitly check for Tripp Lite in this drivers
44 initialization - it *may* work fine with a Belkin by simply removing
45 that test). The other reason is that I'd have to come up with a
46 name - I don't know a name for the protocol, and I don't know which
47 company (if either) originated the protocol. Picking one of the
48 companies arbitrarily to name it after just seems wrong.
49
50 There are a few things still to add. Primarily there's control of
51 individual outlet banks, using (I presume) outlet.n.x variables.
52 I'll probably wait for an example of that to show up in some other
53 driver before adding that, to try to make sure I do it in the way
54 that Russell Kroll envisioned. It also might be nice to give the
55 user control over the delays before shutdown and restart, probably
56 with additional driver parameters. Letting the user turn the buzzer
57 off might be nice too; for that I'd have to investigate whether the
58 command to do that disables it entirely, or just turns it off during
59 the existing alarm condition, to determine whether it would be better
60 implemented through variables or instant commands, and then of course
61 request that those get added to the list of known names. Finally,
62 there are a number of other alarm conditions that can be reported
63 that would be nice to pass on to the user; these would require new
64 variables or new status values.
65
66
67 The following parameters (ups.conf) are supported:
68 lowbatt
69
70 The following variables are supported (RW = read/write):
71 ambient.humidity (1)
72 ambient.temperature (1)
73 battery.charge
74 battery.current (1)
75 battery.temperature
76 battery.voltage
77 battery.voltage.nominal
78 input.frequency
79 input.sensitivity (RW) (1)
80 input.transfer.high (RW)
81 input.transfer.low (RW)
82 input.voltage
83 input.voltage.nominal
84 output.current (1)
85 output.frequency
86 output.voltage
87 output.voltage.nominal
88 ups.firmware
89 ups.id (RW) (1)
90 ups.load
91 ups.mfr
92 ups.model
93 ups.status
94 ups.test.result
95 ups.contacts (1)
96
97 The following instant commands are supported:
98 load.off
99 load.on
100 shutdown.reboot
101 shutdown.reboot.graceful
102 shutdown.return
103 shutdown.stop
104 test.battery.start
105 test.battery.stop
106
107 The following ups.status values are supported:
108 BOOST (1)
109 BYPASS
110 LB
111 OB
112 OFF
113 OL
114 OVER (2)
115 RB (2)
116 TRIM (1)
117
118 (1) these items have not been tested because they are not supported
119 by my SU1000RT2U.
120 (2) these items have not been tested because I haven't tested them.
121 */
122
123
124 #include "main.h"
125 #include "serial.h"
126
127 #define DRIVER_NAME "Tripp Lite SmartOnline driver"
128 #define DRIVER_VERSION "0.05"
129
130 /* driver description structure */
131 upsdrv_info_t upsdrv_info = {
132 DRIVER_NAME,
133 DRIVER_VERSION,
134 "Allan N. Hessenflow <allanh@kallisti.com>",
135 DRV_EXPERIMENTAL,
136 { NULL }
137 };
138
139 #define MAX_RESPONSE_LENGTH 256
140
141 static const char *test_result_names[] = {
142 "No test performed",
143 "Passed",
144 "In progress",
145 "General test failed",
146 "Battery failed",
147 "Deep battery test failed",
148 "Aborted"
149 };
150
151 static struct {
152 int code;
153 const char *name;
154 } sensitivity[] = {
155 {0, "Normal"},
156 {1, "Reduced"},
157 {2, "Low"}
158 };
159
160 static struct {
161 int outlet_banks;
162 unsigned long commands_available;
163 } ups;
164
165 /* bits in commands_available */
166 #define WDG_AVAILABLE (1UL << 1)
167
168 /* message types */
169 #define POLL 'P'
170 #define SET 'S'
171 #define ACCEPT 'A'
172 #define REJECT 'R'
173 #define DATA 'D'
174
175 /* commands */
176 #define AUTO_REBOOT "ARB" /* poll/set */
177 #define AUTO_TEST "ATT" /* poll/set */
178 #define ATX_REBOOT "ATX" /* poll/set */
179 #define AVAILABLE "AVL" /* poll */
180 #define BATTERY_REPLACEMENT_DATE "BRD" /* poll/set */
181 #define BUZZER_TEST "BTT" /* set */
182 #define BATTERY_TEST "BTV" /* poll/set */
183 #define BUZZER "BUZ" /* set */
184 #define ECONOMIC_MODE "ECO" /* set */
185 #define ENABLE_BUZZER "EDB" /* set */
186 #define ENVIRONMENT_INFORMATION "ENV" /* poll */
187 #define OUTLET_RELAYS "LET" /* poll */
188 #define MANUFACTURER "MNU" /* poll */
189 #define MODEL "MOD" /* poll */
190 #define RATINGS "RAT" /* poll */
191 #define RELAY_CYCLE "RNF" /* set */
192 #define RELAY_OFF "ROF" /* set */
193 #define RELAY_ON "RON" /* set */
194 #define ATX_RESUME "RSM" /* set */
195 #define SHUTDOWN_ACTION "SDA" /* set */
196 #define SHUTDOWN_RESTART "SDR" /* set */
197 #define SHUTDOWN_TYPE "SDT" /* poll/set */
198 #define RELAY_STATUS "SOL" /* poll/set */
199 #define SELECT_OUTPUT_VOLTAGE "SOV" /* poll/set */
200 #define STATUS_ALARM "STA" /* poll */
201 #define STATUS_BATTERY "STB" /* poll */
202 #define STATUS_INPUT "STI" /* poll */
203 #define STATUS_OUTPUT "STO" /* poll */
204 #define STATUS_BYPASS "STP" /* poll */
205 #define TELEPHONE "TEL" /* poll/set */
206 #define TEST_RESULT "TSR" /* poll */
207 #define TEST "TST" /* set */
208 #define TRANSFER_FREQUENCY "TXF" /* poll/set */
209 #define TRANSFER_VOLTAGE "TXV" /* poll/set */
210 #define BOOT_DELAY "UBD" /* poll/set */
211 #define BAUD_RATE "UBR" /* poll/set */
212 #define IDENTIFICATION "UID" /* poll/set */
213 #define VERSION_CMD "VER" /* poll */
214 #define VOLTAGE_SENSITIVITY "VSN" /* poll/set */
215 #define WATCHDOG "WDG" /* poll/set */
216
217
do_command(char type,const char * command,const char * parameters,char * response)218 static int do_command(char type, const char *command, const char *parameters, char *response)
219 {
220 char buffer[SMALLBUF];
221 int count, ret;
222
223 ser_flush_io(upsfd);
224
225 if (response) {
226 *response = '\0';
227 }
228
229 snprintf(buffer, sizeof(buffer), "~00%c%03d%s%s", type, (int)(strlen(command) + strlen(parameters)), command, parameters);
230
231 ret = ser_send_pace(upsfd, 10000, "%s", buffer);
232 if (ret <= 0) {
233 upsdebug_with_errno(3, "do_command: send [%s]", buffer);
234 return -1;
235 }
236
237 upsdebugx(3, "do_command: %d bytes sent [%s] -> OK", ret, buffer);
238
239 ret = ser_get_buf_len(upsfd, (unsigned char *)buffer, 4, 3, 0);
240 if (ret < 0) {
241 upsdebug_with_errno(3, "do_command: read");
242 return -1;
243 }
244 if (ret == 0) {
245 upsdebugx(3, "do_command: read -> TIMEOUT");
246 return -1;
247 }
248
249 buffer[ret] = '\0';
250 upsdebugx(3, "do_command: %d byted read [%s]", ret, buffer);
251
252 if (!strcmp(buffer, "~00D")) {
253
254 ret = ser_get_buf_len(upsfd, (unsigned char *)buffer, 3, 3, 0);
255 if (ret < 0) {
256 upsdebug_with_errno(3, "do_command: read");
257 return -1;
258 }
259 if (ret == 0) {
260 upsdebugx(3, "do_command: read -> TIMEOUT");
261 return -1;
262 }
263
264 buffer[ret] = '\0';
265 upsdebugx(3, "do_command: %d bytes read [%s]", ret, buffer);
266
267 count = atoi(buffer);
268 if (count >= MAX_RESPONSE_LENGTH) {
269 upsdebugx(3, "do_command: response exceeds expected size!");
270 return -1;
271 }
272
273 if (count && !response) {
274 upsdebugx(3, "do_command: response not expected!");
275 return -1;
276 }
277
278 if (count == 0) {
279 return 0;
280 }
281
282 ret = ser_get_buf_len(upsfd, (unsigned char *)response, count, 3, 0);
283 if (ret < 0) {
284 upsdebug_with_errno(3, "do_command: read");
285 return -1;
286 }
287 if (ret == 0) {
288 upsdebugx(3, "do_command: read -> TIMEOUT");
289 return -1;
290 }
291
292 response[ret] = '\0';
293 upsdebugx(3, "do_command: %d bytes read [%s]", ret, response);
294
295 /* Tripp Lite pads their string responses with spaces.
296 I don't like that, so I remove them. This is safe to
297 do with all responses for this protocol, so I just
298 do that here. */
299 str_rtrim(response, ' ');
300
301 return ret;
302 }
303
304 if (!strcmp(buffer, "~00A")) {
305 return 0;
306 }
307
308 return -1;
309 }
310
field(char * str,int fieldnum)311 static char *field(char *str, int fieldnum)
312 {
313
314 while (str && fieldnum--) {
315 str = strchr(str, ';');
316 if (str)
317 str++;
318 }
319 if (str && *str == ';')
320 return NULL;
321
322 return str;
323 }
324
get_identification(void)325 static int get_identification(void) {
326 char response[MAX_RESPONSE_LENGTH];
327
328 if (do_command(POLL, IDENTIFICATION, "", response) >= 0) {
329 dstate_setinfo("ups.id", "%s", response);
330 return 1;
331 }
332
333 return 0;
334 }
335
set_identification(const char * val)336 static void set_identification(const char *val) {
337 char response[MAX_RESPONSE_LENGTH];
338
339 if (do_command(POLL, IDENTIFICATION, "", response) < 0)
340 return;
341 if (strcmp(val, response)) {
342 strncpy(response, val, MAX_RESPONSE_LENGTH);
343 response[MAX_RESPONSE_LENGTH - 1] = '\0';
344 do_command(SET, IDENTIFICATION, response, NULL);
345 }
346 }
347
get_transfer_voltage_low(void)348 static int get_transfer_voltage_low(void) {
349 char response[MAX_RESPONSE_LENGTH];
350 char *ptr;
351
352 if (do_command(POLL, TRANSFER_VOLTAGE, "", response) > 0) {
353 ptr = field(response, 0);
354 if (ptr)
355 dstate_setinfo("input.transfer.low", "%d", atoi(ptr));
356 return 1;
357 }
358
359 return 0;
360 }
361
set_transfer_voltage_low(int val)362 static void set_transfer_voltage_low(int val) {
363 char response[MAX_RESPONSE_LENGTH];
364 char *ptr;
365 int high;
366
367 if (do_command(POLL, TRANSFER_VOLTAGE, "", response) <= 0)
368 return;
369 ptr = field(response, 0);
370 if (!ptr || val == atoi(ptr))
371 return;
372 ptr = field(response, 1);
373 if (!ptr)
374 return;
375 high = atoi(ptr);
376 snprintf(response, sizeof(response), "%d;%d", val, high);
377 do_command(SET, TRANSFER_VOLTAGE, response, NULL);
378 }
379
get_transfer_voltage_high(void)380 static int get_transfer_voltage_high(void) {
381 char response[MAX_RESPONSE_LENGTH];
382 char *ptr;
383
384 if (do_command(POLL, TRANSFER_VOLTAGE, "", response) > 0) {
385 ptr = field(response, 1);
386 if (ptr)
387 dstate_setinfo("input.transfer.high", "%d", atoi(ptr));
388 return 1;
389 }
390
391 return 0;
392 }
393
set_transfer_voltage_high(int val)394 static void set_transfer_voltage_high(int val) {
395 char response[MAX_RESPONSE_LENGTH];
396 char *ptr;
397 int low;
398
399 if (do_command(POLL, TRANSFER_VOLTAGE, "", response) <= 0)
400 return;
401 ptr = field(response, 0);
402 if (!ptr)
403 return;
404 low = atoi(ptr);
405 ptr = field(response, 1);
406 if (!ptr || val == atoi(ptr))
407 return;
408 snprintf(response, sizeof(response), "%d;%d", low, val);
409 do_command(SET, TRANSFER_VOLTAGE, response, NULL);
410 }
411
get_sensitivity(void)412 static int get_sensitivity(void) {
413 char response[MAX_RESPONSE_LENGTH];
414 unsigned int i;
415
416 if (do_command(POLL, VOLTAGE_SENSITIVITY, "", response) <= 0)
417 return 0;
418 for (i = 0; i < sizeof(sensitivity) / sizeof(sensitivity[0]); i++) {
419 if (sensitivity[i].code == atoi(response)) {
420 dstate_setinfo("input.sensitivity", "%s",
421 sensitivity[i].name);
422 return 1;
423 }
424 }
425
426 return 0;
427 }
428
set_sensitivity(const char * val)429 static void set_sensitivity(const char *val) {
430 char parm[20];
431 unsigned int i;
432
433 for (i = 0; i < sizeof(sensitivity) / sizeof(sensitivity[0]); i++) {
434 if (!strcasecmp(val, sensitivity[i].name)) {
435 snprintf(parm, sizeof(parm), "%d", i);
436 do_command(SET, VOLTAGE_SENSITIVITY, parm, NULL);
437 break;
438 }
439 }
440 }
441
auto_reboot(int enable)442 static void auto_reboot(int enable) {
443 char parm[20];
444 char response[MAX_RESPONSE_LENGTH];
445 char *ptr;
446 int mode;
447
448 if (enable)
449 mode = 1;
450 else
451 mode = 2;
452 if (do_command(POLL, AUTO_REBOOT, "", response) <= 0)
453 return;
454 ptr = field(response, 0);
455 if (!ptr || atoi(ptr) != mode) {
456 snprintf(parm, sizeof(parm), "%d", mode);
457 do_command(SET, AUTO_REBOOT, parm, NULL);
458 }
459 }
460
instcmd(const char * cmdname,const char * extra)461 static int instcmd(const char *cmdname, const char *extra)
462 {
463 int i;
464 char parm[20];
465
466 if (!strcasecmp(cmdname, "load.off")) {
467 for (i = 0; i < ups.outlet_banks; i++) {
468 snprintf(parm, sizeof(parm), "%d;1", i + 1);
469 do_command(SET, RELAY_OFF, parm, NULL);
470 }
471 return STAT_INSTCMD_HANDLED;
472 }
473 if (!strcasecmp(cmdname, "load.on")) {
474 for (i = 0; i < ups.outlet_banks; i++) {
475 snprintf(parm, sizeof(parm), "%d;1", i + 1);
476 do_command(SET, RELAY_ON, parm, NULL);
477 }
478 return STAT_INSTCMD_HANDLED;
479 }
480 if (!strcasecmp(cmdname, "shutdown.reboot")) {
481 auto_reboot(1);
482 do_command(SET, SHUTDOWN_RESTART, "1", NULL);
483 do_command(SET, SHUTDOWN_ACTION, "10", NULL);
484 return STAT_INSTCMD_HANDLED;
485 }
486 if (!strcasecmp(cmdname, "shutdown.reboot.graceful")) {
487 auto_reboot(1);
488 do_command(SET, SHUTDOWN_RESTART, "1", NULL);
489 do_command(SET, SHUTDOWN_ACTION, "60", NULL);
490 return STAT_INSTCMD_HANDLED;
491 }
492 if (!strcasecmp(cmdname, "shutdown.return")) {
493 auto_reboot(1);
494 do_command(SET, SHUTDOWN_RESTART, "1", NULL);
495 do_command(SET, SHUTDOWN_ACTION, "10", NULL);
496 return STAT_INSTCMD_HANDLED;
497 }
498 #if 0 /* doesn't seem to work */
499 if (!strcasecmp(cmdname, "shutdown.stayoff")) {
500 auto_reboot(0);
501 do_command(SET, SHUTDOWN_ACTION, "10", NULL);
502 return STAT_INSTCMD_HANDLED;
503 }
504 #endif
505 if (!strcasecmp(cmdname, "shutdown.stop")) {
506 do_command(SET, SHUTDOWN_ACTION, "0", NULL);
507 return STAT_INSTCMD_HANDLED;
508 }
509 if (!strcasecmp(cmdname, "test.battery.start")) {
510 do_command(SET, TEST, "3", NULL);
511 return STAT_INSTCMD_HANDLED;
512 }
513 if (!strcasecmp(cmdname, "test.battery.stop")) {
514 do_command(SET, TEST, "0", NULL);
515 return STAT_INSTCMD_HANDLED;
516 }
517 upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
518 return STAT_INSTCMD_UNKNOWN;
519 }
520
setvar(const char * varname,const char * val)521 static int setvar(const char *varname, const char *val)
522 {
523
524 if (!strcasecmp(varname, "ups.id")) {
525 set_identification(val);
526 get_identification();
527 return STAT_SET_HANDLED;
528 }
529 if (!strcasecmp(varname, "input.transfer.low")) {
530 set_transfer_voltage_low(atoi(val));
531 get_transfer_voltage_low();
532 return STAT_SET_HANDLED;
533 }
534 if (!strcasecmp(varname, "input.transfer.high")) {
535 set_transfer_voltage_high(atoi(val));
536 get_transfer_voltage_high();
537 return STAT_SET_HANDLED;
538 }
539 if (!strcasecmp(varname, "input.sensitivity")) {
540 set_sensitivity(val);
541 get_sensitivity();
542 return STAT_SET_HANDLED;
543 }
544
545 upslogx(LOG_NOTICE, "setvar: unknown var [%s]", varname);
546 return STAT_SET_UNKNOWN;
547 }
548
init_comm(void)549 static int init_comm(void)
550 {
551 int i, bit;
552 char response[MAX_RESPONSE_LENGTH];
553
554 ups.commands_available = 0;
555 /* Repeat enumerate command 2x, firmware bug on some units garbles 1st response */
556 if (do_command(POLL, AVAILABLE, "", response) <= 0){
557 upslogx(LOG_NOTICE, "init_comm: Initial response malformed, retrying in 300ms");
558 usleep(3E5);
559 }
560 if (do_command(POLL, AVAILABLE, "", response) <= 0)
561 return 0;
562 i = strlen(response);
563 for (bit = 0; bit < i; bit++)
564 if (response[i - bit - 1] == '1')
565 ups.commands_available |= (1UL << bit);
566
567 if (do_command(POLL, MANUFACTURER, "", response) <= 0)
568 return 0;
569 if (strcmp(response, "Tripp Lite"))
570 return 0;
571
572 return 1;
573 }
574
upsdrv_initinfo(void)575 void upsdrv_initinfo(void)
576 {
577 char response[MAX_RESPONSE_LENGTH];
578 unsigned int min_low_transfer, max_low_transfer;
579 unsigned int min_high_transfer, max_high_transfer;
580 unsigned int i;
581 char *ptr;
582
583 if (!init_comm())
584 fatalx(EXIT_FAILURE, "Unable to detect Tripp Lite SmartOnline UPS on port %s\n",
585 device_path);
586 min_low_transfer = max_low_transfer = 0;
587 min_high_transfer = max_high_transfer = 0;
588
589 /* get all the read-only fields here */
590 if (do_command(POLL, MANUFACTURER, "", response) > 0)
591 dstate_setinfo("ups.mfr", "%s", response);
592 if (do_command(POLL, MODEL, "", response) > 0)
593 dstate_setinfo("ups.model", "%s", response);
594 if (do_command(POLL, VERSION_CMD, "", response) > 0)
595 dstate_setinfo("ups.firmware", "%s", response);
596 if (do_command(POLL, RATINGS, "", response) > 0) {
597 ptr = field(response, 0);
598 if (ptr)
599 dstate_setinfo("input.voltage.nominal", "%d",
600 atoi(ptr));
601 ptr = field(response, 2);
602 if (ptr) {
603 dstate_setinfo("output.voltage.nominal", "%d",
604 atoi(ptr));
605 }
606 ptr = field(response, 14);
607 if (ptr)
608 dstate_setinfo("battery.voltage.nominal", "%d",
609 atoi(ptr));
610 ptr = field(response, 10);
611 if (ptr)
612 min_low_transfer = atoi(ptr);
613 ptr = field(response, 9);
614 if (ptr)
615 max_low_transfer = atoi(ptr);
616 ptr = field(response, 12);
617 if (ptr)
618 min_high_transfer = atoi(ptr);
619 ptr = field(response, 11);
620 if (ptr)
621 max_high_transfer = atoi(ptr);
622 }
623 if (do_command(POLL, OUTLET_RELAYS, "", response) > 0)
624 ups.outlet_banks = atoi(response);
625 /* define things that are settable */
626 if (get_identification()) {
627 dstate_setflags("ups.id", ST_FLAG_RW | ST_FLAG_STRING);
628 dstate_setaux("ups.id", 100);
629 }
630 if (get_transfer_voltage_low() && max_low_transfer) {
631 dstate_setflags("input.transfer.low", ST_FLAG_RW);
632 for (i = min_low_transfer; i <= max_low_transfer; i++)
633 dstate_addenum("input.transfer.low", "%d", i);
634 }
635 if (get_transfer_voltage_high() && max_low_transfer) {
636 dstate_setflags("input.transfer.high", ST_FLAG_RW);
637 for (i = min_high_transfer; i <= max_high_transfer; i++)
638 dstate_addenum("input.transfer.high", "%d", i);
639 }
640 if (get_sensitivity()) {
641 dstate_setflags("input.sensitivity", ST_FLAG_RW);
642 for (i = 0; i < sizeof(sensitivity) / sizeof(sensitivity[0]);
643 i++)
644 dstate_addenum("input.sensitivity", "%s",
645 sensitivity[i].name);
646 }
647 if (ups.outlet_banks) {
648 dstate_addcmd("load.off");
649 dstate_addcmd("load.on");
650 }
651 dstate_addcmd("shutdown.reboot");
652 dstate_addcmd("shutdown.reboot.graceful");
653 dstate_addcmd("shutdown.return");
654 #if 0 /* doesn't work */
655 dstate_addcmd("shutdown.stayoff");
656 #endif
657 dstate_addcmd("shutdown.stop");
658 dstate_addcmd("test.battery.start");
659 dstate_addcmd("test.battery.stop");
660
661 /* add all the variables that change regularly */
662 upsdrv_updateinfo();
663
664 upsh.instcmd = instcmd;
665 upsh.setvar = setvar;
666
667 printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
668 dstate_getinfo("ups.model"), device_path);
669 }
670
upsdrv_updateinfo(void)671 void upsdrv_updateinfo(void)
672 {
673 char response[MAX_RESPONSE_LENGTH];
674 char *ptr, *ptr2;
675 int i;
676 int flags;
677 int contacts_set;
678 int low_battery;
679
680 status_init();
681 if (do_command(POLL, STATUS_OUTPUT, "", response) <= 0) {
682 dstate_datastale();
683 return;
684 }
685 ptr = field(response, 0);
686 /* require output status field to exist */
687 if (!ptr) {
688 dstate_datastale();
689 return;
690 }
691 switch (atoi(ptr)) {
692 case 0:
693 status_set("OL");
694 break;
695 case 1:
696 status_set("OB");
697 break;
698 case 2:
699 status_set("BYPASS");
700 break;
701 case 3:
702 status_set("OL");
703 status_set("TRIM");
704 break;
705 case 4:
706 status_set("OL");
707 status_set("BOOST");
708 break;
709 case 5:
710 status_set("BYPASS");
711 break;
712 case 6:
713 break;
714 case 7:
715 status_set("OFF");
716 break;
717 default:
718 break;
719 }
720 ptr = field(response, 6);
721 if (ptr)
722 dstate_setinfo("ups.load", "%d", atoi(ptr));
723 ptr = field(response, 3);
724 if (ptr)
725 dstate_setinfo("output.voltage", "%03.1f",
726 (double) atoi(ptr) / 10.0);
727 ptr = field(response, 1);
728 if (ptr)
729 dstate_setinfo("output.frequency", "%03.1f",
730 (double) atoi(ptr) / 10.0);
731 ptr = field(response, 4);
732 if (ptr)
733 dstate_setinfo("output.current", "%03.1f",
734 (double) atoi(ptr) / 10.0);
735
736 low_battery = 0;
737 if (do_command(POLL, STATUS_BATTERY, "", response) <= 0) {
738 dstate_datastale();
739 return;
740 }
741 ptr = field(response, 0);
742 if (ptr && atoi(ptr) == 2)
743 status_set("RB");
744 ptr = field(response, 1);
745 if (ptr && atoi(ptr))
746 low_battery = 1;
747 ptr = field(response, 8);
748 if (ptr)
749 dstate_setinfo("battery.temperature", "%d", atoi(ptr));
750 ptr = field(response, 9);
751 if (ptr) {
752 dstate_setinfo("battery.charge", "%d", atoi(ptr));
753 ptr2 = getval("lowbatt");
754 if (ptr2 && atoi(ptr2) > 0 && atoi(ptr2) <= 99 &&
755 atoi(ptr) <= atoi(ptr2))
756 low_battery = 1;
757 }
758 ptr = field(response, 6);
759 if (ptr)
760 dstate_setinfo("battery.voltage", "%03.1f",
761 (double) atoi(ptr) / 10.0);
762 ptr = field(response, 7);
763 if (ptr)
764 dstate_setinfo("battery.current", "%03.1f",
765 (double) atoi(ptr) / 10.0);
766 if (low_battery)
767 status_set("LB");
768
769 if (do_command(POLL, STATUS_ALARM, "", response) <= 0) {
770 dstate_datastale();
771 return;
772 }
773 ptr = field(response, 3);
774 if (ptr && atoi(ptr))
775 status_set("OVER");
776
777 if (do_command(POLL, STATUS_INPUT, "", response) > 0) {
778 ptr = field(response, 2);
779 if (ptr)
780 dstate_setinfo("input.voltage", "%03.1f",
781 (double) atoi(ptr) / 10.0);
782 ptr = field(response, 1);
783 if (ptr)
784 dstate_setinfo("input.frequency",
785 "%03.1f", (double) atoi(ptr) / 10.0);
786 }
787
788 if (do_command(POLL, TEST_RESULT, "", response) > 0) {
789 int r;
790 size_t trsize;
791
792 r = atoi(response);
793 trsize = sizeof(test_result_names) /
794 sizeof(test_result_names[0]);
795
796 if ((r < 0) || (r >= (int) trsize))
797 r = 0;
798
799 dstate_setinfo("ups.test.result", "%s", test_result_names[r]);
800 }
801
802 if (do_command(POLL, ENVIRONMENT_INFORMATION, "", response) > 0) {
803 ptr = field(response, 0);
804 if (ptr)
805 dstate_setinfo("ambient.temperature", "%d", atoi(ptr));
806 ptr = field(response, 1);
807 if (ptr)
808 dstate_setinfo("ambient.humidity", "%d", atoi(ptr));
809 flags = 0;
810 contacts_set = 0;
811 for (i = 0; i < 4; i++) {
812 ptr = field(response, 2 + i);
813 if (ptr) {
814 contacts_set = 1;
815 if (*ptr == '1')
816 flags |= 1 << i;
817 }
818 }
819 if (contacts_set)
820 dstate_setinfo("ups.contacts", "%02X", flags);
821 }
822
823 /* if we are here, status is valid */
824 status_commit();
825 dstate_dataok();
826 }
827
upsdrv_shutdown(void)828 void upsdrv_shutdown(void)
829 {
830 char parm[20];
831
832 if (!init_comm())
833 printf("Status failed. Assuming it's on battery and trying a shutdown anyway.\n");
834 auto_reboot(1);
835 /* in case the power is on, tell it to automatically reboot. if
836 it is off, this has no effect. */
837 snprintf(parm, sizeof(parm), "%d", 1); /* delay before reboot, in minutes */
838 do_command(SET, SHUTDOWN_RESTART, parm, NULL);
839 snprintf(parm, sizeof(parm), "%d", 5); /* delay before shutdown, in seconds */
840 do_command(SET, SHUTDOWN_ACTION, parm, NULL);
841 }
842
upsdrv_help(void)843 void upsdrv_help(void)
844 {
845 }
846
847 /* list flags and values that you want to receive via -x or ups.conf */
upsdrv_makevartable(void)848 void upsdrv_makevartable(void)
849 {
850 addvar(VAR_VALUE, "lowbatt", "Set low battery level, in percent");
851 }
852
upsdrv_initups(void)853 void upsdrv_initups(void)
854 {
855 upsfd = ser_open(device_path);
856 ser_set_speed(upsfd, device_path, B2400);
857 }
858
upsdrv_cleanup(void)859 void upsdrv_cleanup(void)
860 {
861 ser_close(upsfd, device_path);
862 }
863