3 Commits

Author SHA1 Message Date
retr0 0dbf0266a8 fix: _get_pv_surplus() via grid_power-Aggregat (SDM-630 fix, v1.8.11)
SDM-630 liefert grid_power (negativ=Einspeisung), wurde von active_power-
Logik nicht erfasst. Jetzt einheitlich über grid_power-Aggregat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 11:42:08 +02:00
retr0 5942c18df6 fix: config.yaml version auf 1.8.10 2026-05-04 11:35:25 +02:00
retr0 58a33f966d debug: surplus_w in API + Live-Tab anzeigen 2026-05-04 11:31:06 +02:00
3 changed files with 11 additions and 21 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: ShineBridge name: ShineBridge
version: "1.8.9" version: "1.8.11"
slug: shinebridge slug: shinebridge
description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI
url: https://gitea.bitfire.work/retr0/shinebridge url: https://gitea.bitfire.work/retr0/shinebridge
+5 -20
View File
@@ -180,25 +180,9 @@ def _compute_aggregates(allow_stale: bool = False) -> Dict[str, float]:
# ── EMS Hilfsfunktionen ─────────────────────────────────────── # ── EMS Hilfsfunktionen ───────────────────────────────────────
def _get_pv_surplus() -> float: def _get_pv_surplus() -> float:
"""PV-Überschuss in Watt aus laufenden Geräten ermitteln. """PV-Überschuss in Watt. Nutzt grid_power-Aggregat (negativ = Einspeisung)."""
Goodwe: active_power negativ = Einspeisung (Überschuss). agg = _compute_aggregates(allow_stale=True)
Growatt: power_to_grid positiv = Einspeisung. return max(0.0, -agg.get("grid_power", 0.0))
"""
surplus = 0.0
with State.lock:
for inv_cfg in State.inverters_cfg:
d = State.inv_data.get(inv_cfg["id"], {})
if not d.get("modbus_ok") or not d.get("values"):
continue
v = d["values"]
# Goodwe ET: active_power > 0 = Einspeisung, < 0 = Netzbezug
# house_consumption = ppv + pbattery1 - active_power (Bibliotheks-Formel)
if "active_power" in v:
surplus += max(0.0, v["active_power"])
# Growatt
if "power_to_grid" in v:
surplus += max(0.0, v["power_to_grid"])
return surplus
# ── Poll Loop ───────────────────────────────────────────────── # ── Poll Loop ─────────────────────────────────────────────────
@@ -522,7 +506,8 @@ def api_get_surplus_devices():
devices = State.surplus_devices_cfg devices = State.surplus_devices_cfg
z2m_base = State.z2m_base z2m_base = State.z2m_base
states = _surplus_ctrl.get_states() if _surplus_ctrl else {} states = _surplus_ctrl.get_states() if _surplus_ctrl else {}
return jsonify({"devices": devices, "z2m_base": z2m_base, "states": states}) surplus_w = _get_pv_surplus()
return jsonify({"devices": devices, "z2m_base": z2m_base, "states": states, "surplus_w": surplus_w})
@app.post("/api/surplus-devices") @app.post("/api/surplus-devices")
def api_save_surplus_devices(): def api_save_surplus_devices():
+5
View File
@@ -1079,10 +1079,15 @@ function renderSurplusStatus(surplusData) {
</div>`; </div>`;
}).join(''); }).join('');
if (!rows) return ''; if (!rows) return '';
const surplusW = surplusData.surplus_w ?? null;
const surplusInfo = surplusW !== null
? `<span style="font-size:11px;color:var(--text-dim);margin-left:auto">${surplusW >= 0 ? '+' : ''}${Math.round(surplusW)} W Überschuss</span>`
: '';
return `<div class="inv-section"> return `<div class="inv-section">
<div class="inv-header"> <div class="inv-header">
<div class="inv-title">Überschuss-Geräte</div> <div class="inv-title">Überschuss-Geräte</div>
<div class="inv-badge ok">Z2M</div> <div class="inv-badge ok">Z2M</div>
${surplusInfo}
</div> </div>
<div style="padding:0 2px">${rows}</div> <div style="padding:0 2px">${rows}</div>
</div>`; </div>`;