From 63b223b60be5f0aaaf8ffa0766dfef6f050fd770 Mon Sep 17 00:00:00 2001 From: Jeremy Levy Date: Wed, 29 Apr 2026 14:19:08 +0000 Subject: [PATCH] drivers: fix CPS HID input/output voltage decoding for 0764:0601 --- drivers/cps-hid.c | 43 +++++----------------------------------- drivers/libhid.c | 47 ++++++++++++++++++++++++++++++++++++++++++++ drivers/usbhid-ups.c | 2 ++ 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/drivers/cps-hid.c b/drivers/cps-hid.c index 1cc221499c..06746ec0dd 100644 --- a/drivers/cps-hid.c +++ b/drivers/cps-hid.c @@ -485,6 +485,10 @@ static int cps_claim(HIDDevice_t *hd) { * voltage limits as being more appropriate. */ + + + + static int cps_fix_report_desc(HIDDevice_t *pDev, HIDDesc_t *pDesc_arg) { HIDData_t *pData; int retval = 0; @@ -622,29 +626,15 @@ static int cps_fix_report_desc(HIDDevice_t *pDev, HIDDesc_t *pDesc_arg) { && input_pData->Size > 1 && input_pData->Size <= sizeof(long)*8 ) { - /* Note: usually values are signed, but - * here we are about compensating for - * poorly encoded maximums, so limit by - * 2^(size)-1, e.g. for "size==16" the - * limit should be "2^16 - 1 = 65535"; - * note that in HIDParse() we likely - * set 65535 here in that case. See - * also comments there (hidparser.c) - * discussing signed/unsigned nuances. - */ - /* long sizeMax = (1L << (input_pData->Size - 1)) - 1; */ long sizeMax = (1L << (input_pData->Size)) - 1; if (input_logmax > sizeMax) { input_logmax = sizeMax; } } - if (output_logmax_assumed && output_pData->Size > 1 && output_pData->Size <= sizeof(long)*8 ) { - /* See comment above */ - /* long sizeMax = (1L << (output_pData->Size - 1)) - 1; */ long sizeMax = (1L << (output_pData->Size)) - 1; if (output_logmax > sizeMax) { output_logmax = sizeMax; @@ -670,44 +660,21 @@ static int cps_fix_report_desc(HIDDevice_t *pDev, HIDDesc_t *pDesc_arg) { } } - /* Fix for nominal power reporting getting clipped by a too restrictive LogMax. */ if ((pData=FindObject_with_ID_Node(pDesc_arg, 24 /* 0x18 */, USAGE_POW_CONFIG_ACTIVE_POWER))) { long power_logmax = pData->LogMax; - - upsdebugx(4, "Original Report Descriptor: ConfigActivePower " - "LogMin: %ld LogMax: %ld", - pData->LogMin, power_logmax); - if (power_logmax < CPS_NOMINALPWR_LOGMAX) { - /* Set a generous maximum value that will not restrict UPS reporting. - * - * Current findings suggest that the values sent by the UPS are - * accurate, but then get clipped by a too strict LogMax threshold: - * https://github.com/networkupstools/nut/issues/2917#issuecomment-2832243477 - */ pData->LogMax = CPS_NOMINALPWR_LOGMAX; - upsdebugx(3, "Fixing Report Descriptor: " "set ConfigActivePower LogMax = %ld", pData->LogMax); - retval = 1; } } - if (!retval) { - /* We did not `return 1` above, so... */ - upsdebugx(3, - "SKIPPED Report Descriptor fix for UPS: " - "Vendor: %04x, Product: %04x " - "(problematic conditions not matched)", - (unsigned int)vendorID, - (unsigned int)productID); - } - return retval; } + subdriver_t cps_subdriver = { CPS_HID_VERSION, cps_claim, diff --git a/drivers/libhid.c b/drivers/libhid.c index 42509e36c5..c64976c7e0 100644 --- a/drivers/libhid.c +++ b/drivers/libhid.c @@ -151,6 +151,9 @@ reportbuf_t *new_report_buffer(HIDDesc_t *arg_pDesc) /* the functions in this next group operate on buffered reports, but operate on individual items, not whole reports. */ + + + /* refresh the report with the given id in the report buffer rbuf. If the report is not yet in the buffer, or if it is older than "age" seconds, then the report is freshly read from the USB @@ -262,6 +265,49 @@ static int refresh_report_buffer(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDDa conversion is performed. If age>0, the read operation is buffered if the item's age is less than "age". On success, return 0 and store the answer in *value. On failure, return -1 and set errno. */ + +static long debug_maybe_fix_cps_voltage_raw_value(reportbuf_t *rbuf, HIDData_t *hiddata, long raw_value) +{ + usb_ctrl_repindex id; + long reconstructed; + unsigned int b1, b2; + + if (!rbuf || !hiddata) { + return raw_value; + } + + if (!(hiddata->ReportID == 0x0F || hiddata->ReportID == 0x12)) { + return raw_value; + } + + if (hiddata->Offset != 0 || hiddata->Size != 16) { + return raw_value; + } + + id = hiddata->ReportID; + + if (rbuf->len[id] < 3) { + return raw_value; + } + + b1 = (unsigned int)rbuf->data[id][1]; + b2 = (unsigned int)rbuf->data[id][2]; + reconstructed = (long)(b1 | (b2 << 8)); + + /* Heuristic for CPS 0764:0601 family: + * some devices expose UPS.Input.Voltage / UPS.Output.Voltage with + * descriptor metadata that leads GetValue() to return only the low byte + * (e.g. 0x0c -> 12) while the two payload bytes actually hold the real + * voltage in 0.1V units (e.g. 0x090c -> 2316 => 231.6V after exponent). + */ + if (raw_value >= 0 && raw_value <= 255 && + reconstructed >= 1000 && reconstructed <= 3000) { + return reconstructed; + } + + return raw_value; +} + static int get_item_buffered(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDData_t *pData, long *Value, time_t age) { int id = pData->ReportID; @@ -273,6 +319,7 @@ static int get_item_buffered(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDData_t } GetValue(rbuf->data[id], pData, Value); + *Value = debug_maybe_fix_cps_voltage_raw_value(rbuf, pData, *Value); return 0; } diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index b08b0d5291..f1db72c2ab 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -262,6 +262,8 @@ static void ups_status_set(void); static bool_t hid_ups_walk(walkmode_t mode); static int reconnect_ups(void); static int ups_infoval_set(hid_info_t *item, double value); + + static int callback(hid_dev_handle_t argudev, HIDDevice_t *arghd, usb_ctrl_charbuf rdbuf, usb_ctrl_charbufsize rdlen); #ifdef DEBUG