""" 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}")