Skip to content

Commit 75efd0c

Browse files
committed
drivers/cps-hid.c, NEWS.adoc: try to fix frequency scaling dynamically [#2717]
Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
1 parent 99ea972 commit 75efd0c

File tree

2 files changed

+168
-8
lines changed

2 files changed

+168
-8
lines changed

NEWS.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ https://github.com/networkupstools/nut/milestone/11
220220
* in `cps-hid` subdriver, `cps_fix_report_desc()` method should now handle
221221
mismatched `LogMax` ranges for input and output voltages, whose USB Report
222222
Descriptors are wrongly encoded by some firmware versions. [#1512]
223+
* in `cps-hid` subdriver, try to fix frequency scaling based on the values
224+
we see from the device and/or configuration overrides (low, nominal, high)
225+
so `499.0 Hz` reading that comes from some firmware versions gets reported
226+
properly as `49.9Hz`. [#2717]
223227
* USB parameters (per `usb_communication_subdriver_t`) are now set back to
224228
their default values during enumeration after probing each subdriver.
225229
Having an unrelated device connected with a VID:PID matching the

drivers/cps-hid.c

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (C)
44
* 2003 - 2008 Arnaud Quette <arnaud.quette@free.fr>
55
* 2005 - 2006 Peter Selinger <selinger@users.sourceforge.net>
6-
* 2020 - 2024 Jim Klimov <jimklimov+nut@gmail.com>
6+
* 2020 - 2025 Jim Klimov <jimklimov+nut@gmail.com>
77
* 2024 Alejandro González <me@alegon.dev>
88
*
99
* Note: this subdriver was initially generated as a "stub" by the
@@ -32,7 +32,7 @@
3232
#include "cps-hid.h"
3333
#include "usb-common.h"
3434

35-
#define CPS_HID_VERSION "CyberPower HID 0.82"
35+
#define CPS_HID_VERSION "CyberPower HID 0.83"
3636

3737
/* Cyber Power Systems */
3838
#define CPS_VENDORID 0x0764
@@ -54,10 +54,13 @@
5454
* For some devices, the reported battery voltage is off by factor
5555
* of 1.5 so we need to apply a scale factor to it to get the real
5656
* battery voltage. By default, the factor is 1 (no scaling).
57+
* Similarly, some firmwares do not report the exponent well, so
58+
* frequency values are seen as e.g. "499.0" (in "0.1 Hz" units not
59+
* explicitly stated), instead of "49.9 Hz".
5760
*/
58-
static double battery_scale = 1;
59-
static int might_need_battery_scale = 0;
60-
static int battery_scale_checked = 0;
61+
static double battery_scale = 1, input_freq_scale = 1, output_freq_scale = 1;
62+
static int might_need_battery_scale = 0, might_need_freq_scale = 0;
63+
static int battery_scale_checked = 0, input_freq_scale_checked = 0, output_freq_scale_checked = 0;
6164

6265
/*! If the ratio of the battery voltage to the nominal battery voltage exceeds
6366
* this factor, we assume that the battery voltage needs to be scaled by 2/3.
@@ -69,6 +72,8 @@ static void *cps_battery_scale(USBDevice_t *device)
6972
NUT_UNUSED_VARIABLE(device);
7073

7174
might_need_battery_scale = 1;
75+
might_need_freq_scale = 1;
76+
7277
return NULL;
7378
}
7479

@@ -88,6 +93,153 @@ static usb_device_id_t cps_usb_device_table[] = {
8893
{ 0, 0, NULL }
8994
};
9095

96+
/*! Adjusts frequency if it is order(s) of magnitude off
97+
* is_input = 1 for input.frequency and 0 for output.frequency
98+
*/
99+
static void cps_adjust_frequency_scale(double freq_report, int is_input)
100+
{
101+
const char *freq_low_str, *freq_high_str, *freq_nom_str;
102+
double freq_low = 0, freq_high = 0, freq_nom = 0;
103+
104+
if ((is_input && input_freq_scale_checked) || (!is_input && output_freq_scale_checked))
105+
return;
106+
107+
/* May be not available from device itself; but still
108+
* may be set by user as default/override options;
109+
* if not, we default for 50Hz and/or 60Hz range +- 10%.
110+
*/
111+
freq_nom_str = dstate_getinfo(is_input ? "input.frequency.nominal" : "output.frequency.nominal");
112+
freq_low_str = dstate_getinfo(is_input ? "input.frequency.low" : "output.frequency.low");
113+
freq_high_str = dstate_getinfo(is_input ? "input.frequency.high" : "output.frequency.high");
114+
115+
if (freq_nom_str)
116+
freq_nom = strtod(freq_nom_str, NULL);
117+
if (freq_low_str)
118+
freq_low = strtod(freq_low_str, NULL);
119+
if (freq_high_str)
120+
freq_high = strtod(freq_high_str, NULL);
121+
122+
if (d_equal(freq_nom, 0)) {
123+
if (45 < freq_low && freq_low <= 50)
124+
freq_nom = 50;
125+
else if (50 <= freq_high && freq_high <= 55)
126+
freq_nom = 50;
127+
else if (45 < freq_report && freq_report <= 55)
128+
freq_nom = 50;
129+
else if (450 < freq_report && freq_report <= 550)
130+
freq_nom = 50;
131+
else if (55 < freq_low && freq_low <= 60)
132+
freq_nom = 60;
133+
else if (60 <= freq_high && freq_high <= 65)
134+
freq_nom = 60;
135+
else if (55 < freq_report && freq_report <= 65)
136+
freq_nom = 60;
137+
else if (550 < freq_report && freq_report <= 650)
138+
freq_nom = 60;
139+
140+
upsdebugx(3, "%s: '%sput.frequency.nominal' is %s, guessed %0.1f%s",
141+
__func__, is_input ? "in" : "out", NUT_STRARG(freq_nom_str),
142+
d_equal(freq_nom, 0) ? 55 : freq_nom,
143+
d_equal(freq_nom, 0) ? " (50 or 60Hz range)" : "");
144+
}
145+
146+
if (d_equal(freq_low, 0)) {
147+
if (d_equal(freq_nom, 0))
148+
freq_low = 45.0;
149+
else
150+
freq_low = freq_nom * 0.95;
151+
152+
upsdebugx(3, "%s: '%sput.frequency.low' is %s, defaulting to %0.1f",
153+
__func__, is_input ? "in" : "out", NUT_STRARG(freq_low_str), freq_low);
154+
}
155+
156+
if (d_equal(freq_high, 0)) {
157+
if (d_equal(freq_nom, 0))
158+
freq_high = 65.0;
159+
else
160+
freq_high = freq_nom * 1.05;
161+
162+
upsdebugx(3, "%s: '%sput.frequency.high' is %s, defaulting to %0.1f",
163+
__func__, is_input ? "in" : "out", NUT_STRARG(freq_high_str), freq_high);
164+
}
165+
166+
if (freq_low <= freq_report && freq_report <= freq_high) {
167+
if (is_input) {
168+
input_freq_scale = 1.0;
169+
input_freq_scale_checked = 1;
170+
} else {
171+
output_freq_scale = 1.0;
172+
output_freq_scale_checked = 1;
173+
}
174+
/* We should be here once per freq type... */
175+
upsdebugx(1, "%s: Determined scaling factor "
176+
"needed for '%sput.frequency': 1.0",
177+
__func__, is_input ? "in" : "out");
178+
}
179+
else
180+
if (freq_low <= freq_report/10.0 && freq_report/10.0 <= freq_high) {
181+
if (is_input) {
182+
input_freq_scale = 0.1;
183+
input_freq_scale_checked = 1;
184+
} else {
185+
output_freq_scale = 0.1;
186+
output_freq_scale_checked = 1;
187+
}
188+
/* We should be here once per freq type... */
189+
upsdebugx(1, "%s: Determined scaling factor "
190+
"needed for '%sput.frequency': 0.1",
191+
__func__, is_input ? "in" : "out");
192+
}
193+
else
194+
{
195+
/* We might return here, so do not log too loudly */
196+
upsdebugx(2, "%s: Could not determine scaling factor "
197+
"needed for '%sput.frequency', will report "
198+
"it as is (and might detect better later)",
199+
__func__, is_input ? "in" : "out");
200+
}
201+
}
202+
203+
/* returns statically allocated string - must not use it again before
204+
done with result! */
205+
static const char *cps_input_freq_fun(double value)
206+
{
207+
static char buf[8];
208+
209+
if (might_need_freq_scale) {
210+
cps_adjust_frequency_scale(value, 1);
211+
}
212+
213+
upsdebugx(5, "%s: input_freq_scale = %.3f", __func__, input_freq_scale);
214+
snprintf(buf, sizeof(buf), "%.1f", input_freq_scale * value);
215+
216+
return buf;
217+
}
218+
219+
static info_lkp_t cps_input_freq[] = {
220+
{ 0, NULL, &cps_input_freq_fun, NULL }
221+
};
222+
223+
/* returns statically allocated string - must not use it again before
224+
done with result! */
225+
static const char *cps_output_freq_fun(double value)
226+
{
227+
static char buf[8];
228+
229+
if (might_need_freq_scale) {
230+
cps_adjust_frequency_scale(value, 0);
231+
}
232+
233+
upsdebugx(5, "%s: output_freq_scale = %.3f", __func__, output_freq_scale);
234+
snprintf(buf, sizeof(buf), "%.1f", output_freq_scale * value);
235+
236+
return buf;
237+
}
238+
239+
static info_lkp_t cps_output_freq[] = {
240+
{ 0, NULL, &cps_output_freq_fun, NULL }
241+
};
242+
91243
/*! Adjusts @a battery_scale if voltage is well above nominal.
92244
*/
93245
static void cps_adjust_battery_scale(double batt_volt)
@@ -247,18 +399,22 @@ static hid_info_t cps_hid2nut[] = {
247399
{ "BOOL", 0, 0, "UPS.Output.Overload", NULL, NULL, 0, overload_info },
248400

249401
/* Input page */
250-
{ "input.frequency", 0, 0, "UPS.Input.Frequency", NULL, "%.1f", 0, NULL },
402+
/* FIXME: Check if something like "UPS.Flow([N]?).ConfigFrequency"
403+
* is available for "input.frequency.nominal" */
404+
{ "input.frequency", 0, 0, "UPS.Input.Frequency", NULL, "%.1f", 0, cps_input_freq },
251405
{ "input.voltage.nominal", 0, 0, "UPS.Input.ConfigVoltage", NULL, "%.0f", 0, NULL },
252406
{ "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.1f", 0, NULL },
253407
{ "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
254408
{ "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
255-
/* used by CP1350EPFCLCD */
409+
/* used by CP1350EPFCLCD; why oh why "UPS.Output"?.. */
256410
{ "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
257411
{ "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL },
258412
{ "input.sensitivity", ST_FLAG_RW | ST_FLAG_STRING, 0, "UPS.Output.CPSInputSensitivity", NULL, "%s", HU_FLAG_SEMI_STATIC | HU_FLAG_ENUM, cps_sensitivity_info },
259413

260414
/* Output page */
261-
{ "output.frequency", 0, 0, "UPS.Output.Frequency", NULL, "%.1f", 0, NULL },
415+
/* FIXME: Check if something like "UPS.Flow([N]?).ConfigFrequency"
416+
* is available for "output.frequency.nominal" */
417+
{ "output.frequency", 0, 0, "UPS.Output.Frequency", NULL, "%.1f", 0, cps_output_freq },
262418
{ "output.voltage", 0, 0, "UPS.Output.Voltage", NULL, "%.1f", 0, NULL },
263419
{ "output.voltage.nominal", 0, 0, "UPS.Output.ConfigVoltage", NULL, "%.0f", 0, NULL },
264420

0 commit comments

Comments
 (0)