Zum Inhalt

Design

Ziel

Eine Integration verbindet PV-Wechselrichter, Batterien und Wärmepumpen verschiedener Hersteller mit Home Assistant, ohne Cloud-Abhängigkeit. Herstellerunterschiede liegen in JSON-Profilen; der Kern kennt nur Modbus, Dekodierung, Entities, Lizenzierung, Support und Diagnose.

Komponenten

ConfigEntry
  ├─ Profil (JSON)
  ├─ Transport ── AsyncModbusTcpClient ── TCP :502
  │            └─ DemoTransport (simuliert)
  ├─ Coordinator (Poll-Intervall, UpdateFailed)
  ├─ LicenseManager (instanzweit)
  ├─ SupportAccessController (pro Eintrag)
  └─ Entities
       ├─ RegisterSensor pro sichtbarem Register
       ├─ LicenseStatusSensor
       ├─ SupportRemainingSensor
       ├─ ConnectionBinarySensor
       └─ SupportAccessSwitch

Hardware Abstraction Layer

profiles.py parst und prüft Profile, gruppiert Register zu zusammenhängenden Leseblöcken und dekodiert jeden Wert mit modbus_decode.py. modbus_client.py ist eine kleine asyncio-Implementierung von Modbus TCP:

  • Function 3 (Holding lesen), 4 (Input lesen), 6 (einzeln schreiben), 16 (mehrere schreiben),
  • ein Lock pro Client, weil Gateways meist nur einen Socket zulassen,
  • Prüfung von Transaction-ID, Protocol-ID und Unit-ID,
  • Timeout schließt den Socket und löst einen Reconnect aus,
  • Zähler für Requests, Fehler, Exceptions und Latenz.

scale ist ein Divisor, passend zum technischen Design-Dokument ("scale": 10 mit Rohwert 5000 ergibt 500.0).

Profile sind Daten

Eingebaute Profile liegen in custom_components/sg_energy_hub/profiles/. Betreiber können Dateien in <HA-Konfiguration>/sg_energy_hub/profiles/ ablegen. Doppelte IDs und überlappende Registerbereiche werden abgelehnt. Ein neuer Hersteller braucht normalerweise keine Python-Änderung.

Profile tragen ihren Prüfstatus. Das Deye-Beispiel aus dem Design-Dokument wird mit "verified": false ausgeliefert; Herstellerprofile werden erst nach einem Hardware-Test als verifiziert markiert.

Lizenzierung

license.py prüft Ed25519-Signaturen über ein kanonisches JSON-Payload. Der Public Key steht in const.py; der private Schlüssel bleibt beim Hersteller.

Bindung: Das Payload enthält die Home-Assistant-Instanz-UUID (homeassistant.helpers.instance_id). Ein kopiertes Konfigurationsverzeichnis verschiebt die Lizenz daher nicht.

Zustände:

keine Lizenz, Trial < 30 Tage   -> trial,   Basis-Features
signierte Lizenz, nicht abgelaufen -> active, Pro-Features
abgelaufen < 14 Tage            -> grace,   Pro-Features
abgelaufen >= 14 Tage           -> expired, Basis-Features
keine Lizenz, Trial vorbei      -> free,    Basis-Features

gating.py filtert Register (basic versus Pro) und schützt den Schreibzugriff. Pro-Entities erscheinen nach dem Neuladen des Eintrags bei der Lizenzaktivierung.

Support-Zugriff

Der Controller besitzt die Session-Lebensdauer und den Timeout. Ein Provider besitzt den Tunnel. Version 0.1.0 liefert einen manual-Provider, der keine Netzwerkverbindung öffnet; Schalter und Dienste sind trotzdem vollständig testbar. Tokens werden nur bei konfigurierter URL vom Lizenzserver angefordert, an den Provider übergeben und nie geloggt oder gespeichert.

Diagnose

diagnostics.py baut ein JSON-Payload mit:

  • Integrations-, Home-Assistant-, Python- und Plattformversion,
  • Modus, Profilmetadaten, Poll-Intervall, letzter Exception und Modbus-Statistik,
  • Lizenzstatus, Instanz-UUID und letzter Prüfung,
  • aktiver Support-Session,
  • Register-Dump mit Adressen, Typen, Einheiten, Skalierungen und letzten Werten,
  • den letzten 200 Log-Zeilen mit sg_energy_hub.

Schlüssel mit password, token, secret, api_key oder private werden redigiert. Upload und AES-GCM-Verpackung sind nicht Teil von 0.1.0.

Fehlermodell

  • Ein fehlender Wert ist None → unknown; die Integration erfindet nie 0.
  • Ein fehlgeschlagener Poll wirft UpdateFailed → alle Entities unavailable; keine veralteten Daten.
  • Ein kaputtes Profil wirft ConfigEntryError; ein nicht erreichbares Gerät ConfigEntryNotReady, damit Home Assistant erneut versucht.
  • Wire-Fehler sind ModbusConnectionError/ModbusTimeoutError; Gerätefehler sind ModbusExceptionError mit Exception-Code.

Bewusste Grenzen

Bereich 0.1.0
mDNS/Zeroconf-Discovery nicht implementiert
Register-Probing nicht implementiert
Profil-Sync vom Lizenzserver nicht implementiert, last_check wird gespeichert
Verschlüsselter Diagnose-Upload nicht implementiert
WireGuard/Tailscale nur Schnittstelle
Herstellerprofile Demo und ein unverifiziertes Deye-Beispiel

Diese Punkte sind Schnittstellen, keine versteckten Features. Die nächsten Schritte lassen sich ohne Änderung am Config Entry oder Profilformat ergänzen.