Repository navigation
44 drivers emit numbers they never read (null, never 0) #169
Description
Problem
44 of the 91 catalog drivers still fill a field with a number after their device stops answering. In total that is 592 fields. A read that failed or came back short becomes a reading of 0 W, 0 A, 0 % SoC or 50 Hz, and nothing downstream can tell it from a real one:
- A meter at 0 W looks like a site in balance.
- A battery at 0 % looks empty and ready to charge.
sigenergy 1.1.3 is the worked example, for the battery it reported on a plant with no storage.
The rule is null, never 0. A value whose read failed or came back short is nil, so the host sends null. A real 0 that was read is still 0.
How it is measured
drivers/tests/lua_harness/no_invented_numbers.lua (#168) runs a driver twice, with different register values, numeric config and clock. It then makes every read fail for three polls, and makes every Modbus reply half its length for three more. A number still emitted that is equal in both runs and was not read is invented. The data model's default SoC window (0.05 / 1.0) is the one constant allowed. Sourceful's driver registry runs the same file on every publish and refuses a driver that fails it.
drivers/tests/test_no_invented_numbers.py holds this debt in invented-number-baseline.json. New drivers must be clean, and a count can only go down.
What each driver needs
The usual cause is a default set before the read, or a fallback after it:
local l1_v = 0 -- before the read
if regs then l1_v = decode(regs[1]) * 0.1 end
local w = host.decode_i32_be(r[1], r[2]) or 0 -- after itThe fix:
- Start every measurement as nil, and set it only from a register or response that answered in full.
- Drop
or 0(andor 50,or 230) defaults on decoded values. - Emit a field only when it has a value. Emit a DER only when at least one of its fields does, as
sigenergy1.1.3 andambiboxdo. - Per driver:
make invented-number-report ID=<id>lists the fields. When it is clean, remove the driver frominvented-number-baseline.jsonand bump its version withmake bump-driver.
Notes
ctek,ctek_hybridandctek_v2emitev.phases = 3from config while the charger is down. This is a configured installation fact, not a measurement. Leave it out of the emit when nothing was read, or emit it only alongside real readings.hellois a demo driver that emits fixed values. Either remove it from the catalog or keep it in the baseline as an example.zaptec_cloudis not measured: itsdriver_initneeds credentials, which the probe does not supply.
Drivers and fields (main 224c438)
| Driver | Fields | Invented fields |
|---|---|---|
abb_meter |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
abb_terra |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
acrel |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
acuvim |
8 | meter.L1_A, meter.L1_W, meter.L2_A, meter.L2_W, meter.L3_A, meter.L3_W, meter.W, meter.total_export_Wh |
alfen |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
alphaess |
10 | battery.SoC_nom_fract, battery.V, battery.W, meter.Hz, meter.L1_A, meter.L1_V, meter.W, pv.W, pv.mppt1_a, pv.mppt1_v |
atmoce |
13 | battery.SoC_nom_fract, battery.W, meter.L1_A, meter.L1_V, meter.L2_A, meter.L2_V, meter.L3_A, meter.L3_V, meter.W, meter.total_export_Wh, meter.total_import_Wh, pv.W, pv.total_generation_Wh |
carlo_gavazzi |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
chint |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
circutor |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
ctek |
9 | ev.l1_a, ev.l1_v, ev.l2_a, ev.l2_v, ev.l3_a, ev.l3_v, ev.lifetime_wh, ev.phases, ev.w |
ctek_hybrid |
9 | ev.l1_a, ev.l1_v, ev.l2_a, ev.l2_v, ev.l3_a, ev.l3_v, ev.lifetime_wh, ev.phases, ev.w |
ctek_v2 |
9 | ev.l1_a, ev.l1_v, ev.l2_a, ev.l2_v, ev.l3_a, ev.l3_v, ev.lifetime_wh, ev.phases, ev.w |
deye |
28 | battery.a, battery.charge_wh, battery.discharge_wh, battery.soc, battery.temp_c, battery.v, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.rated_w, pv.temp_c, pv.w |
easee |
4 | v2x_charger.l1_a, v2x_charger.l2_a, v2x_charger.session_charge_wh, v2x_charger.w |
etrel |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
ferroamp_modbus |
18 | battery.charge_wh, battery.discharge_wh, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.w |
fronius |
11 | battery.soc, battery.v, battery.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.rated_w, pv.temp_c, pv.w |
goe |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
goodwe |
24 | battery.a, battery.soc, battery.temp_c, battery.v, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.w |
growatt |
20 | battery.soc, battery.temp_c, battery.v, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l2_a, meter.l2_v, meter.l3_a, meter.l3_v, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.w |
hello |
3 | meter.Hz, meter.L1_V, meter.W |
janitza |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
keba |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
kostal |
22 | battery.soc, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.temp_c, pv.w |
kstar |
9 | battery.SoC_nom_fract, battery.W, meter.Hz, meter.L1_A, meter.L1_V, meter.W, pv.W, pv.mppt1_a, pv.mppt1_v |
mennekes |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
schneider_meter |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
schrack_ev |
8 | v2x_charger.l1_a, v2x_charger.l1_v, v2x_charger.l2_a, v2x_charger.l2_v, v2x_charger.l3_a, v2x_charger.l3_v, v2x_charger.session_charge_wh, v2x_charger.w |
siemens_pac |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
sigenergy |
5 | meter.L1_W, meter.L2_W, meter.L3_W, meter.W, pv.W |
sma |
28 | battery.a, battery.charge_wh, battery.discharge_wh, battery.soc, battery.temp_c, battery.v, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.rated_w, pv.temp_c, pv.w |
sma_pv |
21 | meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.rated_w, pv.temp_c, pv.w |
socomec |
13 | meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh |
sofar |
21 | battery.a, battery.soc, battery.temp_c, battery.v, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l2_a, meter.l2_v, meter.l3_a, meter.l3_v, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.w |
solaredge |
20 | meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.temp_c, pv.w |
solax |
15 | battery.A, battery.SoC_nom_fract, battery.V, battery.W, battery.temperature_C, meter.Hz, meter.W, meter.total_export_Wh, meter.total_import_Wh, pv.W, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.total_generation_Wh |
solinteg |
27 | battery.A, battery.SoC_nom_fract, battery.V, battery.W, battery.temperature_C, battery.total_charge_Wh, battery.total_discharge_Wh, meter.Hz, meter.L1_A, meter.L1_V, meter.L1_W, meter.L2_A, meter.L2_V, meter.L2_W, meter.L3_A, meter.L3_V, meter.L3_W, meter.W, meter.total_export_Wh, meter.total_import_Wh, pv.W, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.temperature_C, pv.total_generation_Wh |
solis |
27 | battery.a, battery.charge_wh, battery.discharge_wh, battery.soc, battery.temp_c, battery.v, battery.w, meter.export_wh, meter.hz, meter.import_wh, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.temp_c, pv.w |
solis_string |
21 | pv.dc_w, pv.hz, pv.l1_a, pv.l1_v, pv.l2_a, pv.l2_v, pv.l3_a, pv.l3_v, pv.lifetime_wh, pv.mode, pv.mppt1_a, pv.mppt1_v, pv.mppt2_a, pv.mppt2_v, pv.mppt3_a, pv.mppt3_v, pv.mppt4_a, pv.mppt4_v, pv.status, pv.temp_c, pv.w |
sungrow |
20 | meter.hz, meter.l1_a, meter.l1_v, meter.l1_w, meter.l2_a, meter.l2_v, meter.l2_w, meter.l3_a, meter.l3_v, meter.l3_w, meter.w, pv.lifetime_wh, pv.mppt1_a, pv.mppt1_v, pv.mppt1_w, pv.mppt2_a, pv.mppt2_v, pv.mppt2_w, pv.temp_c, pv.w |
varta |
5 | battery.SoC_nom_fract, battery.W, meter.L1_A, meter.L1_V, meter.W |
victron |
10 | battery.a, battery.soc, battery.temp_c, battery.v, battery.w, meter.l1_w, meter.l2_w, meter.l3_w, meter.w, pv.w |
wallbox |
2 | v2x_charger.session_charge_wh, v2x_charger.w |
🤖 Generated with Claude Code
Activity
davmoz commentedon Oct 7, 2026
Related: #171 moves the catalog to data model v2 keys, and #170 moves
zap.luato the Zap's v2 local API. FTW's NovaCore side is srcfl/ftw#1527.