4 * Platform-specific USB module for *BSD ugen USB driver.
6 * Based on linux-usb.c by Kerb Sibbald.
10 * Copyright (C) 2004-2005 Adam Kropelin
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of version 2 of the GNU General
14 * Public License as published by the Free Software Foundation.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
21 * You should have received a copy of the GNU General Public
22 * License along with this program; if not, write to the Free
23 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
29 #include "../usb_common.h"
30 #include <dev/usb/usb.h>
31 #include <dev/usb/usbhid.h>
33 /* Compatibility cruft for FreeBSD <= 4.7 */
34 #ifndef USB_MAX_DEVNAMES
35 #define USB_MAX_DEVNAMES MAXDEVNAMES
37 #ifndef USB_MAX_DEVNAMELEN
38 #define USB_MAX_DEVNAMELEN MAXDEVNAMELEN
42 * When we are traversing the USB reports given by the UPS and we find
43 * an entry corresponding to an entry in the known_info table above,
44 * we make the following USB_INFO entry in the info table of our
47 typedef struct s_usb_info
{
48 unsigned usage_code
; /* usage code wanted */
49 unsigned unit_exponent
; /* exponent */
50 unsigned unit
; /* units */
51 int data_type
; /* data type */
52 hid_item_t item
; /* HID item (read) */
53 hid_item_t witem
; /* HID item (write) */
54 int report_len
; /* Length of containing report */
55 int ci
; /* which CI does this usage represent? */
56 int value
; /* Previous value of this item */
60 * This "private" structure is returned to us in the driver private
61 * field, and allows us to get to all the info we keep on each UPS.
62 * The info field is malloced for each command we want and the UPS
65 typedef struct s_usb_data
{
66 int fd
; /* Our UPS control pipe fd when open */
67 int intfd
; /* Interrupt pipe fd */
68 char orig_device
[MAXSTRING
]; /* Original port specification */
69 short vendor
; /* UPS vendor id */
70 report_desc_t rdesc
; /* Device's report descrptor */
71 USB_INFO
*info
[CI_MAXCI
+ 1]; /* Info pointers for each command */
75 static void reinitialize_private_structure(UPSINFO
*ups
)
77 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
80 Dmsg0(200, "Reinitializing private structure.\n");
82 * We are being reinitialized, so clear the Cap
83 * array, and release previously allocated memory.
85 for (k
= 0; k
<= CI_MAXCI
; k
++) {
86 ups
->UPS_Cap
[k
] = false;
87 if (my_data
->info
[k
] != NULL
) {
88 free(my_data
->info
[k
]);
89 my_data
->info
[k
] = NULL
;
95 * Initializes the USB device by fetching its report descriptor
96 * and making sure we can drive the device.
98 static int init_device(UPSINFO
*ups
, const char *devname
)
100 int fd
, rc
, rdesclen
;
101 struct usb_device_info devinfo
;
102 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
103 unsigned char *rdesc
;
104 char intdevname
[USB_MAX_DEVNAMELEN
+ 5 + 3 + 1];
106 fd
= open(devname
, O_RDWR
| O_NOCTTY
);
110 memset(&devinfo
, 0, sizeof(devinfo
));
111 rc
= ioctl(fd
, USB_GET_DEVICEINFO
, &devinfo
);
114 Dmsg0(100, "Unable to get device info.\n");
118 /* Only interested if APC or MGE is the vendor */
119 if (devinfo
.udi_vendorNo
!= VENDOR_APC
&&
120 devinfo
.udi_vendorNo
!= VENDOR_MGE
) {
122 Dmsg1(100, "Unsupported vendor (%04x).\n", devinfo
.udi_vendorNo
);
125 my_data
->vendor
= devinfo
.udi_vendorNo
;
127 /* Fetch the report descritor */
128 rdesc
= hidu_fetch_report_descriptor(fd
, &rdesclen
);
131 Dmsg0(100, "Unable to fetch report descriptor.\n");
135 /* Initialize hid parser with this descriptor */
136 my_data
->rdesc
= hid_use_report_desc(rdesc
, rdesclen
);
137 if (!my_data
->rdesc
) {
140 Dmsg0(100, "Unable to init parser with report descriptor.\n");
145 /* Does this device have an UPS application collection? */
146 if (!hidu_locate_item(
148 UPS_USAGE
, /* Match usage code */
149 -1, /* Don't care about application */
150 -1, /* Don't care about physical usage */
151 -1, /* Don't care about logical */
152 HID_KIND_COLLECTION
, /* Match collection type */
154 hid_dispose_report_desc(my_data
->rdesc
);
156 Dmsg0(100, "Device does not have an UPS application collection.\n");
162 /* Open the interrupt pipe */
163 astrncpy(intdevname
, devname
, sizeof(intdevname
));
165 #ifdef HAVE_FREEBSD_OS
166 /* ugen0 -> ugen0.1 */
167 astrncat(intdevname
, ".1", sizeof(intdevname
));
169 /* ugen0.00 -> ugen0.01 */
170 intdevname
[strlen(intdevname
) - 1] = '1';
173 fd
= open(intdevname
, O_RDONLY
| O_NOCTTY
);
175 Dmsg2(100, "Unable to open interrupt pipe %s: %s\n", intdevname
,
177 hid_dispose_report_desc(my_data
->rdesc
);
188 * Internal routine to open the device and ensure that there is a UPS
189 * application on the line. This routine may be called many times
190 * because the device may be unplugged and plugged back in -- the
191 * joys of USB devices.
193 static int open_usb_device(UPSINFO
*ups
)
196 char busname
[] = "/dev/usbN";
197 char devname
[USB_MAX_DEVNAMELEN
+ 5 + 1];
198 struct usb_device_info devinfo
;
199 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
202 * Note, we set ups->fd here so the "core" of apcupsd doesn't
203 * think we are a slave, which is what happens when it is -1.
204 * (ADK: Actually this only appears to be true for apctest as
205 * apcupsd proper uses the UPS_slave flag.)
206 * Internally, we use the fd in our own private space
211 * If no device location specified, we go autodetect it
212 * by searching known places.
214 if (ups
->device
[0] == 0)
217 if (my_data
->orig_device
[0] == 0)
218 astrncpy(my_data
->orig_device
, ups
->device
, sizeof(my_data
->orig_device
));
221 * No range support yet... Name the device specifically or we will
225 for (i
= 0; i
< 10; i
++) {
226 if (init_device(ups
, ups
->device
))
233 * If the above device specified by the user fails,
234 * fall through here and look in predefined places
241 * We could just start trying to open the /dev/ugenN devices,
242 * one after another, but BSD gives us a decent way to enumerate
243 * them. We might as well be polite and use it.
246 /* Max of 10 USB busses */
247 for (i
= 0; i
< 10; i
++) {
248 busname
[8] = '0' + i
;
249 fd
= open(busname
, O_RDWR
| O_NOCTTY
);
253 Dmsg1(200, "Found bus %s.\n", busname
);
255 /* Max 127 devices per bus */
256 for (j
= 0; j
< 127; j
++) {
257 memset(&devinfo
, 0, sizeof(devinfo
));
258 devinfo
.udi_addr
= j
;
259 rc
= ioctl(fd
, USB_DEVICEINFO
, &devinfo
);
263 /* See if this device is bound to ugen driver */
264 for (k
= 0; k
< USB_MAX_DEVNAMES
; k
++)
265 if (strncmp(devinfo
.udi_devnames
[k
], "ugen", 4) == 0)
268 if (k
< USB_MAX_DEVNAMES
) {
269 astrncpy(devname
, "/dev/", sizeof(devname
));
270 astrncat(devname
, devinfo
.udi_devnames
[k
], sizeof(devname
));
271 #if defined(HAVE_OPENBSD_OS) || defined(HAVE_NETBSD_OS)
272 astrncat(devname
, ".00", sizeof(devname
));
274 Dmsg1(200, "Trying device %s.\n", devname
);
275 if (init_device(ups
, devname
)) {
276 astrncpy(ups
->device
, devname
, sizeof(ups
->device
));
290 * Called if there is an ioctl() or read() error, we close() and
291 * re open() the port since the device was probably unplugged.
293 static int usb_link_check(UPSINFO
*ups
)
295 bool comm_err
= true;
298 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
299 static bool linkcheck
= false;
304 linkcheck
= true; /* prevent recursion */
307 Dmsg0(200, "link_check comm lost\n");
309 /* Don't warn until we try to get it at least 2 times and fail */
310 for (tlog
= LINK_RETRY_INTERVAL
* 2; comm_err
; tlog
-= (LINK_RETRY_INTERVAL
)) {
313 tlog
= 10 * 60; /* notify every 10 minutes */
314 log_event(ups
, event_msg
[CMDCOMMFAILURE
].level
,
315 event_msg
[CMDCOMMFAILURE
].msg
);
316 if (once
) { /* execute script once */
317 execute_command(ups
, ups_event
[CMDCOMMFAILURE
]);
322 /* Retry every LINK_RETRY_INTERVAL seconds */
323 sleep(LINK_RETRY_INTERVAL
);
325 if (my_data
->fd
>= 0) {
328 close(my_data
->intfd
);
330 hid_dispose_report_desc(my_data
->rdesc
);
331 reinitialize_private_structure(ups
);
334 if (open_usb_device(ups
) && usb_ups_get_capabilities(ups
) &&
335 usb_ups_read_static_data(ups
)) {
343 generate_event(ups
, CMDCOMMOK
);
344 ups
->clear_commlost();
345 Dmsg0(200, "link check comm OK.\n");
352 int pusb_ups_get_capabilities(UPSINFO
*ups
, const struct s_known_info
*known_info
)
354 int i
, input
, feature
, ci
, phys
, logi
;
355 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
356 hid_item_t input_item
, feature_item
, item
;
361 for (i
= 0; known_info
[i
].usage_code
; i
++) {
362 ci
= known_info
[i
].ci
;
363 phys
= known_info
[i
].physical
;
364 logi
= known_info
[i
].logical
;
366 if (ci
!= CI_NONE
&& !my_data
->info
[ci
]) {
367 /* Try to find an INPUT report containing this usage */
368 input
= hidu_locate_item(
370 known_info
[i
].usage_code
, /* Match usage code */
371 -1, /* Don't care about application */
372 (phys
== P_ANY
) ? -1 : phys
, /* Match physical usage */
373 (logi
== P_ANY
) ? -1 : logi
, /* Match logical usage */
374 HID_KIND_INPUT
, /* Match feature type */
377 /* Try to find a FEATURE report containing this usage */
378 feature
= hidu_locate_item(
380 known_info
[i
].usage_code
, /* Match usage code */
381 -1, /* Don't care about application */
382 (phys
== P_ANY
) ? -1 : phys
, /* Match physical usage */
383 (logi
== P_ANY
) ? -1 : logi
, /* Match logical usage */
384 HID_KIND_FEATURE
, /* Match feature type */
388 * Choose which report to use. We prefer FEATURE since some UPSes
389 * have broken INPUT reports, but we will fall back on INPUT if
390 * FEATURE is not available.
397 continue; // No valid report, bail
399 ups
->UPS_Cap
[ci
] = true;
400 ups
->UPS_Cmd
[ci
] = known_info
[i
].usage_code
;
402 info
= (USB_INFO
*)malloc(sizeof(USB_INFO
));
405 Error_abort0("Out of memory.\n");
408 // Populate READ report data
409 my_data
->info
[ci
] = info
;
410 memset(info
, 0, sizeof(*info
));
412 info
->usage_code
= item
.usage
;
413 info
->unit_exponent
= item
.unit_exponent
;
414 info
->unit
= item
.unit
;
415 info
->data_type
= known_info
[i
].data_type
;
417 info
->report_len
= hid_report_size( /* +1 for report id */
418 my_data
->rdesc
, item
.kind
, item
.report_ID
) + 1;
419 Dmsg6(200, "Got READ ci=%d, rpt=%d (len=%d), usage=0x%x (len=%d), kind=0x%02x\n",
420 ci
, item
.report_ID
, info
->report_len
,
421 known_info
[i
].usage_code
, item
.report_size
, item
.kind
);
423 // If we have a FEATURE report, use that as the writable report
426 Dmsg6(200, "Got WRITE ci=%d, rpt=%d (len=%d), usage=0x%x (len=%d), kind=0x%02x\n",
427 ci
, item
.report_ID
, info
->report_len
,
428 known_info
[i
].usage_code
, item
.report_size
, item
.kind
);
433 ups
->UPS_Cap
[CI_STATUS
] = true; /* we always have status flag */
438 static bool populate_uval(UPSINFO
*ups
, USB_INFO
*info
, unsigned char *data
, USB_VALUE
*uval
)
440 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
445 /* data+1 skips the report tag byte */
446 info
->value
= hid_get_data(data
+1, &info
->item
);
448 exponent
= info
->unit_exponent
;
450 exponent
= exponent
- 16;
452 if (info
->data_type
== T_INDEX
) { /* get string */
453 if (info
->value
== 0)
456 str
= hidu_get_string(my_data
->fd
, info
->value
);
460 astrncpy(val
.sValue
, str
, sizeof(val
.sValue
));
461 val
.value_type
= V_STRING
;
463 Dmsg4(200, "Def val=%d exp=%d sVal=\"%s\" ci=%d\n", info
->value
,
464 exponent
, val
.sValue
, info
->ci
);
465 } else if (info
->data_type
== T_UNITS
) {
466 val
.value_type
= V_DOUBLE
;
468 switch (info
->unit
) {
470 val
.UnitName
= "Volts";
471 exponent
-= 7; /* remove bias */
474 exponent
+= 2; /* remove bias */
475 val
.UnitName
= "Amps";
478 val
.UnitName
= "Hertz";
481 val
.UnitName
= "Seconds";
484 exponent
-= 7; /* remove bias */
485 val
.UnitName
= "Watts";
488 val
.UnitName
= "Degrees K";
491 val
.UnitName
= "AmpSecs";
493 val
.dValue
= info
->value
;
495 val
.dValue
= ((double)info
->value
) * pow_ten(exponent
);
499 val
.value_type
= V_INTEGER
;
500 val
.iValue
= info
->value
;
505 val
.dValue
= info
->value
;
507 val
.dValue
= ((double)info
->value
) * pow_ten(exponent
);
509 // Store a (possibly truncated) copy of the floating point value in the
510 // integer field as well.
511 val
.iValue
= (int)val
.dValue
;
513 Dmsg4(200, "Def val=%d exp=%d dVal=%f ci=%d\n", info
->value
,
514 exponent
, val
.dValue
, info
->ci
);
515 } else { /* should be T_NONE */
518 val
.value_type
= V_INTEGER
;
519 val
.iValue
= info
->value
;
522 val
.dValue
= info
->value
;
524 val
.dValue
= ((double)info
->value
) * pow_ten(exponent
);
526 Dmsg4(200, "Def val=%d exp=%d dVal=%f ci=%d\n", info
->value
,
527 exponent
, val
.dValue
, info
->ci
);
530 memcpy(uval
, &val
, sizeof(*uval
));
537 int pusb_get_value(UPSINFO
*ups
, int ci
, USB_VALUE
*uval
)
539 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
540 USB_INFO
*info
= my_data
->info
[ci
];
541 unsigned char data
[20];
545 * Note we need to check info since CI_STATUS is always true
546 * even when the UPS doesn't directly support that CI.
548 if (!UPS_HAS_CAP(ci
) || !info
)
549 return false; /* UPS does not have capability */
552 * Clear the destination buffer. In the case of a short transfer (see
553 * below) this will increase the likelihood of extracting the correct
554 * value in spite of the missing data.
556 memset(data
, 0, sizeof(data
));
558 /* Fetch the proper report */
559 len
= hidu_get_report(my_data
->fd
, &info
->item
, data
, info
->report_len
);
564 * Some UPSes seem to have broken firmware that sends a different number
565 * of bytes (usually fewer) than the report descriptor specifies. On
566 * UHCI controllers under *BSD, this can lead to random lockups. To
567 * reduce the likelihood of a lockup, we adjust our expected length to
568 * match the actual as soon as a mismatch is detected, so future
569 * transfers will have the proper lengths from the outset. NOTE that
570 * the data returned may not be parsed properly (since the parsing is
571 * necessarily based on the report descriptor) but given that HID
572 * reports are in little endian byte order and we cleared the buffer
573 * above, chances are good that we will actually extract the right
574 * value in spite of the UPS's brokenness.
576 if (info
->report_len
!= len
) {
577 Dmsg4(100, "Report length mismatch, fixing "
578 "(id=%d, ci=%d, expected=%d, actual=%d)\n",
579 info
->item
.report_ID
, ci
, info
->report_len
, len
);
580 info
->report_len
= len
;
583 /* Populate a uval struct using the raw report data */
584 return populate_uval(ups
, info
, data
, uval
);
588 * Read UPS events. I.e. state changes.
590 int pusb_ups_check_state(UPSINFO
*ups
)
594 unsigned char buf
[100];
595 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
596 struct timespec now
, exit
;
602 /* Figure out when we need to exit by */
603 clock_gettime(CLOCK_REALTIME
, &exit
);
604 exit
.tv_sec
+= ups
->wait_time
;
608 /* Figure out how long until we have to exit */
609 clock_gettime(CLOCK_REALTIME
, &now
);
611 if (now
.tv_sec
> exit
.tv_sec
||
612 (now
.tv_sec
== exit
.tv_sec
&&
613 now
.tv_nsec
/ 1000 >= exit
.tv_nsec
/ 1000)) {
614 /* Done already? How time flies... */
618 tv
.tv_sec
= exit
.tv_sec
- now
.tv_sec
;
619 tv
.tv_usec
= (exit
.tv_nsec
- now
.tv_nsec
) / 1000;
620 if (tv
.tv_usec
< 0) {
621 tv
.tv_sec
--; /* Normalize */
622 tv
.tv_usec
+= 1000000;
626 FD_SET(my_data
->intfd
, &rfds
);
628 retval
= select((my_data
->intfd
) + 1, &rfds
, NULL
, NULL
, &tv
);
631 case 0: /* No chars available in TIMER seconds. */
634 if (errno
== EINTR
|| errno
== EAGAIN
) /* assume SIGCHLD */
636 Dmsg1(200, "select error: ERR=%s\n", strerror(errno
));
637 usb_link_check(ups
); /* link is down, wait */
644 retval
= read(my_data
->intfd
, buf
, sizeof(buf
));
645 } while (retval
== -1 && (errno
== EAGAIN
|| errno
== EINTR
));
647 if (retval
< 0) { /* error */
648 Dmsg1(200, "read error: ERR=%s\n", strerror(errno
));
649 usb_link_check(ups
); /* notify that link is down, wait */
653 if (debug_level
>= 300) {
654 logf("Interrupt data: ");
655 for (i
= 0; i
< retval
; i
++)
656 logf("%02x, ", buf
[i
]);
663 * Iterate over all CIs, firing off events for any that are
664 * affected by this report.
666 for (ci
=0; ci
<CI_MAXCI
; ci
++) {
667 if (ups
->UPS_Cap
[ci
] && my_data
->info
[ci
] &&
668 my_data
->info
[ci
]->item
.report_ID
== buf
[0]) {
671 * Check if we received fewer bytes of data from the UPS than we
672 * should have. If so, ignore the report since we can't process it
673 * reliably. If we go ahead and try to process it we may get
674 * sporradic bad readings. UPSes we've seen this issue on so far
677 * "Back-UPS CS 650 FW:817.v7 .I USB FW:v7"
678 * "Back-UPS CS 500 FW:808.q8.I USB FW:q8"
680 if (my_data
->info
[ci
]->report_len
!= retval
) {
681 Dmsg4(100, "Report length mismatch, ignoring "
682 "(id=%d, ci=%d, expected=%d, actual=%d)\n",
683 my_data
->info
[ci
]->item
.report_ID
, ci
,
684 my_data
->info
[ci
]->report_len
, retval
);
685 break; /* don't continue since other CIs will be just as wrong */
688 /* Ignore this event if the value has not changed */
689 value
= hid_get_data(buf
+1, &my_data
->info
[ci
]->item
);
690 if (my_data
->info
[ci
]->value
== value
) {
691 Dmsg3(200, "Ignoring unchanged value (ci=%d, rpt=%d, val=%d)\n",
696 Dmsg3(200, "Processing changed value (ci=%d, rpt=%d, val=%d)\n",
699 /* Populate a uval and report it to the upper layer */
700 populate_uval(ups
, my_data
->info
[ci
], buf
, &uval
);
701 if (usb_report_event(ups
, ci
, &uval
)) {
703 * The upper layer considers this an important event,
704 * so we will return after processing any remaining
705 * CIs for this report.
721 * This is called once by the core code and is the first
724 int pusb_ups_open(UPSINFO
*ups
)
726 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
729 if (my_data
== NULL
) {
730 my_data
= (USB_DATA
*)malloc(sizeof(USB_DATA
));
731 if (my_data
== NULL
) {
732 log_event(ups
, LOG_ERR
, "Out of memory.");
737 memset(my_data
, 0, sizeof(USB_DATA
));
738 ups
->driver_internal_data
= my_data
;
740 reinitialize_private_structure(ups
);
743 if (my_data
->orig_device
[0] == 0)
744 astrncpy(my_data
->orig_device
, ups
->device
, sizeof(my_data
->orig_device
));
746 if (!open_usb_device(ups
)) {
748 if (ups
->device
[0]) {
749 Error_abort1("Cannot open UPS device: \"%s\" --\n"
750 "For a link to detailed USB trouble shooting information,\n"
751 "please see <http://www.apcupsd.com/support.html>.\n", ups
->device
);
753 Error_abort0("Cannot find UPS device --\n"
754 "For a link to detailed USB trouble shooting information,\n"
755 "please see <http://www.apcupsd.com/support.html>.\n");
765 * This is the last routine called from apcupsd core code
767 int pusb_ups_close(UPSINFO
*ups
)
769 /* Should we be politely closing fds here or anything? */
772 if (ups
->driver_internal_data
) {
773 free(ups
->driver_internal_data
);
774 ups
->driver_internal_data
= NULL
;
781 int pusb_ups_setup(UPSINFO
*ups
)
787 int pusb_read_int_from_ups(UPSINFO
*ups
, int ci
, int *value
)
791 if (!pusb_get_value(ups
, ci
, &val
))
798 int pusb_write_int_to_ups(UPSINFO
*ups
, int ci
, int value
, const char *name
)
800 USB_DATA
*my_data
= (USB_DATA
*)ups
->driver_internal_data
;
802 int old_value
, new_value
;
803 unsigned char rpt
[20];
805 if (ups
->UPS_Cap
[ci
] && my_data
->info
[ci
] && my_data
->info
[ci
]->witem
.report_ID
) {
806 info
= my_data
->info
[ci
]; /* point to our info structure */
808 if (hidu_get_report(my_data
->fd
, &info
->item
, rpt
, info
->report_len
) < 1) {
809 Dmsg1(000, "get_report for kill power function %s failed.\n", name
);
813 old_value
= hid_get_data(rpt
+ 1, &info
->item
);
815 hid_set_data(rpt
+ 1, &info
->witem
, value
);
817 if (!hidu_set_report(my_data
->fd
, &info
->witem
, rpt
, info
->report_len
)) {
818 Dmsg1(000, "set_report for kill power function %s failed.\n", name
);
822 if (hidu_get_report(my_data
->fd
, &info
->item
, rpt
, info
->report_len
) < 1) {
823 Dmsg1(000, "get_report for kill power function %s failed.\n", name
);
827 new_value
= hid_get_data(rpt
+ 1, &info
->item
);
829 Dmsg3(100, "function %s ci=%d value=%d OK.\n", name
, ci
, value
);
830 Dmsg4(100, "%s before=%d set=%d after=%d\n", name
, old_value
, value
, new_value
);
834 Dmsg2(000, "function %s ci=%d not available in this UPS.\n", name
, ci
);