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