1 /* nutdrv_qx.c - Driver for USB and serial UPS units with Q* protocols
4 * 2013 Daniele Pezzini <hyouko@gmail.com>
6 * usbhid-ups.c - Copyright (C)
7 * 2003-2012 Arnaud Quette <arnaud.quette@gmail.com>
8 * 2005 John Stamp <kinsayder@hotmail.com>
9 * 2005-2006 Peter Selinger <selinger@users.sourceforge.net>
10 * 2007-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
11 * blazer.c - Copyright (C)
12 * 2008-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
13 * 2012 Arnaud Quette <ArnaudQuette@Eaton.com>
14 * blazer_ser.c - Copyright (C)
15 * 2008 Arjen de Korte <adkorte-guest@alioth.debian.org>
16 * blazer_usb.c - Copyright (C)
17 * 2003-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
18 * 2011-2012 Arnaud Quette <arnaud.quette@free.fr>
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #define DRIVER_VERSION "0.01"
42 /* note: QX_USB/QX_SERIAL set through Makefile */
45 #include "usb-common.h"
48 #define DRIVER_NAME "Generic Q* USB/Serial driver"
50 #define DRIVER_NAME "Generic Q* USB driver"
51 #endif /* QX_SERIAL */
53 #define DRIVER_NAME "Generic Q* Serial driver"
58 #define SER_WAIT_SEC 1 /* 3 seconds for Best UPS */
59 #endif /* QX_SERIAL */
61 #include "nutdrv_qx.h"
63 /* == Subdrivers == */
64 /* Include all known subdrivers */
65 #include "nutdrv_qx_mecer.h"
66 #include "nutdrv_qx_megatec.h"
67 #include "nutdrv_qx_megatec-old.h"
68 #include "nutdrv_qx_mustek.h"
69 #include "nutdrv_qx_voltronic.h"
70 #include "nutdrv_qx_zinto.h"
72 /* Master list of avaiable subdrivers */
73 static subdriver_t
*subdriver_list
[] = {
76 &megatec_old_subdriver
,
84 /* == Driver description structure == */
85 upsdrv_info_t upsdrv_info
= {
88 "Daniele Pezzini <hyouko@gmail.com>" \
89 "Arnaud Quette <arnaud.quette@gmail.com>" \
90 "John Stamp <kinsayder@hotmail.com>" \
91 "Peter Selinger <selinger@users.sourceforge.net>" \
92 "Arjen de Korte <adkorte-guest@alioth.debian.org>",
95 { &comm_upsdrv_info
, NULL
}
102 /* == Data walk modes == */
104 QX_WALKMODE_INIT
= 0,
105 QX_WALKMODE_QUICK_UPDATE
,
106 QX_WALKMODE_FULL_UPDATE
110 /* == Global vars == */
111 /* Pointer to the active subdriver object (changed in subdriver_matcher() function) */
112 static subdriver_t
*subdriver
= NULL
;
114 static int pollfreq
= DEFAULT_POLLFREQ
;
115 static int ups_status
= 0;
116 static bool_t data_has_changed
= FALSE
; /* for SEMI_STATIC data polling */
118 static time_t lastpoll
; /* Timestamp the last polling */
120 #if defined(QX_USB) && defined(QX_SERIAL)
121 static int is_usb
= 0; /* Whether the device is connected through USB (1) or serial (0) */
122 #endif /* QX_USB && QX_SERIAL */
125 char command
[SMALLBUF
]; /* Command sent to the UPS to get answer/to execute an instant command */
126 char answer
[SMALLBUF
]; /* Answer from the UPS, filled at runtime */
127 } previous_item
= { "", "" }; /* Hold the values of the item processed just before the actual one */
130 /* == Support functions == */
131 static int subdriver_matcher(void);
132 static int qx_command(const char *cmd
, char *buf
, size_t buflen
);
133 static int qx_process_answer(item_t
*item
, const int len
);
134 static bool_t
qx_ups_walk(walkmode_t mode
);
135 static void ups_status_set(void);
136 static void ups_alarm_set(void);
137 static void qx_set_var(item_t
*item
);
140 /* == Struct & data for status processing == */
142 const char *status_str
; /* UPS status string */
143 const int status_mask
; /* UPS status mask */
146 static status_lkp_t status_info
[] = {
147 /* Map status strings to bit masks */
148 { "OL", STATUS(OL
) },
149 { "LB", STATUS(LB
) },
150 { "RB", STATUS(RB
) },
151 { "CHRG", STATUS(CHRG
) },
152 { "DISCHRG", STATUS(DISCHRG
) },
153 { "BYPASS", STATUS(BYPASS
) },
154 { "CAL", STATUS(CAL
) },
155 { "OFF", STATUS(OFF
) },
156 { "OVER", STATUS(OVER
) },
157 { "TRIM", STATUS(TRIM
) },
158 { "BOOST", STATUS(BOOST
) },
159 { "FSD", STATUS(FSD
) },
164 /* == battery.{charge,runtime} guesstimation == */
165 /* Support functions */
166 static int qx_battery(void);
167 static int qx_load(void);
168 static void qx_initbattery(void);
172 double packs
; /* Battery voltage multiplier */
174 double act
; /* Actual runtime on battery */
175 double nom
; /* Nominal runtime on battery (full load) */
176 double est
; /* Estimated runtime remaining (full load) */
177 double exp
; /* Load exponent */
180 double act
; /* Actual battery voltage */
181 double high
; /* Battery float voltage */
182 double nom
; /* Nominal battery voltage */
183 double low
; /* Battery low voltage */
186 double act
; /* Actual battery charge */
187 long time
; /* Recharge time from empty to full */
189 } batt
= { 1, { -1, -1, 0, 0 }, { -1, -1, -1, -1 }, { -1, 43200 } };
193 double act
; /* Actual load (reported by the UPS) */
194 double low
; /* Idle load */
195 double eff
; /* Effective load */
196 } load
= { 0, 0.1, 1 };
198 static time_t battery_lastpoll
= 0;
200 /* Fill batt.volt.act and guesstimate the battery charge if it isn't already available. */
201 static int qx_battery(void)
203 const char *val
= dstate_getinfo("battery.voltage");
206 upsdebugx(2, "%s: unable to get battery.voltage", __func__
);
210 batt
.volt
.act
= batt
.packs
* strtod(val
, NULL
);
212 if (batt
.chrg
.act
== -1 && batt
.volt
.low
> 0 && batt
.volt
.high
> batt
.volt
.low
) {
214 batt
.chrg
.act
= 100 * (batt
.volt
.act
- batt
.volt
.low
) / (batt
.volt
.high
- batt
.volt
.low
);
216 if (batt
.chrg
.act
< 0) {
220 if (batt
.chrg
.act
> 100) {
224 dstate_setinfo("battery.charge", "%.0f", batt
.chrg
.act
);
231 /* Load for battery.{charge,runtime} from runtimecal */
232 static int qx_load(void)
234 const char *val
= dstate_getinfo("ups.load");
237 upsdebugx(2, "%s: unable to get ups.load", __func__
);
241 load
.act
= strtod(val
, NULL
);
243 load
.eff
= pow(load
.act
/ 100, batt
.runt
.exp
);
245 if (load
.eff
< load
.low
) {
252 /* Guesstimation: init */
253 static void qx_initbattery(void)
255 if (!dstate_getinfo("battery.charge") || !dstate_getinfo("battery.runtime")) {
259 val
= dstate_getinfo("battery.voltage.high");
261 batt
.volt
.high
= strtod(val
, NULL
);
264 val
= dstate_getinfo("battery.voltage.low");
266 batt
.volt
.low
= strtod(val
, NULL
);
269 val
= dstate_getinfo("battery.voltage.nominal");
271 batt
.volt
.nom
= strtod(val
, NULL
);
274 /* If no values are available for both battery.voltage.{low,high} either from the UPS or provided by the user in ups.conf, try to guesstimate them, but announce it! */
275 if (batt
.volt
.nom
!= -1 && (batt
.volt
.low
== -1 || batt
.volt
.high
== -1)) {
277 upslogx(LOG_INFO
, "No values for battery high/low voltages");
279 /* Basic formula, which should cover most cases */
280 batt
.volt
.low
= 104 * batt
.volt
.nom
/ 120;
281 batt
.volt
.high
= 130 * batt
.volt
.nom
/ 120;
283 /* Publish these data too */
284 dstate_setinfo("battery.voltage.low", "%.2f", batt
.volt
.low
);
285 dstate_setinfo("battery.voltage.high", "%.2f", batt
.volt
.high
);
287 upslogx(LOG_INFO
, "Using 'guesstimation' (low: %f, high: %f)!", batt
.volt
.low
, batt
.volt
.high
);
291 val
= dstate_getinfo("battery.packs");
292 if (val
&& (strspn(val
, "0123456789 .") == strlen(val
))) {
293 batt
.packs
= strtod(val
, NULL
);
296 /* qx_battery -> batt.volt.act */
297 if (!qx_battery() && batt
.volt
.nom
!= -1) {
299 const double packs
[] = { 120, 100, 80, 60, 48, 36, 30, 24, 18, 12, 8, 6, 4, 3, 2, 1, 0.5, -1 };
302 /* The battery voltage will quickly return to at least the nominal value after discharging them.
303 * For overlapping battery.voltage.low/high ranges therefor choose the one with the highest multiplier. */
304 for (i
= 0; packs
[i
] > 0; i
++) {
306 if (packs
[i
] * batt
.volt
.act
> 1.2 * batt
.volt
.nom
) {
310 if (packs
[i
] * batt
.volt
.act
< 0.8 * batt
.volt
.nom
) {
311 upslogx(LOG_INFO
, "Can't autodetect number of battery packs [%.0f/%.2f]", batt
.volt
.nom
, batt
.volt
.act
);
315 batt
.packs
= packs
[i
];
321 upslogx(LOG_INFO
, "Can't autodetect number of battery packs [%.0f/%.2f]", batt
.volt
.nom
, batt
.volt
.act
);
326 /* Update batt.{chrg,volt}.act */
329 val
= getval("runtimecal");
332 double rh
, lh
, rl
, ll
;
334 time(&battery_lastpoll
);
336 if (sscanf(val
, "%lf,%lf,%lf,%lf", &rh
, &lh
, &rl
, &ll
) < 4) {
337 fatalx(EXIT_FAILURE
, "Insufficient parameters for runtimecal");
340 if ((rl
< rh
) || (rh
<= 0)) {
341 fatalx(EXIT_FAILURE
, "Parameter out of range (runtime)");
344 if ((lh
> 100) || (ll
> lh
) || (ll
<= 0)) {
345 fatalx(EXIT_FAILURE
, "Parameter out of range (load)");
348 batt
.runt
.exp
= log(rl
/ rh
) / log(lh
/ ll
);
349 upsdebugx(2, "%s: battery runtime exponent: %.3f", __func__
, batt
.runt
.exp
);
351 batt
.runt
.nom
= rh
* pow(lh
/ 100, batt
.runt
.exp
);
352 upsdebugx(2, "%s: battery runtime nominal: %.1f", __func__
, batt
.runt
.nom
);
356 upslogx(LOG_INFO
, "Battery runtime will not be calculated (runtimecal not set)");
361 val
= dstate_getinfo("battery.charge");
362 if (!val
&& batt
.volt
.nom
!= -1) {
363 batt
.volt
.low
= batt
.volt
.nom
;
364 batt
.volt
.high
= 1.15 * batt
.volt
.nom
;
367 fatalx(EXIT_FAILURE
, "Initial battery charge undetermined");
369 val
= dstate_getinfo("battery.charge");
373 batt
.runt
.est
= batt
.runt
.nom
* strtod(val
, NULL
) / 100;
374 upsdebugx(2, "%s: battery runtime estimate: %.1f", __func__
, batt
.runt
.est
);
376 fatalx(EXIT_FAILURE
, "Initial battery charge undetermined");
379 val
= getval("chargetime");
381 batt
.chrg
.time
= strtol(val
, NULL
, 10);
383 if (batt
.chrg
.time
<= 0) {
384 fatalx(EXIT_FAILURE
, "Charge time out of range [1..s]");
387 upsdebugx(2, "%s: battery charge time: %ld", __func__
, batt
.chrg
.time
);
389 upslogx(LOG_INFO
, "No charge time specified, using built in default [%ld seconds]", batt
.chrg
.time
);
392 val
= getval("idleload");
394 load
.low
= strtod(val
, NULL
) / 100;
396 if ((load
.low
<= 0) || (load
.low
> 1)) {
397 fatalx(EXIT_FAILURE
, "Idle load out of range [0..100]");
400 upsdebugx(2, "%s: minimum load used (idle): %.3f", __func__
, load
.low
);
402 upslogx(LOG_INFO
, "No idle load specified, using built in default [%.1f %%]", 100 * load
.low
);
408 /* == USB communication subdrivers == */
409 #if defined(QX_USB) && !defined(TESTING)
410 static usb_communication_subdriver_t
*usb
= &usb_subdriver
;
411 static usb_dev_handle
*udev
= NULL
;
412 static USBDevice_t usbdevice
;
413 static USBDeviceMatcher_t
*reopen_matcher
= NULL
;
414 static USBDeviceMatcher_t
*regex_matcher
= NULL
;
415 static int langid_fix
= -1;
417 static int (*subdriver_command
)(const char *cmd
, char *buf
, size_t buflen
) = NULL
;
419 /* Cypress communication subdriver */
420 static int cypress_command(const char *cmd
, char *buf
, size_t buflen
)
427 memset(tmp
, 0, sizeof(tmp
));
428 snprintf(tmp
, sizeof(tmp
), "%s", cmd
);
430 for (i
= 0; i
< strlen(tmp
); i
+= ret
) {
432 /* Write data in 8-byte chunks */
433 /* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
434 ret
= usb_control_msg(udev
, USB_ENDPOINT_OUT
+ USB_TYPE_CLASS
+ USB_RECIP_INTERFACE
, 0x09, 0x200, 0, &tmp
[i
], 8, 5000);
437 upsdebugx(3, "send: %s", ret
? usb_strerror() : "timeout");
442 upsdebugx(3, "send: %.*s", (int)strcspn(tmp
, "\r"), tmp
);
445 memset(buf
, 0, buflen
);
447 for (i
= 0; (i
<= buflen
-8) && (strchr(buf
, '\r') == NULL
); i
+= ret
) {
449 /* Read data in 8-byte chunks */
450 /* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
451 ret
= usb_interrupt_read(udev
, 0x81, &buf
[i
], 8, 1000);
453 /* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
455 upsdebugx(3, "read: %s", ret
? usb_strerror() : "timeout");
460 upsdebugx(3, "read: %.*s", (int)strcspn(buf
, "\r"), buf
);
464 /* Phoenix communication subdriver */
465 static int phoenix_command(const char *cmd
, char *buf
, size_t buflen
)
471 for (i
= 0; i
< 8; i
++) {
473 /* Read data in 8-byte chunks */
474 /* ret = usb->get_interrupt(udev, (unsigned char *)tmp, 8, 1000); */
475 ret
= usb_interrupt_read(udev
, 0x81, tmp
, 8, 1000);
477 /* This USB to serial implementation is crappy.
478 * In order to read correct replies we need to flush the output buffers of the converter until we get no more data (ie, it times out). */
481 case -EPIPE
: /* Broken pipe */
482 usb_clear_halt(udev
, 0x81);
483 case -ETIMEDOUT
: /* Connection timed out */
488 upsdebugx(3, "flush: %s", usb_strerror());
492 upsdebug_hex(4, "dump", tmp
, ret
);
496 memset(tmp
, 0, sizeof(tmp
));
497 snprintf(tmp
, sizeof(tmp
), "%s", cmd
);
499 for (i
= 0; i
< strlen(tmp
); i
+= ret
) {
501 /* Write data in 8-byte chunks */
502 /* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
503 ret
= usb_control_msg(udev
, USB_ENDPOINT_OUT
+ USB_TYPE_CLASS
+ USB_RECIP_INTERFACE
, 0x09, 0x200, 0, &tmp
[i
], 8, 1000);
506 upsdebugx(3, "send: %s", ret
? usb_strerror() : "timeout");
511 upsdebugx(3, "send: %.*s", (int)strcspn(tmp
, "\r"), tmp
);
514 memset(buf
, 0, buflen
);
516 for (i
= 0; (i
<= buflen
-8) && (strchr(buf
, '\r') == NULL
); i
+= ret
) {
518 /* Read data in 8-byte chunks */
519 /* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
520 ret
= usb_interrupt_read(udev
, 0x81, &buf
[i
], 8, 1000);
522 /* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
524 upsdebugx(3, "read: %s", ret
? usb_strerror() : "timeout");
529 upsdebugx(3, "read: %.*s", (int)strcspn(buf
, "\r"), buf
);
533 /* Ippon communication subdriver */
534 static int ippon_command(const char *cmd
, char *buf
, size_t buflen
)
541 snprintf(tmp
, sizeof(tmp
), "%s", cmd
);
543 for (i
= 0; i
< strlen(tmp
); i
+= ret
) {
545 /* Write data in 8-byte chunks */
546 ret
= usb_control_msg(udev
, USB_ENDPOINT_OUT
+ USB_TYPE_CLASS
+ USB_RECIP_INTERFACE
, 0x09, 0x2, 0, &tmp
[i
], 8, 1000);
549 upsdebugx(3, "send: %s", (ret
!= -ETIMEDOUT
) ? usb_strerror() : "Connection timed out");
554 upsdebugx(3, "send: %.*s", (int)strcspn(tmp
, "\r"), tmp
);
556 /* Read all 64 bytes of the reply in one large chunk */
557 ret
= usb_interrupt_read(udev
, 0x81, tmp
, sizeof(tmp
), 1000);
559 /* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
561 upsdebugx(3, "read: %s", (ret
!= -ETIMEDOUT
) ? usb_strerror() : "Connection timed out");
565 snprintf(buf
, buflen
, "%.*s", ret
, tmp
);
567 upsdebugx(3, "read: %.*s", (int)strcspn(buf
, "\r"), buf
);
571 /* Krauler communication subdriver */
572 static int krauler_command(const char *cmd
, char *buf
, size_t buflen
)
574 /* Still not implemented:
575 * 0x6 T<n> (don't know how to pass the parameter)
576 * 0x68 and 0x69 both cause shutdown after an undefined interval */
578 const char *str
; /* Megatec command */
579 const int index
; /* Krauler string index for this command */
580 const char prefix
; /* Character to replace the first byte in reply */
582 { "Q1\r", 0x03, '(' },
583 { "F\r", 0x0d, '#' },
584 { "I\r", 0x0c, '#' },
585 { "T\r", 0x04, '\r' },
586 { "TL\r", 0x05, '\r' },
587 { "Q\r", 0x07, '\r' },
588 { "C\r", 0x0b, '\r' },
589 { "CT\r", 0x0b, '\r' },
595 upsdebugx(3, "send: %.*s", (int)strcspn(cmd
, "\r"), cmd
);
597 for (i
= 0; command
[i
].str
; i
++) {
601 if (strcmp(cmd
, command
[i
].str
)) {
605 for (retry
= 0; retry
< 10; retry
++) {
609 if (langid_fix
!= -1) {
610 /* Apply langid_fix value */
611 ret
= usb_get_string(udev
, command
[i
].index
, langid_fix
, buf
, buflen
);
614 ret
= usb_get_string_simple(udev
, command
[i
].index
, buf
, buflen
);
618 upsdebugx(3, "read: %s", ret
? usb_strerror() : "timeout");
622 /* This may serve in the future */
623 upsdebugx(1, "received %d (%d)", ret
, buf
[0]);
625 if (langid_fix
!= -1) {
626 /* Limit this check, at least for now */
627 /* Invalid receive size - message corrupted */
629 upsdebugx(1, "size mismatch: %d / %d", ret
, buf
[0]);
633 /* Simple unicode -> ASCII inplace conversion
634 * FIXME: this code is at least shared with mge-shut/libshut
635 * Create a common function? */
636 unsigned int di
, si
, size
= buf
[0];
637 for (di
= 0, si
= 2; si
< size
; si
+= 2) {
639 if (di
>= (buflen
- 1))
642 if (buf
[si
+ 1]) /* high byte */
653 /* "UPS No Ack" has a special meaning */
654 if (!strcasecmp(buf
, "UPS No Ack")) {
655 upsdebugx(3, "read: %.*s", (int)strcspn(buf
, "\r"), buf
);
659 /* Replace the first byte of what we received with the correct one */
660 buf
[0] = command
[i
].prefix
;
662 upsdebugx(3, "read: %.*s", (int)strcspn(buf
, "\r"), buf
);
671 /* Echo the unknown command back */
672 upsdebugx(3, "read: %.*s", (int)strcspn(cmd
, "\r"), cmd
);
673 return snprintf(buf
, buflen
, "%s", cmd
);
676 static void *cypress_subdriver(USBDevice_t
*device
)
678 subdriver_command
= &cypress_command
;
682 static void *ippon_subdriver(USBDevice_t
*device
)
684 subdriver_command
= &ippon_command
;
688 static void *krauler_subdriver(USBDevice_t
*device
)
690 subdriver_command
= &krauler_command
;
694 static void *phoenix_subdriver(USBDevice_t
*device
)
696 subdriver_command
= &phoenix_command
;
700 /* USB VendorID/ProductID match - note: rightmost comment is used for naming rules by tools/nut-usbinfo.pl */
701 static usb_device_id_t qx_usb_id
[] = {
702 { USB_DEVICE(0x05b8, 0x0000), &cypress_subdriver
}, /* Agiler UPS */
703 { USB_DEVICE(0x0001, 0x0000), &krauler_subdriver
}, /* Krauler UP-M500VA */
704 { USB_DEVICE(0xffff, 0x0000), &krauler_subdriver
}, /* Ablerex 625L USB */
705 { USB_DEVICE(0x0665, 0x5161), &cypress_subdriver
}, /* Belkin F6C1200-UNV/Voltronic Power UPSes */
706 { USB_DEVICE(0x06da, 0x0002), &cypress_subdriver
}, /* Online Yunto YQ450 */
707 { USB_DEVICE(0x06da, 0x0003), &ippon_subdriver
}, /* Mustek Powermust */
708 { USB_DEVICE(0x06da, 0x0004), &cypress_subdriver
}, /* Phoenixtec Innova 3/1 T */
709 { USB_DEVICE(0x06da, 0x0005), &cypress_subdriver
}, /* Phoenixtec Innova RT */
710 { USB_DEVICE(0x06da, 0x0201), &cypress_subdriver
}, /* Phoenixtec Innova T */
711 { USB_DEVICE(0x06da, 0x0601), &phoenix_subdriver
}, /* Online Zinto A */
712 { USB_DEVICE(0x0f03, 0x0001), &cypress_subdriver
}, /* Unitek Alpha 1200Sx */
713 { USB_DEVICE(0x14f0, 0x00c9), &phoenix_subdriver
}, /* GE EP series */
718 static int device_match_func(USBDevice_t
*hd
, void *privdata
)
720 if (subdriver_command
) {
724 switch (is_usb_device_supported(qx_usb_id
, hd
))
729 case POSSIBLY_SUPPORTED
:
736 static USBDeviceMatcher_t device_matcher
= {
741 #endif /* QX_USB && !TESTING */
744 /* == Driver functions implementations == */
746 /* Process instant command and take action. */
747 int instcmd(const char *cmdname
, const char *extradata
)
750 char value
[SMALLBUF
];
752 if (!strcasecmp(cmdname
, "beeper.off")) {
753 /* Compatibility mode for old command */
754 upslogx(LOG_WARNING
, "The 'beeper.off' command has been renamed to 'beeper.disable'");
755 return instcmd("beeper.disable", NULL
);
758 if (!strcasecmp(cmdname
, "beeper.on")) {
759 /* Compatibility mode for old command */
760 upslogx(LOG_WARNING
, "The 'beeper.on' command has been renamed to 'beeper.enable'");
761 return instcmd("beeper.enable", NULL
);
764 upslogx(LOG_INFO
, "%s(%s, %s)", __func__
, cmdname
, extradata
? extradata
: "[NULL]");
766 /* Retrieve item by command name */
767 item
= find_nut_info(cmdname
, QX_FLAG_CMD
, QX_FLAG_SKIP
);
769 /* Check for fallback if not found */
772 if (!strcasecmp(cmdname
, "load.on")) {
773 return instcmd("load.on.delay", "0");
776 if (!strcasecmp(cmdname
, "load.off")) {
777 return instcmd("load.off.delay", "0");
780 if (!strcasecmp(cmdname
, "shutdown.return")) {
784 /* Ensure "ups.start.auto" is set to "yes", if supported */
785 if (dstate_getinfo("ups.start.auto")) {
786 if (setvar("ups.start.auto", "yes") != STAT_SET_HANDLED
) {
787 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
788 return STAT_INSTCMD_FAILED
;
792 ret
= instcmd("load.on.delay", dstate_getinfo("ups.delay.start"));
793 if (ret
!= STAT_INSTCMD_HANDLED
) {
797 return instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
801 if (!strcasecmp(cmdname
, "shutdown.stayoff")) {
805 /* Ensure "ups.start.auto" is set to "no", if supported */
806 if (dstate_getinfo("ups.start.auto")) {
807 if (setvar("ups.start.auto", "no") != STAT_SET_HANDLED
) {
808 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
809 return STAT_INSTCMD_FAILED
;
813 ret
= instcmd("load.on.delay", "-1");
814 if (ret
!= STAT_INSTCMD_HANDLED
) {
818 return instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
822 upsdebugx(2, "%s: command %s unavailable", __func__
, cmdname
);
823 return STAT_INSTCMD_INVALID
;
826 /* If extradata is empty, use the default value from the blazer to NUT table */
827 extradata
= extradata
? extradata
: item
->dfl
;
828 snprintf(value
, sizeof(value
), "%s", extradata
? extradata
: "");
830 /* Preprocess command */
831 if (item
->preprocess
!= NULL
&& item
->preprocess(item
, value
, sizeof(value
))) {
832 /* Something went wrong */
833 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
834 return STAT_INSTCMD_FAILED
;
837 /* No preprocess function -> nothing to do with extradata */
838 if (item
->preprocess
== NULL
)
839 snprintf(value
, sizeof(value
), "%s", "");
841 /* Send the command, get the reply */
842 if (qx_process(item
, strlen(value
) > 0 ? value
: NULL
)) {
843 /* Something went wrong */
844 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
845 return STAT_INSTCMD_FAILED
;
848 /* We got a reply from the UPS: either subdriver->accepted (-> command handled) or the command itself echoed back (-> command failed) */
849 if (strlen(item
->value
) > 0) {
851 if (subdriver
->accepted
!= NULL
&& !strcasecmp(item
->value
, subdriver
->accepted
)) {
852 upslogx(LOG_INFO
, "%s: SUCCEED", __func__
);
853 /* Set the status so that SEMI_STATIC vars are polled */
854 data_has_changed
= TRUE
;
855 return STAT_INSTCMD_HANDLED
;
858 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
859 return STAT_INSTCMD_FAILED
;
863 /* No reply from the UPS -> command handled */
864 upslogx(LOG_INFO
, "%s: SUCCEED", __func__
);
865 /* Set the status so that SEMI_STATIC vars are polled */
866 data_has_changed
= TRUE
;
867 return STAT_INSTCMD_HANDLED
;
870 /* Set r/w variable to a value. */
871 int setvar(const char *varname
, const char *val
)
874 char value
[SMALLBUF
];
875 st_tree_t
*root
= (st_tree_t
*)dstate_getroot();
878 /* Retrieve variable */
879 item
= find_nut_info(varname
, QX_FLAG_SETVAR
, QX_FLAG_SKIP
);
882 upsdebugx(2, "%s: element %s unavailable", __func__
, varname
);
883 return STAT_SET_UNKNOWN
;
886 /* No NUT variable is available for this item, so we're handling a one-time setvar from ups.conf */
887 if (item
->qxflags
& QX_FLAG_NONUT
) {
892 if (!testvar(item
->info_type
)) {
893 upsdebugx(2, "%s: nothing to do.. [%s]", __func__
, item
->info_type
);
894 return STAT_SET_HANDLED
;
897 userval
= getval(item
->info_type
);
899 upslogx(LOG_INFO
, "%s(%s, %s)", __func__
, varname
, userval
? userval
: "[NULL]");
901 snprintf(value
, sizeof(value
), "%s", userval
? userval
: "");
903 /* This item is available in NUT */
906 upslogx(LOG_INFO
, "%s(%s, %s)", __func__
, varname
, strlen(val
) ? val
: "[NULL]");
909 upslogx(LOG_ERR
, "%s: value not given for %s", __func__
, item
->info_type
);
910 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
913 snprintf(value
, sizeof(value
), "%s", val
);
916 if (!strcasecmp(dstate_getinfo(item
->info_type
), value
)) {
917 upslogx(LOG_INFO
, "%s: nothing to do.. [%s]", __func__
, item
->info_type
);
918 return STAT_SET_HANDLED
;
923 /* Check if given value is in the range of accepted values (range) */
924 if (item
->qxflags
& QX_FLAG_RANGE
) {
926 int valuetoset
, min
, max
;
928 if (strspn(value
, "0123456789 .") != strlen(value
)) {
929 upslogx(LOG_ERR
, "%s: non numerical value [%s: %s]", __func__
, item
->info_type
, value
);
930 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
933 valuetoset
= strtol(value
, NULL
, 10);
935 /* No NUT var is available for this item, so take its range from qx2nut table */
936 if (item
->qxflags
& QX_FLAG_NONUT
) {
940 if (!strlen(value
)) {
941 upslogx(LOG_ERR
, "%s: value not given for %s", __func__
, item
->info_type
);
942 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
947 /* Loop on all existing values */
948 for (rvalue
= item
->info_rw
; rvalue
!= NULL
&& strlen(rvalue
->value
) > 0; rvalue
++) {
950 if (rvalue
->preprocess
&& rvalue
->preprocess(rvalue
->value
, sizeof(rvalue
->value
)))
954 min
= strtol(rvalue
->value
, NULL
, 10);
958 max
= strtol(rvalue
->value
, NULL
, 10);
960 /* valuetoset is in the range */
961 if (min
<= valuetoset
&& valuetoset
<= max
) {
971 /* We have a NUT var for this item, so check given value against the already set range */
974 const range_t
*range
= state_getrangelist(root
, item
->info_type
);
976 /* Unable to find tree node for var */
978 upsdebugx(2, "%s: unable to find tree node for %s", __func__
, item
->info_type
);
979 return STAT_SET_UNKNOWN
;
987 /* valuetoset is in the range */
988 if (min
<= valuetoset
&& valuetoset
<= max
) {
998 upslogx(LOG_ERR
, "%s: value out of range [%s: %s]", __func__
, item
->info_type
, value
);
999 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1002 /* Check if given value is in the range of accepted values (enum) */
1003 } else if (item
->qxflags
& QX_FLAG_ENUM
) {
1005 /* No NUT var is available for this item, so take its range from qx2nut table */
1006 if (item
->qxflags
& QX_FLAG_NONUT
) {
1010 if (!strlen(value
)) {
1011 upslogx(LOG_ERR
, "%s: value not given for %s", __func__
, item
->info_type
);
1012 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1015 /* Loop on all existing values */
1016 for (envalue
= item
->info_rw
; envalue
!= NULL
&& strlen(envalue
->value
) > 0; envalue
++) {
1018 if (envalue
->preprocess
&& envalue
->preprocess(envalue
->value
, sizeof(envalue
->value
)))
1021 if (strcasecmp(envalue
->value
, value
))
1030 /* We have a NUT var for this item, so check given value against the already set range */
1033 const enum_t
*enumlist
= state_getenumlist(root
, item
->info_type
);
1035 /* Unable to find tree node for var */
1037 upsdebugx(2, "%s: unable to find tree node for %s", __func__
, item
->info_type
);
1038 return STAT_SET_UNKNOWN
;
1043 /* If this is not the right value, go on to the next */
1044 if (strcasecmp(enumlist
->val
, value
)) {
1045 enumlist
= enumlist
->next
;
1049 /* value found in enumlist */
1057 upslogx(LOG_ERR
, "%s: value out of range [%s: %s]", __func__
, item
->info_type
, value
);
1058 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1061 /* Check if given value is not too long (string) */
1062 } else if (item
->info_flags
& ST_FLAG_STRING
) {
1064 const int aux
= state_getaux(root
, item
->info_type
);
1066 /* Unable to find tree node for var */
1068 upsdebugx(2, "%s: unable to find tree node for %s", __func__
, item
->info_type
);
1069 return STAT_SET_UNKNOWN
;
1072 if (aux
< (int)strlen(value
)) {
1073 upslogx(LOG_ERR
, "%s: value is too long [%s: %s]", __func__
, item
->info_type
, value
);
1074 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1079 /* Preprocess value: from NUT-compliant to UPS-compliant */
1080 if (item
->preprocess
!= NULL
&& item
->preprocess(item
, value
, sizeof(value
))) {
1081 /* Something went wrong */
1082 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
1083 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1086 /* Handle server side variable */
1087 if (item
->qxflags
& QX_FLAG_ABSENT
) {
1088 upsdebugx(2, "%s: setting server side variable %s", __func__
, item
->info_type
);
1089 dstate_setinfo(item
->info_type
, "%s", value
);
1090 upslogx(LOG_INFO
, "%s: SUCCEED", __func__
);
1091 return STAT_SET_HANDLED
;
1094 /* No preprocess function -> nothing to do with val */
1095 if (item
->preprocess
== NULL
)
1096 snprintf(value
, sizeof(value
), "%s", "");
1098 /* Actual variable setting */
1099 if (qx_process(item
, strlen(value
) > 0 ? value
: NULL
)) {
1100 /* Something went wrong */
1101 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
1102 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1105 /* We got a reply from the UPS: either subdriver->accepted (-> command handled) or the command itself echoed back (-> command failed) */
1106 if (strlen(item
->value
) > 0) {
1108 if (subdriver
->accepted
!= NULL
&& !strcasecmp(item
->value
, subdriver
->accepted
)) {
1109 upslogx(LOG_INFO
, "%s: SUCCEED", __func__
);
1110 /* Set the status so that SEMI_STATIC vars are polled */
1111 data_has_changed
= TRUE
;
1112 return STAT_SET_HANDLED
;
1115 upslogx(LOG_ERR
, "%s: FAILED", __func__
);
1116 return STAT_SET_UNKNOWN
; /* TODO: HANDLED but FAILED, not UNKNOWN! */
1120 /* No reply from the UPS -> command handled */
1121 upslogx(LOG_INFO
, "%s: SUCCEED", __func__
);
1122 /* Set the status so that SEMI_STATIC vars are polled */
1123 data_has_changed
= TRUE
;
1124 return STAT_SET_HANDLED
;
1127 /* Try to shutdown the UPS */
1128 void upsdrv_shutdown(void)
1134 upsdebugx(1, "%s...", __func__
);
1136 /* Get user-defined delays */
1139 item
= find_nut_info("ups.delay.start", 0, QX_FLAG_SKIP
);
1141 /* Don't know what happened */
1143 fatalx(EXIT_FAILURE
, "Unable to set start delay");
1145 /* Set the default value */
1146 dstate_setinfo(item
->info_type
, "%s", item
->dfl
);
1148 /* Set var flags/range/enum */
1151 /* Retrieve user defined delay settings */
1152 val
= getval(QX_VAR_ONDELAY
);
1154 if (val
&& setvar(item
->info_type
, val
) != STAT_SET_HANDLED
) {
1155 fatalx(EXIT_FAILURE
, "Start delay '%s' out of range", val
);
1158 /* Shutdown delay */
1159 item
= find_nut_info("ups.delay.shutdown", 0, QX_FLAG_SKIP
);
1161 /* Don't know what happened */
1163 fatalx(EXIT_FAILURE
, "Unable to set shutdown delay");
1165 /* Set the default value */
1166 dstate_setinfo(item
->info_type
, "%s", item
->dfl
);
1168 /* Set var flags/range/enum */
1171 /* Retrieve user defined delay settings */
1172 val
= getval(QX_VAR_OFFDELAY
);
1174 if (val
&& setvar(item
->info_type
, val
) != STAT_SET_HANDLED
) {
1175 fatalx(EXIT_FAILURE
, "Shutdown delay '%s' out of range", val
);
1178 /* Stop pending shutdowns */
1179 if (find_nut_info("shutdown.stop", QX_FLAG_CMD
, QX_FLAG_SKIP
)) {
1181 for (retry
= 1; retry
<= MAXTRIES
; retry
++) {
1183 if (instcmd("shutdown.stop", NULL
) != STAT_INSTCMD_HANDLED
) {
1191 if (retry
> MAXTRIES
) {
1192 upslogx(LOG_NOTICE
, "No shutdown pending");
1198 for (retry
= 1; retry
<= MAXTRIES
; retry
++) {
1200 if (testvar("stayoff")) {
1202 if (instcmd("shutdown.stayoff", NULL
) != STAT_INSTCMD_HANDLED
) {
1208 if (instcmd("shutdown.return", NULL
) != STAT_INSTCMD_HANDLED
) {
1214 fatalx(EXIT_SUCCESS
, "Shutting down in %s seconds", dstate_getinfo("ups.delay.shutdown"));
1217 fatalx(EXIT_FAILURE
, "Shutdown failed!");
1220 void upsdrv_help(void)
1222 printf("Read The Fine Manual ('man 8 nutdrv_qx')\n");
1225 /* Adding flags/vars */
1226 void upsdrv_makevartable(void)
1228 char temp
[SMALLBUF
];
1231 upsdebugx(1, "%s...", __func__
);
1233 snprintf(temp
, sizeof(temp
), "Set shutdown delay, in seconds (default=%s)", DEFAULT_OFFDELAY
);
1234 addvar(VAR_VALUE
, QX_VAR_OFFDELAY
, temp
);
1236 snprintf(temp
, sizeof(temp
), "Set startup delay, in seconds (default=%s)", DEFAULT_ONDELAY
);
1237 addvar(VAR_VALUE
, QX_VAR_ONDELAY
, temp
);
1239 addvar(VAR_FLAG
, "stayoff", "If invoked the UPS won't return after a shutdown when FSD arises");
1241 snprintf(temp
, sizeof(temp
), "Set polling frequency, in seconds, to reduce data flow (default=%d)", DEFAULT_POLLFREQ
);
1242 addvar(VAR_VALUE
, QX_VAR_POLLFREQ
, temp
);
1244 addvar(VAR_VALUE
, "protocol", "Preselect communication protocol (skip autodetection)");
1246 /* battery.{charge,runtime} guesstimation */
1247 addvar(VAR_VALUE
, "runtimecal", "Parameters used for runtime calculation");
1248 addvar(VAR_VALUE
, "chargetime", "Nominal charge time for UPS battery");
1249 addvar(VAR_VALUE
, "idleload", "Minimum load to be used for runtime calculation");
1252 addvar(VAR_VALUE
, "subdriver", "Serial-over-USB subdriver selection");
1253 addvar(VAR_VALUE
, "vendorid", "Regular expression to match UPS Manufacturer numerical ID (4 digits hexadecimal)");
1254 addvar(VAR_VALUE
, "productid", "Regular expression to match UPS Product numerical ID (4 digits hexadecimal)");
1256 addvar(VAR_VALUE
, "vendor", "Regular expression to match UPS Manufacturer string");
1257 addvar(VAR_VALUE
, "product", "Regular expression to match UPS Product string");
1258 addvar(VAR_VALUE
, "serial", "Regular expression to match UPS Serial number");
1260 addvar(VAR_VALUE
, "bus", "Regular expression to match USB bus name");
1262 addvar(VAR_VALUE
, "langid_fix", "Apply the language ID workaround to the krauler subdriver (0x409 or 0x4095)");
1266 addvar(VAR_VALUE
, "cablepower", "Set cable power for serial interface");
1267 #endif /* QX_SERIAL */
1269 /* Subdrivers flags/vars */
1270 for (i
= 0; subdriver_list
[i
] != NULL
; i
++) {
1272 if (subdriver_list
[i
]->makevartable
!= NULL
)
1273 subdriver_list
[i
]->makevartable();
1278 /* Update UPS status/infos */
1279 void upsdrv_updateinfo(void)
1282 static int retry
= 0;
1284 upsdebugx(1, "%s...", __func__
);
1288 /* Clear status buffer before beginning */
1291 /* Do a full update (polling) every pollfreq or upon data change (i.e. setvar/instcmd) */
1292 if ((now
> (lastpoll
+ pollfreq
)) || (data_has_changed
== TRUE
)) {
1294 upsdebugx(1, "Full update...");
1296 /* Clear ups_status */
1301 if (qx_ups_walk(QX_WALKMODE_FULL_UPDATE
) == FALSE
) {
1303 if (retry
< MAXTRIES
|| retry
== MAXTRIES
) {
1304 upsdebugx(1, "Communications with the UPS lost: status read failed!");
1314 data_has_changed
= FALSE
;
1321 upsdebugx(1, "Quick update...");
1323 /* Quick poll data only to see if the UPS is still connected */
1324 if (qx_ups_walk(QX_WALKMODE_QUICK_UPDATE
) == FALSE
) {
1326 if (retry
< MAXTRIES
|| retry
== MAXTRIES
) {
1327 upsdebugx(1, "Communications with the UPS lost: status read failed!");
1341 if (retry
> MAXTRIES
) {
1342 upslogx(LOG_NOTICE
, "Communications with the UPS re-established");
1350 /* Initialise data from UPS */
1351 void upsdrv_initinfo(void)
1355 upsdebugx(1, "%s...", __func__
);
1357 dstate_setinfo("driver.version.data", "%s", subdriver
->name
);
1359 /* Initialise data */
1360 if (qx_ups_walk(QX_WALKMODE_INIT
) == FALSE
) {
1361 fatalx(EXIT_FAILURE
, "Can't initialise data from the UPS");
1364 /* Init battery guesstimation */
1367 if (dstate_getinfo("ups.delay.start")) {
1369 /* Retrieve user defined delay settings */
1370 val
= getval(QX_VAR_ONDELAY
);
1372 if (val
&& setvar("ups.delay.start", val
) != STAT_SET_HANDLED
) {
1373 fatalx(EXIT_FAILURE
, "Start delay '%s' out of range", val
);
1378 if (dstate_getinfo("ups.delay.shutdown")) {
1380 /* Retrieve user defined delay settings */
1381 val
= getval(QX_VAR_OFFDELAY
);
1383 if (val
&& setvar("ups.delay.shutdown", val
) != STAT_SET_HANDLED
) {
1384 fatalx(EXIT_FAILURE
, "Shutdown delay '%s' out of range", val
);
1389 if (!find_nut_info("load.off", QX_FLAG_CMD
, QX_FLAG_SKIP
) && find_nut_info("load.off.delay", QX_FLAG_CMD
, QX_FLAG_SKIP
)) {
1390 /* Adds default with a delay value of '0' (= immediate) */
1391 dstate_addcmd("load.off");
1394 if (!find_nut_info("load.on", QX_FLAG_CMD
, QX_FLAG_SKIP
) && find_nut_info("load.on.delay", QX_FLAG_CMD
, QX_FLAG_SKIP
)) {
1395 /* Adds default with a delay value of '0' (= immediate) */
1396 dstate_addcmd("load.on");
1399 /* Init polling frequency */
1400 val
= getval(QX_VAR_POLLFREQ
);
1402 pollfreq
= strtol(val
, NULL
, 10);
1404 dstate_setinfo("driver.parameter.pollfreq", "%d", pollfreq
);
1408 /* Install handlers */
1409 upsh
.setvar
= setvar
;
1410 upsh
.instcmd
= instcmd
;
1412 /* Subdriver initinfo */
1413 if (subdriver
->initinfo
!= NULL
)
1414 subdriver
->initinfo();
1417 /* Open the port and the like and choose the subdriver */
1418 void upsdrv_initups(void)
1420 upsdebugx(1, "%s...", __func__
);
1422 #if defined(QX_SERIAL) && defined(QX_USB)
1424 /* Whether the device is connected through USB or serial */
1426 !strcasecmp(dstate_getinfo("driver.parameter.port"), "auto") ||
1427 getval("subdriver") ||
1428 getval("vendorid") ||
1429 getval("productid") ||
1431 getval("product") ||
1434 getval("langid_fix")
1443 #endif /* QX_SERIAL && QX_USB */
1459 { "normal", 1, 0 }, /* Default */
1460 { "reverse", 0, 1 },
1470 /* Open and lock the serial port and set the speed to 2400 baud. */
1471 upsfd
= ser_open(device_path
);
1472 ser_set_speed(upsfd
, device_path
, B2400
);
1474 if (tcgetattr(upsfd
, &tio
)) {
1475 fatal_with_errno(EXIT_FAILURE
, "tcgetattr");
1478 /* Use canonical mode input processing (to read reply line) */
1479 tio
.c_lflag
|= ICANON
; /* Canonical input (erase and kill processing) */
1481 tio
.c_cc
[VEOF
] = _POSIX_VDISABLE
;
1482 tio
.c_cc
[VEOL
] = '\r';
1483 tio
.c_cc
[VERASE
] = _POSIX_VDISABLE
;
1484 tio
.c_cc
[VINTR
] = _POSIX_VDISABLE
;
1485 tio
.c_cc
[VKILL
] = _POSIX_VDISABLE
;
1486 tio
.c_cc
[VQUIT
] = _POSIX_VDISABLE
;
1487 tio
.c_cc
[VSUSP
] = _POSIX_VDISABLE
;
1488 tio
.c_cc
[VSTART
] = _POSIX_VDISABLE
;
1489 tio
.c_cc
[VSTOP
] = _POSIX_VDISABLE
;
1491 if (tcsetattr(upsfd
, TCSANOW
, &tio
)) {
1492 fatal_with_errno(EXIT_FAILURE
, "tcsetattr");
1495 val
= getval("cablepower");
1496 for (i
= 0; val
&& cablepower
[i
].val
; i
++) {
1498 if (!strcasecmp(val
, cablepower
[i
].val
)) {
1503 if (!cablepower
[i
].val
) {
1504 fatalx(EXIT_FAILURE
, "Value '%s' not valid for 'cablepower'", val
);
1507 ser_set_dtr(upsfd
, cablepower
[i
].dtr
);
1508 ser_set_rts(upsfd
, cablepower
[i
].rts
);
1510 /* Allow some time to settle for the cablepower */
1513 #endif /* TESTING */
1516 } else { /* is_usb */
1519 #endif /* QX_SERIAL */
1528 int (*command
)(const char *cmd
, char *buf
, size_t buflen
);
1529 } usbsubdriver
[] = {
1530 { "cypress", &cypress_command
},
1531 { "phoenix", &phoenix_command
},
1532 { "ippon", &ippon_command
},
1533 { "krauler", &krauler_command
},
1538 char tbuf
[255]; /* Some devices choke on size > 255 */
1539 char *regex_array
[6];
1541 char *subdrv
= getval("subdriver");
1543 regex_array
[0] = getval("vendorid");
1544 regex_array
[1] = getval("productid");
1545 regex_array
[2] = getval("vendor");
1546 regex_array
[3] = getval("product");
1547 regex_array
[4] = getval("serial");
1548 regex_array
[5] = getval("bus");
1550 /* Check for language ID workaround (#1) */
1551 if (getval("langid_fix")) {
1552 /* Skip "0x" prefix and set back to hexadecimal */
1553 if (sscanf(getval("langid_fix") + 2, "%x", &langid_fix
) != 1) {
1554 upslogx(LOG_NOTICE
, "Error enabling language ID workaround");
1556 upsdebugx(2, "Language ID workaround enabled (using '0x%x')", langid_fix
);
1560 /* Pick up the subdriver name if set explicitly */
1565 if (!regex_array
[0] || !regex_array
[1]) {
1566 fatalx(EXIT_FAILURE
, "When specifying a subdriver, 'vendorid' and 'productid' are mandatory.");
1569 for (i
= 0; usbsubdriver
[i
].name
; i
++) {
1571 if (strcasecmp(subdrv
, usbsubdriver
[i
].name
)) {
1575 subdriver_command
= usbsubdriver
[i
].command
;
1579 if (!subdriver_command
) {
1580 fatalx(EXIT_FAILURE
, "Subdriver '%s' not found!", subdrv
);
1585 ret
= USBNewRegexMatcher(®ex_matcher
, regex_array
, REG_ICASE
| REG_EXTENDED
);
1589 fatal_with_errno(EXIT_FAILURE
, "USBNewRegexMatcher");
1591 break; /* All is well */
1593 fatalx(EXIT_FAILURE
, "Invalid regular expression: %s", regex_array
[ret
]);
1596 /* Link the matchers */
1597 regex_matcher
->next
= &device_matcher
;
1599 ret
= usb
->open(&udev
, &usbdevice
, regex_matcher
, NULL
);
1601 fatalx(EXIT_FAILURE
,
1602 "No supported devices found. Please check your device availability with 'lsusb'\n"
1603 "and make sure you have an up-to-date version of NUT. If this does not help,\n"
1604 "try running the driver with at least 'subdriver', 'vendorid' and 'productid'\n"
1605 "options specified. Please refer to the man page for details about these options\n"
1606 "(man 8 nutdrv_qx).\n");
1609 if (!subdriver_command
) {
1610 fatalx(EXIT_FAILURE
, "No subdriver selected");
1613 /* Create a new matcher for later reopening */
1614 ret
= USBNewExactMatcher(&reopen_matcher
, &usbdevice
);
1616 fatal_with_errno(EXIT_FAILURE
, "USBNewExactMatcher");
1619 /* Link the matchers */
1620 reopen_matcher
->next
= regex_matcher
;
1622 dstate_setinfo("ups.vendorid", "%04x", usbdevice
.VendorID
);
1623 dstate_setinfo("ups.productid", "%04x", usbdevice
.ProductID
);
1625 /* Check for language ID workaround (#2) */
1626 if (langid_fix
!= -1) {
1627 /* Future improvement:
1628 * Asking for the zero'th index is special - it returns a string descriptor that contains all the language IDs supported by the device.
1629 * Typically there aren't many - often only one.
1630 * The language IDs are 16 bit numbers, and they start at the third byte in the descriptor.
1631 * See USB 2.0 specification, section 9.6.7, for more information on this.
1632 * This should allow automatic application of the workaround */
1633 ret
= usb_get_string(udev
, 0, 0, tbuf
, sizeof(tbuf
));
1635 langid
= tbuf
[2] | (tbuf
[3] << 8);
1636 upsdebugx(1, "First supported language ID: 0x%x (please report to the NUT maintainer!)", langid
);
1640 #endif /* TESTING */
1644 #endif /* QX_SERIAL */
1648 /* Choose subdriver */
1649 if (!subdriver_matcher())
1650 fatalx(EXIT_FAILURE
, "Device not supported!");
1652 /* Subdriver initups */
1653 if (subdriver
->initups
!= NULL
)
1654 subdriver
->initups();
1657 /* Close the ports and the like */
1658 void upsdrv_cleanup(void)
1660 upsdebugx(1, "%s...", __func__
);
1670 ser_set_dtr(upsfd
, 0);
1671 ser_close(upsfd
, device_path
);
1674 } else { /* is_usb */
1677 #endif /* QX_SERIAL */
1682 USBFreeExactMatcher(reopen_matcher
);
1683 USBFreeRegexMatcher(regex_matcher
);
1684 free(usbdevice
.Vendor
);
1685 free(usbdevice
.Product
);
1686 free(usbdevice
.Serial
);
1687 free(usbdevice
.Bus
);
1691 #endif /* QX_SERIAL */
1695 #endif /* TESTING */
1700 /* == Support functions == */
1702 /* Generic command processing function: send a command and read a reply.
1703 * Returns < 0 on error, 0 on timeout and the number of bytes read on success. */
1704 static int qx_command(const char *cmd
, char *buf
, size_t buflen
)
1713 /* Communication: USB */
1715 #endif /* QX_SERIAL */
1718 ret
= usb
->open(&udev
, &usbdevice
, reopen_matcher
, NULL
);
1725 ret
= (*subdriver_command
)(cmd
, buf
, buflen
);
1733 case -EBUSY
: /* Device or resource busy */
1734 fatal_with_errno(EXIT_FAILURE
, "Got disconnected by another driver");
1736 case -EPERM
: /* Operation not permitted */
1737 fatal_with_errno(EXIT_FAILURE
, "Permissions problem");
1739 case -EPIPE
: /* Broken pipe */
1740 if (usb_clear_halt(udev
, 0x81) == 0) {
1741 upsdebugx(1, "Stall condition cleared");
1745 case -ETIME
: /* Timer expired */
1747 if (usb_reset(udev
) == 0) {
1748 upsdebugx(1, "Device reset handled");
1750 case -ENODEV
: /* No such device */
1751 case -EACCES
: /* Permission denied */
1752 case -EIO
: /* I/O error */
1753 case -ENXIO
: /* No such device or address */
1754 case -ENOENT
: /* No such file or directory */
1755 /* Uh oh, got to reconnect! */
1760 case -ETIMEDOUT
: /* Connection timed out */
1761 case -EOVERFLOW
: /* Value too large for defined data type */
1762 case -EPROTO
: /* Protocol error */
1768 /* Communication: serial */
1769 } else { /* !is_usb */
1770 #endif /* QX_SERIAL */
1776 ser_flush_io(upsfd
);
1778 ret
= ser_send(upsfd
, "%s", cmd
);
1781 upsdebugx(3, "send: %s", ret
? strerror(errno
) : "timeout");
1785 upsdebugx(3, "send: '%.*s'", (int)strcspn(cmd
, "\r"), cmd
);
1787 ret
= ser_get_buf(upsfd
, buf
, buflen
, SER_WAIT_SEC
, 0);
1790 upsdebugx(3, "read: %s", ret
? strerror(errno
) : "timeout");
1794 upsdebugx(3, "read: '%.*s'", (int)strcspn(buf
, "\r"), buf
);
1800 #endif /* QX_SERIAL */
1806 testing_t
*testing
= subdriver
->testing
;
1809 memset(buf
, 0, buflen
);
1811 upsdebugx(3, "send: '%.*s'", (int)strcspn(cmd
, "\r"), cmd
);
1813 for (i
= 0; cmd
&& testing
[i
].cmd
; i
++) {
1815 if (strcasecmp(cmd
, testing
[i
].cmd
)) {
1819 upsdebugx(3, "read: '%.*s'", (int)strcspn(testing
[i
].answer
, "\r"), testing
[i
].answer
);
1820 return snprintf(buf
, buflen
, "%s", testing
[i
].answer
);
1824 /* If the driver expects some kind of reply in case of error.. */
1825 if (subdriver
->rejected
!= NULL
) {
1827 /* ..fulfill its expectations.. */
1828 upsdebugx(3, "read: '%.*s'", (int)strcspn(subdriver
->rejected
, "\r"), subdriver
->rejected
);
1829 return snprintf(buf
, buflen
, "%s", subdriver
->rejected
);
1834 /* ..echo back the command */
1835 upsdebugx(3, "read: '%.*s'", (int)strcspn(cmd
, "\r"), cmd
);
1836 return snprintf(buf
, buflen
, "%s", cmd
);
1840 #endif /* TESTING */
1843 /* Update ups_status to remember this status item.
1844 * Interpretation is done in ups_status_set(). */
1845 void update_status(const char *value
)
1847 status_lkp_t
*status_item
;
1850 upsdebugx(5, "%s: %s", __func__
, value
);
1852 if (*value
== '!') {
1857 for (status_item
= status_info
; status_item
->status_str
!= NULL
; status_item
++) {
1859 if (strcasecmp(status_item
->status_str
, value
))
1863 ups_status
&= ~status_item
->status_mask
;
1865 ups_status
|= status_item
->status_mask
;
1871 upsdebugx(5, "%s: Warning! %s not in list of known values", __func__
, value
);
1874 /* Choose subdriver */
1875 static int subdriver_matcher(void)
1877 const char *protocol
= getval("protocol");
1880 /* Select the subdriver for this device */
1881 for (i
= 0; subdriver_list
[i
] != NULL
; i
++) {
1885 /* If protocol is set in ups.conf, use it */
1888 char subdrv_name
[SMALLBUF
];
1890 /* Get rid of subdriver version */
1891 snprintf(subdrv_name
, sizeof(subdrv_name
), "%.*s", (int)strcspn(subdriver_list
[i
]->name
, " "), subdriver_list
[i
]->name
);
1893 if (strcasecmp(subdrv_name
, protocol
)) {
1894 upsdebugx(2, "Skipping protocol %s", subdriver_list
[i
]->name
);
1900 /* Give every subdriver some tries */
1901 for (j
= 0; j
< MAXTRIES
; j
++) {
1903 subdriver
= subdriver_list
[i
];
1905 if (subdriver
->claim()) {
1913 if (subdriver
!= NULL
)
1919 upslogx(LOG_ERR
, "Device not supported!");
1923 upslogx(LOG_INFO
, "Using protocol: %s", subdriver
->name
);
1928 /* Set vars boundaries */
1929 static void qx_set_var(item_t
*item
)
1931 if (!(item
->qxflags
& QX_FLAG_NONUT
))
1932 dstate_setflags(item
->info_type
, item
->info_flags
);
1934 /* Set max length for strings, if needed */
1935 if (item
->info_flags
& ST_FLAG_STRING
&& !(item
->qxflags
& QX_FLAG_NONUT
))
1936 dstate_setaux(item
->info_type
, strtol(item
->info_rw
[0].value
, NULL
, 10));
1939 if (item
->qxflags
& QX_FLAG_ENUM
) {
1942 char buf
[LARGEBUF
] = "";
1944 /* Loop on all existing values */
1945 for (envalue
= item
->info_rw
; envalue
!= NULL
&& strlen(envalue
->value
) > 0; envalue
++) {
1947 if (envalue
->preprocess
&& envalue
->preprocess(envalue
->value
, sizeof(envalue
->value
)))
1950 /* This item is not available yet in NUT, so publish these data in the logs */
1951 if (item
->qxflags
& QX_FLAG_NONUT
) {
1953 snprintfcat(buf
, sizeof(buf
), " %s", envalue
->value
);
1955 /* This item is available in NUT, add its enum to the variable */
1958 dstate_addenum(item
->info_type
, "%s", envalue
->value
);
1964 if (item
->qxflags
& QX_FLAG_NONUT
)
1965 upslogx(LOG_INFO
, "%s, settable values:%s", item
->info_type
, strlen(buf
) > 0 ? buf
: " none");
1970 if (item
->qxflags
& QX_FLAG_RANGE
) {
1972 info_rw_t
*rvalue
, *from
= NULL
, *to
= NULL
;
1975 /* Loop on all existing values */
1976 for (rvalue
= item
->info_rw
; rvalue
!= NULL
&& strlen(rvalue
->value
) > 0; rvalue
++) {
1978 if (rvalue
->preprocess
&& rvalue
->preprocess(rvalue
->value
, sizeof(rvalue
->value
)))
1988 /* This item is not available yet in NUT, so publish these data in the logs */
1989 if (item
->qxflags
& QX_FLAG_NONUT
) {
1991 upslogx(LOG_INFO
, "%s, settable range: %s..%s", item
->info_type
, from
->value
, to
->value
);
1994 /* This item is available in NUT, add its range to the variable */
1997 dstate_addrange(item
->info_type
, strtol(from
->value
, NULL
, 10), strtol(to
->value
, NULL
, 10));
2006 /* This item is not available yet in NUT and we weren't able to get its range; let people know it */
2007 if ((item
->qxflags
& QX_FLAG_NONUT
) && !ok
)
2008 upslogx(LOG_INFO
, "%s, settable range: none", item
->info_type
);
2013 /* Walk UPS variables and set elements of the qx2nut array. */
2014 static bool_t
qx_ups_walk(walkmode_t mode
)
2019 /* Clear batt.{chrg,runt}.act for guesstimation */
2020 if (mode
== QX_WALKMODE_FULL_UPDATE
) {
2025 /* 3 modes: QX_WALKMODE_INIT, QX_WALKMODE_QUICK_UPDATE and QX_WALKMODE_FULL_UPDATE */
2027 /* Device data walk */
2028 for (item
= subdriver
->qx2nut
; item
->info_type
!= NULL
; item
++) {
2030 /* Skip this item */
2031 if (item
->qxflags
& QX_FLAG_SKIP
)
2034 upsdebugx(10, "%s: processing: %s", __func__
, item
->info_type
);
2036 /* Filter data according to mode */
2039 /* Device capabilities enumeration */
2040 case QX_WALKMODE_INIT
:
2042 /* Special case for handling server side variables */
2043 if (item
->qxflags
& QX_FLAG_ABSENT
) {
2046 if (dstate_getinfo(item
->info_type
))
2049 dstate_setinfo(item
->info_type
, "%s", item
->dfl
);
2051 /* Set var flags/range/enum */
2057 /* Allow duplicates for these NUT variables */
2058 if (!strncmp(item
->info_type
, "ups.alarm", 9) || !strncmp(item
->info_type
, "ups.status", 10))
2061 /* This one doesn't exist yet */
2062 if (dstate_getinfo(item
->info_type
) == NULL
)
2067 case QX_WALKMODE_QUICK_UPDATE
:
2069 /* Quick update only deals with status and alarms! */
2070 if (!(item
->qxflags
& QX_FLAG_QUICK_POLL
))
2075 case QX_WALKMODE_FULL_UPDATE
:
2077 /* These don't need polling after initinfo() */
2078 if (item
->qxflags
& (QX_FLAG_ABSENT
| QX_FLAG_CMD
| QX_FLAG_SETVAR
| QX_FLAG_STATIC
))
2081 /* These need to be polled after user changes (setvar / instcmd) */
2082 if ((item
->qxflags
& QX_FLAG_SEMI_STATIC
) && (data_has_changed
== FALSE
))
2089 fatalx(EXIT_FAILURE
, "%s: unknown update mode!", __func__
);
2093 /* Instant commands */
2094 if (item
->qxflags
& QX_FLAG_CMD
) {
2095 dstate_addcmd(item
->info_type
);
2100 if (item
->qxflags
& QX_FLAG_SETVAR
) {
2102 if (item
->qxflags
& QX_FLAG_NONUT
) {
2103 setvar(item
->info_type
, NULL
);
2104 item
->qxflags
|= QX_FLAG_SKIP
;
2111 /* Check whether the previous item uses the same command and then use its answer, if available.. */
2112 if (strlen(previous_item
.command
) > 0 && strlen(previous_item
.answer
) > 0 && !strcasecmp(previous_item
.command
, item
->command
)) {
2114 snprintf(item
->answer
, sizeof(item
->answer
), "%s", previous_item
.answer
);
2116 /* Process the answer */
2117 retcode
= qx_process_answer(item
, strlen(item
->answer
));
2119 /* ..otherwise: execute command to get answer from the UPS */
2121 retcode
= qx_process(item
, NULL
);
2123 /* Record item as previous_item */
2124 snprintf(previous_item
.command
, sizeof(previous_item
.command
), "%s", item
->command
);
2125 snprintf(previous_item
.answer
, sizeof(previous_item
.answer
), "%s", item
->answer
);
2129 if (item
->qxflags
& QX_FLAG_QUICK_POLL
)
2132 if (mode
== QX_WALKMODE_INIT
)
2133 /* Skip this item from now on */
2134 item
->qxflags
|= QX_FLAG_SKIP
;
2136 /* Clear data from the item */
2137 snprintf(item
->answer
, sizeof(item
->answer
), "%s", "");
2138 snprintf(item
->value
, sizeof(item
->value
), "%s", "");
2140 /* Don't know what happened, try again later... */
2145 /* Process the value we got back (set status bits and set the value of other parameters) */
2146 retcode
= ups_infoval_set(item
);
2148 /* Clear data from the item */
2149 snprintf(item
->answer
, sizeof(item
->answer
), "%s", "");
2150 snprintf(item
->value
, sizeof(item
->value
), "%s", "");
2152 /* Uh-oh! Some error! */
2156 /* Set var flags/range/enum (not for ups.{alarm.status}, hence the retcode check) */
2157 if (retcode
&& mode
== QX_WALKMODE_INIT
) {
2163 /* Clear data from previous_item */
2164 snprintf(previous_item
.command
, sizeof(previous_item
.command
), "%s", "");
2165 snprintf(previous_item
.answer
, sizeof(previous_item
.answer
), "%s", "");
2167 /* Update battery guesstimation */
2168 if (mode
== QX_WALKMODE_FULL_UPDATE
&& (batt
.runt
.act
== -1 || batt
.chrg
.act
== -1)) {
2170 if (getval("runtimecal")) {
2177 if (ups_status
& STATUS(OL
)) {
2179 batt
.runt
.est
+= batt
.runt
.nom
* difftime(battery_now
, battery_lastpoll
) / batt
.chrg
.time
;
2180 if (batt
.runt
.est
> batt
.runt
.nom
) {
2181 batt
.runt
.est
= batt
.runt
.nom
;
2187 batt
.runt
.est
-= load
.eff
* difftime(battery_now
, battery_lastpoll
);
2188 if (batt
.runt
.est
< 0) {
2194 if (batt
.chrg
.act
== -1)
2195 dstate_setinfo("battery.charge", "%.0f", 100 * batt
.runt
.est
/ batt
.runt
.nom
);
2197 if (batt
.runt
.act
== -1 && !qx_load())
2198 dstate_setinfo("battery.runtime", "%.0f", batt
.runt
.est
/ load
.eff
);
2200 battery_lastpoll
= battery_now
;
2212 /* Convert the local status information to NUT format and set NUT alarms. */
2213 static void ups_alarm_set(void)
2215 if (ups_status
& STATUS(RB
)) {
2216 alarm_set("Replace battery!");
2218 if (ups_status
& STATUS(FSD
)) {
2219 alarm_set("Shutdown imminent!");
2223 /* Convert the local status information to NUT format and set NUT status. */
2224 static void ups_status_set(void)
2226 if (ups_status
& STATUS(OL
)) {
2227 status_set("OL"); /* On line */
2229 status_set("OB"); /* On battery */
2231 if (ups_status
& STATUS(DISCHRG
)) {
2232 status_set("DISCHRG"); /* Discharging */
2234 if (ups_status
& STATUS(CHRG
)) {
2235 status_set("CHRG"); /* Charging */
2237 if (ups_status
& STATUS(LB
)) {
2238 status_set("LB"); /* Low battery */
2240 if (ups_status
& STATUS(OVER
)) {
2241 status_set("OVER"); /* Overload */
2243 if (ups_status
& STATUS(RB
)) {
2244 status_set("RB"); /* Replace battery */
2246 if (ups_status
& STATUS(TRIM
)) {
2247 status_set("TRIM"); /* SmartTrim */
2249 if (ups_status
& STATUS(BOOST
)) {
2250 status_set("BOOST"); /* SmartBoost */
2252 if (ups_status
& STATUS(BYPASS
)) {
2253 status_set("BYPASS"); /* On bypass */
2255 if (ups_status
& STATUS(OFF
)) {
2256 status_set("OFF"); /* UPS is off */
2258 if (ups_status
& STATUS(CAL
)) {
2259 status_set("CAL"); /* Calibration */
2261 if (ups_status
& STATUS(FSD
)) {
2262 status_set("FSD"); /* Forced shutdown */
2266 /* Find element definition in qx2nut array by NUT varname optionally filtered by its qxflags:
2267 * - 'flag': flags that have to be set in the item, i.e. if one of the flags is absent in the item it won't be returned
2268 * - 'noflag': flags that have to be absent in the item, i.e. if at least one of the flags is set in the item it won't be returned */
2269 item_t
*find_nut_info(const char *varname
, const unsigned long flag
, const unsigned long noflag
)
2273 for (item
= subdriver
->qx2nut
; item
->info_type
!= NULL
; item
++) {
2275 if (strcasecmp(item
->info_type
, varname
))
2278 if (flag
&& ((item
->qxflags
& flag
) != flag
))
2281 if (noflag
&& (item
->qxflags
& noflag
))
2287 upsdebugx(2, "%s: info type %s not found", __func__
, varname
);
2291 /* Process the answer we got back from the UPS
2292 * Return -1 on errors, 0 on success */
2293 static int qx_process_answer(item_t
*item
, const int len
)
2295 /* Query rejected by the UPS */
2296 if (subdriver
->rejected
&& !strcasecmp(item
->answer
, subdriver
->rejected
)) {
2297 upsdebugx(2, "%s: query rejected by the UPS (%s)", __func__
, item
->info_type
);
2302 if (item
->answer_len
&& len
< item
->answer_len
) {
2303 upsdebugx(2, "%s: short reply (%s)", __func__
, item
->info_type
);
2307 /* Wrong leading character */
2308 if (item
->leading
&& item
->answer
[0] != item
->leading
) {
2309 upsdebugx(2, "%s: %s - invalid start character [%02x], expected [%02x]", __func__
, item
->info_type
, item
->answer
[0], item
->leading
);
2314 if (strlen(item
->answer
)) {
2315 snprintf(item
->value
, sizeof(item
->value
), "%.*s", item
->to
? 1 + item
->to
- item
->from
: (int)strcspn(item
->answer
, "\r") - item
->from
, item
->answer
+ item
->from
);
2317 snprintf(item
->value
, sizeof(item
->value
), "%s", "");
2323 /* Send the command to the UPS and process the reply.
2324 * Return -1 on errors, 0 on success */
2325 int qx_process(item_t
*item
, const char *command
)
2327 char buf
[SMALLBUF
] = "";
2329 /* Send the command */
2330 int len
= qx_command(command
? command
: item
->command
, buf
, sizeof(buf
));
2332 snprintf(item
->answer
, sizeof(item
->answer
), "%s", buf
);
2334 /* Process the answer to get the value */
2335 return qx_process_answer(item
, len
);
2338 /* Process the value we got back (set status bits and set the value of other parameters). */
2339 /* Return -1 on failure, 0 for a status update and 1 in all other cases */
2340 int ups_infoval_set(item_t
*item
)
2342 char value
[SMALLBUF
] = "";
2344 /* Item need to be preprocessed? */
2345 if (item
->preprocess
!= NULL
){
2347 /* Process the value returned by the UPS to NUT standards */
2348 if (item
->preprocess(item
, value
, sizeof(value
))) {
2349 upsdebugx(4, "%s: failed to preprocess value [%s: %s]", __func__
, item
->info_type
, item
->value
);
2353 /* Deal with status items */
2354 if (!strncmp(item
->info_type
, "ups.status", 10)) {
2355 if (strlen(value
) > 0)
2356 update_status(value
);
2360 /* Deal with alarm items */
2361 if (!strncmp(item
->info_type
, "ups.alarm", 9)) {
2362 if (strlen(value
) > 0)
2369 snprintf(value
, sizeof(value
), "%s", item
->value
);
2371 /* Cover most of the cases: either left/right filled with hashes, spaces or a mix of both */
2372 if (item
->qxflags
& QX_FLAG_TRIM
) {
2376 snprintf(buf
, sizeof(buf
), "%s", ltrim(value
, ' '));
2377 snprintf(value
, sizeof(value
), "%s", buf
);
2379 snprintf(buf
, sizeof(buf
), "%s", rtrim(value
, ' '));
2380 snprintf(value
, sizeof(value
), "%s", buf
);
2382 snprintf(buf
, sizeof(buf
), "%s", ltrim(value
, '#'));
2383 snprintf(value
, sizeof(value
), "%s", buf
);
2385 snprintf(buf
, sizeof(buf
), "%s", rtrim(value
, '#'));
2386 snprintf(value
, sizeof(value
), "%s", buf
);
2388 snprintf(buf
, sizeof(buf
), "%s", ltrim(value
, ' '));
2389 snprintf(value
, sizeof(value
), "%s", buf
);
2391 snprintf(buf
, sizeof(buf
), "%s", rtrim(value
, ' '));
2392 snprintf(value
, sizeof(value
), "%s", buf
);
2394 snprintf(buf
, sizeof(buf
), "%s", ltrim(value
, '#'));
2395 snprintf(value
, sizeof(value
), "%s", buf
);
2397 snprintf(buf
, sizeof(buf
), "%s", rtrim(value
, '#'));
2398 snprintf(value
, sizeof(value
), "%s", buf
);
2402 if (strcasecmp(item
->dfl
, "%s")) {
2404 if (strspn(value
, "0123456789 .") != strlen(value
)) {
2405 upsdebugx(2, "%s: non numerical value [%s: %s]", __func__
, item
->info_type
, value
);
2409 snprintf(value
, sizeof(value
), item
->dfl
, strtod(value
, NULL
));
2414 if (item
->qxflags
& QX_FLAG_NONUT
) {
2415 upslogx(LOG_INFO
, "%s: %s", item
->info_type
, value
);
2419 if (!strlen(value
)) {
2420 upsdebugx(1, "%s: non significant value [%s]", __func__
, item
->info_type
);
2424 dstate_setinfo(item
->info_type
, "%s", value
);
2426 /* Fill batt.{chrg,runt}.act for guesstimation */
2427 if (!strcasecmp(item
->info_type
, "battery.charge"))
2428 batt
.chrg
.act
= strtol(value
, NULL
, 10);
2429 else if (!strcasecmp(item
->info_type
, "battery.runtime"))
2430 batt
.runt
.act
= strtol(value
, NULL
, 10);
2435 /* Return actual status */