HAOS Add-on: MVP + NuttX Binary + .gitignore

- haos-addon/: vollständiges HA Add-on (config.yaml, Dockerfile, build.yaml)
  - Python Backend: pymodbus Modbus TCP → paho-mqtt MQTT Discovery
  - Unterstützte Modelle: MIC 1500/2000 TL-X, SPH 5000 TL3, MOD 6000 TL3
  - Web UI: Wechselrichter-Auswahl, Modbus/MQTT-Konfig, Live-Sensor-Grid (dark theme)
  - MQTT HA Discovery für alle Sensoren mit device_class, state_class, icon
- ShineLAN-X/releases/nuttx-mbusd-shinelanx.bin: NuttX Firmware (ohne DFU, 0x08000000)
- .gitignore: Logs, MQTT-JSON, shinelanx-modbus/ ausgeschlossen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-04-24 22:30:45 +02:00
parent 4d2da56baf
commit 0d6b860664
12 changed files with 1327 additions and 47 deletions
+94
View File
@@ -0,0 +1,94 @@
import json
import logging
import time
from typing import Dict, Optional
import paho.mqtt.client as mqtt
from inverters import Inverter
log = logging.getLogger(__name__)
DEVICE_ID = "growatt_shinelanx"
class MqttPublisher:
def __init__(self, broker: str, port: int, user: str, password: str, topic_prefix: str):
self.topic_prefix = topic_prefix.rstrip("/")
self._client = mqtt.Client(client_id=DEVICE_ID, clean_session=True)
if user:
self._client.username_pw_set(user, password)
self._client.on_connect = self._on_connect
self._client.on_disconnect = self._on_disconnect
self._broker = broker
self._port = port
self._connected = False
self._inverter: Optional[Inverter] = None
def _on_connect(self, client, userdata, flags, rc):
if rc == 0:
self._connected = True
log.info("MQTT verbunden: %s:%d", self._broker, self._port)
if self._inverter:
self._publish_discovery(self._inverter)
else:
log.error("MQTT Verbindungsfehler rc=%d", rc)
def _on_disconnect(self, client, userdata, rc):
self._connected = False
log.warning("MQTT getrennt rc=%d", rc)
def connect(self):
try:
self._client.connect_async(self._broker, self._port, keepalive=60)
self._client.loop_start()
except Exception as e:
log.error("MQTT connect fehlgeschlagen: %s", e)
def disconnect(self):
self._client.loop_stop()
self._client.disconnect()
@property
def connected(self) -> bool:
return self._connected
def setup_inverter(self, inverter: Inverter):
self._inverter = inverter
if self._connected:
self._publish_discovery(inverter)
def _publish_discovery(self, inverter: Inverter):
device_payload = {
"identifiers": [DEVICE_ID],
"name": "Growatt ShineLAN-X",
"manufacturer": inverter.manufacturer,
"model": inverter.name,
}
for sensor in inverter.sensors:
config = {
"name": sensor.name,
"unique_id": f"{DEVICE_ID}_{sensor.id}",
"state_topic": f"{self.topic_prefix}/state",
"value_template": f"{{{{ value_json.{sensor.id} }}}}",
"unit_of_measurement": sensor.unit,
"state_class": sensor.state_class,
"icon": sensor.icon,
"device": device_payload,
}
if sensor.device_class:
config["device_class"] = sensor.device_class
topic = f"homeassistant/sensor/{DEVICE_ID}/{sensor.id}/config"
self._client.publish(topic, json.dumps(config), retain=True, qos=1)
log.info("MQTT Discovery für %d Sensoren veröffentlicht", len(inverter.sensors))
def publish_data(self, values: Dict[str, float]):
if not self._connected:
log.warning("MQTT nicht verbunden, Daten verworfen")
return
payload = json.dumps(values)
self._client.publish(f"{self.topic_prefix}/state", payload, retain=True, qos=0)
def publish_status(self, status: str):
self._client.publish(f"{self.topic_prefix}/status", status, retain=True, qos=1)