From 2b1487fcef4083f979138af593f8dcb84c3ce3c5 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 00:09:06 +0100 Subject: [PATCH 01/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 194 ++++++++++++++++-- 1 file changed, 179 insertions(+), 15 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 59483aefa0..92eb465d71 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Tuple, Union +from typing import Dict, Union, Optional from pymodbus.constants import Endian +from control import data from dataclass_utils import dataclass_from_dict from modules.common import modbus from modules.common.abstract_device import AbstractBat @@ -14,6 +15,7 @@ from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup +import pymodbus log = logging.getLogger(__name__) @@ -21,6 +23,19 @@ class SolaredgeBat(AbstractBat): + # Define all possible registers with their data types + REGISTERS = { + "Battery1StateOfEnergy": (0xe184, ModbusDataType.FLOAT_32,), # Mirror: 0xf584 + "Battery1InstantaneousPower": (0xe174, ModbusDataType.FLOAT_32,), # Mirror: 0xf574 + "Battery2StateOfEnergy": (0xe284, ModbusDataType.FLOAT_32,), + "Battery2InstantaneousPower": (0xe274, ModbusDataType.FLOAT_32,), + "StorageControlMode": (0xe004, ModbusDataType.UINT_16,), + "StorageBackupReserved": (0xe008, ModbusDataType.FLOAT_32,), + "StorageChargeDischargeDefaultMode": (0xe00a, ModbusDataType.UINT_16,), + "RemoteControlCommandMode": (0xe00d, ModbusDataType.UINT_16,), + "RemoteControlCommandDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,), + } + def __init__(self, device_id: int, component_config: Union[Dict, SolaredgeBatSetup], @@ -31,32 +46,181 @@ def __init__(self, self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.last_mode = 'undefined' + self.battery_index = 1 # Nach Umsetzung des PR 2236, hier entfernen und unten als battery_index ersetzen. def update(self) -> None: self.store.set(self.read_state()) - def read_state(self): - power, soc = self.get_values() - imported, exported = self.get_imported_exported(power) - return BatState( + def read_state(self) -> BatState: + unit = self.component_config.configuration.modbus_id + + registers_to_read = [ + f"Battery{self.battery_index}InstantaneousPower", + f"Battery{self.battery_index}StateOfEnergy", + ] + values = self._read_registers(registers_to_read, unit) + power = values[f"Battery{self.battery_index}InstantaneousPower"] + soc = values[f"Battery{self.battery_index}StateOfEnergy"] + if power == FLOAT32_UNSUPPORTED: + power = 0 + + imported, exported = self.sim_counter.sim_count(power) + + bat_state = BatState( power=power, soc=soc, imported=imported, exported=exported ) + log.debug(f"Bat {self.__tcp_client.address}: {bat_state}") + return bat_state + + """ + Die Steuerung bei SolarEdge basiert auf folgenden Einstellungen: + + Zunächst muss der Storage Control Mode gesetzt werden: + 1 - Maximize Self Consumption FW < 4.20.36 + 2 - Time of Use (Steuerung durch SolarEdge Profile) FW >= 4.20.36 + 4 - Remote Control (Steuerung durch openWB) + + Dann der Remote Control Command Mode und Default Mode: + 1 - Charge excess PV power only. + 7 - Maximize self-consumption - def get_values(self) -> Tuple[float, float]: + Im Remote Control Command Mode 1 wird nur geladen, das Entladen ist gesperrt. + Im Remote Control Command Mode 7 kann die Speicherentladung durch das Discharge Limit begrenzt werden. + + to-Do: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 prüfen. + """ + def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id - soc = self.__tcp_client.read_holding_registers( - 62852, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) - power = self.__tcp_client.read_holding_registers( - 62836, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) - if power == FLOAT32_UNSUPPORTED: - power = 0 - return power, soc + PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode + + if PowerLimitMode == 'no_limit': + """" + Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). + Falls externe Steuerungen aktiv sind, sollten diese nicht beeinfusst werden. + Daher erfolgt im Modus "Immer" der Speichersteuerung gar keine Steuerung. + """ + return + + """ + SolarEdge entlaedt den Speicher immer nur bis zur Backup-Reserve. + Bei Systemen ohne Backup-Modul arbeitet sie als SoC-Reserve. + Eine Steuerung macht nur Sinn, wenn der SoC vom Speicher hoeher als die SoC-Reserve ist. + """ + registers_to_read = [ + f"Battery{self.battery_index}StateOfEnergy", + "StorageBackupReserved", + ] + values = self._read_registers(registers_to_read, unit) + soc = values[f"Battery{self.battery_index}StateOfEnergy"] + backup_reserve = values["StorageBackupReserved"] + + if power_limit is None: + if self.last_mode is not None: + # Keine Ladung mit Speichersteuerung aktiv, Steuerung deaktivieren. + log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") + values_to_write = { + "RemoteControlCommandDischargeLimit": 5000, + "StorageChargeDischargeDefaultMode": 0, + "RemoteControlCommandMode": 0, + "StorageControlMode": 2, + } + self._write_registers(values_to_write, unit) + self.last_mode = None + return + elif power_limit == 0: + if self.last_mode != 'stop' and backup_reserve < soc: + # Speichersteuerung aktivieren, Speicher-Entladung sperren. + log.debug("Speichersteuerung aktivieren. Speicher-Entladung sperren.") + values_to_write = { + "StorageControlMode": 4, + "StorageChargeDischargeDefaultMode": 1, + "RemoteControlCommandMode": 1, + } + self._write_registers(values_to_write, unit) + self.last_mode = 'stop' + elif self.last_mode == 'stop' and backup_reserve > soc: + # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. + log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") + values_to_write = { + "RemoteControlCommandDischargeLimit": 5000, + "StorageChargeDischargeDefaultMode": 0, + "RemoteControlCommandMode": 0, + "StorageControlMode": 2, + } + self._write_registers(values_to_write, unit) + self.last_mode = None + elif power_limit > 0: + if self.last_mode != 'limited' and backup_reserve < soc: + # Speichersteuerung aktivieren, Speicher-Entladung steuern. + log.debug("Speichersteuerung aktivieren. Speicher-Entladung steuern.") + values_to_write = { + "StorageControlMode": 4, + "StorageChargeDischargeDefaultMode": 7, + "RemoteControlCommandMode": 7, + "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) + } + self._write_registers(values_to_write, unit) + self.last_mode = 'limited' + elif self.last_mode == 'limited': + if backup_reserve > soc: + # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. + log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") + values_to_write = { + "RemoteControlCommandDischargeLimit": 5000, + "StorageChargeDischargeDefaultMode": 0, + "RemoteControlCommandMode": 0, + "StorageControlMode": 2, + } + self._write_registers(values_to_write, unit) + self.last_mode = None + else: + log.debug(f"Speichersteuerung aktiv, PowerLimt = {power_limt} W.") + values_to_write = { + "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) + } + self._write_registers(values_to_write, unit) + + def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: + values = {} + for key in register_names: + log.debug(f"Bat raw values {self.__tcp_client.address}: {values}") + address, data_type = self.REGISTERS[key] + values[key] = self.__tcp_client.read_holding_registers( + address, data_type, wordorder=Endian.Little, unit=unit + ) + log.debug(f"Bat raw values {self.__tcp_client.address}: {values}") + return values + + def _write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: int) -> None: + for key, value in values_to_write.items(): + address, data_type = self.REGISTERS[key] + encoded_value = self._encode_value(value, data_type) + self.__tcp_client.write_registers(address, encoded_value, unit=unit) + log.debug(f"Neuer Wert {encoded_value} in Register {address} geschrieben.") + + def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> list: + builder = pymodbus.payload.BinaryPayloadBuilder( + byteorder=pymodbus.constants.Endian.Big, + wordorder=pymodbus.constants.Endian.Little + ) + encode_methods = { + ModbusDataType.UINT_32: builder.add_32bit_uint, + ModbusDataType.INT_32: builder.add_32bit_int, + ModbusDataType.UINT_16: builder.add_16bit_uint, + ModbusDataType.INT_16: builder.add_16bit_int, + ModbusDataType.FLOAT_32: builder.add_32bit_float, + } + + if data_type in encode_methods: + encode_methods[data_type](int(value)) + else: + raise ValueError(f"Unsupported data type: {data_type}") - def get_imported_exported(self, power: float) -> Tuple[float, float]: - return self.sim_counter.sim_count(power) + return builder.to_registers() component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 6b8f74d59dcb7feebb3afb2bc54e91111be7e0ff Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 00:30:41 +0100 Subject: [PATCH 02/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 92eb465d71..2daa37691e 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -178,7 +178,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self._write_registers(values_to_write, unit) self.last_mode = None else: - log.debug(f"Speichersteuerung aktiv, PowerLimt = {power_limt} W.") + log.debug("Speichersteuerung aktiv, Discharge-Limit gesetzt.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) } From 06011e4439c1958688438f199d0eaa4c7d89b7ea Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 19:39:23 +0100 Subject: [PATCH 03/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 119 ++++++++---------- 1 file changed, 51 insertions(+), 68 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 2daa37691e..c31bbfdd8c 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -27,8 +27,10 @@ class SolaredgeBat(AbstractBat): REGISTERS = { "Battery1StateOfEnergy": (0xe184, ModbusDataType.FLOAT_32,), # Mirror: 0xf584 "Battery1InstantaneousPower": (0xe174, ModbusDataType.FLOAT_32,), # Mirror: 0xf574 + "Battery1Status": (0xe186, ModbusDataType.UINT_32,), "Battery2StateOfEnergy": (0xe284, ModbusDataType.FLOAT_32,), "Battery2InstantaneousPower": (0xe274, ModbusDataType.FLOAT_32,), + "Battery2Status": (0xe286, ModbusDataType.UINT_32,), "StorageControlMode": (0xe004, ModbusDataType.UINT_16,), "StorageBackupReserved": (0xe008, ModbusDataType.FLOAT_32,), "StorageChargeDischargeDefaultMode": (0xe00a, ModbusDataType.UINT_16,), @@ -76,24 +78,19 @@ def read_state(self) -> BatState: log.debug(f"Bat {self.__tcp_client.address}: {bat_state}") return bat_state - """ - Die Steuerung bei SolarEdge basiert auf folgenden Einstellungen: - - Zunächst muss der Storage Control Mode gesetzt werden: - 1 - Maximize Self Consumption FW < 4.20.36 - 2 - Time of Use (Steuerung durch SolarEdge Profile) FW >= 4.20.36 - 4 - Remote Control (Steuerung durch openWB) - - Dann der Remote Control Command Mode und Default Mode: - 1 - Charge excess PV power only. - 7 - Maximize self-consumption - - Im Remote Control Command Mode 1 wird nur geladen, das Entladen ist gesperrt. - Im Remote Control Command Mode 7 kann die Speicherentladung durch das Discharge Limit begrenzt werden. - - to-Do: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 prüfen. - """ def set_power_limit(self, power_limit: Optional[int]) -> None: + """ + Die Steuerung bei SolarEdge basiert auf folgenden Einstellungen: + Zunächst muss der Storage Control Mode gesetzt werden: + 1 - Maximize Self Consumption FW < 4.20.36 + 2 - Time of Use (Steuerung durch SolarEdge Profile) FW >= 4.20.36 + 4 - Remote Control (Für Steuerung durch openWB) + Dann der Remote Control Command Mode und Default Mode: + 7 - Maximize self-consumption + anschließend das DischargLimit setzen. + + todo: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 prüfen. + """ unit = self.component_config.configuration.modbus_id PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode @@ -105,22 +102,10 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: """ return - """ - SolarEdge entlaedt den Speicher immer nur bis zur Backup-Reserve. - Bei Systemen ohne Backup-Modul arbeitet sie als SoC-Reserve. - Eine Steuerung macht nur Sinn, wenn der SoC vom Speicher hoeher als die SoC-Reserve ist. - """ - registers_to_read = [ - f"Battery{self.battery_index}StateOfEnergy", - "StorageBackupReserved", - ] - values = self._read_registers(registers_to_read, unit) - soc = values[f"Battery{self.battery_index}StateOfEnergy"] - backup_reserve = values["StorageBackupReserved"] - if power_limit is None: + # Keine Ladung mit Speichersteuerung. if self.last_mode is not None: - # Keine Ladung mit Speichersteuerung aktiv, Steuerung deaktivieren. + # Steuerung deaktivieren. log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") values_to_write = { "RemoteControlCommandDischargeLimit": 5000, @@ -130,42 +115,29 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None + # Steuerung bereits inaktiv. return - elif power_limit == 0: - if self.last_mode != 'stop' and backup_reserve < soc: - # Speichersteuerung aktivieren, Speicher-Entladung sperren. - log.debug("Speichersteuerung aktivieren. Speicher-Entladung sperren.") - values_to_write = { - "StorageControlMode": 4, - "StorageChargeDischargeDefaultMode": 1, - "RemoteControlCommandMode": 1, - } - self._write_registers(values_to_write, unit) - self.last_mode = 'stop' - elif self.last_mode == 'stop' and backup_reserve > soc: - # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. - log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") - values_to_write = { - "RemoteControlCommandDischargeLimit": 5000, - "StorageChargeDischargeDefaultMode": 0, - "RemoteControlCommandMode": 0, - "StorageControlMode": 2, - } - self._write_registers(values_to_write, unit) - self.last_mode = None - elif power_limit > 0: - if self.last_mode != 'limited' and backup_reserve < soc: - # Speichersteuerung aktivieren, Speicher-Entladung steuern. - log.debug("Speichersteuerung aktivieren. Speicher-Entladung steuern.") - values_to_write = { - "StorageControlMode": 4, - "StorageChargeDischargeDefaultMode": 7, - "RemoteControlCommandMode": 7, - "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) - } - self._write_registers(values_to_write, unit) - self.last_mode = 'limited' - elif self.last_mode == 'limited': + + elif power_limit >= 0: + """ + Ladung mit Speichersteuerung. + SolarEdge entlaedt den Speicher immer nur bis zur Backup-Reserve. + Bei Systemen ohne Backup-Modul arbeitet sie als SoC-Reserve. + Steuerung beenden, wenn der SoC vom Speicher die SoC-Reserve unterschreitet. + + todo: Ggf. nicht fuer alle Systeme korrekt. Alternative BatteryStatus pruefen. + """ + registers_to_read = [ + f"Battery{self.battery_index}StateOfEnergy", + "StorageBackupReserved", + "RemoteControlCommandDischargeLimit", + ] + values = self._read_registers(registers_to_read, unit) + soc = values[f"Battery{self.battery_index}StateOfEnergy"] + backup_reserve = values["StorageBackupReserved"] + discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) + + if self.last_mode == 'limited': if backup_reserve > soc: # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") @@ -177,12 +149,23 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - else: - log.debug("Speichersteuerung aktiv, Discharge-Limit gesetzt.") + elif discharge_limit not in range (power_limit-10, power_limit+10): + log.debug(f"Speichersteuerung aktiv, Discharge-Limit {power_limit} W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) } self._write_registers(values_to_write, unit) + elif self.last_mode != 'limited' and backup_reserve < soc: + # Speichersteuerung aktivieren, Speicher-Entladung steuern. + log.debug(f"Speichersteuerung aktivieren. Discharge-Limit: {power_limit} W.") + values_to_write = { + "StorageControlMode": 4, + "StorageChargeDischargeDefaultMode": 7, + "RemoteControlCommandMode": 7, + "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) + } + self._write_registers(values_to_write, unit) + self.last_mode = 'limited' def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: values = {} From 013148ddffda2fe1aa532741b4394f594be62237 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 19:49:19 +0100 Subject: [PATCH 04/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index c31bbfdd8c..825b73b7f6 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -149,7 +149,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - elif discharge_limit not in range (power_limit-10, power_limit+10): + elif discharge_limit not in range(power_limit-10, power_limit+10): log.debug(f"Speichersteuerung aktiv, Discharge-Limit {power_limit} W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) From ed46ac48c356d7f25c57efea82c10fc48fad2331 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 20:24:34 +0100 Subject: [PATCH 05/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 825b73b7f6..790c413867 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -135,7 +135,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: values = self._read_registers(registers_to_read, unit) soc = values[f"Battery{self.battery_index}StateOfEnergy"] backup_reserve = values["StorageBackupReserved"] - discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) + discharge_limit = values["RemoteControlCommandDischargeLimit"] if self.last_mode == 'limited': if backup_reserve > soc: @@ -149,7 +149,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - elif discharge_limit not in range(power_limit-10, power_limit+10): + elif int(discharge_limit) not in range(power_limit-10, power_limit+10): log.debug(f"Speichersteuerung aktiv, Discharge-Limit {power_limit} W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) From f03e949102e79097c8eea495a0eb73a8a1976d56 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 22:06:09 +0100 Subject: [PATCH 06/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 790c413867..2514c140e6 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -81,15 +81,15 @@ def read_state(self) -> BatState: def set_power_limit(self, power_limit: Optional[int]) -> None: """ Die Steuerung bei SolarEdge basiert auf folgenden Einstellungen: - Zunächst muss der Storage Control Mode gesetzt werden: + Zunaechst muss der Storage Control Mode gesetzt werden: 1 - Maximize Self Consumption FW < 4.20.36 2 - Time of Use (Steuerung durch SolarEdge Profile) FW >= 4.20.36 - 4 - Remote Control (Für Steuerung durch openWB) + 4 - Remote Control (Fuer Steuerung durch openWB) Dann der Remote Control Command Mode und Default Mode: 7 - Maximize self-consumption - anschließend das DischargLimit setzen. + anschliessend das DischargLimit setzen. - todo: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 prüfen. + todo: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 pruefen. """ unit = self.component_config.configuration.modbus_id PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode @@ -102,6 +102,8 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: """ return + self.inverter_firmware = self. + if power_limit is None: # Keine Ladung mit Speichersteuerung. if self.last_mode is not None: @@ -149,7 +151,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - elif int(discharge_limit) not in range(power_limit-10, power_limit+10): + elif int(discharge_limit) not in range(int(power_limit)-10, int(power_limit)+10): log.debug(f"Speichersteuerung aktiv, Discharge-Limit {power_limit} W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) From fd82c83ec529b47399c9649bdfd651cf511a3bd4 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 20 Mar 2025 22:08:47 +0100 Subject: [PATCH 07/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 2514c140e6..17e1c08901 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -102,8 +102,6 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: """ return - self.inverter_firmware = self. - if power_limit is None: # Keine Ladung mit Speichersteuerung. if self.last_mode is not None: From 1592b920d5550e1632fd71c510b1078772c30874 Mon Sep 17 00:00:00 2001 From: cr0i Date: Sun, 23 Mar 2025 04:29:57 +0100 Subject: [PATCH 08/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 17e1c08901..418b4c1573 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -32,7 +32,6 @@ class SolaredgeBat(AbstractBat): "Battery2InstantaneousPower": (0xe274, ModbusDataType.FLOAT_32,), "Battery2Status": (0xe286, ModbusDataType.UINT_32,), "StorageControlMode": (0xe004, ModbusDataType.UINT_16,), - "StorageBackupReserved": (0xe008, ModbusDataType.FLOAT_32,), "StorageChargeDischargeDefaultMode": (0xe00a, ModbusDataType.UINT_16,), "RemoteControlCommandMode": (0xe00d, ModbusDataType.UINT_16,), "RemoteControlCommandDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,), @@ -50,6 +49,7 @@ def __init__(self, self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) self.last_mode = 'undefined' self.battery_index = 1 # Nach Umsetzung des PR 2236, hier entfernen und unten als battery_index ersetzen. + self.soc_reserve = 10 # SoC-Grenze bis zu der der Speicher entladen wird. In Config aufnehmen. def update(self) -> None: self.store.set(self.read_state()) @@ -79,6 +79,17 @@ def read_state(self) -> BatState: return bat_state def set_power_limit(self, power_limit: Optional[int]) -> None: + unit = self.component_config.configuration.modbus_id + PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode + + if PowerLimitMode == 'no_limit': + """" + Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). + Falls externe Steuerungen aktiv sind, sollten diese nicht beeinfusst werden. + Daher erfolgt im Modus "Immer" der Speichersteuerung gar keine Steuerung. + """ + return + """ Die Steuerung bei SolarEdge basiert auf folgenden Einstellungen: Zunaechst muss der Storage Control Mode gesetzt werden: @@ -91,20 +102,13 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: todo: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 pruefen. """ - unit = self.component_config.configuration.modbus_id - PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode - - if PowerLimitMode == 'no_limit': - """" - Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). - Falls externe Steuerungen aktiv sind, sollten diese nicht beeinfusst werden. - Daher erfolgt im Modus "Immer" der Speichersteuerung gar keine Steuerung. - """ - return - if power_limit is None: # Keine Ladung mit Speichersteuerung. - if self.last_mode is not None: + registers_to_read = [ + "StorageControlMode", + ] + values = self._read_registers(registers_to_read, unit) + if values["StorageControlMode"] == 4: # Steuerung deaktivieren. log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") values_to_write = { @@ -115,30 +119,27 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - # Steuerung bereits inaktiv. - return + else: + # Steuerung bereits inaktiv. + return elif power_limit >= 0: """ Ladung mit Speichersteuerung. - SolarEdge entlaedt den Speicher immer nur bis zur Backup-Reserve. - Bei Systemen ohne Backup-Modul arbeitet sie als SoC-Reserve. + SolarEdge entlaedt den Speicher immer nur bis zur SoC-Reserve. Steuerung beenden, wenn der SoC vom Speicher die SoC-Reserve unterschreitet. - - todo: Ggf. nicht fuer alle Systeme korrekt. Alternative BatteryStatus pruefen. """ registers_to_read = [ f"Battery{self.battery_index}StateOfEnergy", - "StorageBackupReserved", + "StorageControlMode", "RemoteControlCommandDischargeLimit", ] values = self._read_registers(registers_to_read, unit) - soc = values[f"Battery{self.battery_index}StateOfEnergy"] - backup_reserve = values["StorageBackupReserved"] - discharge_limit = values["RemoteControlCommandDischargeLimit"] + soc = int(values[f"Battery{self.battery_index}StateOfEnergy"]) + discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) - if self.last_mode == 'limited': - if backup_reserve > soc: + if values["StorageControlMode"] == 4: # Speichersteuerung aktiv. + if self.soc_reserve > soc: # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") values_to_write = { @@ -149,23 +150,25 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - elif int(discharge_limit) not in range(int(power_limit)-10, int(power_limit)+10): - log.debug(f"Speichersteuerung aktiv, Discharge-Limit {power_limit} W.") + elif discharge_limit not in range(int(power_limit)-10, int(power_limit)+10): + # DischargeLimit nur setzen, bei Abweichung von mehr als 10W. + log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) } self._write_registers(values_to_write, unit) - elif self.last_mode != 'limited' and backup_reserve < soc: - # Speichersteuerung aktivieren, Speicher-Entladung steuern. - log.debug(f"Speichersteuerung aktivieren. Discharge-Limit: {power_limit} W.") - values_to_write = { - "StorageControlMode": 4, - "StorageChargeDischargeDefaultMode": 7, - "RemoteControlCommandMode": 7, - "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) - } - self._write_registers(values_to_write, unit) - self.last_mode = 'limited' + else: # Speichersteuerung inaktiv. + if self.soc_reserve < soc: + # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. + log.debug(f"Speichersteuerung aktivieren. Discharge-Limit: {int(power_limit)} W.") + values_to_write = { + "StorageControlMode": 4, + "StorageChargeDischargeDefaultMode": 7, + "RemoteControlCommandMode": 7, + "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) + } + self._write_registers(values_to_write, unit) + self.last_mode = 'limited' def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: values = {} From 3dfc8cb692068beee7b5f8b642d585480bc2d159 Mon Sep 17 00:00:00 2001 From: cr0i Date: Sun, 23 Mar 2025 04:35:22 +0100 Subject: [PATCH 09/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 418b4c1573..e68ce9e469 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -47,7 +47,6 @@ def __init__(self, self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.last_mode = 'undefined' self.battery_index = 1 # Nach Umsetzung des PR 2236, hier entfernen und unten als battery_index ersetzen. self.soc_reserve = 10 # SoC-Grenze bis zu der der Speicher entladen wird. In Config aufnehmen. @@ -118,7 +117,6 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "StorageControlMode": 2, } self._write_registers(values_to_write, unit) - self.last_mode = None else: # Steuerung bereits inaktiv. return @@ -149,7 +147,6 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "StorageControlMode": 2, } self._write_registers(values_to_write, unit) - self.last_mode = None elif discharge_limit not in range(int(power_limit)-10, int(power_limit)+10): # DischargeLimit nur setzen, bei Abweichung von mehr als 10W. log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") @@ -168,7 +165,6 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) } self._write_registers(values_to_write, unit) - self.last_mode = 'limited' def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: values = {} From fc4faa1c0de1f581e6ad6a0d1fac938572960835 Mon Sep 17 00:00:00 2001 From: cr0i Date: Sun, 23 Mar 2025 04:38:23 +0100 Subject: [PATCH 10/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index e68ce9e469..9e636f6283 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -107,7 +107,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "StorageControlMode", ] values = self._read_registers(registers_to_read, unit) - if values["StorageControlMode"] == 4: + if values["StorageControlMode"] == 4: # Steuerung deaktivieren. log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") values_to_write = { @@ -136,7 +136,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: soc = int(values[f"Battery{self.battery_index}StateOfEnergy"]) discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) - if values["StorageControlMode"] == 4: # Speichersteuerung aktiv. + if values["StorageControlMode"] == 4: # Speichersteuerung aktiv. if self.soc_reserve > soc: # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") From a670195c93019e1c76520c2f086f81c79ab946f8 Mon Sep 17 00:00:00 2001 From: cr0i Date: Wed, 9 Apr 2025 19:05:05 +0200 Subject: [PATCH 11/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 9e636f6283..3b93e04fb2 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -27,11 +27,10 @@ class SolaredgeBat(AbstractBat): REGISTERS = { "Battery1StateOfEnergy": (0xe184, ModbusDataType.FLOAT_32,), # Mirror: 0xf584 "Battery1InstantaneousPower": (0xe174, ModbusDataType.FLOAT_32,), # Mirror: 0xf574 - "Battery1Status": (0xe186, ModbusDataType.UINT_32,), "Battery2StateOfEnergy": (0xe284, ModbusDataType.FLOAT_32,), "Battery2InstantaneousPower": (0xe274, ModbusDataType.FLOAT_32,), - "Battery2Status": (0xe286, ModbusDataType.UINT_32,), "StorageControlMode": (0xe004, ModbusDataType.UINT_16,), + "StorageBackupReserved": (0xe008, ModbusDataType.FLOAT_32,), "StorageChargeDischargeDefaultMode": (0xe00a, ModbusDataType.UINT_16,), "RemoteControlCommandMode": (0xe00d, ModbusDataType.UINT_16,), "RemoteControlCommandDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,), @@ -47,15 +46,17 @@ def __init__(self, self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.battery_index = 1 # Nach Umsetzung des PR 2236, hier entfernen und unten als battery_index ersetzen. - self.soc_reserve = 10 # SoC-Grenze bis zu der der Speicher entladen wird. In Config aufnehmen. + # Battery Index wird erst in PR 2236 umgesetzt, solange wird Default-Wert 1 genutzt: + self.battery_index = getattr(self.component_config.configuration, "battery_index", 1) + # SoC Reserve muss in Configurtion erst noch umgesetzt werden, solange wird Default-Wert 10 genutzt: + self.soc_reserve_configured = getattr(self.component_config.configuration, "soc_reserve", 10) + self.StorageControlMode_Default = 1 # Fallback, falls bisheriger StorageControlMode unbekannt ist def update(self) -> None: self.store.set(self.read_state()) def read_state(self) -> BatState: unit = self.component_config.configuration.modbus_id - registers_to_read = [ f"Battery{self.battery_index}InstantaneousPower", f"Battery{self.battery_index}StateOfEnergy", @@ -65,9 +66,7 @@ def read_state(self) -> BatState: soc = values[f"Battery{self.battery_index}StateOfEnergy"] if power == FLOAT32_UNSUPPORTED: power = 0 - imported, exported = self.sim_counter.sim_count(power) - bat_state = BatState( power=power, soc=soc, @@ -84,23 +83,11 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: if PowerLimitMode == 'no_limit': """" Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). - Falls externe Steuerungen aktiv sind, sollten diese nicht beeinfusst werden. - Daher erfolgt im Modus "Immer" der Speichersteuerung gar keine Steuerung. + Falls andere Steuerungen vorhanden sind, sollten diese nicht beeinflusst werden, + daher erfolgt im Modus "Immer" der Speichersteuerung keine Steuerung. """ return - """ - Die Steuerung bei SolarEdge basiert auf folgenden Einstellungen: - Zunaechst muss der Storage Control Mode gesetzt werden: - 1 - Maximize Self Consumption FW < 4.20.36 - 2 - Time of Use (Steuerung durch SolarEdge Profile) FW >= 4.20.36 - 4 - Remote Control (Fuer Steuerung durch openWB) - Dann der Remote Control Command Mode und Default Mode: - 7 - Maximize self-consumption - anschliessend das DischargLimit setzen. - - todo: Firmware Version aus Register 40044 als String(16) auslesen und gegen Version 4.20.36 pruefen. - """ if power_limit is None: # Keine Ladung mit Speichersteuerung. registers_to_read = [ @@ -114,7 +101,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "RemoteControlCommandDischargeLimit": 5000, "StorageChargeDischargeDefaultMode": 0, "RemoteControlCommandMode": 0, - "StorageControlMode": 2, + "StorageControlMode": self.StorageControlMode_Default, } self._write_registers(values_to_write, unit) else: @@ -130,34 +117,37 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: registers_to_read = [ f"Battery{self.battery_index}StateOfEnergy", "StorageControlMode", + "StorageBackupReserved", "RemoteControlCommandDischargeLimit", ] values = self._read_registers(registers_to_read, unit) soc = int(values[f"Battery{self.battery_index}StateOfEnergy"]) + soc_reserve = max(int(self.soc_reserve_configured), int(values["StorageBackupReserved"])) discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) if values["StorageControlMode"] == 4: # Speichersteuerung aktiv. - if self.soc_reserve > soc: + if soc_reserve >= soc: # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") values_to_write = { "RemoteControlCommandDischargeLimit": 5000, "StorageChargeDischargeDefaultMode": 0, "RemoteControlCommandMode": 0, - "StorageControlMode": 2, + "StorageControlMode": self.StorageControlMode_Default, } self._write_registers(values_to_write, unit) elif discharge_limit not in range(int(power_limit)-10, int(power_limit)+10): - # DischargeLimit nur setzen, bei Abweichung von mehr als 10W. + # DischargeLimit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) } self._write_registers(values_to_write, unit) - else: # Speichersteuerung inaktiv. - if self.soc_reserve < soc: + else: # Speichersteuerung ist inaktiv. + if soc_reserve < soc: # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. log.debug(f"Speichersteuerung aktivieren. Discharge-Limit: {int(power_limit)} W.") + self.StorageControlMode_Default = values["StorageControlMode"] values_to_write = { "StorageControlMode": 4, "StorageChargeDischargeDefaultMode": 7, From 8036b6ff4d02cbbb0906e8da73f91770e51f3e27 Mon Sep 17 00:00:00 2001 From: cr0i Date: Wed, 9 Apr 2025 19:15:36 +0200 Subject: [PATCH 12/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 3b93e04fb2..cb76ec9255 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -194,5 +194,8 @@ def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> return builder.to_registers() + def power_limit_controllable(self) -> bool: + return True + component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 6c6ca07de36f8620d9eea59386c62a6e6e4f44c9 Mon Sep 17 00:00:00 2001 From: cr0i Date: Fri, 18 Apr 2025 18:02:42 +0200 Subject: [PATCH 13/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index cb76ec9255..2d7cc06b39 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Optional +from typing import Any, TypedDict, Dict, Union, Optional from pymodbus.constants import Endian @@ -22,6 +22,11 @@ FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000 +class KwargsDict(TypedDict): + device_id: int + client: modbus.ModbusTcpClient_ + + class SolaredgeBat(AbstractBat): # Define all possible registers with their data types REGISTERS = { @@ -36,13 +41,13 @@ class SolaredgeBat(AbstractBat): "RemoteControlCommandDischargeLimit": (0xe010, ModbusDataType.FLOAT_32,), } - def __init__(self, - device_id: int, - component_config: Union[Dict, SolaredgeBatSetup], - tcp_client: modbus.ModbusTcpClient_) -> None: - self.__device_id = device_id - self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config) - self.__tcp_client = tcp_client + def __init__(self, component_config: SolaredgeBatSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__device_id: int = self.kwargs['device_id'] + self.__tcp_client: modbus.ModbusTcpClient_ = self.kwargs['client'] self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) From 750250381a76489448c73fc422598dfc972cf21c Mon Sep 17 00:00:00 2001 From: cr0i Date: Fri, 18 Apr 2025 18:44:42 +0200 Subject: [PATCH 14/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 2d7cc06b39..82068f88fc 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -5,7 +5,6 @@ from pymodbus.constants import Endian from control import data -from dataclass_utils import dataclass_from_dict from modules.common import modbus from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState From 1ccf078c12fe2ee577fe900af1cbbd8d2c2c4956 Mon Sep 17 00:00:00 2001 From: cr0i Date: Fri, 18 Apr 2025 22:57:39 +0200 Subject: [PATCH 15/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 135 ++++++++++++------ 1 file changed, 91 insertions(+), 44 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 82068f88fc..5532d519c7 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -3,6 +3,7 @@ from typing import Any, TypedDict, Dict, Union, Optional from pymodbus.constants import Endian +import pymodbus from control import data from modules.common import modbus @@ -14,11 +15,16 @@ from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.solaredge.solaredge.config import SolaredgeBatSetup -import pymodbus log = logging.getLogger(__name__) +# Constants for magic numbers and control modes FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000 +MAX_DISCHARGE_LIMIT = 5000 +DEFAULT_CONTROL_MODE = 1 # Control Mode Max Eigenverbrauch +REMOTE_CONTROL_MODE = 4 # Control Mode Remotesteuerung +DEFAULT_COMMAND_MODE = 0 # Command Mode ohne Steuerung +ACTIVE_COMMAND_MODE = 7 # Command Mode Max Eigenverbrauch bei Steuerung class KwargsDict(TypedDict): @@ -45,6 +51,10 @@ def __init__(self, component_config: SolaredgeBatSetup, **kwargs: Any) -> None: self.kwargs: KwargsDict = kwargs def initialize(self) -> None: + # Validate kwargs + if 'device_id' not in self.kwargs or 'client' not in self.kwargs: + raise ValueError("device_id and client must be provided in kwargs") + self.__device_id: int = self.kwargs['device_id'] self.__tcp_client: modbus.ModbusTcpClient_ = self.kwargs['client'] self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") @@ -54,7 +64,7 @@ def initialize(self) -> None: self.battery_index = getattr(self.component_config.configuration, "battery_index", 1) # SoC Reserve muss in Configurtion erst noch umgesetzt werden, solange wird Default-Wert 10 genutzt: self.soc_reserve_configured = getattr(self.component_config.configuration, "soc_reserve", 10) - self.StorageControlMode_Default = 1 # Fallback, falls bisheriger StorageControlMode unbekannt ist + self.StorageControlMode_Read = DEFAULT_CONTROL_MODE def update(self) -> None: self.store.set(self.read_state()) @@ -65,11 +75,22 @@ def read_state(self) -> BatState: f"Battery{self.battery_index}InstantaneousPower", f"Battery{self.battery_index}StateOfEnergy", ] - values = self._read_registers(registers_to_read, unit) + try: + values = self._read_registers(registers_to_read, unit) + except pymodbus.exceptions.ModbusException as e: + log.error(f"Failed to read registers: {e}") + self.fault_state.add_fault(f"Modbus read error: {e}") + return BatState(power=0, soc=0, imported=0, exported=0) + power = values[f"Battery{self.battery_index}InstantaneousPower"] soc = values[f"Battery{self.battery_index}StateOfEnergy"] + if power == FLOAT32_UNSUPPORTED: power = 0 + if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: + log.warning(f"Invalid SoC: {soc}, using 0") + soc = 0 + imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( power=power, @@ -82,10 +103,14 @@ def read_state(self) -> BatState: def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id - PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode + try: + PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode + except AttributeError: + log.warning("PowerLimitMode not found, assuming 'no_limit'") + PowerLimitMode = 'no_limit' if PowerLimitMode == 'no_limit': - """" + """ Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). Falls andere Steuerungen vorhanden sind, sollten diese nicht beeinflusst werden, daher erfolgt im Modus "Immer" der Speichersteuerung keine Steuerung. @@ -94,23 +119,25 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: if power_limit is None: # Keine Ladung mit Speichersteuerung. - registers_to_read = [ - "StorageControlMode", - ] - values = self._read_registers(registers_to_read, unit) - if values["StorageControlMode"] == 4: + registers_to_read = ["StorageControlMode"] + try: + values = self._read_registers(registers_to_read, unit) + except pymodbus.exceptions.ModbusException as e: + log.error(f"Failed to read registers: {e}") + self.fault_state.add_fault(f"Modbus read error: {e}") + return + + if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Steuerung deaktivieren. log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") values_to_write = { - "RemoteControlCommandDischargeLimit": 5000, - "StorageChargeDischargeDefaultMode": 0, - "RemoteControlCommandMode": 0, - "StorageControlMode": self.StorageControlMode_Default, + "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, + "StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE, + "RemoteControlCommandMode": DEFAULT_COMMAND_MODE, + "StorageControlMode": self.StorageControlMode_Read, } self._write_registers(values_to_write, unit) - else: - # Steuerung bereits inaktiv. - return + return elif power_limit >= 0: """ @@ -124,65 +151,84 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "StorageBackupReserved", "RemoteControlCommandDischargeLimit", ] - values = self._read_registers(registers_to_read, unit) - soc = int(values[f"Battery{self.battery_index}StateOfEnergy"]) + try: + values = self._read_registers(registers_to_read, unit) + except pymodbus.exceptions.ModbusException as e: + log.error(f"Failed to read registers: {e}") + self.fault_state.add_fault(f"Modbus read error: {e}") + return + + soc = values[f"Battery{self.battery_index}StateOfEnergy"] + if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: + log.warning(f"Invalid SoC: {soc}, using 0") + soc = 0 + soc = int(soc) # SolarEdge protocol may require integer SoC for comparisons soc_reserve = max(int(self.soc_reserve_configured), int(values["StorageBackupReserved"])) discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) - if values["StorageControlMode"] == 4: # Speichersteuerung aktiv. + if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung aktiv. if soc_reserve >= soc: - # Speichersteuerung deaktivieren, SoC-Reserve unterschritten. + # Speichersteuerung deaktivieren, wenn SoC-Reserve unterschritten. log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") values_to_write = { - "RemoteControlCommandDischargeLimit": 5000, - "StorageChargeDischargeDefaultMode": 0, - "RemoteControlCommandMode": 0, - "StorageControlMode": self.StorageControlMode_Default, + "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, + "StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE, + "RemoteControlCommandMode": DEFAULT_COMMAND_MODE, + "StorageControlMode": self.StorageControlMode_Read, } self._write_registers(values_to_write, unit) - elif discharge_limit not in range(int(power_limit)-10, int(power_limit)+10): + elif discharge_limit not in range(int(power_limit) - 10, int(power_limit) + 10): # DischargeLimit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") values_to_write = { - "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) + "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) else: # Speichersteuerung ist inaktiv. if soc_reserve < soc: # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. log.debug(f"Speichersteuerung aktivieren. Discharge-Limit: {int(power_limit)} W.") - self.StorageControlMode_Default = values["StorageControlMode"] + self.StorageControlMode_Read = values["StorageControlMode"] values_to_write = { - "StorageControlMode": 4, - "StorageChargeDischargeDefaultMode": 7, - "RemoteControlCommandMode": 7, - "RemoteControlCommandDischargeLimit": int(min(power_limit, 5000)) + "StorageControlMode": REMOTE_CONTROL_MODE, + "StorageChargeDischargeDefaultMode": ACTIVE_COMMAND_MODE, + "RemoteControlCommandMode": ACTIVE_COMMAND_MODE, + "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: values = {} for key in register_names: - log.debug(f"Bat raw values {self.__tcp_client.address}: {values}") address, data_type = self.REGISTERS[key] - values[key] = self.__tcp_client.read_holding_registers( - address, data_type, wordorder=Endian.Little, unit=unit - ) + try: + values[key] = self.__tcp_client.read_holding_registers( + address, data_type, wordorder=Endian.Little, unit=unit + ) + except pymodbus.exceptions.ModbusException as e: + log.error(f"Failed to read register {key} at address {address}: {e}") + self.fault_state.add_fault(f"Modbus read error: {e}") + values[key] = 0 # Fallback value log.debug(f"Bat raw values {self.__tcp_client.address}: {values}") return values + # TODO: Optimize to read multiple contiguous registers in a single request if supported by ModbusTcpClient_ def _write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: int) -> None: for key, value in values_to_write.items(): address, data_type = self.REGISTERS[key] encoded_value = self._encode_value(value, data_type) - self.__tcp_client.write_registers(address, encoded_value, unit=unit) - log.debug(f"Neuer Wert {encoded_value} in Register {address} geschrieben.") + try: + self.__tcp_client.write_registers(address, encoded_value, unit=unit) + log.debug(f"Neuer Wert {encoded_value} in Register {address} geschrieben.") + except pymodbus.exceptions.ModbusException as e: + log.error(f"Failed to write register {key} at address {address}: {e}") + self.fault_state.add_fault(f"Modbus write error: {e}") def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> list: builder = pymodbus.payload.BinaryPayloadBuilder( - byteorder=pymodbus.constants.Endian.Big, - wordorder=pymodbus.constants.Endian.Little - ) + byteorder=pymodbus.constants.Endian.Big, + wordorder=pymodbus.constants.Endian.Little + ) encode_methods = { ModbusDataType.UINT_32: builder.add_32bit_uint, ModbusDataType.INT_32: builder.add_32bit_int, @@ -190,12 +236,13 @@ def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> ModbusDataType.INT_16: builder.add_16bit_int, ModbusDataType.FLOAT_32: builder.add_32bit_float, } - if data_type in encode_methods: - encode_methods[data_type](int(value)) + if data_type == ModbusDataType.FLOAT_32: + encode_methods[data_type](float(value)) + else: + encode_methods[data_type](int(value)) else: raise ValueError(f"Unsupported data type: {data_type}") - return builder.to_registers() def power_limit_controllable(self) -> bool: From 0fbc9fb2d54165dcac2f9164bf15c71fc8a65920 Mon Sep 17 00:00:00 2001 From: cr0i Date: Wed, 23 Apr 2025 22:29:26 +0200 Subject: [PATCH 16/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 5532d519c7..a6018d114f 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -65,6 +65,7 @@ def initialize(self) -> None: # SoC Reserve muss in Configurtion erst noch umgesetzt werden, solange wird Default-Wert 10 genutzt: self.soc_reserve_configured = getattr(self.component_config.configuration, "soc_reserve", 10) self.StorageControlMode_Read = DEFAULT_CONTROL_MODE + self.last_mode = 'undefined' def update(self) -> None: self.store.set(self.read_state()) @@ -109,7 +110,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: log.warning("PowerLimitMode not found, assuming 'no_limit'") PowerLimitMode = 'no_limit' - if PowerLimitMode == 'no_limit': + if PowerLimitMode == 'no_limit' and self.last_mode != 'limited': """ Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). Falls andere Steuerungen vorhanden sind, sollten diese nicht beeinflusst werden, @@ -137,6 +138,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "StorageControlMode": self.StorageControlMode_Read, } self._write_registers(values_to_write, unit) + self.last_mode = None return elif power_limit >= 0: @@ -177,6 +179,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "StorageControlMode": self.StorageControlMode_Read, } self._write_registers(values_to_write, unit) + self.last_mode = None elif discharge_limit not in range(int(power_limit) - 10, int(power_limit) + 10): # DischargeLimit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") @@ -196,6 +199,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) + self.last_mode = 'limited' def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: values = {} From b2149384b17b2710bc8b5640250e278678eb7343 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 24 Apr 2025 19:49:53 +0200 Subject: [PATCH 17/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index a6018d114f..865dce25af 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -104,6 +104,7 @@ def read_state(self) -> BatState: def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id + try: PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode except AttributeError: @@ -120,15 +121,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: if power_limit is None: # Keine Ladung mit Speichersteuerung. - registers_to_read = ["StorageControlMode"] - try: - values = self._read_registers(registers_to_read, unit) - except pymodbus.exceptions.ModbusException as e: - log.error(f"Failed to read registers: {e}") - self.fault_state.add_fault(f"Modbus read error: {e}") - return - - if values["StorageControlMode"] == REMOTE_CONTROL_MODE: + if self.last_mode == 'limited': # Steuerung deaktivieren. log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") values_to_write = { @@ -139,7 +132,8 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None - return + else: + return elif power_limit >= 0: """ @@ -159,16 +153,14 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: log.error(f"Failed to read registers: {e}") self.fault_state.add_fault(f"Modbus read error: {e}") return - soc = values[f"Battery{self.battery_index}StateOfEnergy"] if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: log.warning(f"Invalid SoC: {soc}, using 0") soc = 0 - soc = int(soc) # SolarEdge protocol may require integer SoC for comparisons soc_reserve = max(int(self.soc_reserve_configured), int(values["StorageBackupReserved"])) discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) - if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung aktiv. + if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung ist aktiv. if soc_reserve >= soc: # Speichersteuerung deaktivieren, wenn SoC-Reserve unterschritten. log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") @@ -180,13 +172,16 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: } self._write_registers(values_to_write, unit) self.last_mode = None + elif discharge_limit not in range(int(power_limit) - 10, int(power_limit) + 10): - # DischargeLimit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. + # Limit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) + self.last_mode = 'limited' + else: # Speichersteuerung ist inaktiv. if soc_reserve < soc: # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. From 8be889159e0cedb7f94100ed920e2adec97eb2ad Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 24 Apr 2025 20:46:20 +0200 Subject: [PATCH 18/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 865dce25af..758ee90d40 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -51,10 +51,6 @@ def __init__(self, component_config: SolaredgeBatSetup, **kwargs: Any) -> None: self.kwargs: KwargsDict = kwargs def initialize(self) -> None: - # Validate kwargs - if 'device_id' not in self.kwargs or 'client' not in self.kwargs: - raise ValueError("device_id and client must be provided in kwargs") - self.__device_id: int = self.kwargs['device_id'] self.__tcp_client: modbus.ModbusTcpClient_ = self.kwargs['client'] self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") From 29e495fdf1e340104b0b7507ba54afe2b1cabc07 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 24 Apr 2025 22:13:03 +0200 Subject: [PATCH 19/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 7feb0f1dbe..7b46f454c5 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -32,11 +32,6 @@ ACTIVE_COMMAND_MODE = 7 # Command Mode Max Eigenverbrauch bei Steuerung -class KwargsDict(TypedDict): - device_id: int - client: modbus.ModbusTcpClient_ - - class KwargsDict(TypedDict): device_id: int client: modbus.ModbusTcpClient_ From 340fe109de14b0c903bb94864db2656788572c2c Mon Sep 17 00:00:00 2001 From: cr0i Date: Tue, 3 Jun 2025 00:14:29 +0200 Subject: [PATCH 20/35] Update bat.py --- .../modules/devices/solaredge/solaredge/bat.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 7b46f454c5..56a4b904b4 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -81,7 +81,7 @@ def read_state(self) -> BatState: values = self._read_registers(registers_to_read, unit) except pymodbus.exceptions.ModbusException as e: log.error(f"Failed to read registers: {e}") - self.fault_state.add_fault(f"Modbus read error: {e}") + self.fault_state.error(f"Modbus read error: {e}") return BatState(power=0, soc=0, imported=0, exported=0) power = values[f"Battery{self.battery_index}InstantaneousPower"] @@ -107,12 +107,12 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id try: - PowerLimitMode = data.data.bat_all_data.data.config.power_limit_mode + power_limit_mode = data.data.bat_all_data.data.config.power_limit_mode except AttributeError: - log.warning("PowerLimitMode not found, assuming 'no_limit'") - PowerLimitMode = 'no_limit' + log.warning("power_limit_mode not found, assuming 'no_limit'") + power_limit_mode = 'no_limit' - if PowerLimitMode == 'no_limit' and self.last_mode != 'limited': + if power_limit_mode == 'no_limit' and self.last_mode != 'limited': """ Keine Speichersteuerung, andere Steuerungen zulassen (SolarEdge One, ioBroker, Node-Red etc.). Falls andere Steuerungen vorhanden sind, sollten diese nicht beeinflusst werden, @@ -152,7 +152,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: values = self._read_registers(registers_to_read, unit) except pymodbus.exceptions.ModbusException as e: log.error(f"Failed to read registers: {e}") - self.fault_state.add_fault(f"Modbus read error: {e}") + self.fault_state.error(f"Modbus read error: {e}") return soc = values[f"Battery{self.battery_index}StateOfEnergy"] if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: @@ -207,7 +207,7 @@ def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[in ) except pymodbus.exceptions.ModbusException as e: log.error(f"Failed to read register {key} at address {address}: {e}") - self.fault_state.add_fault(f"Modbus read error: {e}") + self.fault_state.error(f"Modbus read error: {e}") values[key] = 0 # Fallback value log.debug(f"Bat raw values {self.__tcp_client.address}: {values}") return values @@ -222,7 +222,7 @@ def _write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: log.debug(f"Neuer Wert {encoded_value} in Register {address} geschrieben.") except pymodbus.exceptions.ModbusException as e: log.error(f"Failed to write register {key} at address {address}: {e}") - self.fault_state.add_fault(f"Modbus write error: {e}") + self.fault_state.error(f"Modbus write error: {e}") def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> list: builder = pymodbus.payload.BinaryPayloadBuilder( From e08c6c118c9d8cf0aa29a107ec14948375b87cc5 Mon Sep 17 00:00:00 2001 From: cr0i Date: Wed, 4 Jun 2025 19:30:48 +0200 Subject: [PATCH 21/35] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 56a4b904b4..8dbea2360b 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import logging -from typing import Any, TypedDict, Dict, Union, Optional +from typing import Any, TypedDict, Dict, Union, Optional, Tuple from pymodbus.constants import Endian @@ -23,7 +23,6 @@ log = logging.getLogger(__name__) -# Constants for magic numbers and control modes FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000 MAX_DISCHARGE_LIMIT = 5000 DEFAULT_CONTROL_MODE = 1 # Control Mode Max Eigenverbrauch @@ -61,8 +60,6 @@ def initialize(self) -> None: self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - # Battery Index wird erst in PR 2236 umgesetzt, solange wird Default-Wert 1 genutzt: - self.battery_index = getattr(self.component_config.configuration, "battery_index", 1) # SoC Reserve muss in Configurtion erst noch umgesetzt werden, solange wird Default-Wert 10 genutzt: self.soc_reserve_configured = getattr(self.component_config.configuration, "soc_reserve", 10) self.StorageControlMode_Read = DEFAULT_CONTROL_MODE @@ -71,37 +68,53 @@ def initialize(self) -> None: def update(self) -> None: self.store.set(self.read_state()) - def read_state(self) -> BatState: + def read_state(self): + power, soc = self.get_values() + imported, exported = self.get_imported_exported(power) + return BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + + def get_values(self) -> Tuple[float, float]: unit = self.component_config.configuration.modbus_id - registers_to_read = [ - f"Battery{self.battery_index}InstantaneousPower", - f"Battery{self.battery_index}StateOfEnergy", - ] - try: - values = self._read_registers(registers_to_read, unit) - except pymodbus.exceptions.ModbusException as e: - log.error(f"Failed to read registers: {e}") - self.fault_state.error(f"Modbus read error: {e}") - return BatState(power=0, soc=0, imported=0, exported=0) + # Use 1 as fallback if battery_index is not set + battery_index = getattr(self.component_config.configuration, "battery_index", 1) + + # Define base registers for Battery 1 in hex + base_soc_reg = 0xE184 # Battery 1 SoC + base_power_reg = 0xE174 # Battery 1 Power + offset = 0x100 # 256 bytes in hex + + # Adjust registers based on battery_index + if battery_index == 1: + soc_reg = base_soc_reg + power_reg = base_power_reg + elif battery_index == 2: + soc_reg = base_soc_reg + offset # 0xE284 + power_reg = base_power_reg + offset # 0xE274 + else: + raise ValueError(f"Invalid battery_index: {battery_index}. Must be 1 or 2.") - power = values[f"Battery{self.battery_index}InstantaneousPower"] - soc = values[f"Battery{self.battery_index}StateOfEnergy"] + # Read SoC and Power from the appropriate registers + soc = self.__tcp_client.read_holding_registers( + soc_reg, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit + ) + power = self.__tcp_client.read_holding_registers( + power_reg, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit + ) + # Handle unsupported FLOAT32 case if power == FLOAT32_UNSUPPORTED: power = 0 - if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Invalid SoC: {soc}, using 0") - soc = 0 - imported, exported = self.sim_counter.sim_count(power) - bat_state = BatState( - power=power, - soc=soc, - imported=imported, - exported=exported - ) - log.debug(f"Bat {self.__tcp_client.address}: {bat_state}") - return bat_state + return power, soc + + def get_imported_exported(self, power: float) -> Tuple[float, float]: + return self.sim_counter.sim_count(power) + def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id @@ -249,4 +262,4 @@ def power_limit_controllable(self) -> bool: return True -component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) +component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) \ No newline at end of file From 539e4e070f48a9003b21615bfb1f2682f5e21f8f Mon Sep 17 00:00:00 2001 From: cr0i Date: Wed, 4 Jun 2025 19:38:51 +0200 Subject: [PATCH 22/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 8dbea2360b..b761d43949 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -115,7 +115,6 @@ def get_values(self) -> Tuple[float, float]: def get_imported_exported(self, power: float) -> Tuple[float, float]: return self.sim_counter.sim_count(power) - def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id @@ -262,4 +261,4 @@ def power_limit_controllable(self) -> bool: return True -component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) \ No newline at end of file +component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 104f2f6fd20592ab1c72636fa0de8598129389e5 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 5 Jun 2025 19:33:18 +0200 Subject: [PATCH 23/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index b761d43949..0abbe1c59a 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -60,8 +60,8 @@ def initialize(self) -> None: self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - # SoC Reserve muss in Configurtion erst noch umgesetzt werden, solange wird Default-Wert 10 genutzt: - self.soc_reserve_configured = getattr(self.component_config.configuration, "soc_reserve", 10) + self.min_soc = 100 + self.soc_reserve = 100 self.StorageControlMode_Read = DEFAULT_CONTROL_MODE self.last_mode = 'undefined' @@ -71,6 +71,8 @@ def update(self) -> None: def read_state(self): power, soc = self.get_values() imported, exported = self.get_imported_exported(power) + self.min_soc = min(int(soc), int(self.min_soc)) + log.debug(f"Min-SoC: {int(self.min_soc)}%.") return BatState( power=power, soc=soc, @@ -170,7 +172,8 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: log.warning(f"Invalid SoC: {soc}, using 0") soc = 0 - soc_reserve = max(int(self.soc_reserve_configured), int(values["StorageBackupReserved"])) + soc_reserve = max(int(self.min_soc + 2), int(values["StorageBackupReserved"])) + log.debug(f"SoC-Reserve: {int(soc_reserve)}%.") discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung ist aktiv. From 4341014743ed8f32cf07c7c74aa42275da0ffc90 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 5 Jun 2025 20:09:32 +0200 Subject: [PATCH 24/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 0abbe1c59a..8d6ca7e6e4 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -60,8 +60,8 @@ def initialize(self) -> None: self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.min_soc = 100 - self.soc_reserve = 100 + self.min_soc = 35 + self.soc_reserve = 35 self.StorageControlMode_Read = DEFAULT_CONTROL_MODE self.last_mode = 'undefined' From 8eff0a72cfe12c24a727f9a2e41bdc2fb1ce836e Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 5 Jun 2025 21:10:25 +0200 Subject: [PATCH 25/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 8d6ca7e6e4..ed6cdbd7e3 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -61,7 +61,6 @@ def initialize(self) -> None: self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) self.min_soc = 35 - self.soc_reserve = 35 self.StorageControlMode_Read = DEFAULT_CONTROL_MODE self.last_mode = 'undefined' From 962bd9081df254f9b99a64344d5b1120d58e019c Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 5 Jun 2025 21:28:05 +0200 Subject: [PATCH 26/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index ed6cdbd7e3..645d4a3118 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -117,6 +117,7 @@ def get_imported_exported(self, power: float) -> Tuple[float, float]: return self.sim_counter.sim_count(power) def set_power_limit(self, power_limit: Optional[int]) -> None: + battery_index = getattr(self.component_config.configuration, "battery_index", 1) unit = self.component_config.configuration.modbus_id try: @@ -156,7 +157,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: Steuerung beenden, wenn der SoC vom Speicher die SoC-Reserve unterschreitet. """ registers_to_read = [ - f"Battery{self.battery_index}StateOfEnergy", + f"Battery{battery_index}StateOfEnergy", "StorageControlMode", "StorageBackupReserved", "RemoteControlCommandDischargeLimit", @@ -167,7 +168,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: log.error(f"Failed to read registers: {e}") self.fault_state.error(f"Modbus read error: {e}") return - soc = values[f"Battery{self.battery_index}StateOfEnergy"] + soc = values[f"Battery{battery_index}StateOfEnergy"] if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: log.warning(f"Invalid SoC: {soc}, using 0") soc = 0 From 3133f9385fcb7989c343fc05bf8de85730d7db79 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 5 Jun 2025 21:48:15 +0200 Subject: [PATCH 27/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 645d4a3118..395a405a56 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -177,8 +177,9 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung ist aktiv. - if soc_reserve >= soc: - # Speichersteuerung deaktivieren, wenn SoC-Reserve unterschritten. + if soc_reserve > soc: + # Speichersteuerung erst deaktivieren, wenn SoC-Reserve unterschritten wird. + # Darf wegen 2 Speichern nicht bereits bei SoC-Reserve deaktiviert werden! log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") values_to_write = { "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, From c8920ca2cf0b8fea7d0517f22193d28a68198ecc Mon Sep 17 00:00:00 2001 From: cr0i Date: Fri, 6 Jun 2025 02:58:54 +0200 Subject: [PATCH 28/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 395a405a56..e16d8bff19 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -70,8 +70,6 @@ def update(self) -> None: def read_state(self): power, soc = self.get_values() imported, exported = self.get_imported_exported(power) - self.min_soc = min(int(soc), int(self.min_soc)) - log.debug(f"Min-SoC: {int(self.min_soc)}%.") return BatState( power=power, soc=soc, @@ -107,10 +105,15 @@ def get_values(self) -> Tuple[float, float]: power_reg, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit ) - # Handle unsupported FLOAT32 case + # Handle unsupported case if power == FLOAT32_UNSUPPORTED: power = 0 - + if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: + log.warning(f"Invalid SoC: {soc}, using 0") + soc = 0 + else: + self.min_soc = min(int(soc), int(self.min_soc)) + log.debug(f"Min-SoC: {int(self.min_soc)}%.") return power, soc def get_imported_exported(self, power: float) -> Tuple[float, float]: From bc91903c042f6537bf6730df445cb2ecb767f551 Mon Sep 17 00:00:00 2001 From: cr0i Date: Fri, 6 Jun 2025 03:47:57 +0200 Subject: [PATCH 29/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index e16d8bff19..453e8bf105 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -109,8 +109,7 @@ def get_values(self) -> Tuple[float, float]: if power == FLOAT32_UNSUPPORTED: power = 0 if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Invalid SoC: {soc}, using 0") - soc = 0 + log.warning(f"Invalid SoC: {soc}") else: self.min_soc = min(int(soc), int(self.min_soc)) log.debug(f"Min-SoC: {int(self.min_soc)}%.") @@ -173,8 +172,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: return soc = values[f"Battery{battery_index}StateOfEnergy"] if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Invalid SoC: {soc}, using 0") - soc = 0 + log.warning(f"Invalid SoC: {soc}") soc_reserve = max(int(self.min_soc + 2), int(values["StorageBackupReserved"])) log.debug(f"SoC-Reserve: {int(soc_reserve)}%.") discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) From 125d3408b8afcc08c7541452cd0f819e10035259 Mon Sep 17 00:00:00 2001 From: cr0i Date: Fri, 6 Jun 2025 20:18:52 +0200 Subject: [PATCH 30/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 453e8bf105..cc29087f23 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -113,6 +113,7 @@ def get_values(self) -> Tuple[float, float]: else: self.min_soc = min(int(soc), int(self.min_soc)) log.debug(f"Min-SoC: {int(self.min_soc)}%.") + return power, soc def get_imported_exported(self, power: float) -> Tuple[float, float]: From 0abae5ad48c4740cb581595fc71e47aed73ee4d3 Mon Sep 17 00:00:00 2001 From: cr0i Date: Mon, 9 Jun 2025 18:40:34 +0200 Subject: [PATCH 31/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index cc29087f23..1edd176b12 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -60,7 +60,7 @@ def initialize(self) -> None: self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.min_soc = 35 + self.min_soc = 8 self.StorageControlMode_Read = DEFAULT_CONTROL_MODE self.last_mode = 'undefined' From 2c7dbb7a32da979d40092378a833fd5566e2fe13 Mon Sep 17 00:00:00 2001 From: cr0i Date: Mon, 9 Jun 2025 18:41:52 +0200 Subject: [PATCH 32/35] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 1edd176b12..d0bc79c92c 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -120,8 +120,9 @@ def get_imported_exported(self, power: float) -> Tuple[float, float]: return self.sim_counter.sim_count(power) def set_power_limit(self, power_limit: Optional[int]) -> None: - battery_index = getattr(self.component_config.configuration, "battery_index", 1) unit = self.component_config.configuration.modbus_id + # Use 1 as fallback if battery_index is not set + battery_index = getattr(self.component_config.configuration, "battery_index", 1) try: power_limit_mode = data.data.bat_all_data.data.config.power_limit_mode From 4ca383017cd4d25e81cfda65e1024b9fcf98501f Mon Sep 17 00:00:00 2001 From: cr0i Date: Mon, 9 Jun 2025 19:11:28 +0200 Subject: [PATCH 33/35] Update bat.py --- .../modules/devices/solaredge/solaredge/bat.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index d0bc79c92c..69dc48ac3f 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -109,10 +109,10 @@ def get_values(self) -> Tuple[float, float]: if power == FLOAT32_UNSUPPORTED: power = 0 if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Invalid SoC: {soc}") + log.warning(f"Invalid SoC Speicher {battery_index}: {soc}") else: self.min_soc = min(int(soc), int(self.min_soc)) - log.debug(f"Min-SoC: {int(self.min_soc)}%.") + log.debug(f"Min-SoC Speicher {battery_index}: {int(self.min_soc)}%.") return power, soc @@ -142,7 +142,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: # Keine Ladung mit Speichersteuerung. if self.last_mode == 'limited': # Steuerung deaktivieren. - log.debug("Keine Speichersteuerung gefordert, Steuerung deaktivieren.") + log.debug(f"Speicher {battery_index}:Keine Steuerung gefordert, Steuerung deaktivieren.") values_to_write = { "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, "StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE, @@ -174,16 +174,16 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: return soc = values[f"Battery{battery_index}StateOfEnergy"] if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Invalid SoC: {soc}") + log.warning(f"Speicher {battery_index}: Invalid SoC: {soc}") soc_reserve = max(int(self.min_soc + 2), int(values["StorageBackupReserved"])) - log.debug(f"SoC-Reserve: {int(soc_reserve)}%.") + log.debug(f"Speicher {battery_index}: SoC-Reserve: {int(soc_reserve)}%.") discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung ist aktiv. if soc_reserve > soc: # Speichersteuerung erst deaktivieren, wenn SoC-Reserve unterschritten wird. # Darf wegen 2 Speichern nicht bereits bei SoC-Reserve deaktiviert werden! - log.debug("Speichersteuerung deaktivieren. SoC-Reserve unterschritten.") + log.debug(f"Speicher {battery_index}:Steuerung deaktivieren. SoC-Reserve unterschritten") values_to_write = { "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, "StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE, @@ -195,7 +195,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: elif discharge_limit not in range(int(power_limit) - 10, int(power_limit) + 10): # Limit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. - log.debug(f"Speichersteuerung aktiv, Discharge-Limit {int(power_limit)}W.") + log.debug(f"Speicher {battery_index}:Discharge-Limit {int(power_limit)}W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) } @@ -205,7 +205,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: else: # Speichersteuerung ist inaktiv. if soc_reserve < soc: # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. - log.debug(f"Speichersteuerung aktivieren. Discharge-Limit: {int(power_limit)} W.") + log.debug(f"Speicher {battery_index}:Discharge-Limit aktivieren: {int(power_limit)}W.") self.StorageControlMode_Read = values["StorageControlMode"] values_to_write = { "StorageControlMode": REMOTE_CONTROL_MODE, From f8e5b963fe34d648f625dd67bc101485218c7ea6 Mon Sep 17 00:00:00 2001 From: cr0i Date: Mon, 9 Jun 2025 19:23:21 +0200 Subject: [PATCH 34/35] Update bat.py --- .../modules/devices/solaredge/solaredge/bat.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 69dc48ac3f..a847a4b37c 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -109,10 +109,10 @@ def get_values(self) -> Tuple[float, float]: if power == FLOAT32_UNSUPPORTED: power = 0 if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Invalid SoC Speicher {battery_index}: {soc}") + log.warning(f"Invalid SoC Speicher{battery_index}: {soc}") else: self.min_soc = min(int(soc), int(self.min_soc)) - log.debug(f"Min-SoC Speicher {battery_index}: {int(self.min_soc)}%.") + log.debug(f"Min-SoC Speicher{battery_index}: {int(self.min_soc)}%.") return power, soc @@ -142,7 +142,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: # Keine Ladung mit Speichersteuerung. if self.last_mode == 'limited': # Steuerung deaktivieren. - log.debug(f"Speicher {battery_index}:Keine Steuerung gefordert, Steuerung deaktivieren.") + log.debug(f"Speicher{battery_index}:Keine Steuerung gefordert, Steuerung deaktivieren.") values_to_write = { "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, "StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE, @@ -174,16 +174,16 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: return soc = values[f"Battery{battery_index}StateOfEnergy"] if soc == FLOAT32_UNSUPPORTED or not 0 <= soc <= 100: - log.warning(f"Speicher {battery_index}: Invalid SoC: {soc}") + log.warning(f"Speicher{battery_index}: Invalid SoC: {soc}") soc_reserve = max(int(self.min_soc + 2), int(values["StorageBackupReserved"])) - log.debug(f"Speicher {battery_index}: SoC-Reserve: {int(soc_reserve)}%.") + log.debug(f"SoC-Reserve Speicher{battery_index}: {int(soc_reserve)}%.") discharge_limit = int(values["RemoteControlCommandDischargeLimit"]) if values["StorageControlMode"] == REMOTE_CONTROL_MODE: # Speichersteuerung ist aktiv. if soc_reserve > soc: # Speichersteuerung erst deaktivieren, wenn SoC-Reserve unterschritten wird. # Darf wegen 2 Speichern nicht bereits bei SoC-Reserve deaktiviert werden! - log.debug(f"Speicher {battery_index}:Steuerung deaktivieren. SoC-Reserve unterschritten") + log.debug(f"Speicher{battery_index}: Steuerung deaktivieren. SoC-Reserve unterschritten") values_to_write = { "RemoteControlCommandDischargeLimit": MAX_DISCHARGE_LIMIT, "StorageChargeDischargeDefaultMode": DEFAULT_COMMAND_MODE, @@ -195,7 +195,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: elif discharge_limit not in range(int(power_limit) - 10, int(power_limit) + 10): # Limit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. - log.debug(f"Speicher {battery_index}:Discharge-Limit {int(power_limit)}W.") + log.debug(f"Discharge-Limit Speicher{battery_index}: {int(power_limit)}W.") values_to_write = { "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) } @@ -205,7 +205,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: else: # Speichersteuerung ist inaktiv. if soc_reserve < soc: # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. - log.debug(f"Speicher {battery_index}:Discharge-Limit aktivieren: {int(power_limit)}W.") + log.debug(f"Discharge-Limit aktivieren, Speicher{battery_index}: {int(power_limit)}W.") self.StorageControlMode_Read = values["StorageControlMode"] values_to_write = { "StorageControlMode": REMOTE_CONTROL_MODE, From 175dbbc3acc9ad6ecf6168b9b6aa661ec0cb7439 Mon Sep 17 00:00:00 2001 From: cr0i Date: Thu, 12 Jun 2025 15:49:46 +0200 Subject: [PATCH 35/35] Update power_limit sign --- packages/modules/devices/solaredge/solaredge/bat.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index a847a4b37c..bf25c63ecc 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -154,7 +154,7 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: else: return - elif power_limit >= 0: + elif abs(power_limit) >= 0: """ Ladung mit Speichersteuerung. SolarEdge entlaedt den Speicher immer nur bis zur SoC-Reserve. @@ -193,11 +193,11 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self._write_registers(values_to_write, unit) self.last_mode = None - elif discharge_limit not in range(int(power_limit) - 10, int(power_limit) + 10): + elif discharge_limit not in range(int(abs(power_limit)) - 10, int(abs(power_limit)) + 10): # Limit nur bei Abweichung von mehr als 10W, um Konflikte bei 2 Speichern zu verhindern. - log.debug(f"Discharge-Limit Speicher{battery_index}: {int(power_limit)}W.") + log.debug(f"Discharge-Limit Speicher{battery_index}: {int(abs(power_limit))}W.") values_to_write = { - "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) + "RemoteControlCommandDischargeLimit": int(min(abs(power_limit), MAX_DISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) self.last_mode = 'limited' @@ -205,13 +205,13 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: else: # Speichersteuerung ist inaktiv. if soc_reserve < soc: # Speichersteuerung nur aktivieren, wenn SoC ueber SoC-Reserve. - log.debug(f"Discharge-Limit aktivieren, Speicher{battery_index}: {int(power_limit)}W.") + log.debug(f"Discharge-Limit aktivieren, Speicher{battery_index}: {int(abs(power_limit))}W.") self.StorageControlMode_Read = values["StorageControlMode"] values_to_write = { "StorageControlMode": REMOTE_CONTROL_MODE, "StorageChargeDischargeDefaultMode": ACTIVE_COMMAND_MODE, "RemoteControlCommandMode": ACTIVE_COMMAND_MODE, - "RemoteControlCommandDischargeLimit": int(min(power_limit, MAX_DISCHARGE_LIMIT)) + "RemoteControlCommandDischargeLimit": int(min(abs(power_limit), MAX_DISCHARGE_LIMIT)) } self._write_registers(values_to_write, unit) self.last_mode = 'limited'