Fix three flat-emitter wire defects: power-flows frame, lugs energy, node descriptions - #41
Merged
Merged
Conversation
The emitter published pv, grid and battery in the meter frame -- positive = consumption, the frame each individual meter reports in. The four power-flows properties are not four meters; they are the four terms of one balance at the panel node, and a balance only closes when every term shares a frame. Published in the meter frame the sum missed zero by twice the site load, and a producing array read positive where a panel reads negative. Restated in the node frame -- positive = power leaving the panel node -- the signs now match what SPAN documents for a shipping panel: grid positive while exporting, pv negative while producing, battery positive while charging, site positive while consuming. See SPAN-API-Client-Docs, docs/public/power-and-energy-conventions.md, "The power-flows capability is an exception", which also records that these signs are longstanding and did not change in the parent/child migration. Also drops the PV-surplus clamp from the grid computation. It existed to stop a charging battery adding grid import, but that import is real: charging beyond what PV covers is drawn from the utility, and suppressing it broke the balance. test_power_flows_sum_to_zero holds the identity across nine operating states. grid is derived from the lugs and BESS rather than back-solved from the other three, so the assertion has teeth -- a residual would satisfy it by construction and detect nothing.
…nd it The lugs imported- and exported-energy accumulators were built by summing the gross consumption and gross production of the circuits behind the lugs. A meter reads one net quantity, so only one of its two accumulators can advance in a given tick. Summing gross totals advanced both at once: with 2 kW of production against 6 kW of load the lugs carry ~4 kW in a single direction, but the gross sum reported 6000 Wh consumed and 2000 Wh produced. Register each lugs with the energy integrator and observe its own active power, so imported- and exported-energy derive from the same signed reading the lugs publishes as active-power. test_lugs_energy_integrates_its_own_meter_not_the_circuits_behind_it asserts that at most one accumulator advances per tick and that the advancing one matches the net.
The root $description published {"type": ...} per node and nothing else, so the
tree a consumer discovers the Homie way was empty: 39 nodes declaring zero
properties, where a panel on the flat data model declares 438. A consumer that
discovers properties from the description rather than by watching retained
topics arrive would find nothing to subscribe to.
It stayed invisible because our own consumer reads values off the wire and
takes only type from the description, so no test and no integration ever asked
the description what it contained.
Build each node's entry from Node.description(), which the SDK already models
correctly, and carry the version, name, children and extensions keys a panel
publishes. Drop the id key, which a panel does not publish at this level.
test_description_declares_the_properties_each_node_publishes compares the
declared count against the built graph's property count rather than restating
the naming rules, so it cannot drift from them.
Three of the defects found in this sweep are the same defect wearing different clothes: a device-frame value published where the panel's mirror of it belongs. None could be caught mechanically -- reference direction has no machine-readable form in the catalogs, so every conformance check stayed green while three of four power-flows properties were inverted. Writing the rule down is currently the only thing between it and the next contributor. Records the frame table, that the panel's reading is the mirror of the device's because the panel is the interface between them, why site has no mirror and was therefore already correct, that the negation belongs in the wire layer rather than in a snapshot field every other reader shares, and the node-balance invariant. Also records the stakes, which are easy to understate: these values feed Home Assistant long-term statistics, so a wrong sign is persisted and aggregated rather than merely displayed, and fixing the publisher afterwards does not repair what the recorder already stored.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three defects found while checking the flat emitter's retained tree against what a shipping panel publishes. Independent of each other; one commit each.
1.
power-flowswas published in the meter frameThe emitter published
pv,gridandbatterypositive-for-consumption — the frame each individual meter reports in. But the fourpower-flowsproperties are not four meters. They are the four terms of one balance at the panel node, and a balance only closes when every term shares a frame.In the meter frame the sum missed zero by exactly twice the site load, and a producing array read positive where a panel reads negative — which is what surfaced this: a simulated PV system showed the opposite sign from the hardware it stands in for.
Restated in the node frame — positive = power leaving the panel node — the signs match what SPAN documents:
gridpvbatterysiteSource:
SPAN-API-Client-Docs,docs/public/power-and-energy-conventions.md, "Thepower-flowscapability is an exception" — which also records that these signs are longstanding and did not change in the parent/child migration.This also drops the PV-surplus clamp from the grid computation. It existed to stop a charging battery adding grid import, but that import is real: charging beyond what PV covers is drawn from the utility, and suppressing it broke the balance.
test_power_flows_sum_to_zeroholds the identity across nine operating states.gridis derived from the lugs and BESS rather than back-solved from the other three, so the assertion has teeth — a residual would satisfy it by construction and detect nothing.2. Lugs energy was summed from the circuits, not read from the lugs
imported-energyandexported-energyon each lugs were built by summing the gross consumption and gross production of the circuits behind it. A meter reads one net quantity, so only one of its two accumulators can advance in a tick. The gross sum advanced both at once: with 2 kW of production against 6 kW of load the lugs carry ~4 kW in a single direction, but the accumulators reported 6000 Wh consumed and 2000 Wh produced.Each lugs is now registered with the energy integrator and observes its own active power, so both accumulators derive from the same signed reading the lugs publishes as
active-power.3. Node
$descriptionentries declared no propertiesThe root
$descriptionpublished{"type": ...}per node and nothing else, so the tree a consumer discovers the Homie way was empty: 39 nodes declaring zero properties, where a panel on the flat data model declares 438. A consumer that discovers properties from the description rather than by watching retained topics arrive would find nothing to subscribe to.It stayed invisible because our own consumer reads values off the wire and takes only
typefrom the description, so nothing ever asked the description what it contained.Each node's entry is now built from
Node.description(), which the SDK already models correctly, and the payload carries theversion,name,childrenandextensionskeys a panel publishes. Theidkey is dropped — a panel does not publish it at this level.Verification
413 tests pass; ruff clean; mypy introduces no new errors (the 55 pre-existing errors on
mainare unchanged and none are in the touched files).Every new assertion was falsified before being trusted — the fix reverted, the test watched to fail, then restored:
power_flow_pvpower_flow_batterypower_flow_grid{'type'}vs{'name','properties','type'}