Skip to content

44 drivers emit numbers they never read (null, never 0) #169

Description

@davmoz

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 it

The fix:

  1. Start every measurement as nil, and set it only from a register or response that answered in full.
  2. Drop or 0 (and or 50, or 230) defaults on decoded values.
  3. Emit a field only when it has a value. Emit a DER only when at least one of its fields does, as sigenergy 1.1.3 and ambibox do.
  4. Per driver: make invented-number-report ID=<id> lists the fields. When it is clean, remove the driver from invented-number-baseline.json and bump its version with make bump-driver.

Notes

  • ctek, ctek_hybrid and ctek_v2 emit ev.phases = 3 from 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.
  • hello is a demo driver that emits fixed values. Either remove it from the catalog or keep it in the baseline as an example.
  • zaptec_cloud is not measured: its driver_init needs 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions