Skip to content

feat: add GatewayMQTT component for ESP32 MQTT integration#3557

Open
mgazza wants to merge 10 commits intomainfrom
feat/gateway-mqtt-component
Open

feat: add GatewayMQTT component for ESP32 MQTT integration#3557
mgazza wants to merge 10 commits intomainfrom
feat/gateway-mqtt-component

Conversation

@mgazza
Copy link
Collaborator

@mgazza mgazza commented Mar 13, 2026

Summary

  • New GatewayMQTT component (gateway.py) providing full inverter telemetry and control via ESP32 gateway MQTT/protobuf interface
  • Python protobuf bindings for GatewayStatus (telemetry) and ExecutionPlan (schedule) in proto/
  • Registered in COMPONENT_LIST with predbat_gateway_ event filter, phase 1
  • Async aiomqtt listener with TLS, exponential backoff reconnect
  • JWT token refresh via oauth-refresh edge function (same pattern as Fox OAuth)
  • EMS multi-inverter support with aggregate + per-sub-inverter entities
  • 12 tests covering protobuf decode, plan serialization, commands, EMS entities, token refresh

Test plan

  • pytest tests/test_gateway.py -v — 11 pass, 1 skipped (no aiomqtt locally)
  • Verify component initializes correctly with gateway config in apps.yaml
  • Verify MQTT connection to EMQX broker with JWT auth
  • Verify telemetry decode populates all entities
  • Verify plan publish sends valid protobuf to /schedule topic with retain

🤖 Generated with Claude Code

mgazza and others added 10 commits March 13, 2026 07:55
… plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… decode and commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GatewayMQTT now inherits from ComponentBase with full instance methods:
- initialize() stores config and builds MQTT topic strings
- run() starts background MQTT listener on first call, does housekeeping after
- _mqtt_loop() connects with TLS, subscribes to /status and /online, reconnects
- _process_telemetry() decodes protobuf and publishes entities via set_state_wrapper
- publish_plan/publish_command for outbound control
- is_alive() checks MQTT connected + telemetry freshness
- select_event/number_event for UI-driven mode/rate/SOC changes
- final() sends AUTO mode and cancels listener on shutdown
- Token refresh via Supabase edge function (same pattern as OAuthMixin)

All existing static methods and tests preserved unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ewal

Add extract_jwt_expiry and token_needs_refresh static methods. Wire into
_check_token_refresh to extract expiry from JWT claims directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…erter entities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix MQTT topic prefix: gw/ -> predbat/devices/ (critical)
- Add retain=True on schedule topic publish
- Set api_started=True on first telemetry decode
- Add get_error_count() with error tracking
- Fix edge function name: refresh-mqtt-token (matching spec)
- Remove unused _TOKEN_REFRESH_THRESHOLD constant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consistent with Fox OAuth pattern — single oauth-refresh edge function
handles all providers including gateway MQTT token refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
oauth-refresh edge function reads refresh token from instance secrets,
so the component doesn't need to hold or pass it. Consistent with
how Fox OAuth works via OAuthMixin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mgazza
Copy link
Collaborator Author

mgazza commented Mar 13, 2026

Code Review: GatewayMQTT Component

Verdict: Approve

Correctness

  • decode_telemetry() static method and ENTITY_MAP approach is clean and testable
  • Token refresh via oauth-refresh with provider: "predbat_gateway" is consistent with FoxESS OAuth pattern
  • _check_token_refresh correctly uses _refresh_in_progress guard to prevent concurrent refreshes
  • is_alive() handles the "gateway offline but broker connected" case well

Minor Issues (follow-up)

  1. instance_id extraction (gateway.py): self.args.get("user_id", "") — verify user_id is injected by ComponentBase framework since it's not in the explicit args defined in components.py

  2. Reconnect backoff has no cap: _RECONNECT_BASE_DELAY * (2 ** attempt) with 10 max attempts means max delay is ~85 min. Consider capping at 60s

  3. build_command selective fields: Only mode, power_w, target_soc kwargs are included — other kwargs silently dropped. Fine for v1, note for future commands

Test Coverage

12 tests covering protobuf round-trip, entity mapping, plan serialization, command format, EMS entities, and JWT token handling. Good coverage for v1.

🤖 Generated with Claude Code

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.

1 participant