fix(goodwe): Netzbezug-Periode zeigt 0 kWh — integrierter grid_power-Zähler

e_total_imp vom Goodwe-WR ist ein Lifetime-Zähler seit Inbetriebnahme.
Beim ersten Verbinden speichert save_period_start_if_new() den aktuellen
Wert als Periodenstart → Delta = 0.

Fix: _int_import/_int_export werden je Poll-Zyklus aus grid_power integriert
(W × dt / 3.600.000 → kWh), in der measurements-DB persistiert und
beim Neustart aus der DB wiederhergestellt. AGG_SENSOR_IDS bevorzugt nun
_int_import vor e_total_imp, SDM-630 (import_kwh) bleibt erste Wahl.
Private Keys (Prefix _) werden nicht an MQTT gepublished.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-05-07 09:55:24 +02:00
parent 512b743b16
commit a9f33c8e9e
3 changed files with 24 additions and 4 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: ShineBridge
version: "1.8.22"
version: "1.8.23"
slug: shinebridge
description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI
url: https://gitea.bitfire.work/retr0/shinebridge
+22 -3
View File
@@ -55,8 +55,8 @@ AGG_SENSOR_IDS: Dict[str, List[str]] = {
"total_energy_today": ["energy_today", "e_day"],
"total_energy_total": ["energy_total", "e_total"],
"grid_power": ["grid_power"],
"grid_import_kwh": ["import_kwh", "e_total_imp"],
"grid_export_kwh": ["export_kwh", "e_total_exp"],
"grid_import_kwh": ["import_kwh", "_int_import", "e_total_imp"],
"grid_export_kwh": ["export_kwh", "_int_export", "e_total_exp"],
"bat_charge_power": ["bat_charge_power"],
"bat_discharge_power": ["bat_discharge_power"],
"bat_charge_total": ["bat_charge_total", "e_bat_charge_total"],
@@ -310,6 +310,11 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
q.append(pt)
log.info("[%s] %d Sensoren aus DB geladen", inv_id, len(hist_data))
# Integrierte Grid-Zähler — starten bei 0, wachsen mit jedem Poll, persistieren in DB
inv_int_import = hist_data["_int_import"][-1][1] if hist_data.get("_int_import") else 0.0
inv_int_export = hist_data["_int_export"][-1][1] if hist_data.get("_int_export") else 0.0
last_grid_ts = time.time()
while not stop.is_set():
t0 = time.time()
values = reader.read(inverter)
@@ -322,6 +327,19 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
if values and "grid_power" not in values and "import_kwh" in values and "total_power" in values:
values["grid_power"] = values["total_power"]
# grid_power integrieren → interne kWh-Zähler (Periodenberechnung für Goodwe / kein SDM-630)
if values is not None and "grid_power" in values:
dt_s = t0 - last_grid_ts
if 0 < dt_s < 300:
gp = values["grid_power"]
if gp > 0:
inv_int_import = round(inv_int_import + gp * dt_s / 3_600_000, 6)
elif gp < 0:
inv_int_export = round(inv_int_export + (-gp) * dt_s / 3_600_000, 6)
values["_int_import"] = inv_int_import
values["_int_export"] = inv_int_export
last_grid_ts = t0
# EMS: PV-Überschuss aus anderen Geräten holen und Ladestrom regeln
if ems is not None and values is not None and inv_cfg.get("ems_enabled", True):
pv_surplus = _get_pv_surplus()
@@ -347,7 +365,8 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
q.append((now, val))
history.write_batch(inv_id, now, values)
if _publisher:
_publisher.publish_data(values, prefix)
pub_values = {k: v for k, v in values.items() if not k.startswith("_")}
_publisher.publish_data(pub_values, prefix)
_publisher.publish_status("online", prefix)
else:
d["modbus_ok"] = False