1 /*
2  * blazer.c: driver core for Megatec/Q1 protocol based UPSes
3  *
4  * OBSOLETION WARNING: Please to not base new development on this
5  * codebase, instead create a new subdriver for nutdrv_qx which
6  * generally covers all Megatec/Qx protocol family and aggregates
7  * device support from such legacy drivers over time.
8  *
9  * A document describing the protocol implemented by this driver can be
10  * found online at http://www.networkupstools.org/ups-protocols/megatec.html
11  *
12  * Copyright (C)
13  *   2008,2009 - Arjen de Korte <adkorte-guest@alioth.debian.org>
14  *   2012 - Arnaud Quette <ArnaudQuette@Eaton.com>
15  *
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 2 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, write to the Free Software
28  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29  */
30 
31 #include "main.h"
32 #include "blazer.h"
33 #include "nut_float.h"
34 
35 static long	ondelay = 3;	/* minutes */
36 static long	offdelay = 30;	/* seconds */
37 
38 static int	proto;
39 static int	online = 1;
40 
41 static struct {
42 	double	packs;	/* battery voltage multiplier */
43 	struct {
44 		double	nom;	/* nominal runtime on battery (full load) */
45 		double	est;	/* estimated runtime remaining (full load) */
46 		double	exp;	/* load exponent */
47 	} runt;
48 	struct {
49 		double	act;	/* actual battery voltage */
50 		double	high;	/* battery float voltage */
51 		double	nom;	/* nominal battery voltage */
52 		double	low;	/* battery low voltage */
53 	} volt;
54 	struct {
55 		double	act;	/* actual battery charge */
56 		long	time;	/* recharge time from empty to full */
57 	} chrg;
58 } batt = { 1, { -1, 0, 0 }, { -1, -1, -1, -1 }, { -1, 43200 } };
59 
60 static struct {
61 	double	act;	/* actual load (reported by UPS) */
62 	double	low;	/* idle load */
63 	double	eff;	/* effective load */
64 } load = { 0, 0.1, 1 };
65 
66 static time_t	lastpoll = 0;
67 
68 /*
69  * This little structure defines the various flavors of the Megatec protocol.
70  * Only the .name and .status are mandatory, .rating and .vendor elements are
71  * optional. If only some models support the last two, fill them in anyway
72  * and tell people to use the 'norating' and 'novendor' options to bypass
73  * getting them.
74  */
75 static const struct {
76 	const char	*name;
77 	const char	*status;
78 	const char	*rating;
79 	const char	*vendor;
80 } command[] = {
81 	{ "megatec", "Q1\r", "F\r", "I\r" },
82 	{ "mustek", "QS\r", "F\r", "I\r" },
83 	{ "megatec/old", "D\r", "F\r", "I\r" },
84 	{ "zinto", "Q1\r", "F\r", "FW?\r" },
85 	{ NULL, NULL, NULL, NULL }
86 };
87 
88 
89 /*
90  * Do whatever we think is needed when we read a battery voltage from the UPS.
91  * Basically all it does now, is guestimating the battery charge, but this
92  * could be extended.
93  */
blazer_battery(const char * ptr,char ** endptr)94 static double blazer_battery(const char *ptr, char **endptr)
95 {
96 	batt.volt.act = batt.packs * strtod(ptr, endptr);
97 
98 	if ((!getval("runtimecal") || !dstate_getinfo("battery.charge")) &&
99 			(batt.volt.low > 0) && (batt.volt.high > batt.volt.low)) {
100 		batt.chrg.act = 100 * (batt.volt.act - batt.volt.low) / (batt.volt.high - batt.volt.low);
101 
102 		if (batt.chrg.act < 0) {
103 			batt.chrg.act = 0;
104 		}
105 
106 		if (batt.chrg.act > 100) {
107 			batt.chrg.act = 100;
108 		}
109 
110 		dstate_setinfo("battery.charge", "%.0f", batt.chrg.act);
111 	}
112 
113 	return batt.volt.act;
114 }
115 
116 
117 /*
118  * Do whatever we think is needed when we read the load from the UPS.
119  */
blazer_load(const char * ptr,char ** endptr)120 static double blazer_load(const char *ptr, char **endptr)
121 {
122 	load.act = strtod(ptr, endptr);
123 
124 	load.eff = pow(load.act / 100, batt.runt.exp);
125 
126 	if (load.eff < load.low) {
127 		load.eff = load.low;
128 	}
129 
130 	return load.act;
131 }
132 
133 /*
134  * The battery voltage will quickly return to at least the nominal value after
135  * discharging them. For overlapping battery.voltage.low/high ranges therefor
136  * choose the one with the highest multiplier.
137  */
blazer_packs(const char * ptr,char ** endptr)138 static double blazer_packs(const char *ptr, char **endptr)
139 {
140 	const double packs[] = {
141 		120, 100, 80, 60, 48, 36, 30, 24, 18, 12, 8, 6, 4, 3, 2, 1, 0.5, -1
142 	};
143 
144 	const char	*val;
145 	int		i;
146 
147 	val = dstate_getinfo("battery.voltage.nominal");
148 
149 	batt.volt.nom = strtod(val ? val : ptr, endptr);
150 
151 	for (i = 0; packs[i] > 0; i++) {
152 
153 		if (packs[i] * batt.volt.act > 1.2 * batt.volt.nom) {
154 			continue;
155 		}
156 
157 		if (packs[i] * batt.volt.act < 0.8 * batt.volt.nom) {
158 			upslogx(LOG_INFO, "Can't autodetect number of battery packs [%.0f/%.2f]", batt.volt.nom, batt.volt.act);
159 			break;
160 		}
161 
162 		batt.packs = packs[i];
163 		break;
164 	}
165 
166 	return batt.volt.nom;
167 }
168 
169 
blazer_status(const char * cmd)170 static int blazer_status(const char *cmd)
171 {
172 	const struct {
173 		const char	*var;
174 		const char	*fmt;
175 		double	(*conv)(const char *, char **);
176 	} status[] = {
177 		{ "input.voltage", "%.1f", strtod },
178 		{ "input.voltage.fault", "%.1f", strtod },
179 		{ "output.voltage", "%.1f", strtod },
180 		{ "ups.load", "%.0f", blazer_load },
181 		{ "input.frequency", "%.1f", strtod },
182 		{ "battery.voltage", "%.2f", blazer_battery },
183 		{ "ups.temperature", "%.1f", strtod },
184 		{ NULL, NULL, NULL }
185 	};
186 
187 	char	buf[SMALLBUF], *val, *last = NULL;
188 	int	i;
189 
190 	/*
191 	 * > [Q1\r]
192 	 * < [(226.0 195.0 226.0 014 49.0 27.5 30.0 00001000\r]
193 	 *    01234567890123456789012345678901234567890123456
194 	 *    0         1         2         3         4
195 	 */
196 	if (blazer_command(cmd, buf, sizeof(buf)) < 46) {
197 		upsdebugx(2, "%s: short reply", __func__);
198 		return -1;
199 	}
200 
201 	if (buf[0] != '(') {
202 		upsdebugx(2, "%s: invalid start character [%02x]", __func__, buf[0]);
203 		return -1;
204 	}
205 
206 	for (i = 0, val = strtok_r(buf+1, " ", &last); status[i].var; i++, val = strtok_r(NULL, " \r\n", &last)) {
207 
208 		if (!val) {
209 			upsdebugx(2, "%s: parsing failed", __func__);
210 			return -1;
211 		}
212 
213 		if (strspn(val, "0123456789.") != strlen(val)) {
214 			upsdebugx(2, "%s: non numerical value [%s]", __func__, val);
215 			continue;
216 		}
217 
218 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
219 #pragma GCC diagnostic push
220 #endif
221 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
222 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
223 #endif
224 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
225 #pragma GCC diagnostic ignored "-Wformat-security"
226 #endif
227 		dstate_setinfo(status[i].var, status[i].fmt, status[i].conv(val, NULL));
228 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
229 #pragma GCC diagnostic pop
230 #endif
231 
232 	}
233 
234 	if (!val) {
235 		upsdebugx(2, "%s: parsing failed", __func__);
236 		return -1;
237 	}
238 
239 	if (strspn(val, "01") != 8) {
240 		upsdebugx(2, "Invalid status [%s]", val);
241 		return -1;
242 	}
243 
244 	if (val[7] == '1') {	/* Beeper On */
245 		dstate_setinfo("ups.beeper.status", "enabled");
246 	} else {
247 		dstate_setinfo("ups.beeper.status", "disabled");
248 	}
249 
250 	if (val[4] == '1') {	/* UPS Type is Standby (0 is On_line) */
251 		dstate_setinfo("ups.type", "offline / line interactive");
252 	} else {
253 		dstate_setinfo("ups.type", "online");
254 	}
255 
256 	status_init();
257 
258 	if (val[0] == '1') {	/* Utility Fail (Immediate) */
259 		status_set("OB");
260 		online = 0;
261 	} else {
262 		status_set("OL");
263 		online = 1;
264 	}
265 
266 	if (val[1] == '1') {	/* Battery Low */
267 		status_set("LB");
268 	}
269 
270 	if (val[2] == '1') {	/* Bypass/Boost or Buck Active */
271 
272 		double	vi, vo;
273 
274 		vi = strtod(dstate_getinfo("input.voltage"),  NULL);
275 		vo = strtod(dstate_getinfo("output.voltage"), NULL);
276 
277 		if (vo < 0.5 * vi) {
278 			upsdebugx(2, "%s: output voltage too low", __func__);
279 		} else if (vo < 0.95 * vi) {
280 			status_set("TRIM");
281 		} else if (vo < 1.05 * vi) {
282 			status_set("BYPASS");
283 		} else if (vo < 1.5 * vi) {
284 			status_set("BOOST");
285 		} else {
286 			upsdebugx(2, "%s: output voltage too high", __func__);
287 		}
288 	}
289 
290 	if (val[5] == '1') {	/* Test in Progress */
291 		status_set("CAL");
292 	}
293 
294 	alarm_init();
295 
296 	if (val[3] == '1') {	/* UPS Failed */
297 		alarm_set("UPS selftest failed!");
298 	}
299 
300 	if (val[6] == '1') {	/* Shutdown Active */
301 		alarm_set("Shutdown imminent!");
302 		status_set("FSD");
303 	}
304 
305 	alarm_commit();
306 
307 	status_commit();
308 
309 	return 0;
310 }
311 
312 
blazer_rating(const char * cmd)313 static int blazer_rating(const char *cmd)
314 {
315 	const struct {
316 		const char	*var;
317 		const char	*fmt;
318 		double		(*conv)(const char *, char **);
319 	} rating[] = {
320 		{ "input.voltage.nominal", "%.0f", strtod },
321 		{ "input.current.nominal", "%.1f", strtod },
322 		{ "battery.voltage.nominal", "%.1f", blazer_packs },
323 		{ "input.frequency.nominal", "%.0f", strtod },
324 		{ NULL, NULL, NULL }
325 	};
326 
327 	char	buf[SMALLBUF], *val, *last = NULL;
328 	int	i;
329 
330 	/*
331 	 * > [F\r]
332 	 * < [#220.0 000 024.0 50.0\r]
333 	 *    0123456789012345678901
334 	 *    0         1         2
335 	 */
336 	if (blazer_command(cmd, buf, sizeof(buf)) < 22) {
337 		upsdebugx(2, "%s: short reply", __func__);
338 		return -1;
339 	}
340 
341 	if (buf[0] != '#') {
342 		upsdebugx(2, "%s: invalid start character [%02x]", __func__, buf[0]);
343 		return -1;
344 	}
345 
346 	for (i = 0, val = strtok_r(buf+1, " ", &last); rating[i].var; i++, val = strtok_r(NULL, " \r\n", &last)) {
347 
348 		if (!val) {
349 			upsdebugx(2, "%s: parsing failed", __func__);
350 			return -1;
351 		}
352 
353 		if (strspn(val, "0123456789.") != strlen(val)) {
354 			upsdebugx(2, "%s: non numerical value [%s]", __func__, val);
355 			continue;
356 		}
357 
358 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
359 #pragma GCC diagnostic push
360 #endif
361 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
362 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
363 #endif
364 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
365 #pragma GCC diagnostic ignored "-Wformat-security"
366 #endif
367 		dstate_setinfo(rating[i].var, rating[i].fmt, rating[i].conv(val, NULL));
368 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
369 #pragma GCC diagnostic pop
370 #endif
371 
372 	}
373 
374 	return 0;
375 }
376 
377 
blazer_vendor(const char * cmd)378 static int blazer_vendor(const char *cmd)
379 {
380 	const struct {
381 		const char	*var;
382 		const int	len;
383 	} information[] = {
384 		{ "ups.mfr",      15 },
385 		{ "ups.model",    10 },
386 		{ "ups.firmware", 10 },
387 		{ NULL, 0 }
388 	};
389 
390 	char	buf[SMALLBUF];
391 	int	i, index;
392 
393 	/*
394 	 * > [I\r]
395 	 * < [#-------------   ------     VT12046Q  \r]
396 	 *    012345678901234567890123456789012345678
397 	 *    0         1         2         3
398 	 */
399 	if (blazer_command(cmd, buf, sizeof(buf)) < 39) {
400 		upsdebugx(2, "%s: short reply", __func__);
401 		return -1;
402 	}
403 
404 	if (buf[0] != '#') {
405 		upsdebugx(2, "%s: invalid start character [%02x]", __func__, buf[0]);
406 		return -1;
407 	}
408 
409 	for (i = 0, index = 1; information[i].var; index += information[i++].len+1) {
410 		char	val[SMALLBUF];
411 
412 		snprintf(val, sizeof(val), "%.*s", information[i].len, &buf[index]);
413 
414 		dstate_setinfo(information[i].var, "%s", str_rtrim(val, ' '));
415 	}
416 
417 	return 0;
418 }
419 
420 
blazer_instcmd(const char * cmdname,const char * extra)421 static int blazer_instcmd(const char *cmdname, const char *extra)
422 {
423 	const struct {
424 		const char *cmd;
425 		const char *ups;
426 	} instcmd[] = {
427 		{ "beeper.toggle", "Q\r" },
428 		{ "load.off", "S00R0000\r" },
429 		{ "load.on", "C\r" },
430 		{ "shutdown.stop", "C\r" },
431 		{ "test.battery.start.deep", "TL\r" },
432 		{ "test.battery.start.quick", "T\r" },
433 		{ "test.battery.stop", "CT\r" },
434 		{ NULL, NULL }
435 	};
436 
437 	char	buf[SMALLBUF] = "";
438 	int	i;
439 
440 	upslogx(LOG_INFO, "instcmd(%s, %s)", cmdname, extra ? extra : "[NULL]");
441 
442 	for (i = 0; instcmd[i].cmd; i++) {
443 
444 		if (strcasecmp(cmdname, instcmd[i].cmd)) {
445 			continue;
446 		}
447 
448 		snprintf(buf, sizeof(buf), "%s", instcmd[i].ups);
449 
450 		/*
451 		 * If a command is invalid, it will be echoed back
452 		 * As an exception, Best UPS units will report "ACK" in case of success!
453 		 * Other UPSes will reply "(ACK" in case of success.
454 		 */
455 		if (blazer_command(buf, buf, sizeof(buf)) > 0) {
456 			if (strncmp(buf, "ACK", 3) && strncmp(buf, "(ACK", 4)) {
457 				upslogx(LOG_ERR, "instcmd: command [%s] failed", cmdname);
458 				return STAT_INSTCMD_FAILED;
459 			}
460 		}
461 
462 		upslogx(LOG_INFO, "instcmd: command [%s] handled", cmdname);
463 		return STAT_INSTCMD_HANDLED;
464 	}
465 
466 	if (!strcasecmp(cmdname, "shutdown.return")) {
467 
468 		/*
469 		 * Sn: Shutdown after n minutes and then turn on when mains is back
470 		 * SnRm: Shutdown after n minutes and then turn on after m minutes
471 		 * Accepted values for n: .2 -> .9 , 01 -> 10
472 		 * Accepted values for m: 0001 -> 9999
473 		 * Note: "S01R0001" and "S01R0002" may not work on early (GE)
474 		 * firmware versions.  The failure mode is that the UPS turns
475 		 * off and never returns.  The fix is to push the return value
476 		 * up by 2, i.e. S01R0003, and it will return online properly.
477 		 * (thus the default of ondelay=3 mins)
478 		 */
479 
480 		if (ondelay == 0) {
481 
482 			if (offdelay < 60) {
483 				snprintf(buf, sizeof(buf), "S.%ld\r", offdelay / 6);
484 			} else {
485 				snprintf(buf, sizeof(buf), "S%02ld\r", offdelay / 60);
486 			}
487 
488 		} else if (offdelay < 60) {
489 
490 			snprintf(buf, sizeof(buf), "S.%ldR%04ld\r", offdelay / 6, ondelay);
491 
492 		} else {
493 
494 			snprintf(buf, sizeof(buf), "S%02ldR%04ld\r", offdelay / 60, ondelay);
495 
496 		}
497 
498 	} else if (!strcasecmp(cmdname, "shutdown.stayoff")) {
499 
500 		/*
501 		 * SnR0000
502 		 * Shutdown after n minutes and stay off
503 		 * Accepted values for n: .2 -> .9 , 01 -> 10
504 		 */
505 
506 		if (offdelay < 60) {
507 			snprintf(buf, sizeof(buf), "S.%ldR0000\r", offdelay / 6);
508 		} else {
509 			snprintf(buf, sizeof(buf), "S%02ldR0000\r", offdelay / 60);
510 		}
511 
512 	} else if (!strcasecmp(cmdname, "test.battery.start")) {
513 		long	delay = extra ? strtol(extra, NULL, 10) : 10;
514 
515 		if ((delay < 1) || (delay > 99)) {
516 			upslogx(LOG_ERR,
517 				"instcmd: command [%s] failed, delay [%s] out of range",
518 				cmdname, extra);
519 			return STAT_INSTCMD_FAILED;
520 		}
521 
522 		snprintf(buf, sizeof(buf), "T%02ld\r", delay);
523 	} else {
524 		upslogx(LOG_ERR, "instcmd: command [%s] not found", cmdname);
525 		return STAT_INSTCMD_UNKNOWN;
526 	}
527 
528 	/*
529 	 * If a command is invalid, it will be echoed back.
530 	 * As an exception, Best UPS units will report "ACK" in case of success!
531 	 * Other UPSes will reply "(ACK" in case of success.
532 	 */
533 	if (blazer_command(buf, buf, sizeof(buf)) > 0) {
534 		if (strncmp(buf, "ACK", 3) && strncmp(buf, "(ACK", 4)) {
535 			upslogx(LOG_ERR, "instcmd: command [%s] failed", cmdname);
536 			return STAT_INSTCMD_FAILED;
537 		}
538 	}
539 
540 	upslogx(LOG_INFO, "instcmd: command [%s] handled", cmdname);
541 	return STAT_INSTCMD_HANDLED;
542 }
543 
544 
blazer_makevartable(void)545 void blazer_makevartable(void)
546 {
547 	addvar(VAR_VALUE, "ondelay", "Delay before UPS startup (minutes)");
548 	addvar(VAR_VALUE, "offdelay", "Delay before UPS shutdown (seconds)");
549 
550 	addvar(VAR_VALUE, "runtimecal", "Parameters used for runtime calculation");
551 	addvar(VAR_VALUE, "chargetime", "Nominal charge time for UPS battery");
552 	addvar(VAR_VALUE, "idleload", "Minimum load to be used for runtime calculation");
553 
554 	addvar(VAR_FLAG, "norating", "Skip reading rating information from UPS");
555 	addvar(VAR_FLAG, "novendor", "Skip reading vendor information from UPS");
556 
557 	addvar(VAR_FLAG, "protocol", "Preselect communication protocol (skip autodetection)");
558 }
559 
560 
blazer_initups(void)561 void blazer_initups(void)
562 {
563 	const char	*val;
564 
565 	val = getval("ondelay");
566 	if (val) {
567 		ondelay = strtol(val, NULL, 10);
568 	}
569 
570 	if ((ondelay < 0) || (ondelay > 9999)) {
571 		fatalx(EXIT_FAILURE, "Start delay '%ld' out of range [0..9999]", ondelay);
572 	}
573 
574 	val = getval("offdelay");
575 	if (val) {
576 		offdelay = strtol(val, NULL, 10);
577 	}
578 
579 	if ((offdelay < 12) || (offdelay > 600)) {
580 		fatalx(EXIT_FAILURE, "Shutdown delay '%ld' out of range [12..600]", offdelay);
581 	}
582 
583 	/* Truncate to nearest setable value */
584 	if (offdelay < 60) {
585 		offdelay -= (offdelay % 6);
586 	} else {
587 		offdelay -= (offdelay % 60);
588 	}
589 
590 	val = dstate_getinfo("battery.voltage.high");
591 	if (val) {
592 		batt.volt.high = strtod(val, NULL);
593 	}
594 
595 	val = dstate_getinfo("battery.voltage.low");
596 	if (val) {
597 		batt.volt.low = strtod(val, NULL);
598 	}
599 }
600 
601 
blazer_initbattery(void)602 static void blazer_initbattery(void)
603 {
604 	const char	*val;
605 
606 	/* If no values were provided by the user in ups.conf, try to guesstimate
607 	 * battery.charge, but announce it! */
608 	if ( (!d_equal(batt.volt.nom, 1)) && ((d_equal(batt.volt.high, -1)) || (d_equal(batt.volt.low, -1)))) {
609 		upslogx(LOG_INFO, "No values provided for battery high/low voltages in ups.conf\n");
610 
611 		/* Basic formula, which should cover most cases */
612 		batt.volt.low = 104 * batt.volt.nom / 120;
613 		batt.volt.high = 130 * batt.volt.nom / 120;
614 
615 		/* Publish these data too */
616 		dstate_setinfo("battery.voltage.low", "%.2f", batt.volt.low);
617 		dstate_setinfo("battery.voltage.high", "%.2f", batt.volt.high);
618 
619 		upslogx(LOG_INFO, "Using 'guestimation' (low: %f, high: %f)!", batt.volt.low, batt.volt.high);
620 	}
621 
622 	val = getval("runtimecal");
623 	if (val) {
624 		double	rh, lh, rl, ll;
625 
626 		time(&lastpoll);
627 
628 		if (sscanf(val, "%lf,%lf,%lf,%lf", &rh, &lh, &rl, &ll) < 4) {
629 			fatalx(EXIT_FAILURE, "Insufficient parameters for runtimecal");
630 		}
631 
632 		if ((rl < rh) || (rh <= 0)) {
633 			fatalx(EXIT_FAILURE, "Parameter out of range (runtime)");
634 		}
635 
636 		if ((lh > 100) || (ll > lh) || (ll <= 0)) {
637 			fatalx(EXIT_FAILURE, "Parameter out of range (load)");
638 		}
639 
640 		batt.runt.exp = log(rl / rh) / log(lh / ll);
641 		upsdebugx(2, "battery runtime exponent : %.3f", batt.runt.exp);
642 
643 		batt.runt.nom = rh * pow(lh / 100, batt.runt.exp);
644 		upsdebugx(2, "battery runtime nominal  : %.1f", batt.runt.nom);
645 
646 	} else {
647 		upslogx(LOG_INFO, "Battery runtime will not be calculated (runtimecal not set)");
648 		return;
649 	}
650 
651 	if (batt.chrg.act < 0) {
652 		batt.volt.low = batt.volt.nom;
653 		batt.volt.high = 1.15 * batt.volt.nom;
654 
655 		blazer_battery(dstate_getinfo("battery.voltage"), NULL);
656 	}
657 
658 	val = dstate_getinfo("battery.charge");
659 	if (val) {
660 		batt.runt.est = batt.runt.nom * strtod(val, NULL) / 100;
661 		upsdebugx(2, "battery runtime estimate : %.1f", batt.runt.est);
662 	} else {
663 		fatalx(EXIT_FAILURE, "Initial battery charge undetermined");
664 	}
665 
666 	val = getval("chargetime");
667 	if (val) {
668 		batt.chrg.time = strtol(val, NULL, 10);
669 
670 		if (batt.chrg.time <= 0) {
671 			fatalx(EXIT_FAILURE, "Charge time out of range [1..s]");
672 		}
673 
674 		upsdebugx(2, "battery charge time      : %ld", batt.chrg.time);
675 	} else {
676 		upslogx(LOG_INFO, "No charge time specified, using built in default [%ld seconds]", batt.chrg.time);
677 	}
678 
679 	val = getval("idleload");
680 	if (val) {
681 		load.low = strtod(val, NULL) / 100;
682 
683 		if ((load.low <= 0) || (load.low > 1)) {
684 			fatalx(EXIT_FAILURE, "Idle load out of range [0..100]");
685 		}
686 
687 		upsdebugx(2, "minimum load used (idle) : %.3f", load.low);
688 	} else {
689 		upslogx(LOG_INFO, "No idle load specified, using built in default [%.1f %%]", 100 * load.low);
690 	}
691 }
692 
693 
blazer_initinfo(void)694 void blazer_initinfo(void)
695 {
696 	const char	*protocol = getval("protocol");
697 	int	retry;
698 
699 	for (proto = 0; command[proto].status; proto++) {
700 
701 		int	ret = -1;
702 
703 		if (protocol && strcasecmp(protocol, command[proto].name)) {
704 			upsdebugx(2, "Skipping %s protocol...", command[proto].name);
705 			continue;
706 		}
707 
708 		upsdebugx(2, "Trying %s protocol...", command[proto].name);
709 
710 		for (retry = 1; retry <= MAXTRIES; retry++) {
711 
712 			ret = blazer_status(command[proto].status);
713 			if (ret < 0) {
714 				upsdebugx(2, "Status read %d failed", retry);
715 				continue;
716 			}
717 
718 			upsdebugx(2, "Status read in %d tries", retry);
719 			break;
720 		}
721 
722 		if (!ret) {
723 			upslogx(LOG_INFO, "Supported UPS detected with %s protocol", command[proto].name);
724 			break;
725 		}
726 	}
727 
728 	if (!command[proto].status) {
729 		fatalx(EXIT_FAILURE, "No supported UPS detected");
730 	}
731 
732 	if (command[proto].rating && !testvar("norating")) {
733 		int	ret = -1;
734 
735 		for (retry = 1; retry <= MAXTRIES; retry++) {
736 
737 			ret = blazer_rating(command[proto].rating);
738 			if (ret < 0) {
739 				upsdebugx(1, "Rating read %d failed", retry);
740 				continue;
741 			}
742 
743 			upsdebugx(2, "Ratings read in %d tries", retry);
744 			break;
745 		}
746 
747 		if (ret) {
748 			upslogx(LOG_DEBUG, "Rating information unavailable");
749 		}
750 	}
751 
752 	if (command[proto].vendor && !testvar("novendor")) {
753 		int	ret = -1;
754 
755 		for (retry = 1; retry <= MAXTRIES; retry++) {
756 
757 			ret = blazer_vendor(command[proto].vendor);
758 			if (ret < 0) {
759 				upsdebugx(1, "Vendor information read %d failed", retry);
760 				continue;
761 			}
762 
763 			upslogx(LOG_INFO, "Vendor information read in %d tries", retry);
764 			break;
765 		}
766 
767 		if (ret) {
768 			upslogx(LOG_DEBUG, "Vendor information unavailable");
769 		}
770 	}
771 
772 	blazer_initbattery();
773 
774 	dstate_setinfo("ups.delay.start", "%ld", 60 * ondelay);
775 	dstate_setinfo("ups.delay.shutdown", "%ld", offdelay);
776 
777 	dstate_addcmd("beeper.toggle");
778 	dstate_addcmd("load.off");
779 	dstate_addcmd("load.on");
780 	dstate_addcmd("shutdown.return");
781 	dstate_addcmd("shutdown.stayoff");
782 	dstate_addcmd("shutdown.stop");
783 	dstate_addcmd("test.battery.start");
784 	dstate_addcmd("test.battery.start.deep");
785 	dstate_addcmd("test.battery.start.quick");
786 	dstate_addcmd("test.battery.stop");
787 
788 	upsh.instcmd = blazer_instcmd;
789 }
790 
791 
upsdrv_updateinfo(void)792 void upsdrv_updateinfo(void)
793 {
794 	static int	retry = 0;
795 
796 	if (blazer_status(command[proto].status)) {
797 
798 		if (retry < MAXTRIES) {
799 			upsdebugx(1, "Communications with UPS lost: status read failed!");
800 			retry++;
801 		} else if (retry == MAXTRIES) {
802 			upslogx(LOG_WARNING, "Communications with UPS lost: status read failed!");
803 			retry++;
804 		} else {
805 			dstate_datastale();
806 		}
807 
808 		return;
809 	}
810 
811 	if (getval("runtimecal")) {
812 		time_t	now;
813 
814 		time(&now);
815 
816 		if (online) {	/* OL */
817 			batt.runt.est += batt.runt.nom * difftime(now, lastpoll) / batt.chrg.time;
818 			if (batt.runt.est > batt.runt.nom) {
819 				batt.runt.est = batt.runt.nom;
820 			}
821 		} else {	/* OB */
822 			batt.runt.est -= load.eff * difftime(now, lastpoll);
823 			if (batt.runt.est < 0) {
824 				batt.runt.est = 0;
825 			}
826 		}
827 
828 		dstate_setinfo("battery.charge", "%.0f", 100 * batt.runt.est / batt.runt.nom);
829 		dstate_setinfo("battery.runtime", "%.0f", batt.runt.est / load.eff);
830 
831 		lastpoll = now;
832 	}
833 
834 	if (retry > MAXTRIES) {
835 		upslogx(LOG_NOTICE, "Communications with UPS re-established");
836 	}
837 
838 	retry = 0;
839 
840 	dstate_dataok();
841 }
842 
843 void upsdrv_shutdown(void)
844 	__attribute__((noreturn));
845 
upsdrv_shutdown(void)846 void upsdrv_shutdown(void)
847 {
848 	int	retry;
849 
850 	/* Stop pending shutdowns */
851 	for (retry = 1; retry <= MAXTRIES; retry++) {
852 
853 		if (blazer_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) {
854 			continue;
855 		}
856 
857 		break;
858 
859 	}
860 
861 	if (retry > MAXTRIES) {
862 		upslogx(LOG_NOTICE, "No shutdown pending");
863 	}
864 
865 	/* Shutdown */
866 	for (retry = 1; retry <= MAXTRIES; retry++) {
867 
868 		if (blazer_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) {
869 			continue;
870 		}
871 
872 		fatalx(EXIT_SUCCESS, "Shutting down in %ld seconds", offdelay);
873 
874 	}
875 
876 	fatalx(EXIT_FAILURE, "Shutdown failed!");
877 }
878