Feature: Aggregat-Gerät + Energie-Dashboard Sensoren (v1.2.0)
- main.py: AGG_SENSOR_IDS/AGGREGATE_META — Mapping sensor_id → Aggregat-Bucket _compute_aggregates() summiert alle online Geräte nach jedem Poll /api/data liefert jetzt auch "aggregates" Schlüssel - mqtt_publisher.py: publish_aggregates() + _publish_aggregate_discovery() Eigenes HA-Gerät "ShineBridge Gesamt" (device_id: shinebridge_aggregate) MQTT Topic: shinebridge/aggregate/state - index.html: renderAggregates() — "Gesamt"-Sektion oben im Live-Tab Aggregierte Sensoren (alle kompatibel mit HA Energie-Dashboard): PV: total_pv_power, total_ac_power, total_energy_today, total_energy_total Netz (SDM-630): grid_power, grid_import_kwh, grid_export_kwh Batterie (SPH): bat_charge/discharge_power/total, bat_soc (Ø) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
@@ -9,13 +8,18 @@ from inverters import Inverter
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
AGG_DEVICE_ID = "shinebridge_aggregate"
|
||||
AGG_TOPIC = "shinebridge/aggregate"
|
||||
|
||||
|
||||
class MqttPublisher:
|
||||
def __init__(self, broker: str, port: int, user: str, password: str):
|
||||
def __init__(self, broker: str, port: int, user: str, password: str,
|
||||
agg_meta: Optional[Dict] = None):
|
||||
self._broker = broker
|
||||
self._port = port
|
||||
self._connected = False
|
||||
self._registered: List[Tuple[Inverter, str, str]] = [] # (inverter, device_id, topic_prefix)
|
||||
self._registered: List[Tuple] = []
|
||||
self._agg_meta: Dict = agg_meta or {}
|
||||
|
||||
self._client = mqtt.Client(client_id="shinebridge_hub", clean_session=True)
|
||||
if user:
|
||||
@@ -29,6 +33,8 @@ class MqttPublisher:
|
||||
log.info("MQTT verbunden: %s:%d", self._broker, self._port)
|
||||
for entry in self._registered:
|
||||
self._publish_discovery(*entry)
|
||||
if self._agg_meta:
|
||||
self._publish_aggregate_discovery()
|
||||
else:
|
||||
log.error("MQTT Verbindungsfehler rc=%d", rc)
|
||||
|
||||
@@ -51,7 +57,10 @@ class MqttPublisher:
|
||||
def connected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
def register_inverter(self, inverter: Inverter, device_id: str, topic_prefix: str, display_name: str = None):
|
||||
# ── Gerät-Discovery ──────────────────────────────────────
|
||||
|
||||
def register_inverter(self, inverter: Inverter, device_id: str,
|
||||
topic_prefix: str, display_name: str = None):
|
||||
entry = (inverter, device_id, topic_prefix, display_name)
|
||||
self._registered = [r for r in self._registered if r[1] != device_id]
|
||||
self._registered.append(entry)
|
||||
@@ -61,7 +70,8 @@ class MqttPublisher:
|
||||
def unregister_inverter(self, device_id: str):
|
||||
self._registered = [r for r in self._registered if r[1] != device_id]
|
||||
|
||||
def _publish_discovery(self, inverter: Inverter, device_id: str, topic_prefix: str, display_name: str = None):
|
||||
def _publish_discovery(self, inverter: Inverter, device_id: str,
|
||||
topic_prefix: str, display_name: str = None):
|
||||
device_payload = {
|
||||
"identifiers": [device_id],
|
||||
"name": display_name or inverter.name,
|
||||
@@ -85,10 +95,46 @@ class MqttPublisher:
|
||||
self._client.publish(topic, json.dumps(config), retain=True, qos=1)
|
||||
log.info("MQTT Discovery: %d Sensoren für %s", len(inverter.sensors), device_id)
|
||||
|
||||
# ── Aggregat-Discovery ────────────────────────────────────
|
||||
|
||||
def _publish_aggregate_discovery(self):
|
||||
device_payload = {
|
||||
"identifiers": [AGG_DEVICE_ID],
|
||||
"name": "ShineBridge Gesamt",
|
||||
"manufacturer": "ShineBridge",
|
||||
"model": "Aggregat",
|
||||
}
|
||||
for sensor_id, meta in self._agg_meta.items():
|
||||
config = {
|
||||
"name": meta["name"],
|
||||
"unique_id": f"{AGG_DEVICE_ID}_{sensor_id}",
|
||||
"state_topic": f"{AGG_TOPIC}/state",
|
||||
"value_template": f"{{{{ value_json.{sensor_id} }}}}",
|
||||
"unit_of_measurement": meta["unit"],
|
||||
"state_class": meta["state_class"],
|
||||
"icon": meta["icon"],
|
||||
"device": device_payload,
|
||||
}
|
||||
if meta.get("device_class"):
|
||||
config["device_class"] = meta["device_class"]
|
||||
topic = f"homeassistant/sensor/{AGG_DEVICE_ID}/{sensor_id}/config"
|
||||
self._client.publish(topic, json.dumps(config), retain=True, qos=1)
|
||||
log.info("MQTT Discovery: %d Aggregat-Sensoren", len(self._agg_meta))
|
||||
|
||||
# ── Daten publizieren ─────────────────────────────────────
|
||||
|
||||
def publish_data(self, values: dict, topic_prefix: str):
|
||||
if not self._connected:
|
||||
return
|
||||
self._client.publish(f"{topic_prefix}/state", json.dumps(values), retain=True, qos=0)
|
||||
self._client.publish(f"{topic_prefix}/state", json.dumps(values),
|
||||
retain=True, qos=0)
|
||||
|
||||
def publish_status(self, status: str, topic_prefix: str):
|
||||
self._client.publish(f"{topic_prefix}/status", status, retain=True, qos=1)
|
||||
|
||||
def publish_aggregates(self, values: dict):
|
||||
if not self._connected or not values:
|
||||
return
|
||||
self._client.publish(f"{AGG_TOPIC}/state", json.dumps(values),
|
||||
retain=True, qos=0)
|
||||
self._client.publish(f"{AGG_TOPIC}/status", "online", retain=True, qos=1)
|
||||
|
||||
Reference in New Issue
Block a user