Files
battery-charging-optimizer/legacy/v1/battery_power_control.py

168 lines
5.0 KiB
Python

"""
Battery Power Control via Modbus
Hilfs-Script für das Schreiben der Batterieleistung über Modbus
Speicherort: /config/pyscript/battery_power_control.py
Verwendet die bewährte float_to_regs_be Methode von Felix's ess_set_power.py
"""
import struct
@service
def set_battery_power_modbus(power_w: float = 0.0, hub: str = "openEMS", slave: int = 1):
"""
Schreibt die Batterieleistung direkt über Modbus Register 706
Register 706 = ess0/SetActivePowerEquals (FLOAT32 Big-Endian)
Args:
power_w: Leistung in Watt (negativ = laden, positiv = entladen)
hub: Modbus Hub Name (default: "openEMS")
slave: Modbus Slave ID (default: 1)
"""
ADDR_EQUALS = 706
def float_to_regs_be(val: float):
"""Konvertiert Float zu Big-Endian Register-Paar"""
b = struct.pack(">f", float(val)) # Big Endian
return [(b[0] << 8) | b[1], (b[2] << 8) | b[3]] # [hi, lo]
# Konvertiere zu Float
try:
p = float(power_w)
except Exception:
log.warning(f"Konnte {power_w} nicht zu Float konvertieren, nutze 0.0")
p = 0.0
# Konvertiere zu Register-Paar
regs = float_to_regs_be(p)
log.info(f"OpenEMS ESS Ziel: {p:.1f} W -> Register {ADDR_EQUALS} -> {regs}")
try:
service.call(
"modbus",
"write_register",
hub=hub,
slave=slave,
address=ADDR_EQUALS,
value=regs # Liste mit 2 Registern für FLOAT32
)
log.info(f"Erfolgreich {p:.1f}W geschrieben")
except Exception as e:
log.error(f"Fehler beim Modbus-Schreiben: {e}")
raise
@service
def start_charging_cycle(power_w: float = -10000.0, hub: str = "openEMS", slave: int = 1):
"""
Startet einen kompletten Lade-Zyklus
Ablauf:
1. ESS → REMOTE Mode (manuelle Steuerung aktivieren)
2. Leistung über Modbus setzen (Register 706)
3. Keep-Alive aktivieren (alle 30s neu schreiben)
Args:
power_w: Ladeleistung in Watt (negativ = laden, positiv = entladen)
hub: Modbus Hub Name (default: "openEMS")
slave: Modbus Slave ID (default: 1)
"""
log.info(f"Starte Lade-Zyklus mit {power_w}W")
# 1. ESS in REMOTE Mode setzen
# WICHTIG: VOR dem Schreiben der Leistung!
service.call('rest_command', 'set_ess_remote_mode')
task.sleep(1.0) # Warte auf Modusänderung
# 2. Ladeleistung setzen (mit korrekter FLOAT32-Konvertierung)
set_battery_power_modbus(power_w=power_w, hub=hub, slave=slave)
# 3. Status für Keep-Alive setzen
state.set('pyscript.battery_charging_active', True)
state.set('pyscript.battery_charging_power', power_w)
state.set('pyscript.battery_charging_hub', hub)
state.set('pyscript.battery_charging_slave', slave)
log.info("Lade-Zyklus gestartet (ESS in REMOTE Mode)")
@service
def stop_charging_cycle():
"""
Stoppt den Lade-Zyklus und aktiviert automatischen Betrieb
Ablauf:
1. ESS → INTERNAL Mode (zurück zur automatischen Steuerung)
2. Status zurücksetzen (Keep-Alive deaktivieren)
"""
log.info("Stoppe Lade-Zyklus")
# 1. ESS zurück in INTERNAL Mode
# WICHTIG: Nach dem Laden, um wieder auf Automatik zu schalten!
service.call('rest_command', 'set_ess_internal_mode')
task.sleep(1.0) # Warte auf Modusänderung
# 2. Status zurücksetzen
state.set('pyscript.battery_charging_active', False)
state.set('pyscript.battery_charging_power', 0)
log.info("Automatischer Betrieb aktiviert (ESS in INTERNAL Mode)")
@time_trigger('cron(*/30 * * * *)')
def keep_alive_charging():
"""
Keep-Alive: Schreibt alle 30 Sekunden die Leistung neu
Verhindert Timeout im REMOTE Mode
"""
# Prüfe ob Laden aktiv ist
if not state.get('pyscript.battery_charging_active'):
return
power_w = state.get('pyscript.battery_charging_power')
hub = state.get('pyscript.battery_charging_hub') or "openEMS"
slave = state.get('pyscript.battery_charging_slave') or 1
if power_w is None:
return
try:
power_w = float(power_w)
log.debug(f"Keep-Alive: Schreibe {power_w}W")
set_battery_power_modbus(power_w=power_w, hub=hub, slave=int(slave))
except Exception as e:
log.error(f"Keep-Alive Fehler: {e}")
@service
def emergency_stop():
"""
Notfall-Stop: Deaktiviert sofort alle manuellen Steuerungen
"""
log.warning("NOTFALL-STOP ausgelöst!")
try:
# Alles zurück auf Auto
stop_charging_cycle()
# Optimierung deaktivieren
input_boolean.battery_optimizer_enabled = False
# Notification
service.call('notify.persistent_notification',
title="Batterie-Optimierung",
message="NOTFALL-STOP ausgelöst! Alle Automatisierungen deaktiviert.")
except Exception as e:
log.error(f"Fehler beim Notfall-Stop: {e}")