Feature: Kathrein Wallbox + EMS-Controller (v1.5.0)
- wallbox_client.py: WallboxReader FC03, EMS enable/set_current - ems_controller.py: PV-Überschussladen + 4h-Timeout Zwangsladen bis 06:00 - inverters.py: KATHREIN_WALLBOX mit 18 Sensoren (Meter + EVSE + EMS) - main.py: kathrein-Protokollzweig, _get_pv_surplus() aus laufenden Geräten Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ from inverters import INVERTERS
|
||||
import history
|
||||
from modbus_client import ModbusReader
|
||||
from goodwe_client import GoodweReader
|
||||
from wallbox_client import WallboxReader
|
||||
from ems_controller import EmsController
|
||||
from mqtt_publisher import MqttPublisher
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -139,6 +141,29 @@ def _compute_aggregates() -> Dict[str, float]:
|
||||
)
|
||||
return result
|
||||
|
||||
# ── EMS Hilfsfunktionen ───────────────────────────────────────
|
||||
|
||||
def _get_pv_surplus() -> float:
|
||||
"""PV-Überschuss in Watt aus laufenden Geräten ermitteln.
|
||||
Goodwe: active_power negativ = Einspeisung (Überschuss).
|
||||
Growatt: power_to_grid positiv = Einspeisung.
|
||||
"""
|
||||
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: active_power < 0 bedeutet Einspeisung
|
||||
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 ─────────────────────────────────────────────────
|
||||
|
||||
def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
||||
@@ -156,10 +181,16 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
||||
return
|
||||
|
||||
host = inv_cfg["modbus_ip"]
|
||||
ems: Optional[EmsController] = None
|
||||
if inverter.protocol == "goodwe_udp":
|
||||
reader = GoodweReader(host=host, family=inverter.goodwe_family)
|
||||
log.info("[%s] Poll-Loop: %s @ %s (Goodwe UDP/8899) alle %ds",
|
||||
inv_id, inverter.name, host, interval)
|
||||
elif inverter.protocol == "kathrein":
|
||||
reader = WallboxReader(host=host, port=port)
|
||||
ems = EmsController()
|
||||
log.info("[%s] Poll-Loop: %s @ %s:%s (Kathrein EMS) alle %ds",
|
||||
inv_id, inverter.name, host, port, interval)
|
||||
else:
|
||||
reader = ModbusReader(host=host, port=port, slave=slave)
|
||||
log.info("[%s] Poll-Loop: %s @ %s:%s alle %ds",
|
||||
@@ -184,6 +215,15 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
||||
while not stop.is_set():
|
||||
t0 = time.time()
|
||||
values = reader.read(inverter)
|
||||
|
||||
# EMS: PV-Überschuss aus anderen Geräten holen und Ladestrom regeln
|
||||
if ems is not None and values is not None:
|
||||
pv_surplus = _get_pv_surplus()
|
||||
charging_state = int(values.get("charging_state", 0))
|
||||
ems_status = ems.update(reader, pv_surplus, charging_state)
|
||||
values["ems_status_code"] = float(charging_state)
|
||||
log.debug("[%s] EMS: %s", inv_id, ems_status)
|
||||
|
||||
with State.lock:
|
||||
d = State.inv_data.setdefault(inv_id, {"poll_count": 0})
|
||||
if values is not None:
|
||||
|
||||
Reference in New Issue
Block a user