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