ShineBridge v1.8.31 — EMS-Fixes: konfigurierbarer Max-Ladestrom, Zwangsladen-Retry, Fault-State-Handling

- ems_max_current_ma konfigurierbar statt hardcoded 32A (Zwangsladen darf jetzt nicht mehr
  mehr fordern als die tatsächliche Wallbox liefern kann, z.B. 16A bei einer 11kW-Wallbox)
- Zwangsladen setzt den Ladestrom bei jedem Poll erneut (statt nur einmal beim Start) —
  ein einzelner fehlgeschlagener Modbus-Write hängt nicht mehr die ganze Nacht
- Unbekannte/Fehler-Ladezustände (z.B. Fahrzeug-Störung) werden nicht mehr wie 'abgesteckt'
  behandelt — Strom wird pausiert, aber der Zwangslade-Timer bleibt erhalten
- Neues Settings-Feld 'Max. Ladestrom (A)' im EMS-Dialog

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-08-25 12:46:59 +02:00
co-authored by Claude Sonnet 5
parent b36b0194f7
commit f5ed7c5df6
5 changed files with 38 additions and 7 deletions
+26 -6
View File
@@ -23,6 +23,11 @@ TARGET_HOUR = 6 # Zielstunde: Vollladung bis 06:00
POLL_INTERVAL_S = 30 # Sekunden zwischen EMS-Updates
PHASES = 3 # Anzahl der Phasen
VOLTAGE_V = 230 # Netzspannung
MAX_CHARGE_MA = 32000 # Absolutes Wallbox-Limit (Hardware-Grenze WallboxReader).
# ACHTUNG: die tatsächlich installierte Wallbox/Zuleitung kann
# niedriger dimensioniert sein (z.B. 11kW-Wallbox = 16A) — dann
# über max_charge_ma im Konstruktor auf den echten Wert begrenzen,
# sonst fordert das Zwangsladen mehr an als das Gerät liefern kann.
# Charging-State Werte (Kathrein Modbus)
STATE_IDLE = 0
@@ -39,11 +44,13 @@ class EmsController:
pv_timeout_h: float = PV_TIMEOUT_H,
target_hour: int = TARGET_HOUR,
phases: int = PHASES,
max_charge_ma: int = MAX_CHARGE_MA,
):
self.min_pv_power = min_pv_power
self.pv_timeout_h = pv_timeout_h
self.target_hour = target_hour
self.phases = phases
self.max_charge_ma = max_charge_ma
self._pv_ok_since: Optional[float] = None # Zeitpunkt letzter ausreichender PV
self._no_pv_since: Optional[float] = None # Zeitpunkt seit kein PV
@@ -66,7 +73,7 @@ class EmsController:
total_w = surplus_w + wallbox_w
current_a = total_w / (VOLTAGE_V * self.phases)
ma = int(current_a * 1000)
return max(6000, min(32000, ma))
return max(6000, min(self.max_charge_ma, ma))
def update(self, wallbox_reader, pv_surplus_w: float, charging_state: int,
wallbox_power_w: float = 0.0) -> str:
@@ -84,8 +91,8 @@ class EmsController:
if wallbox_reader.enable_ems():
self._ems_enabled = True
log.info("[EMS] EMS aktiviert")
else:
# Auto weg → alles zurücksetzen
elif charging_state == STATE_IDLE:
# Wirklich kein Fahrzeug angesteckt → alles zurücksetzen, Timer neu starten
if self._ems_enabled:
wallbox_reader.set_current(0)
self._ems_enabled = False
@@ -93,6 +100,16 @@ class EmsController:
self._no_pv_since = None
log.info("[EMS] Auto abgesteckt — EMS zurückgesetzt")
return "kein Fahrzeug"
else:
# Unbekannter/nicht abgedeckter Ladezustand (z.B. Fehler/Störung am Fahrzeug).
# NICHT wie "abgesteckt" behandeln — sonst verliert der Zwangslade-Timer seinen
# Fortschritt und die Deadline (Zielzeit) wird nie mehr erreicht, obwohl das Auto
# die ganze Zeit angesteckt war. Strom sicherheitshalber pausieren, aber Zustand/
# Timer erhalten, damit nach Rückkehr in einen bekannten Zustand nahtlos weitergeht.
wallbox_reader.set_current(0)
log.warning("[EMS] Unbekannter Ladezustand %d (evtl. Störung am Fahrzeug) — "
"Strom pausiert, Zwangslade-Timer bleibt erhalten", charging_state)
return f"unbekannter Zustand ({charging_state})"
if charging_state == STATE_COMPLETED:
wallbox_reader.set_current(0)
@@ -116,13 +133,16 @@ class EmsController:
no_pv_h = (now - self._no_pv_since) / 3600.0
if no_pv_h >= self.pv_timeout_h:
# Zwangsladen bis Zielzeit
# Zwangsladen bis Zielzeit — Strom bei JEDEM Update erneut setzen (nicht nur beim
# ersten Eintritt), sonst bleibt ein einzelner fehlgeschlagener/verworfener Write
# (Modbus-Hänger, Fahrzeug-Störung) unbemerkt für den Rest der Nacht stehen.
target = self._next_target()
mins_left = (target - datetime.now()).total_seconds() / 60
if not self._forced_charging:
wallbox_reader.set_current(32000, self.phases)
self._forced_charging = True
log.info("[EMS] Zwangsladen gestartet — %dmin bis %02d:00", int(mins_left), self.target_hour)
log.info("[EMS] Zwangsladen gestartet — %dmin bis %02d:00, max %.0fA",
int(mins_left), self.target_hour, self.max_charge_ma / 1000)
wallbox_reader.set_current(self.max_charge_ma, self.phases)
return f"Zwangsladen ({no_pv_h:.1f}h kein PV, {int(mins_left)}min bis {self.target_hour:02d}:00)"
# Warten auf PV