feat: dispatch on the panel's real data-model-version - #155
Merged
Conversation
The guard that refuses a parent/child panel was written, tested, and never invoked: create_span_client hardcoded data_model_version = None, so every panel resolved to the flat parser no matter what it reported. A v1.0 panel did not fail cleanly either — the flat parser reached for energy.ebus.device.circuit/space, which parent/child firmware keeps under deviceClasses, and the run died on "Schema missing 'energy.ebus.device.circuit/space' property": a complaint about a missing property, for a panel whose actual problem is that nothing installed can parse it. The Homie schema is now fetched over REST before the broker is opened, and its dataModelVersion selects the adapter. SPAN confirmed the absence of that field on this endpoint is a reliable flat-versus-parent/child signal, mirroring MQTT's info/data-model-version, and that dispatching on it before opening MQTT is supported. A 1.0 panel now raises SpanPanelAdapterMissingError naming the adapter to install. Dispatch also moved to wherever a parser is built, not just the factory path. A directly constructed SpanMqttClient — which the README documents and the integration uses — previously always resolved the flat adapter, carrying the same defect the factory had. The protocol changes shape once, here, because this is the release that breaks it: - __init__ takes the schema rather than panel_size. Deriving panel_size means reading a block only the flat schema has, so the bootstrap had to understand a wire format it is meant to know nothing about, and an adapter shaped differently had no way to say so. - build_field_metadata() takes no arguments; the adapter holds its schema. Tier 1 dispatch moved to span_panel_api.dispatch so the transport can reach it without importing the factory. adapters.py still answers "what is installed"; dispatch.py answers "what does this panel need". Also pins the enum-tolerance rule that schema_1 inherits: v1.0 requires consumers not to raise on an unrecognised value in a $format-extended enum, which is the opposite of the version rule one import away. The difference is blast radius — an unknown enum member affects one property, an unknown schema version means the whole tree may be misread. 438 tests pass, coverage 94%.
The check builds a parser against a real bootstrap-only wheel, so it still passed a panel size and died on AttributeError before reaching the error it exists to assert. The unit suite could not catch this: it never runs against an install that has no adapter. It now passes a flat schema, which is also the case this check is about — every panel in the field reports no data-model-version, so dispatch asks for the default key and finds nothing providing it.
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.
Phase 2, Part A — the half that does not depend on the §5 integration fork.
The bug
create_span_clienthardcodeddata_model_version = None, so the guard thatrefuses a parent/child panel was written, tested, and never invoked. Every
panel resolved to the flat parser regardless of what it reported.
It did not even fail cleanly. Reverting the dispatch in a test shows what a
1.0panel actually got:The flat parser reaches for a type that parent/child firmware keeps under
deviceClasses— so the user sees a complaint about a missing property, for apanel whose real problem is that nothing installed can parse it.
The fix
The Homie schema is fetched over REST before the broker is opened, and its
dataModelVersionselects the adapter. SPAN confirmed this is supported:A
1.0panel now raisesSpanPanelAdapterMissingErrornaming the adapter toinstall and what is already present.
Dispatch also moved to wherever a parser is built, not just the factory
path. A directly constructed
SpanMqttClient— which the README documents andthe integration uses — previously always resolved the flat adapter, carrying
the identical defect. There is now one answer rather than two.
Protocol reshape — breaking, and deliberately all at once
This is the release that breaks the protocol, so it changes shape once:
__init__(serial_number, panel_size)__init__(serial_number, schema)panel_sizeis read from a block only the flat schema has. The bootstrap had to understand a wire format it is meant to know nothing about, and a differently-shaped adapter had no way to say so.build_field_metadata(schema_types)build_field_metadata()Tier 1 dispatch moved from the private
factory._select_adapter_keytospan_panel_api.dispatch.select_adapter_key, so the transport can dispatchwithout importing the factory.
adapters.pystill answers what is installed;dispatch.pyanswers what does this panel need.Also pinned: the enum rule that runs the other way
v1.0 requires consumers not to raise on an unrecognised value in a
$format-extended enum — the opposite of thedata-model-versionrule livingone import away. The difference is blast radius: an unknown enum member affects
one property, an unknown schema version means the whole tree may be misread.
Both are now tested, because
schema_1inherits the enum one and the two areeasy to confuse.
Verification
select_adapter_key(None)failstest_a_parent_child_panel_is_refused_rather_than_parsed_as_flatwith theexact
ValueErrorquoted above.dataModelVersionparsing (absent / present / unquoted), thefactory refusing a v1.0 panel, direct construction dispatching, diagnostics
being filled in by dispatch, and enum pass-through.
Not in this PR
schema_1itself — gated on the §5 fork, which the design doc's"Re-checked 2026-08-04" section now scopes.