Skip to content

Comments

Gen3 gRPC integration (refactored from #169)#171

Open
Griswoldlabs wants to merge 13 commits intomainfrom
gen3-grpc-integration
Open

Gen3 gRPC integration (refactored from #169)#171
Griswoldlabs wants to merge 13 commits intomainfrom
gen3-grpc-integration

Conversation

@Griswoldlabs
Copy link

Gen3 (MAIN 40 / MLO 48) gRPC Support — Refactored

Supersedes #169. This is the refactored integration that uses span-panel-api v1.1.15 for all gRPC transport — no transport code remains in the integration itself.

What's included

  • Auto-detect Gen2 vs Gen3 via config flow dropdown (auto/gen2/gen3)
  • Gen3 panels communicate via gRPC on port 50065 (no auth required)
  • Push-buffered streaming — gRPC stream keeps client buffer fresh every ~1s; coordinator polls at scan_interval to avoid overwhelming HA's recorder
  • Per-circuit: power, voltage, current, apparent power, reactive power, frequency, power factor sensors
  • Main feed: power, voltage, current, frequency sensors
  • Breaker state monitoring via binary sensors
  • Gen2 code completely untouched — zero risk to existing users
  • Capability-gated platform loading — Gen3 only loads BINARY_SENSOR + SENSOR (no switches, no selects)
  • v1 → v2 config migration stamps panel_generation=gen2 on existing entries

Architecture (Phase 1 + 2a + 2b)

Phase Change
1 CONF_PANEL_GENERATION constant, capabilities property, capability-gated platform loading, config flow Gen3 step, capability-gated sensor factory
2a SpanPanel.update() migrated to single get_snapshot() call; from_snapshot() factories on all domain objects
2b SpanPanelApi._create_client() Gen3 branch, register_push_callback(), coordinator push-streaming extensions, Gen3-only sensor definitions, simulation normalizes to Gen2

Key files changed

File Change
__init__.py Capability-gated platform loading; migration stamps panel_generation
config_flow.py Generation dropdown + async_step_gen3_setup
coordinator.py Push-buffered polling (both Gen2 and Gen3 use timer)
span_panel_api.py _create_client() Gen3/Gen2 branching; register_push_callback()
span_panel.py update() via get_snapshot() for both generations
sensors/factory.py Capability-gated sensor groups; Gen3-only sensors
sensor_definitions.py CIRCUIT_GEN3_SENSORS, PANEL_GEN3_SENSORS
manifest.json span-panel-api[grpc]~=1.1.15, version 1.3.2

Dependencies

Testing

  • Tested on MAIN 40 panel (pre-firmware 7.2.0)
  • Circuit IID mapping validated on both MAIN 40 and MLO 48 (@cecilkootz)
  • Dual-phase detection (Furnace, Dryer, Water Heater, Range) validated
  • Multi-panel support fix (serial number from gRPC, unique_id collision fix) (@haggerty23)
  • Push throttle fix: 1/s → scan_interval polling to prevent recorder overload (@cecilkootz)

⚠️ Firmware 7.2.0 caveat

SPAN firmware 7.2.0 (rolling out Feb 2026) disables local gRPC access:

Credits


Full design docs:

cayossarian and others added 13 commits February 17, 2026 12:25
… loading

- const.py: Add CONF_PANEL_GENERATION constant
- span_panel_api.py: Add capabilities property delegating to client (falls back to GEN2_FULL)
- __init__.py: Replace static PLATFORMS with capability-gated platform loading;
  store active_platforms per entry so unload is exact
- config_flow.py: Add panel generation dropdown (auto/gen2/gen3);
  add async_step_gen3_setup for gRPC probe without JWT auth
- sensors/factory.py: Capability-gate DSM, energy, hardware status, battery, and solar
  sensor groups; power sensors always created
- manifest.json: Bump version to 1.3.2; require span-panel-api[grpc]~=1.1.15
- pyproject.toml: Update span-panel-api dev dep to 1.1.15 with grpc extra
- .github/workflows/ci.yml: Update sed command for new dep format and version
- scripts/sync-dependencies.py: Handle package extras in version regex
- docs/dev/gen3-grpc-integration-plan.md: Add Phase 1 complete / Phase 2 deferred plan
Replace the four-call get_all_data() fetch with a single get_snapshot()
call that returns a unified SpanPanelSnapshot. Each domain object now
has a from_snapshot() factory that maps snapshot fields to its internal
structure — enabling Gen3 data to flow through the same entity classes
as Gen2 for the metrics they share.

Changes:
- span_panel.py: update() calls api.get_snapshot() and passes the
  snapshot to the four from_snapshot() factories atomically
- span_panel_api.py: add get_snapshot() delegating to client.get_snapshot()
  with the same error handling pattern as other API methods
- span_panel_circuit.py: add from_snapshot(SpanCircuitSnapshot)
- span_panel_data.py: add from_snapshot(SpanPanelSnapshot)
- span_panel_hardware_status.py: add from_snapshot(SpanPanelSnapshot)
- span_panel_storage_battery.py: add from_snapshot(SpanPanelSnapshot)
- tests/conftest.py: register real SpanPanelSnapshot/SpanCircuitSnapshot
  on the mock so test fixtures can construct real snapshot instances
- tests/test_span_panel.py: rewrite TestSpanPanelUpdate to use
  get_snapshot() mock returning a real SpanPanelSnapshot fixture
coordinator.py:
- Detect PUSH_STREAMING capability at init; pass update_interval=None to
  disable polling timer for Gen3
- _register_push_callback(), _on_push_data(), _async_push_update() drive
  entity updates from gRPC stream callbacks
- _push_update_pending guard prevents stacking concurrent async tasks
- async_shutdown() unregisters push callback before super()

span_panel_api.py:
- _create_client() Gen3 branch instantiates SpanGrpcClient
- register_push_callback() exposes callback registration without callers
  accessing _client directly
- Simulation always uses Gen2 transport: _panel_generation normalised to
  "gen2" when simulation_mode=True, regardless of panel_generation setting

__init__.py:
- async_unload_entry: fixed cleanup to use coordinator.span_panel (not
  the nonexistent coordinator.span_panel_api)
- v1->v2 migration stamps panel_generation="gen2" on existing entries
  (all v1 entries pre-date Gen3 support)

sensor_definitions.py:
- CIRCUIT_GEN3_SENSORS: voltage, current, apparent/reactive power,
  frequency, power factor per circuit (gated on PUSH_STREAMING)
- PANEL_GEN3_SENSORS: main feed voltage, current, frequency

sensors/factory.py + sensors/circuit.py:
- Gen3-only sensor creation from SpanCircuitSnapshot non-None fields

span_panel.py, span_panel_circuit.py, span_panel_data.py:
- snapshot-based update() and from_snapshot() factories

docs/dev/gen3-grpc-integration-plan.md:
- Mark Phase 2b complete; document circuit IID mapping bug fix in library
Covers editable install workflow, local HA core and Docker container
options, debug logging config, and diagnostic symptom table.
…er overload

Gen3 gRPC stream pushes data every ~1s. The previous callback-driven approach
forwarded each notification to HA as a state write, causing CPU/disk spikes
(reported by cecilkootz on MLO 48).

Now both Gen2 and Gen3 use the same polling timer (default 15s). The gRPC
stream still runs in the background keeping the client buffer fresh; the
coordinator simply reads the latest snapshot at each scan interval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace warning-level debug log with info-level log that shows the
number of circuits, entities created, and the resolved serial number.
Helps diagnose sensor registration issues during setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump python constraint to >=3.14.2,<3.15 to match HA core requirement
- Pin homeassistant to 2026.2.2 (latest stable with pytest-homeassistant-custom-component support)
- Add mashumaro >=3.17.0 floor to prevent poetry under-resolving below HA package_constraints.txt minimum
- Drop homeassistant-stubs path-dep approach; restore homeassistant-stubs = 2026.2.2
- Update pyright pythonVersion to 3.14
- Update CI workflows (ci.yml, ci-simulation-example.yml) to python-version: "3.14"
- Regenerate poetry.lock
- Update .python-version from 3.13.2 to 3.14.2 (already on main)
- Tighten pytest-homeassistant-custom-component to ^0.13.315 (matches main)
- Regenerate poetry.lock
- Update integration description to note Gen2 (REST/OpenAPI) and Gen3 (gRPC) support
- Annotate feature list with Gen2/Gen3 availability
- Update installation steps and authorization section for Gen3 (no auth required)
- Replace outdated Limitations section with Panel Generation Support section
  covering Gen3 read-only constraints, missing features, and Gen3-exclusive metrics
- Add Gen2 vs Gen3 feature comparison table
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants