Skip to content

Commit d77c859

Browse files
authored
Merge pull request #2371 from jimklimov/belkin-hid-exponents
Unit-test and fix the Belkin/Liebert HID UPS exponents math
2 parents f6bff66 + 1ee28c2 commit d77c859

File tree

6 files changed

+348
-43
lines changed

6 files changed

+348
-43
lines changed

NEWS.adoc

+7-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ https://github.com/networkupstools/nut/milestone/10
8787
`configure` script option `--with-debuginfo`. Note that default autoconf
8888
behavior usually embeds moderate optimizations and debug information on
8989
its own. [PR #2310]
90+
* A fix applied among clean-ups between NUT v2.7.4 and v2.8.0 releases
91+
backfired for `usbhid-ups` subdriver `belkin-hid` which in practice
92+
relied on the broken older behavior; more details in its entry below.
93+
[PR #2371]
9094

9195
- nut-usbinfo.pl, nut-scanner and libnutscan:
9296
* Library API version for `libnutscan` was bumped from 2.2.0 to 2.5.0
@@ -182,7 +186,9 @@ https://github.com/networkupstools/nut/milestone/10
182186
by cooperation with Cyber Power Systems. [#2312]
183187
* `belkin-hid` subdriver now supports Liebert PSI5 devices which have a
184188
different numeric reading scale than earlier handled models. [issue #2271,
185-
PR #2272, PR #2369]
189+
PR #2272, PR #2369] Generally the wrong-scale processing was addressed,
190+
including a regression in NUT v2.8.0 which led to zero values
191+
in voltage data points which NUT v2.7.4 reported well [#2371]
186192
* The `onlinedischarge` configuration flag name was too ambiguous and got
187193
deprecated (will be supported but no longer promoted by documentation),
188194
introducing `onlinedischarge_onbattery` as the meaningful alias. [#2213]

drivers/belkin-hid.c

+51-41
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
* Copyright (C)
44
* 2003 - 2008 Arnaud Quette <[email protected]>
55
* 2005 Peter Selinger <[email protected]>
6-
* 2011, 2014 Charles Lepple <clepple+nut@gmail>
6+
* 2011, 2014 Charles Lepple <[email protected]>
7+
* 2024 James R. Parks <[email protected]>
8+
* 2024 Jim Klimov <[email protected]>
79
*
810
* Sponsored by MGE UPS SYSTEMS <http://www.mgeups.com>
911
*
@@ -31,7 +33,7 @@
3133

3234
#include <math.h> /* for fabs() */
3335

34-
#define BELKIN_HID_VERSION "Belkin/Liebert HID 0.20"
36+
#define BELKIN_HID_VERSION "Belkin/Liebert HID 0.21"
3537

3638
/* Belkin */
3739
#define BELKIN_VENDORID 0x050d
@@ -88,32 +90,39 @@ static const char *liebert_charging_fun(double value);
8890
static const char *liebert_lowbatt_fun(double value);
8991
static const char *liebert_replacebatt_fun(double value);
9092
static const char *liebert_shutdownimm_fun(double value);
93+
94+
/* These lookup functions also cover the 1e-7 factor which seems to
95+
* be due to a broken report descriptor in certain Liebert units.
96+
* Exposed for unit testing - not "static" */
9197
static const char *liebert_config_voltage_fun(double value);
9298
static const char *liebert_line_voltage_fun(double value);
93-
static const char *liebert_psi5_line_voltage_fun(double value);
99+
100+
static double liebert_config_voltage_mult = 1.0;
101+
static double liebert_line_voltage_mult = 1.0;
102+
static char liebert_conversion_buf[10];
94103

95104
static info_lkp_t liebert_online_info[] = {
96105
{ 0, NULL, liebert_online_fun, NULL }
97106
};
98107

99108
static info_lkp_t liebert_discharging_info[] = {
100-
{ 0, NULL, liebert_discharging_fun, NULL }
109+
{ 0, NULL, liebert_discharging_fun, NULL }
101110
};
102111

103112
static info_lkp_t liebert_charging_info[] = {
104-
{ 0, NULL, liebert_charging_fun, NULL }
113+
{ 0, NULL, liebert_charging_fun, NULL }
105114
};
106115

107116
static info_lkp_t liebert_lowbatt_info[] = {
108-
{ 0, NULL, liebert_lowbatt_fun, NULL }
117+
{ 0, NULL, liebert_lowbatt_fun, NULL }
109118
};
110119

111120
static info_lkp_t liebert_replacebatt_info[] = {
112-
{ 0, NULL, liebert_replacebatt_fun, NULL }
121+
{ 0, NULL, liebert_replacebatt_fun, NULL }
113122
};
114123

115124
static info_lkp_t liebert_shutdownimm_info[] = {
116-
{ 0, NULL, liebert_shutdownimm_fun, NULL }
125+
{ 0, NULL, liebert_shutdownimm_fun, NULL }
117126
};
118127

119128
static info_lkp_t liebert_config_voltage_info[] = {
@@ -124,17 +133,6 @@ static info_lkp_t liebert_line_voltage_info[] = {
124133
{ 0, NULL, liebert_line_voltage_fun, NULL },
125134
};
126135

127-
static info_lkp_t liebert_psi5_line_voltage_info[] = {
128-
{ 0, NULL, liebert_psi5_line_voltage_fun, NULL },
129-
};
130-
131-
static double liebert_config_voltage_mult = 1.0;
132-
static double liebert_line_voltage_mult = 1.0;
133-
static char liebert_conversion_buf[10];
134-
135-
/* These lookup functions also cover the 1e-7 factor which seems to be due to a
136-
* broken report descriptor in certain Liebert units.
137-
*/
138136
static const char *liebert_online_fun(double value)
139137
{
140138
return value ? "online" : "!online";
@@ -171,8 +169,13 @@ static const char *liebert_shutdownimm_fun(double value)
171169
*/
172170
static const char *liebert_config_voltage_fun(double value)
173171
{
174-
if( value < 1 ) {
175-
if( fabs(value - 1e-7) < 1e-9 ) {
172+
/* Does not fire with devices seen diring investigation for
173+
* https://github.com/networkupstools/nut/issues/2370
174+
* as the ones seen serve nominal "config" values as integers
175+
* (e.g. "230" for line and "24" for battery).
176+
*/
177+
if (value < 1) {
178+
if (fabs(value - 1e-7) < 1e-9) {
176179
liebert_config_voltage_mult = 1e8;
177180
liebert_line_voltage_mult = 1e7; /* stomp this in case input voltage was low */
178181
upsdebugx(2, "ConfigVoltage = %g -> assuming correction factor = %g",
@@ -189,26 +192,33 @@ static const char *liebert_config_voltage_fun(double value)
189192

190193
static const char *liebert_line_voltage_fun(double value)
191194
{
192-
if( value < 1 ) {
193-
if( fabs(value - 1e-7) < 1e-9 ) {
195+
/* Keep large readings like "230" or "24" as is */
196+
if (value < 1) {
197+
int picked_scale = 0;
198+
/* NOTE: Start with tiniest scale first */
199+
200+
/* Practical use-case for mult=1e7:
201+
* 1.39e-06 => 13.9
202+
* 2.201e-05 => 220.1
203+
* NOTE: The clause below is in fact broken for this use-case,
204+
* but was present in sources for ages (worked wrongly with an
205+
* integer-oriented abs() so collapsed into "if (0 < 1e-9) {")!
206+
* if (fabs(value - 1e-7) < 1e-9) {
207+
*/
208+
if (fabs(value - 1e-5) < 4*1e-5) {
194209
liebert_line_voltage_mult = 1e7;
195-
upsdebugx(2, "Input/OutputVoltage = %g -> assuming correction factor = %g",
196-
value, liebert_line_voltage_mult);
197-
} else {
198-
upslogx(LOG_NOTICE, "LineVoltage exponent looks wrong, but not correcting.");
210+
picked_scale = 1;
211+
} else
212+
/* Practical use-case for mult=1e5:
213+
* 0.000273 => 27.3
214+
* 0.001212 => 121.2
215+
*/
216+
if (fabs(value - 1e-3) < 4*1e-3) {
217+
liebert_line_voltage_mult = 1e5;
218+
picked_scale = 1;
199219
}
200-
}
201-
202-
snprintf(liebert_conversion_buf, sizeof(liebert_conversion_buf), "%.1f",
203-
value * liebert_line_voltage_mult);
204-
return liebert_conversion_buf;
205-
}
206220

207-
static const char *liebert_psi5_line_voltage_fun(double value)
208-
{
209-
if( value < 1 ) {
210-
if( fabs(value - 1e-3) < 1e-3 ) {
211-
liebert_line_voltage_mult = 1e5;
221+
if (picked_scale) {
212222
upsdebugx(2, "Input/OutputVoltage = %g -> assuming correction factor = %g",
213223
value, liebert_line_voltage_mult);
214224
} else {
@@ -507,12 +517,12 @@ static hid_info_t belkin_hid2nut[] = {
507517
/* Liebert PSI5 */
508518
{ "input.voltage.nominal", 0, 0, "UPS.Flow.ConfigVoltage", NULL, "%.0f", 0, NULL },
509519
{ "input.frequency", 0, 0, "UPS.PowerConverter.Input.Frequency", NULL, "%s", 0, divide_by_100_conversion },
510-
{ "input.voltage", 0, 0, "UPS.PowerConverter.Input.Voltage", NULL, "%s", 0, liebert_psi5_line_voltage_info },
520+
{ "input.voltage", 0, 0, "UPS.PowerConverter.Input.Voltage", NULL, "%s", 0, liebert_line_voltage_info },
511521
{ "output.voltage.nominal", 0, 0, "UPS.Flow.ConfigVoltage", NULL, "%.0f", 0, NULL },
512522
{ "output.frequency", 0, 0, "UPS.PowerConverter.Output.Frequency", NULL, "%s", 0, divide_by_100_conversion },
513-
{ "output.voltage", 0, 0, "UPS.PowerConverter.Output.Voltage", NULL, "%s", 0, liebert_psi5_line_voltage_info },
523+
{ "output.voltage", 0, 0, "UPS.PowerConverter.Output.Voltage", NULL, "%s", 0, liebert_line_voltage_info },
514524
{ "ups.load", 0, 0, "UPS.OutletSystem.Outlet.PercentLoad", NULL, "%.0f", 0, NULL },
515-
{ "battery.voltage", 0, 0, "UPS.BatterySystem.Battery.Voltage", NULL, "%s", 0, liebert_psi5_line_voltage_info },
525+
{ "battery.voltage", 0, 0, "UPS.BatterySystem.Battery.Voltage", NULL, "%s", 0, liebert_line_voltage_info },
516526
{ "battery.voltage.nominal", 0, 0, "UPS.BatterySystem.Battery.ConfigVoltage", NULL, "%.0f", 0, NULL },
517527
{ "battery.capacity", 0, 0, "UPS.Flow.ConfigApparentPower", NULL, "%.0f", 0, NULL },
518528
/* status */

tests/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
/nuttimetest
1717
/nuttimetest.log
1818
/nuttimetest.trs
19+
/getexponenttest-belkin-hid
20+
/getexponenttest-belkin-hid.log
21+
/getexponenttest-belkin-hid.trs
1922
/getvaluetest
2023
/getvaluetest.log
2124
/getvaluetest.trs

tests/Makefile.am

+16-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ all: $(TESTS)
1616
EXTRA_DIST = nut-driver-enumerator-test.sh nut-driver-enumerator-test--ups.conf
1717

1818
TESTS =
19+
noinst_LTLIBRARIES =
1920
CLEANFILES = *.trs *.log
2021

2122
AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/drivers
@@ -65,7 +66,19 @@ hidparser.c: $(top_srcdir)/drivers/hidparser.c
6566
test -s "$@" || ln -s -f "$(top_srcdir)/drivers/hidparser.c" "$@"
6667

6768
if WITH_USB
68-
TESTS += getvaluetest
69+
TESTS += getvaluetest getexponenttest-belkin-hid
70+
71+
# We only need to call a few methods, not use the whole source - so
72+
# not linking it as a getvaluetest_SOURCE file (has too many deps):
73+
noinst_LTLIBRARIES += libdriverstubusb.la
74+
nodist_libdriverstubusb_la_SOURCES = driver-stub-usb.c
75+
libdriverstubusb_la_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS)
76+
#libdriverstubusb_la_LIBADD = $(top_builddir)/common/libcommon.la
77+
78+
getexponenttest-belkin-hid.c: $(top_srcdir)/drivers/belkin-hid.c
79+
getexponenttest_belkin_hid_SOURCES = getexponenttest-belkin-hid.c
80+
getexponenttest_belkin_hid_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS)
81+
getexponenttest_belkin_hid_LDADD = $(top_builddir)/common/libcommon.la libdriverstubusb.la
6982

7083
getvaluetest_SOURCES = getvaluetest.c
7184
nodist_getvaluetest_SOURCES = hidparser.c
@@ -75,6 +88,7 @@ getvaluetest_LDADD = $(top_builddir)/common/libcommon.la
7588
else !WITH_USB
7689
EXTRA_DIST += getvaluetest.c hidparser.c
7790
endif !WITH_USB
91+
EXTRA_DIST += driver-stub-usb.c
7892

7993
if WITH_GPIO
8094
TESTS += gpiotest
@@ -101,6 +115,7 @@ EXTRA_DIST += generic_gpio_utest.h generic_gpio_test.txt
101115
# Make sure out-of-dir dependencies exist (especially when dev-building parts):
102116
$(top_builddir)/drivers/libdummy_mockdrv.la \
103117
$(top_builddir)/common/libnutconf.la \
118+
$(top_builddir)/common/libcommonclient.la \
104119
$(top_builddir)/common/libcommon.la: dummy
105120
+@cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F)
106121

tests/driver-stub-usb.c

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* placeholder/mock method implementations to just directly use driver
2+
* source code as done for belkin-hid.c (and eventually similar code)
3+
* for almost-in-vivo testing (and minimal intrusion to that codebase).
4+
* See also: getexponenttest-belkin-hid.c
5+
*
6+
* Copyright (C)
7+
* 2024 Jim Klimov <[email protected]>
8+
*
9+
* This program is free software; you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation; either version 2 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program; if not, write to the Free Software
21+
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22+
*/
23+
24+
#include "common.h"
25+
26+
#include "usb-common.h"
27+
int is_usb_device_supported(usb_device_id_t *usb_device_id_list, USBDevice_t *device) {
28+
NUT_UNUSED_VARIABLE(usb_device_id_list);
29+
NUT_UNUSED_VARIABLE(device);
30+
return -1;
31+
}
32+
33+
#include "usbhid-ups.h"
34+
hid_dev_handle_t udev = HID_DEV_HANDLE_CLOSED;
35+
info_lkp_t beeper_info[] = { { 0, NULL, NULL, NULL } };
36+
info_lkp_t date_conversion[] = { { 0, NULL, NULL, NULL } };
37+
info_lkp_t stringid_conversion[] = { { 0, NULL, NULL, NULL } };
38+
info_lkp_t divide_by_10_conversion[] = { { 0, NULL, NULL, NULL } };
39+
info_lkp_t divide_by_100_conversion[] = { { 0, NULL, NULL, NULL } };
40+
41+
void possibly_supported(const char *mfr, HIDDevice_t *hd) {
42+
NUT_UNUSED_VARIABLE(mfr);
43+
NUT_UNUSED_VARIABLE(hd);
44+
}
45+
46+
int fix_report_desc(HIDDevice_t *arg_pDev, HIDDesc_t *arg_pDesc) {
47+
NUT_UNUSED_VARIABLE(arg_pDev);
48+
NUT_UNUSED_VARIABLE(arg_pDesc);
49+
return -1;
50+
}
51+
52+
#include "libhid.h"
53+
usage_lkp_t hid_usage_lkp[] = { {NULL, 0} };
54+
char *HIDGetItemString(hid_dev_handle_t arg_udev, const char *hidpath, char *buf, size_t buflen, usage_tables_t *utab) {
55+
NUT_UNUSED_VARIABLE(arg_udev);
56+
NUT_UNUSED_VARIABLE(hidpath);
57+
NUT_UNUSED_VARIABLE(buf);
58+
NUT_UNUSED_VARIABLE(buflen);
59+
NUT_UNUSED_VARIABLE(utab);
60+
return NULL;
61+
}
62+
63+
#include "main.h"
64+
char *getval(const char *var) {
65+
NUT_UNUSED_VARIABLE(var);
66+
return NULL;
67+
}

0 commit comments

Comments
 (0)