|
26 | 26 | #include "powercom-hid.h" |
27 | 27 | #include "usb-common.h" |
28 | 28 |
|
29 | | -#define POWERCOM_HID_VERSION "PowerCOM HID 0.72" |
| 29 | +#include <ctype.h> /* isdigit() */ |
| 30 | + |
| 31 | +#define POWERCOM_HID_VERSION "PowerCOM HID 0.73" |
30 | 32 | /* FIXME: experimental flag to be put in upsdrv_info */ |
31 | 33 |
|
32 | 34 | /* PowerCOM */ |
@@ -382,6 +384,78 @@ static info_lkp_t powercom_overload_conversion[] = { |
382 | 384 | { 0, NULL, powercom_overload_conversion_fun, NULL } |
383 | 385 | }; |
384 | 386 |
|
| 387 | +/* FAIL-SAFE HACK (proper solution welcome) as proposed in |
| 388 | + * https://github.com/networkupstools/nut/issues/2766#issuecomment-3751553822 : |
| 389 | + * Use global udev handle from usbhid-ups.h for a custom conversion function |
| 390 | + * that reads Report 0xa4 directly via generic USB call. Formally this is |
| 391 | + * now triggered by a read of ACPresent which is always served, and ends |
| 392 | + * up requesting another report instead. |
| 393 | + */ |
| 394 | +static const char *powercom_hack_voltage(double value) |
| 395 | +{ |
| 396 | + static char buf[32]; |
| 397 | + static char last_valid[32] = "0.0"; /* Cache last valid reading */ |
| 398 | + usb_ctrl_char data[8]; |
| 399 | + int ret, retry, max_retries = 1; |
| 400 | + |
| 401 | + /* We do not care for ACPresent reading received here */ |
| 402 | + NUT_UNUSED_VARIABLE(value); |
| 403 | + |
| 404 | + /* Global 'udev' is defined in usbhid-ups.h and initialized by main driver */ |
| 405 | + if (!udev) |
| 406 | + return last_valid; |
| 407 | + |
| 408 | + /* If we don't have a valid value yet (driver startup), try harder to get one */ |
| 409 | + if (!strcmp(last_valid, "0.0")) { |
| 410 | + /* Retry up to 5 times (approx 0.5s) */ |
| 411 | + max_retries = 5; |
| 412 | + } |
| 413 | + |
| 414 | + for (retry = 0; retry < max_retries; retry++) { |
| 415 | + /* Portable read of Report 0xa4 (Feature) using NUT's wrapper */ |
| 416 | + /* 0xA1: Dir=IN, Type=Class, Recp=Interface */ |
| 417 | + ret = usb_control_msg(udev, 0xA1, 0x01, (0x03 << 8) | 0xa4, 0, data, sizeof(data), 1000); |
| 418 | + |
| 419 | + if (ret > 0) { |
| 420 | + char msg[32], *start, *end; |
| 421 | + int len = (ret > 8) ? 8 : ret; |
| 422 | + int j = 0, i; |
| 423 | + |
| 424 | + /* Skip byte 0 (Report ID) */ |
| 425 | + for (i=1; i<len; i++) { |
| 426 | + msg[j++] = (char)data[i]; |
| 427 | + } |
| 428 | + msg[j] = '\0'; |
| 429 | + |
| 430 | + /* Parse " 13.7 2" -> "13.7" */ |
| 431 | + start = msg; |
| 432 | + while (*start && !isdigit((unsigned char)(*start)) && *start != '.') { |
| 433 | + start++; |
| 434 | + } |
| 435 | + end = start; |
| 436 | + while (*end && (isdigit((unsigned char)(*end)) || *end == '.')) { |
| 437 | + end++; |
| 438 | + } |
| 439 | + *end = '\0'; |
| 440 | + |
| 441 | + /* VALIDATION */ |
| 442 | + if (strlen(start) > 0 && strchr(start, '.') != NULL) { |
| 443 | + snprintf(buf, sizeof(buf), "%s", start); |
| 444 | + snprintf(last_valid, sizeof(last_valid), "%s", start); |
| 445 | + return buf; |
| 446 | + } |
| 447 | + } |
| 448 | + if (max_retries > 1) { |
| 449 | + usleep(100000); |
| 450 | + } |
| 451 | + } |
| 452 | + return last_valid; |
| 453 | +} |
| 454 | + |
| 455 | +static info_lkp_t powercom_hack_voltage_lkp[] = { |
| 456 | + { 0, NULL, powercom_hack_voltage, NULL } |
| 457 | +}; |
| 458 | + |
385 | 459 | /* --------------------------------------------------------------- */ |
386 | 460 | /* Vendor-specific usage table */ |
387 | 461 | /* --------------------------------------------------------------- */ |
@@ -465,6 +539,13 @@ static hid_info_t powercom_hid2nut[] = { |
465 | 539 | * { "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL }, |
466 | 540 | * { "battery.voltage.nominal", 0, 0, "UPS.Battery.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL }, |
467 | 541 | */ |
| 542 | + /* HACK (proper fix welcome): Formally we map battery.voltage |
| 543 | + * to *ACPresent* (Report 0x0a) which is always present. |
| 544 | + * But then we use our HACK conversion function to ignore the |
| 545 | + * passed value, and read and interpret Report 0xa4 instead! |
| 546 | + */ |
| 547 | + { "battery.voltage", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, "%s", 0, powercom_hack_voltage_lkp }, |
| 548 | + |
468 | 549 | { "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL }, |
469 | 550 | { "battery.charge", 0, 0, "UPS.Battery.RemainingCapacity", NULL, "%.0f", 0, NULL }, |
470 | 551 | { "battery.charge.low", 0, 0, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", 0, NULL }, |
|
0 commit comments