Merged
Conversation
…hot models - Add PanelCapability flags and PanelGeneration enum to models.py - Add SpanPanelSnapshot / SpanCircuitSnapshot unified data models - Add SpanPanelClientProtocol + capability mixin Protocols (protocol.py) - Add SpanGrpcClient Gen3 transport (grpc/client.py, grpc/models.py, grpc/const.py) - Add create_span_client() factory with auto-detection (factory.py) - Add SpanPanelGrpcError, SpanPanelGrpcConnectionError to exceptions.py - Add get_snapshot() / connect() / ping() shims to SpanPanelClient for protocol conformance - Add grpcio as optional [grpc] dependency; omit grpc/* and factory.py from coverage - Add design document docs/Dev/grpc-transport-design.md - Bump version to 1.1.15
…s fields Add 17 optional Gen2-specific fields to SpanPanelSnapshot so that the HA integration can derive all domain objects (SpanPanelHardwareStatus, SpanPanelData, SpanPanelCircuit, SpanPanelStorageBattery) from a single snapshot. Gen3 clients leave these fields None; Gen2-only entities are capability-gated in the integration. Fields added to SpanPanelSnapshot: - feedthrough_power_w, feedthrough_energy_produced/consumed_wh - main_meter_energy_produced/consumed_wh - current_run_config - hardware_door_state, hardware_uptime, hardware_proximity_proven - hardware_is_ethernet/wifi/cellular_connected - hardware_update_status, hardware_env, hardware_manufacturer, hardware_model Updated SpanPanelClient.get_snapshot() to populate all new fields from the existing get_status(), get_panel_state(), get_circuits() calls.
The original _parse_instances() computed circuit_id as instance_id - METRIC_IID_OFFSET (hardcoded 27), reverse-engineered from one MAIN40 where trait 26 IIDs happened to be 28-52. On the MLO48, trait 26 IIDs are [2, 35, 36, ...] — the offset differs, so most computed circuit_ids were out of range and silently discarded, leaving the panel with no circuits discovered. Reported in PR #169. Two bugs fixed: 1. Offset-based circuit_id: replaced with positional pairing. Trait 16 and trait 26 IIDs are now collected independently, sorted, deduplicated, and paired by position (circuit_id = idx + 1). Works correctly regardless of actual IID values or panel model. 2. GetRevision instance_id: _get_circuit_name() was passing the positional circuit_id as the trait 16 instance ID. On the MAIN40 this accidentally worked (IIDs 1-25 match positions); on the MLO48 trait 16 IIDs are non-contiguous so names were fetched from wrong instances. CircuitInfo now stores name_iid (the actual trait 16 IID) and _fetch_circuit_names() uses it directly. Also adds _metric_iid_to_circuit reverse map built at connect time for O(1) streaming lookup, replacing the broken IID-offset arithmetic in _decode_and_store_metric(). Removes METRIC_IID_OFFSET from grpc/const.py — the constant embodied the incorrect assumption. Updates grpc-transport-design.md with root cause analysis and fix.
Covers editable install workflow for both local HA core and Docker container deployments, debug logging config, diagnostic symptom table, and iteration workflow for protobuf decoder fixes.
Replace positional pairing with Breaker Group (BG) based mapping. Each BG IID equals its corresponding trait 26 metric IID and contains an explicit reference to the trait 16 name IID, eliminating fragile positional assumptions. Changes: - Add _fetch_breaker_groups() using trait 15 as authoritative source - Add _query_breaker_group() to parse single/dual-phase BG instances - Add _extract_trait_ref_iid() helper for protobuf ref extraction - Add breaker_position field to CircuitInfo (physical slot 1-48) - Detect dual-phase circuits (field 11=single, field 13=dual) - Build _metric_iid_to_circuit reverse map for O(1) stream lookup - Filter orphan metric IIDs (e.g. 2, 401, 402) automatically - Fall back to positional pairing if no BG instances available Validated on MAIN 40 (25 circuits, 4 dual-phase) and MLO 48 (31 circuits, 10 dual-phase) — all correct. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nto grpc_addition
Gen3 gRPC does not expose serial/firmware via a dedicated trait yet. The panel_resource_id (captured during instance discovery) serves as a unique, stable panel identifier that can be used for entity unique_id generation in Home Assistant. Without this fix, serial_number is empty and HA sensors cannot be registered in the entity registry (no unique_id = no persistent entity). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a check before the deps-installed condition to detect when the virtual environment has been recreated (e.g. for a new Python version) but the .deps-installed marker still reflects the old install. Uses pre_commit importability as the sentinel since it is always a dev dependency. Also add exit code checking for `poetry run pre-commit install` so failures are surfaced rather than silently swallowed.
Home Assistant now requires Python >=3.14.2. Update mypy python_version from 3.13 to 3.14 so type checking reflects the actual runtime. Update black target-version from py312 to py313 (py314 not yet supported by black 25.1.0).
Extract _decode_main_feed_leg() from _decode_main_feed() and _parse_instance_item() from _parse_instances() to bring both methods below the complexity threshold flagged by CodeFactor. - _decode_main_feed: CC 20 -> 7 (D -> B) - _parse_instances: CC 23 -> 13 (D -> C) Also fix pre-existing type and attribute issues surfaced by mypy/pylint when the file was first staged: - _extract_trait_ref_iid: broaden parameter to ProtobufValue | None and fix the return type to always produce int - _parse_breaker_group: remove unnecessary "or b""" coercions now that _extract_trait_ref_iid handles None directly - Initialize _raw_bg_iids/_raw_name_iids/_raw_metric_iids in __init__ to satisfy pylint attribute-defined-outside-init
Split the monolithic README into a concise top-level overview with dedicated detail pages: - README.md: high-level introduction, quick start via create_span_client, Gen2 vs Gen3 capability table, documentation table of contents - docs/gen2-client.md: connection patterns, auth, full API reference, timeout/retry/caching, Home Assistant integration, simulation mode - docs/gen3-client.md: gRPC usage, streaming callbacks, snapshot model, low-level PanelData access, error handling - docs/error-handling.md: exception hierarchy, HTTP to exception mapping, retry configuration, Gen3 gRPC errors - docs/development.md: setup, test/lint commands, project structure, OpenAPI client regeneration, Gen3 internals, contributing guide
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.
Summary
Adds complete Gen3 gRPC transport support to the span-panel-api library, enabling communication with Gen3 Span panels (MAIN 40 / MLO 48) that use gRPC on port 50065 instead of REST.
What's included
grpc/subpackage —SpanGrpcClientwith manual protobuf encoding (no.protocompilation needed), Subscribe RPC push-streaming, and automatic reconnection with 5s backoffSpanPanelClientProtocol+ capability mixins (AuthCapableProtocol,CircuitControlProtocol,StreamingCapableProtocol) for static type-safe dispatch across transportsPanelCapabilityflags — Runtime feature advertisement. Gen2:GEN2_FULL; Gen3:GEN3_INITIAL(read-only sensors + push streaming)SpanPanelSnapshot/SpanCircuitSnapshotreturned byget_snapshot()on both transportscreate_span_client()factory — Creates appropriate client by generation or auto-detects (probe Gen2 HTTP, fallback to Gen3 gRPC)grpciooptional dependency — Install withspan-panel-api[grpc]Testing
Related
gen3-grpc-integrationbranch)Changelog
See
CHANGELOG.mdentry for v1.1.15 (already included in this branch).Test plan
create_span_client()auto-detection with Gen2 panelpoetry installwith[grpc]extra pulls grpcio🤖 Generated with Claude Code