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:
@@ -0,0 +1,113 @@
|
||||
import logging
|
||||
import struct
|
||||
import time
|
||||
from typing import Dict, Optional
|
||||
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
from pymodbus.exceptions import ModbusException
|
||||
|
||||
from inverters import Inverter, Sensor
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SLAVE = 0 # Kathrein: Unit-ID 0 (Broadcast)
|
||||
EMS_CTRL_REG = 0x00A0
|
||||
EMS_CURRENT_REG = 0x00A2
|
||||
EMS_ENABLE = 0x8000
|
||||
MIN_CURRENT_MA = 6000
|
||||
MAX_CURRENT_MA = 32000
|
||||
|
||||
|
||||
class WallboxReader:
|
||||
def __init__(self, host: str, port: int = 502, timeout: float = 10.0):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.timeout = timeout
|
||||
self._client: Optional[ModbusTcpClient] = None
|
||||
|
||||
def _connect(self) -> bool:
|
||||
if self._client and self._client.connected:
|
||||
return True
|
||||
self._client = ModbusTcpClient(self.host, port=self.port, timeout=self.timeout)
|
||||
if not self._client.connect():
|
||||
log.error("[Wallbox %s] Verbindung fehlgeschlagen", self.host)
|
||||
self._client = None
|
||||
return False
|
||||
return True
|
||||
|
||||
def _disconnect(self):
|
||||
if self._client:
|
||||
self._client.close()
|
||||
self._client = None
|
||||
|
||||
def read(self, inverter: Inverter) -> Optional[Dict[str, float]]:
|
||||
if not self._connect():
|
||||
return None
|
||||
|
||||
reg_cache: Dict[int, int] = {}
|
||||
for start, length in inverter.read_ranges:
|
||||
try:
|
||||
result = self._client.read_holding_registers(start, count=length, slave=SLAVE)
|
||||
if result.isError():
|
||||
log.error("[Wallbox] FC03 Fehler Reg 0x%04X: %s", start, result)
|
||||
self._disconnect()
|
||||
return None
|
||||
for i, val in enumerate(result.registers):
|
||||
reg_cache[start + i] = val
|
||||
except ModbusException as e:
|
||||
log.error("[Wallbox] Modbus Ausnahme: %s", e)
|
||||
self._disconnect()
|
||||
return None
|
||||
|
||||
return _extract(inverter.sensors, reg_cache)
|
||||
|
||||
def enable_ems(self) -> bool:
|
||||
if not self._connect():
|
||||
return False
|
||||
try:
|
||||
r = self._client.write_register(EMS_CTRL_REG, EMS_ENABLE, slave=SLAVE)
|
||||
return not r.isError()
|
||||
except ModbusException as e:
|
||||
log.error("[Wallbox] EMS enable Fehler: %s", e)
|
||||
return False
|
||||
|
||||
def set_current(self, ma: int) -> bool:
|
||||
"""Ladestrom setzen. ma=0 → Pause, ma=0xFFFF → Abbruch."""
|
||||
if not self._connect():
|
||||
return False
|
||||
val = 0 if ma == 0 else max(MIN_CURRENT_MA, min(MAX_CURRENT_MA, ma))
|
||||
try:
|
||||
r = self._client.write_register(EMS_CURRENT_REG, val, slave=SLAVE)
|
||||
ok = not r.isError()
|
||||
if ok:
|
||||
log.info("[Wallbox] Strom gesetzt: %d mA", val)
|
||||
return ok
|
||||
except ModbusException as e:
|
||||
log.error("[Wallbox] set_current Fehler: %s", e)
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
self._disconnect()
|
||||
|
||||
|
||||
def _extract(sensors: list, regs: Dict[int, int]) -> Dict[str, float]:
|
||||
values: Dict[str, float] = {}
|
||||
for s in sensors:
|
||||
if s.reg not in regs:
|
||||
continue
|
||||
dt = getattr(s, "data_type", "uint16")
|
||||
try:
|
||||
if dt == "float32":
|
||||
if s.reg + 1 not in regs:
|
||||
continue
|
||||
raw = struct.unpack(">f", struct.pack(">HH", regs[s.reg], regs[s.reg + 1]))[0]
|
||||
elif dt == "uint32":
|
||||
if s.reg + 1 not in regs:
|
||||
continue
|
||||
raw = (regs[s.reg] << 16) | regs[s.reg + 1]
|
||||
else:
|
||||
raw = regs[s.reg]
|
||||
values[s.id] = round(float(raw) * s.scale, 3)
|
||||
except Exception:
|
||||
pass
|
||||
return values
|
||||
Reference in New Issue
Block a user