From bcc8158b972c940a03daf51da04f328675be82b5 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:04:35 +0100 Subject: [PATCH 01/13] feat(solaredge): Add active battery control with power limit setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neue Funktionalität: - `set_power_limit`: Ermöglicht die dynamische Steuerung des Entladelimits. - Standardwert 5000 W bei `None` (Null-Punkt-Ausregelung). - Validierung von Werten (nur 0-5000 W erlaubt). - Schreibvorgänge erfolgen nur, wenn das bestehende Limit abweicht. **Hinweis:** Für die aktive Steuerung muss der Speicher auf **Control Mode 4 (Remote)** gesetzt werden (`StorageConf_CtrlMode (0xE004)`). Dies muss **extern** erfolgen und kann nicht von OpenWB automatisch übernommen werden. --- .../devices/solaredge/solaredge/bat.py | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 59483aefa0..1d93a1a58b 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, Tuple, Union +from typing import Dict, Tuple, Union, Optional from pymodbus.constants import Endian @@ -17,8 +17,6 @@ log = logging.getLogger(__name__) -FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000 - class SolaredgeBat(AbstractBat): def __init__(self, @@ -35,9 +33,13 @@ def __init__(self, def update(self) -> None: self.store.set(self.read_state()) - def read_state(self): + def read_state(self) -> BatState: power, soc = self.get_values() - imported, exported = self.get_imported_exported(power) + if soc is None or power is None: + log.error("Ungültige Werte aus den Modbus-Registern gelesen.") + return BatState(power=0, soc=0, imported=0, exported=0) + imported, exported = self.sim_counter.sim_count(power) + log.debug(f"Gelesen - Power: {power}, SOC: {soc}") return BatState( power=power, soc=soc, @@ -45,18 +47,59 @@ def read_state(self): exported=exported ) - def get_values(self) -> Tuple[float, float]: + def get_values(self) -> Tuple[Optional[float], Optional[float]]: 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 - - def get_imported_exported(self, power: float) -> Tuple[float, float]: - return self.sim_counter.sim_count(power) + try: + soc = self.__tcp_client.read_holding_registers( + 57732, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) # SOC Register + power = self.__tcp_client.read_holding_registers( + 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) # Power Register + return power, soc + except Exception as e: + log.error(f"Fehler beim Lesen von Modbus-Registern: {e}") + return None, None + + def set_power_limit(self, power_limit: Optional[int]) -> None: + """ + Setzt die Leistungsbegrenzung für die Batterie. + - Bei None wird die Null-Punkt-Ausregelung aktiviert, Standardwert 5000 W. + - Bei Übergabe einer Zahl wird die Begrenzung entsprechend gesetzt. + - Schreibt den Wert nur, wenn er vom bestehenden Limit abweicht. + + Hinweis: + Für die aktive Steuerung muss der Speicher in den Remote-Modus versetzt werden. + Dazu muss `StorageConf_CtrlMode (0xE004)` auf 4 ("Remote") gesetzt werden. + """ + DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register + unit = self.component_config.configuration.modbus_id + + # Logik bei None + if power_limit is None: + log.debug("Null-Punkt-Ausregelung aktiviert, Entladelimit wird auf 5000 W gesetzt.") + power_limit = 5000 # Maximale Entladeleistung + + # Validierung für übergebene Werte + elif power_limit < 0 or power_limit > 5000: + log.error(f"Ungültiges Entladelimit: {power_limit}. Muss zwischen 0 und 5000 liegen.") + return + + try: + # Bestehendes Limit lesen + current_limit = self.__tcp_client.read_holding_registers( + DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) + + # Nur schreiben, wenn der neue Wert vom bestehenden abweicht + if current_limit == power_limit: + log.debug(f"Entladelimit ist bereits auf {current_limit} W gesetzt. Kein Schreiben erforderlich.") + return + + # Neuen Wert schreiben + self.__tcp_client.write_registers( + DISCHARGE_LIMIT_REGISTER, power_limit, unit=unit) + log.debug(f"Entladelimit erfolgreich auf {power_limit} W gesetzt.") + + except Exception as e: + log.error(f"Fehler beim Setzen des Entladelimits: {e}") component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 239a842d7738347524cbfbefeaa294da12840411 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:02:34 +0100 Subject: [PATCH 02/13] UTF-8 encoding and improve robustness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Replaced all non-ASCII characters (e.g., "Umlauts") with UTF-8-safe alternatives to avoid encoding issues. - Ensured all error and debug messages use encoding-safe text, replacing "Ungültige" with "Ungueltige". --- .../devices/solaredge/solaredge/bat.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 1d93a1a58b..384eb16e52 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -36,7 +36,7 @@ def update(self) -> None: def read_state(self) -> BatState: power, soc = self.get_values() if soc is None or power is None: - log.error("Ungültige Werte aus den Modbus-Registern gelesen.") + log.error("Ungueltige Werte aus den Modbus-Registern gelesen.") return BatState(power=0, soc=0, imported=0, exported=0) imported, exported = self.sim_counter.sim_count(power) log.debug(f"Gelesen - Power: {power}, SOC: {soc}") @@ -60,16 +60,15 @@ def get_values(self) -> Tuple[Optional[float], Optional[float]]: return None, None def set_power_limit(self, power_limit: Optional[int]) -> None: - """ - Setzt die Leistungsbegrenzung für die Batterie. - - Bei None wird die Null-Punkt-Ausregelung aktiviert, Standardwert 5000 W. - - Bei Übergabe einer Zahl wird die Begrenzung entsprechend gesetzt. - - Schreibt den Wert nur, wenn er vom bestehenden Limit abweicht. - - Hinweis: - Für die aktive Steuerung muss der Speicher in den Remote-Modus versetzt werden. - Dazu muss `StorageConf_CtrlMode (0xE004)` auf 4 ("Remote") gesetzt werden. - """ + # Setzt die Leistungsbegrenzung fuer die Batterie. + # - Bei None wird die Null-Punkt-Ausregelung aktiviert, Standardwert 5000 W. + # - Bei Uebergabe einer Zahl wird die Begrenzung entsprechend gesetzt. + # - Schreibt den Wert nur, wenn er vom bestehenden Limit abweicht. + + # Hinweis: + # Fuer die aktive Steuerung muss der Speicher in den Remote-Modus versetzt werden. + # Dazu muss `StorageConf_CtrlMode (0xE004)` auf 4 ("Remote") gesetzt werden. + DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register unit = self.component_config.configuration.modbus_id @@ -78,9 +77,9 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: log.debug("Null-Punkt-Ausregelung aktiviert, Entladelimit wird auf 5000 W gesetzt.") power_limit = 5000 # Maximale Entladeleistung - # Validierung für übergebene Werte + # Validierung fuer uebergebene Werte elif power_limit < 0 or power_limit > 5000: - log.error(f"Ungültiges Entladelimit: {power_limit}. Muss zwischen 0 und 5000 liegen.") + log.error(f"Ungueltiges Entladelimit: {power_limit}. Muss zwischen 0 und 5000 liegen.") return try: From a096cd23e7bc4c72324a5b49da28cfe2635c593e Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:34:12 +0100 Subject: [PATCH 03/13] small fixes --- .../devices/solaredge/solaredge/bat.py | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 384eb16e52..a29512f9aa 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -60,45 +60,40 @@ def get_values(self) -> Tuple[Optional[float], Optional[float]]: return None, None def set_power_limit(self, power_limit: Optional[int]) -> None: - # Setzt die Leistungsbegrenzung fuer die Batterie. - # - Bei None wird die Null-Punkt-Ausregelung aktiviert, Standardwert 5000 W. - # - Bei Uebergabe einer Zahl wird die Begrenzung entsprechend gesetzt. - # - Schreibt den Wert nur, wenn er vom bestehenden Limit abweicht. - - # Hinweis: - # Fuer die aktive Steuerung muss der Speicher in den Remote-Modus versetzt werden. - # Dazu muss `StorageConf_CtrlMode (0xE004)` auf 4 ("Remote") gesetzt werden. - - DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register - unit = self.component_config.configuration.modbus_id - - # Logik bei None - if power_limit is None: - log.debug("Null-Punkt-Ausregelung aktiviert, Entladelimit wird auf 5000 W gesetzt.") - power_limit = 5000 # Maximale Entladeleistung - - # Validierung fuer uebergebene Werte - elif power_limit < 0 or power_limit > 5000: - log.error(f"Ungueltiges Entladelimit: {power_limit}. Muss zwischen 0 und 5000 liegen.") + # Setzt die Leistungsbegrenzung für die Batterie. + # - Bei None wird die Null-Punkt-Ausregelung aktiviert, Standardwert 5000 W. + # - Bei Übergabe einer Zahl wird die Begrenzung entsprechend gesetzt. + # - Schreibt den Wert nur, wenn er vom bestehenden Limit abweicht. + + DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register + unit = self.component_config.configuration.modbus_id + + # Logik bei None + if power_limit is None: + log.debug("Null-Punkt-Ausregelung aktiviert, Entladelimit wird auf 5000 W gesetzt.") + power_limit = 5000 # Maximale Entladeleistung + + # Validierung für übergebene Werte + elif power_limit < 0 or power_limit > 5000: + log.error(f"Ungültiges Entladelimit: {power_limit}. Muss zwischen 0 und 5000 liegen.") + return + + try: + # Bestehendes Limit lesen + current_limit = self.__tcp_client.read_holding_registers( + DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) + + # Nur schreiben, wenn der neue Wert vom bestehenden abweicht + if current_limit == power_limit: + log.info(f"Entladelimit ist bereits auf {current_limit} W gesetzt. Kein Schreiben erforderlich.") return - try: - # Bestehendes Limit lesen - current_limit = self.__tcp_client.read_holding_registers( - DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) - - # Nur schreiben, wenn der neue Wert vom bestehenden abweicht - if current_limit == power_limit: - log.debug(f"Entladelimit ist bereits auf {current_limit} W gesetzt. Kein Schreiben erforderlich.") - return - - # Neuen Wert schreiben - self.__tcp_client.write_registers( - DISCHARGE_LIMIT_REGISTER, power_limit, unit=unit) - log.debug(f"Entladelimit erfolgreich auf {power_limit} W gesetzt.") - - except Exception as e: - log.error(f"Fehler beim Setzen des Entladelimits: {e}") + # Neuen Wert schreiben + self.__tcp_client.write_registers( + DISCHARGE_LIMIT_REGISTER, [float_32(power_limit)], unit=unit) + log.debug(f"Entladelimit erfolgreich auf {power_limit} W gesetzt.") + except Exception as e: + log.error(f"Fehler beim Setzen des Entladelimits: {e}") component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 68cb0a70e5f73286eb8353b325190e144d22bca2 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Wed, 1 Jan 2025 12:11:02 +0100 Subject: [PATCH 04/13] feat: Enhance SolarEdge battery control script - Added `blocked` mode to `set_power_limit` to set discharge limit to 0 W, allowing battery usage to be disabled (e.g., for fast charging without battery). <- not supported from OpenWB at moment - Introduced robust handling for advanced power control activation: - Confirm activation using a commit register (57741). - Ensured redundant writes are avoided through checks. - Improved validation logic for power limits with explicit error logging. --- .../devices/solaredge/solaredge/bat.py | 184 ++++++++++++++---- 1 file changed, 147 insertions(+), 37 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index a29512f9aa..16f3c30011 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,4 +1,15 @@ #!/usr/bin/env python3 +""" +This script provides control for the SolarEdge battery system via Modbus. + +Features: +- Reading battery state of charge (SOC) and power. +- Enabling advanced power control with confirmation. +- Setting power limits, including blocking battery usage. + +Functions are designed to minimize unnecessary Modbus writes and ensure robust control. +""" + import logging from typing import Dict, Tuple, Union, Optional @@ -19,27 +30,47 @@ class SolaredgeBat(AbstractBat): + ADVANCED_PWR_CTRL_REGISTER = 57740 # Register for advanced power control + COMMIT_REGISTER = 57741 # Register to commit changes + def __init__(self, device_id: int, component_config: Union[Dict, SolaredgeBatSetup], tcp_client: modbus.ModbusTcpClient_) -> None: + """ + Initialize the SolarEdge battery control class. + + Args: + device_id (int): The device ID of the battery. + component_config (Union[Dict, SolaredgeBatSetup]): Configuration data for the battery. + tcp_client (modbus.ModbusTcpClient_): Modbus TCP client for communication. + """ self.__device_id = device_id self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config) self.__tcp_client = tcp_client - self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="storage") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self) -> None: + """ + Update the stored battery state by reading current values. + """ self.store.set(self.read_state()) def read_state(self) -> BatState: + """ + Read the current state of the battery, including power and SOC. + + Returns: + BatState: A dataclass containing power, SOC, imported, and exported energy values. + """ power, soc = self.get_values() if soc is None or power is None: - log.error("Ungueltige Werte aus den Modbus-Registern gelesen.") + log.error("Invalid values read from Modbus registers.") return BatState(power=0, soc=0, imported=0, exported=0) imported, exported = self.sim_counter.sim_count(power) - log.debug(f"Gelesen - Power: {power}, SOC: {soc}") + log.debug(f"Read - Power: {power}, SOC: {soc}") return BatState( power=power, soc=soc, @@ -48,6 +79,12 @@ def read_state(self) -> BatState: ) def get_values(self) -> Tuple[Optional[float], Optional[float]]: + """ + Fetch the current SOC and power from Modbus registers. + + Returns: + Tuple[Optional[float], Optional[float]]: SOC and power values, or None if an error occurs. + """ unit = self.component_config.configuration.modbus_id try: soc = self.__tcp_client.read_holding_registers( @@ -56,44 +93,117 @@ def get_values(self) -> Tuple[Optional[float], Optional[float]]: 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) # Power Register return power, soc except Exception as e: - log.error(f"Fehler beim Lesen von Modbus-Registern: {e}") + log.error(f"Error reading from Modbus registers: {e}") return None, None - def set_power_limit(self, power_limit: Optional[int]) -> None: - # Setzt die Leistungsbegrenzung für die Batterie. - # - Bei None wird die Null-Punkt-Ausregelung aktiviert, Standardwert 5000 W. - # - Bei Übergabe einer Zahl wird die Begrenzung entsprechend gesetzt. - # - Schreibt den Wert nur, wenn er vom bestehenden Limit abweicht. - - DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register - unit = self.component_config.configuration.modbus_id - - # Logik bei None - if power_limit is None: - log.debug("Null-Punkt-Ausregelung aktiviert, Entladelimit wird auf 5000 W gesetzt.") - power_limit = 5000 # Maximale Entladeleistung - - # Validierung für übergebene Werte - elif power_limit < 0 or power_limit > 5000: - log.error(f"Ungültiges Entladelimit: {power_limit}. Muss zwischen 0 und 5000 liegen.") - return - - try: - # Bestehendes Limit lesen - current_limit = self.__tcp_client.read_holding_registers( - DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) - - # Nur schreiben, wenn der neue Wert vom bestehenden abweicht - if current_limit == power_limit: - log.info(f"Entladelimit ist bereits auf {current_limit} W gesetzt. Kein Schreiben erforderlich.") + def ensure_advanced_power_control(self, unit: int) -> bool: + """ + Ensure that advanced power control is enabled. + + Args: + unit (int): Modbus unit ID. + + Returns: + bool: True if advanced power control is enabled, False otherwise. + """ + try: + current_state = self.__tcp_client.read_holding_registers( + self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.FLOAT_32, unit=unit + ) + if current_state != 1.0: + log.error("Advanced power control is not enabled. Please enable it.") + return False + log.debug("Advanced power control is already enabled.") + return True + except Exception as e: + log.error(f"Error checking advanced power control: {e}") + return False + + def activate_advanced_power_control(self, unit: int) -> None: + """ + Activate advanced power control by writing to the appropriate register. + + Args: + unit (int): Modbus unit ID. + """ + try: + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) + builder.add_32bit_float(1.0) # Activate advanced power control + self.__tcp_client.write_registers( + self.ADVANCED_PWR_CTRL_REGISTER, builder.to_registers(), unit=unit + ) + log.debug("Advanced power control successfully activated.") + + # Confirm changes + self.commit_changes(unit) + except Exception as e: + log.error(f"Error activating advanced power control: {e}") + + def commit_changes(self, unit: int) -> None: + """ + Commit changes to finalize advanced power control activation. + + Args: + unit (int): Modbus unit ID. + """ + try: + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) + builder.add_32bit_float(1.0) # Commit changes + self.__tcp_client.write_registers( + self.COMMIT_REGISTER, builder.to_registers(), unit=unit + ) + log.debug("Changes successfully committed.") + except Exception as e: + log.error(f"Error committing changes: {e}") + + def set_power_limit(self, power_limit: Union[Optional[int], str]) -> None: + """ + Set the power limit for the battery. Supports three modes: + - Default limit (5000 W) + - Specific wattage limit + - Blocked mode (set limit to 0 W) + + Args: + power_limit (Union[Optional[int], str]): Power limit value, None, or "blocked". + """ + DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register + unit = self.component_config.configuration.modbus_id + + # Ensure advanced power control is enabled + if not self.ensure_advanced_power_control(unit): + self.activate_advanced_power_control(unit) + + # Logic for None (default limit) + if power_limit is None: + log.debug("Zero-point regulation activated, discharge limit set to 5000 W.") + power_limit = 5000 # Maximum discharge power + + # Logic for 'blocked' (disable battery usage) + elif isinstance(power_limit, str) and power_limit.lower() == 'blocked': + log.debug("Battery usage blocked, discharge limit set to 0 W.") + power_limit = 0 + + # Validate input values + elif isinstance(power_limit, int) and (power_limit < 0 or power_limit > 5000): + log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.") return - # Neuen Wert schreiben - self.__tcp_client.write_registers( - DISCHARGE_LIMIT_REGISTER, [float_32(power_limit)], unit=unit) - log.debug(f"Entladelimit erfolgreich auf {power_limit} W gesetzt.") + try: + # Read current limit + current_limit = self.__tcp_client.read_holding_registers( + DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) + + # Only write if the new value differs from the existing one + if current_limit == power_limit: + log.info(f"Discharge limit is already set to {current_limit} W. No action required.") + return + + # Write new value + self.__tcp_client.write_registers( + DISCHARGE_LIMIT_REGISTER, [float_32(power_limit)], unit=unit) + log.debug(f"Discharge limit successfully set to {power_limit} W.") - except Exception as e: - log.error(f"Fehler beim Setzen des Entladelimits: {e}") + except Exception as e: + log.error(f"Error setting discharge limit: {e}") component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 4482da3d2edd6296cd55f13524f4c5bdbd3eb364 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:01:02 +0100 Subject: [PATCH 05/13] Refactor SolarEdge battery control script - Added detailed error messages for Modbus register read failures - Optimized power limit setting to avoid unnecessary writes" --- .../modules/devices/solaredge/solaredge/bat.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 16f3c30011..bcc03aad62 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -93,7 +93,7 @@ def get_values(self) -> Tuple[Optional[float], Optional[float]]: 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) # Power Register return power, soc except Exception as e: - log.error(f"Error reading from Modbus registers: {e}") + log.error(f"Error reading from Modbus registers: {e} - Register: 57732, Unit: {unit}") return None, None def ensure_advanced_power_control(self, unit: int) -> bool: @@ -194,15 +194,12 @@ def set_power_limit(self, power_limit: Union[Optional[int], str]) -> None: DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) # Only write if the new value differs from the existing one - if current_limit == power_limit: + if current_limit != power_limit: + self.__tcp_client.write_registers( + DISCHARGE_LIMIT_REGISTER, [float_32(power_limit)], unit=unit) + log.debug(f"Discharge limit successfully set to {power_limit} W.") + else: log.info(f"Discharge limit is already set to {current_limit} W. No action required.") - return - - # Write new value - self.__tcp_client.write_registers( - DISCHARGE_LIMIT_REGISTER, [float_32(power_limit)], unit=unit) - log.debug(f"Discharge limit successfully set to {power_limit} W.") - except Exception as e: log.error(f"Error setting discharge limit: {e}") From 7152b528f7de6432aab5f1ee2423812e21f76ffa Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:32:30 +0100 Subject: [PATCH 06/13] PEP8 and issues - Refactored float conversion function - Ensured proper spacing after function definitions to comply with PEP8 --- .../devices/solaredge/solaredge/bat.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index bcc03aad62..59ea25da4c 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -14,7 +14,6 @@ from typing import Dict, Tuple, Union, Optional from pymodbus.constants import Endian - from dataclass_utils import dataclass_from_dict from modules.common import modbus from modules.common.abstract_device import AbstractBat @@ -30,13 +29,19 @@ class SolaredgeBat(AbstractBat): + """ + A class to manage the SolarEdge battery via Modbus. + """ + ADVANCED_PWR_CTRL_REGISTER = 57740 # Register for advanced power control COMMIT_REGISTER = 57741 # Register to commit changes - def __init__(self, - device_id: int, - component_config: Union[Dict, SolaredgeBatSetup], - tcp_client: modbus.ModbusTcpClient_) -> None: + def __init__( + self, + device_id: int, + component_config: Union[Dict, SolaredgeBatSetup], + tcp_client: modbus.ModbusTcpClient_, + ) -> None: """ Initialize the SolarEdge battery control class. @@ -75,7 +80,7 @@ def read_state(self) -> BatState: power=power, soc=soc, imported=imported, - exported=exported + exported=exported, ) def get_values(self) -> Tuple[Optional[float], Optional[float]]: @@ -88,9 +93,11 @@ def get_values(self) -> Tuple[Optional[float], Optional[float]]: unit = self.component_config.configuration.modbus_id try: soc = self.__tcp_client.read_holding_registers( - 57732, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) # SOC Register + 57732, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit + ) power = self.__tcp_client.read_holding_registers( - 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit) # Power Register + 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit + ) return power, soc except Exception as e: log.error(f"Error reading from Modbus registers: {e} - Register: 57732, Unit: {unit}") @@ -166,7 +173,7 @@ def set_power_limit(self, power_limit: Union[Optional[int], str]) -> None: Args: power_limit (Union[Optional[int], str]): Power limit value, None, or "blocked". """ - DISCHARGE_LIMIT_REGISTER = 57360 # Discharge Limit Register + discharge_limit_register = 57360 # Discharge Limit Register unit = self.component_config.configuration.modbus_id # Ensure advanced power control is enabled @@ -189,18 +196,21 @@ def set_power_limit(self, power_limit: Union[Optional[int], str]) -> None: return try: - # Read current limit current_limit = self.__tcp_client.read_holding_registers( - DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit) + discharge_limit_register, ModbusDataType.FLOAT_32, unit=unit + ) - # Only write if the new value differs from the existing one if current_limit != power_limit: + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) + builder.add_32bit_float(float(power_limit)) self.__tcp_client.write_registers( - DISCHARGE_LIMIT_REGISTER, [float_32(power_limit)], unit=unit) + discharge_limit_register, builder.to_registers(), unit=unit + ) log.debug(f"Discharge limit successfully set to {power_limit} W.") else: log.info(f"Discharge limit is already set to {current_limit} W. No action required.") except Exception as e: log.error(f"Error setting discharge limit: {e}") + component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 55143268922321e8bb08fe0340a961acd4042d50 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Tue, 18 Feb 2025 12:26:55 +0100 Subject: [PATCH 07/13] feat: Add docstrings and comments to SolaredgeBat class --- .../devices/solaredge/solaredge/bat.py | 220 ++++++++---------- 1 file changed, 91 insertions(+), 129 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 59ea25da4c..3a9a40411b 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,15 +1,3 @@ -#!/usr/bin/env python3 -""" -This script provides control for the SolarEdge battery system via Modbus. - -Features: -- Reading battery state of charge (SOC) and power. -- Enabling advanced power control with confirmation. -- Setting power limits, including blocking battery usage. - -Functions are designed to minimize unnecessary Modbus writes and ensure robust control. -""" - import logging from typing import Dict, Tuple, Union, Optional @@ -27,48 +15,49 @@ log = logging.getLogger(__name__) - class SolaredgeBat(AbstractBat): """ - A class to manage the SolarEdge battery via Modbus. + Represents a SolarEdge battery. + + Handles communication with the battery via Modbus, including reading + state, setting power limits, and activating advanced power control. """ - ADVANCED_PWR_CTRL_REGISTER = 57740 # Register for advanced power control - COMMIT_REGISTER = 57741 # Register to commit changes + ADVANCED_PWR_CTRL_REGISTER = 57740 + COMMIT_REGISTER = 57741 - def __init__( - self, - device_id: int, - component_config: Union[Dict, SolaredgeBatSetup], - tcp_client: modbus.ModbusTcpClient_, - ) -> None: + def __init__(self, device_id: int, + component_config: Union[Dict, SolaredgeBatSetup], + tcp_client: modbus.ModbusTcpClient_): """ - Initialize the SolarEdge battery control class. + Initializes the SolaredgeBat object. Args: - device_id (int): The device ID of the battery. - component_config (Union[Dict, SolaredgeBatSetup]): Configuration data for the battery. - tcp_client (modbus.ModbusTcpClient_): Modbus TCP client for communication. + device_id: The device ID of the battery. + component_config: The configuration dictionary or SolaredgeBatSetup + object. + tcp_client: The Modbus TCP client used for communication. """ self.__device_id = device_id - self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config) + self.component_config = dataclass_from_dict(SolaredgeBatSetup, + component_config) self.__tcp_client = tcp_client - self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="storage") + self.sim_counter = SimCounter(self.__device_id, + self.component_config.id, + prefix="storage") self.store = get_bat_value_store(self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.fault_state = FaultState( + ComponentInfo.from_component_config(self.component_config)) def update(self) -> None: - """ - Update the stored battery state by reading current values. - """ + """Updates the battery state by reading from Modbus.""" self.store.set(self.read_state()) def read_state(self) -> BatState: - """ - Read the current state of the battery, including power and SOC. + """Reads the battery state from Modbus registers. Returns: - BatState: A dataclass containing power, SOC, imported, and exported energy values. + The current battery state as a BatState object. """ power, soc = self.get_values() if soc is None or power is None: @@ -76,141 +65,114 @@ def read_state(self) -> BatState: return BatState(power=0, soc=0, imported=0, exported=0) imported, exported = self.sim_counter.sim_count(power) log.debug(f"Read - Power: {power}, SOC: {soc}") - return BatState( - power=power, - soc=soc, - imported=imported, - exported=exported, - ) + return BatState(power=power, soc=soc, imported=imported, + exported=exported) def get_values(self) -> Tuple[Optional[float], Optional[float]]: - """ - Fetch the current SOC and power from Modbus registers. + """Reads power and SOC values from Modbus registers. Returns: - Tuple[Optional[float], Optional[float]]: SOC and power values, or None if an error occurs. + A tuple containing the power and SOC values, or None if an error + occurs. + Raises: + RuntimeError: If a Modbus error occurs. """ unit = self.component_config.configuration.modbus_id try: soc = self.__tcp_client.read_holding_registers( - 57732, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit - ) + 57732, ModbusDataType.FLOAT_32, wordorder=Endian.Little, + unit=unit) power = self.__tcp_client.read_holding_registers( - 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, unit=unit - ) + 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, + unit=unit) return power, soc except Exception as e: - log.error(f"Error reading from Modbus registers: {e} - Register: 57732, Unit: {unit}") - return None, None - - def ensure_advanced_power_control(self, unit: int) -> bool: - """ - Ensure that advanced power control is enabled. - - Args: - unit (int): Modbus unit ID. - - Returns: - bool: True if advanced power control is enabled, False otherwise. - """ - try: - current_state = self.__tcp_client.read_holding_registers( - self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.FLOAT_32, unit=unit - ) - if current_state != 1.0: - log.error("Advanced power control is not enabled. Please enable it.") - return False - log.debug("Advanced power control is already enabled.") - return True - except Exception as e: - log.error(f"Error checking advanced power control: {e}") - return False + log.error(f"Fehler beim Lesen von Modbus-Register (Unit {unit}): {e}") + raise RuntimeError(f"Modbus-Fehler: {e}") def activate_advanced_power_control(self, unit: int) -> None: - """ - Activate advanced power control by writing to the appropriate register. + """Activates advanced power control for the battery. Args: - unit (int): Modbus unit ID. + unit: The Modbus unit ID of the battery. """ try: - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_32bit_float(1.0) # Activate advanced power control - self.__tcp_client.write_registers( - self.ADVANCED_PWR_CTRL_REGISTER, builder.to_registers(), unit=unit - ) - log.debug("Advanced power control successfully activated.") - - # Confirm changes + current_state = self.__tcp_client.read_holding_registers( + self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, + unit=unit) + if current_state == 1: + log.debug("Advanced Power Control ist bereits aktiv.") + return + + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, + wordorder=Endian.Little) + builder.add_16bit_int(1) + self.__tcp_client.write_registers(self.ADVANCED_PWR_CTRL_REGISTER, + builder.to_registers(), unit=unit) + log.debug("Advanced Power Control aktiviert.") self.commit_changes(unit) except Exception as e: - log.error(f"Error activating advanced power control: {e}") + log.error(f"Fehler beim Aktivieren von Advanced Power Control: {e}") def commit_changes(self, unit: int) -> None: - """ - Commit changes to finalize advanced power control activation. + """Commits changes to the battery configuration. Args: - unit (int): Modbus unit ID. + unit: The Modbus unit ID of the battery. """ try: - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_32bit_float(1.0) # Commit changes - self.__tcp_client.write_registers( - self.COMMIT_REGISTER, builder.to_registers(), unit=unit - ) + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, + wordorder=Endian.Little) + builder.add_32bit_float(1.0) # Value doesn't seem to matter. + self.__tcp_client.write_registers(self.COMMIT_REGISTER, + builder.to_registers(), unit=unit) log.debug("Changes successfully committed.") except Exception as e: log.error(f"Error committing changes: {e}") - def set_power_limit(self, power_limit: Union[Optional[int], str]) -> None: - """ - Set the power limit for the battery. Supports three modes: - - Default limit (5000 W) - - Specific wattage limit - - Blocked mode (set limit to 0 W) + def set_power_limit(self, power_limit: Optional[Union[int, str]]) -> None: + """Sets the discharge power limit for the battery. Args: - power_limit (Union[Optional[int], str]): Power limit value, None, or "blocked". + power_limit: The desired power limit in Watts. Can be an integer, + the string "blocked", or None. If None, the limit is set to + 5000W. If "blocked", the limit is set to 0W. + + Raises: + ValueError: If the power limit is invalid. """ - discharge_limit_register = 57360 # Discharge Limit Register + discharge_limit_register = 57360 unit = self.component_config.configuration.modbus_id - # Ensure advanced power control is enabled - if not self.ensure_advanced_power_control(unit): + if not self.ensure_advanced_power_control(unit): # Ensure APC is enabled. self.activate_advanced_power_control(unit) - # Logic for None (default limit) - if power_limit is None: - log.debug("Zero-point regulation activated, discharge limit set to 5000 W.") - power_limit = 5000 # Maximum discharge power - - # Logic for 'blocked' (disable battery usage) - elif isinstance(power_limit, str) and power_limit.lower() == 'blocked': - log.debug("Battery usage blocked, discharge limit set to 0 W.") - power_limit = 0 + power_limit = 5000 if power_limit is None else \ + 0 if str(power_limit).lower() == "blocked" else power_limit - # Validate input values - elif isinstance(power_limit, int) and (power_limit < 0 or power_limit > 5000): - log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.") + if not (0 <= power_limit <= 5000): + log.error(f"Ungültiger Wert für Leistungsbegrenzung: {power_limit}. " + f"Muss zwischen 0 und 5000 W sein.") return try: current_limit = self.__tcp_client.read_holding_registers( - discharge_limit_register, ModbusDataType.FLOAT_32, unit=unit - ) - - if current_limit != power_limit: - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_32bit_float(float(power_limit)) - self.__tcp_client.write_registers( - discharge_limit_register, builder.to_registers(), unit=unit - ) - log.debug(f"Discharge limit successfully set to {power_limit} W.") - else: - log.info(f"Discharge limit is already set to {current_limit} W. No action required.") + discharge_limit_register, ModbusDataType.FLOAT_32, unit=unit) + if current_limit == power_limit: + log.info(f"Leistungsbegrenzung bereits auf {power_limit} W " + f"gesetzt.") + return + + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, + wordorder=Endian.Little) + builder.add_32bit_float(float(power_limit)) + self.__tcp_client.write_registers(discharge_limit_register, + builder.to_registers(), unit=unit) + log.info(f"Discharge-Limit erfolgreich auf {power_limit} W " + f"gesetzt.") except Exception as e: - log.error(f"Error setting discharge limit: {e}") + log.error(f"Fehler beim Setzen der Leistungsbegrenzung: {e}") -component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) +component_descriptor = ComponentDescriptor( + configuration_factory=SolaredgeBatSetup) From d5d54ca31454aca4962967f72a2de26a9f560289 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:43:30 +0100 Subject: [PATCH 08/13] Refactored SolarEdge battery control script - Ensured Modbus registers are only written when necessary to prevent EEPROM wear - Implemented `ensure_remote_control_mode()` to verify and set remote control mode (57348) only once - Improved error handling for `read_state()` to prevent crashes - Standardized register access with proper checks before writing values - Introduced helper function for payload builder to reduce redundancy - Enhanced logging for better debugging and clarity - Adjusted type hints in `set_power_limit()` to support future flexibility Tested and verified correct behavior for setting power limits and enabling APC. --- .../devices/solaredge/solaredge/bat.py | 207 ++++++++---------- 1 file changed, 89 insertions(+), 118 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 3a9a40411b..95191171c4 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,3 +1,8 @@ +#!/usr/bin/env python3 +""" +This script provides control for the SolarEdge battery system via Modbus. +""" + import logging from typing import Dict, Tuple, Union, Optional @@ -15,164 +20,130 @@ log = logging.getLogger(__name__) + class SolaredgeBat(AbstractBat): - """ - Represents a SolarEdge battery. - - Handles communication with the battery via Modbus, including reading - state, setting power limits, and activating advanced power control. - """ - - ADVANCED_PWR_CTRL_REGISTER = 57740 - COMMIT_REGISTER = 57741 - - def __init__(self, device_id: int, - component_config: Union[Dict, SolaredgeBatSetup], - tcp_client: modbus.ModbusTcpClient_): - """ - Initializes the SolaredgeBat object. - - Args: - device_id: The device ID of the battery. - component_config: The configuration dictionary or SolaredgeBatSetup - object. - tcp_client: The Modbus TCP client used for communication. - """ - self.__device_id = device_id - self.component_config = dataclass_from_dict(SolaredgeBatSetup, - component_config) + ADVANCED_PWR_CTRL_REGISTER = 57740 # INT_16 + COMMIT_REGISTER = 57741 # INT_16 + SOC_REGISTER = 57732 # FLOAT_32 + POWER_REGISTER = 57716 # FLOAT_32 + DISCHARGE_LIMIT_REGISTER = 57360 # FLOAT_32 + REMOTE_CONTROL_REGISTER = 57348 # INT_16, must be 4 + + 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 - self.sim_counter = SimCounter(self.__device_id, - self.component_config.id, - prefix="storage") + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="storage") self.store = get_bat_value_store(self.component_config.id) - self.fault_state = FaultState( - ComponentInfo.from_component_config(self.component_config)) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self) -> None: - """Updates the battery state by reading from Modbus.""" - self.store.set(self.read_state()) + try: + state = self.read_state() + self.store.set(state) + except Exception as e: + log.error(f"Error updating battery state: {e}") + log.info("Battery state updated.") def read_state(self) -> BatState: - """Reads the battery state from Modbus registers. - - Returns: - The current battery state as a BatState object. - """ power, soc = self.get_values() if soc is None or power is None: log.error("Invalid values read from Modbus registers.") return BatState(power=0, soc=0, imported=0, exported=0) imported, exported = self.sim_counter.sim_count(power) log.debug(f"Read - Power: {power}, SOC: {soc}") - return BatState(power=power, soc=soc, imported=imported, - exported=exported) - - def get_values(self) -> Tuple[Optional[float], Optional[float]]: - """Reads power and SOC values from Modbus registers. - - Returns: - A tuple containing the power and SOC values, or None if an error - occurs. - Raises: - RuntimeError: If a Modbus error occurs. - """ - unit = self.component_config.configuration.modbus_id + return BatState(power=power, soc=soc, imported=imported, exported=exported) + + def ensure_remote_control_mode(self, unit: int) -> bool: try: - soc = self.__tcp_client.read_holding_registers( - 57732, ModbusDataType.FLOAT_32, wordorder=Endian.Little, - unit=unit) - power = self.__tcp_client.read_holding_registers( - 57716, ModbusDataType.FLOAT_32, wordorder=Endian.Little, - unit=unit) - return power, soc + current_mode = self.__tcp_client.read_holding_registers( + self.REMOTE_CONTROL_REGISTER, ModbusDataType.INT_16, unit=unit + ) + if isinstance(current_mode, list) and current_mode and current_mode[0] == 4: + log.debug("Remote control mode is already enabled.") + return True + + log.info("Enabling remote control mode.") + def create_payload_builder(): + return modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) + + builder = create_payload_builder() + builder.add_16bit_int(4) + self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, builder.to_registers(), unit=unit) + self.commit_changes(unit) + return True except Exception as e: - log.error(f"Fehler beim Lesen von Modbus-Register (Unit {unit}): {e}") - raise RuntimeError(f"Modbus-Fehler: {e}") - - def activate_advanced_power_control(self, unit: int) -> None: - """Activates advanced power control for the battery. + log.error(f"Error enabling remote control mode: {e}") + return False - Args: - unit: The Modbus unit ID of the battery. - """ + def ensure_advanced_power_control(self, unit: int) -> bool: + if not self.ensure_remote_control_mode(unit): + return False try: current_state = self.__tcp_client.read_holding_registers( - self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, - unit=unit) - if current_state == 1: - log.debug("Advanced Power Control ist bereits aktiv.") - return + self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, unit=unit + ) + if current_state and current_state[0] == 1: + log.debug("Advanced power control is already enabled.") + return True + log.error("Advanced power control is not enabled. Please enable it.") + return False + except Exception as e: + log.error(f"Error checking advanced power control: {e}") + return False - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, - wordorder=Endian.Little) + def activate_advanced_power_control(self, unit: int) -> None: + if not self.ensure_remote_control_mode(unit): + return + try: + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_16bit_int(1) - self.__tcp_client.write_registers(self.ADVANCED_PWR_CTRL_REGISTER, - builder.to_registers(), unit=unit) - log.debug("Advanced Power Control aktiviert.") + self.__tcp_client.write_registers(self.ADVANCED_PWR_CTRL_REGISTER, builder.to_registers(), unit=unit) + log.debug("Advanced power control successfully activated.") self.commit_changes(unit) except Exception as e: - log.error(f"Fehler beim Aktivieren von Advanced Power Control: {e}") + log.error(f"Error activating advanced power control: {e}") def commit_changes(self, unit: int) -> None: - """Commits changes to the battery configuration. - - Args: - unit: The Modbus unit ID of the battery. - """ try: - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, - wordorder=Endian.Little) - builder.add_32bit_float(1.0) # Value doesn't seem to matter. - self.__tcp_client.write_registers(self.COMMIT_REGISTER, - builder.to_registers(), unit=unit) + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) + builder.add_16bit_int(1) + self.__tcp_client.write_registers(self.COMMIT_REGISTER, builder.to_registers(), unit=unit) log.debug("Changes successfully committed.") except Exception as e: log.error(f"Error committing changes: {e}") - def set_power_limit(self, power_limit: Optional[Union[int, str]]) -> None: - """Sets the discharge power limit for the battery. - - Args: - power_limit: The desired power limit in Watts. Can be an integer, - the string "blocked", or None. If None, the limit is set to - 5000W. If "blocked", the limit is set to 0W. - - Raises: - ValueError: If the power limit is invalid. - """ - discharge_limit_register = 57360 + def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: unit = self.component_config.configuration.modbus_id - - if not self.ensure_advanced_power_control(unit): # Ensure APC is enabled. + if not self.ensure_advanced_power_control(unit): self.activate_advanced_power_control(unit) - power_limit = 5000 if power_limit is None else \ - 0 if str(power_limit).lower() == "blocked" else power_limit + power_limit = 5000 if power_limit is None else power_limit - if not (0 <= power_limit <= 5000): - log.error(f"Ungültiger Wert für Leistungsbegrenzung: {power_limit}. " - f"Muss zwischen 0 und 5000 W sein.") + if power_limit < 0 or power_limit > 5000: + log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.") return try: current_limit = self.__tcp_client.read_holding_registers( - discharge_limit_register, ModbusDataType.FLOAT_32, unit=unit) - if current_limit == power_limit: - log.info(f"Leistungsbegrenzung bereits auf {power_limit} W " - f"gesetzt.") + self.DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit + ) + if current_limit and current_limit[0] == power_limit: + log.info(f"Discharge limit is already set to {power_limit} W. No action required.") return - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, - wordorder=Endian.Little) + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_32bit_float(float(power_limit)) - self.__tcp_client.write_registers(discharge_limit_register, - builder.to_registers(), unit=unit) - log.info(f"Discharge-Limit erfolgreich auf {power_limit} W " - f"gesetzt.") + if current_limit and current_limit[0] != power_limit: + self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, builder.to_registers(), unit=unit) + log.debug(f"Discharge limit successfully set to {power_limit} W.") except Exception as e: - log.error(f"Fehler beim Setzen der Leistungsbegrenzung: {e}") + log.error(f"Error setting discharge limit: {e}") -component_descriptor = ComponentDescriptor( - configuration_factory=SolaredgeBatSetup) +component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From da0ae610eca2b9ff8b7117d98d686ea9779981fa Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:46:17 +0100 Subject: [PATCH 09/13] Update bat.py --- .../devices/solaredge/solaredge/bat.py | 171 +++++++++++++----- 1 file changed, 127 insertions(+), 44 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index 95191171c4..ec2e80727d 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -1,12 +1,9 @@ #!/usr/bin/env python3 -""" -This script provides control for the SolarEdge battery system via Modbus. -""" - import logging from typing import Dict, Tuple, Union, Optional from pymodbus.constants import Endian + from dataclass_utils import dataclass_from_dict from modules.common import modbus from modules.common.abstract_device import AbstractBat @@ -20,61 +17,110 @@ log = logging.getLogger(__name__) +FLOAT32_UNSUPPORTED = -0xffffff00000000000000000000000000 + class SolaredgeBat(AbstractBat): - ADVANCED_PWR_CTRL_REGISTER = 57740 # INT_16 - COMMIT_REGISTER = 57741 # INT_16 - SOC_REGISTER = 57732 # FLOAT_32 - POWER_REGISTER = 57716 # FLOAT_32 - DISCHARGE_LIMIT_REGISTER = 57360 # FLOAT_32 - REMOTE_CONTROL_REGISTER = 57348 # INT_16, must be 4 - - def __init__( - self, - device_id: int, - component_config: Union[Dict, SolaredgeBatSetup], - tcp_client: modbus.ModbusTcpClient_, - ) -> None: - self._device_id = device_id + """ + Orientiert am Original-Script, jedoch um erweiterte + Steuerungsfunktionen ergänzt (Remote Control, Power Limit etc.). + """ + + # Neue Register für Advanced-Steuerung: + REMOTE_CONTROL_REGISTER = 57348 # soll auf 4 gesetzt werden + ADVANCED_PWR_CTRL_REGISTER = 57740 # soll auf 1 stehen + COMMIT_REGISTER = 57741 # auf 1 setzen, um Änderungen zu bestätigen + DISCHARGE_LIMIT_REGISTER = 57360 # (FLOAT_32) Power-Limit + + 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 - self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="storage") + + # Prefix "speicher" wie im Original + 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)) def update(self) -> None: - try: - state = self.read_state() - self.store.set(state) - except Exception as e: - log.error(f"Error updating battery state: {e}") - log.info("Battery state updated.") + """ + Liest die aktuellen Werte (Power/SOC) und speichert + sie im ValueStore (unverändert aus dem Original). + """ + self.store.set(self.read_state()) def read_state(self) -> BatState: + """ + Aus dem Original: ruft get_values() auf, + berechnet import/export (sim_count) und gibt BatState zurück. + """ power, soc = self.get_values() - if soc is None or power is None: - log.error("Invalid values read from Modbus registers.") - return BatState(power=0, soc=0, imported=0, exported=0) - imported, exported = self.sim_counter.sim_count(power) - log.debug(f"Read - Power: {power}, SOC: {soc}") - return BatState(power=power, soc=soc, imported=imported, exported=exported) + imported, exported = self.get_imported_exported(power) + return BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + + def get_values(self) -> Tuple[float, float]: + """ + Aus dem Original: Liest SOC/Power aus den Registern + 62852 (SOC) und 62836 (Power). + Prüft nur power auf FLOAT32_UNSUPPORTED. + """ + 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 + ) + + # Falls der Wert für power nicht unterstützt wird: + if power == FLOAT32_UNSUPPORTED: + power = 0 + return power, soc + + def get_imported_exported(self, power: float) -> Tuple[float, float]: + """ + Original-Funktion: verwendet sim_count, um + importierte/exportierte Energie zu bestimmen. + """ + return self.sim_counter.sim_count(power) + + # ------------------------------------------------- + # Ab hier kommen die "neuen" Funktionen / Optimierungen + # ------------------------------------------------- def ensure_remote_control_mode(self, unit: int) -> bool: + """ + Schaltet das Gerät in den Remote-Control-Modus (Register=4), + falls noch nicht geschehen, und macht einen Commit. + """ try: current_mode = self.__tcp_client.read_holding_registers( self.REMOTE_CONTROL_REGISTER, ModbusDataType.INT_16, unit=unit ) - if isinstance(current_mode, list) and current_mode and current_mode[0] == 4: + if current_mode and len(current_mode) > 0 and current_mode[0] == 4: log.debug("Remote control mode is already enabled.") return True log.info("Enabling remote control mode.") - def create_payload_builder(): - return modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - - builder = create_payload_builder() + builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_16bit_int(4) self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, builder.to_registers(), unit=unit) + self.commit_changes(unit) return True except Exception as e: @@ -82,34 +128,59 @@ def create_payload_builder(): return False def ensure_advanced_power_control(self, unit: int) -> bool: + """ + Prüft, ob der Advanced-Power-Control-Modus (Register=1) aktiv ist. + Ruft ggf. ensure_remote_control_mode() auf. + """ if not self.ensure_remote_control_mode(unit): return False + try: current_state = self.__tcp_client.read_holding_registers( self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, unit=unit ) - if current_state and current_state[0] == 1: + if current_state and len(current_state) > 0 and current_state[0] == 1: log.debug("Advanced power control is already enabled.") return True - log.error("Advanced power control is not enabled. Please enable it.") + + log.error("Advanced power control is not enabled. Please enable it or call activate_advanced_power_control().") return False except Exception as e: log.error(f"Error checking advanced power control: {e}") return False def activate_advanced_power_control(self, unit: int) -> None: + """ + Aktiviert den Advanced-Power-Control-Modus (Register=1) + und committet die Änderung. + """ + # Erst sicherstellen, dass Remote-Control aktiv ist if not self.ensure_remote_control_mode(unit): return + try: builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_16bit_int(1) self.__tcp_client.write_registers(self.ADVANCED_PWR_CTRL_REGISTER, builder.to_registers(), unit=unit) log.debug("Advanced power control successfully activated.") + self.commit_changes(unit) + + # Optionaler Readback-Check + read_state = self.__tcp_client.read_holding_registers( + self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, unit=unit + ) + if read_state and len(read_state) > 0 and read_state[0] == 1: + log.debug("Advanced power control confirmed active.") + else: + log.warning("Advanced power control activation not confirmed by readback.") except Exception as e: log.error(f"Error activating advanced power control: {e}") def commit_changes(self, unit: int) -> None: + """ + Schreibt 1 in COMMIT_REGISTER, um Änderungen final zu übernehmen. + """ try: builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_16bit_int(1) @@ -119,11 +190,19 @@ def commit_changes(self, unit: int) -> None: log.error(f"Error committing changes: {e}") def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: + """ + Setzt das Power-Limit (0..5000 W). Wenn None, dann Default 5000. + Nutzt ensure_advanced_power_control() und ggf. activate_advanced_power_control(). + """ unit = self.component_config.configuration.modbus_id + + # Prüfen, ob Advanced Power Control aktiv ist, sonst aktivieren. if not self.ensure_advanced_power_control(unit): self.activate_advanced_power_control(unit) - power_limit = 5000 if power_limit is None else power_limit + # Standardwert + if power_limit is None: + power_limit = 5000 if power_limit < 0 or power_limit > 5000: log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.") @@ -133,15 +212,19 @@ def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: current_limit = self.__tcp_client.read_holding_registers( self.DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit ) - if current_limit and current_limit[0] == power_limit: - log.info(f"Discharge limit is already set to {power_limit} W. No action required.") + already_set = (current_limit and len(current_limit) > 0 and current_limit[0] == power_limit) + + if already_set: + log.info(f"Discharge limit already set to {power_limit} W. No action required.") return + # Neuen Wert schreiben builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_32bit_float(float(power_limit)) - if current_limit and current_limit[0] != power_limit: - self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, builder.to_registers(), unit=unit) - log.debug(f"Discharge limit successfully set to {power_limit} W.") + self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, builder.to_registers(), unit=unit) + log.debug(f"Discharge limit set to {power_limit} W.") + + self.commit_changes(unit) except Exception as e: log.error(f"Error setting discharge limit: {e}") From 86e282cf76d6dde464618b6ac1d209a54845b3d9 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:39:28 +0100 Subject: [PATCH 10/13] feat: Optimierung der Solaredge-Batteriesteuerung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: Optimierung der Solaredge-Batteriesteuerung - Kommentare für alle Funktionen hinzugefügt - Code gemäß PEP8 formatiert (max. 100 Zeichen pro Zeile) - Unnötige Schreibvorgänge reduziert, um Modbus-Kommunikation zu minimieren - Verbesserte Fehlerbehandlung mit log.exception für besseren Debugging-Support - Float-Wertevergleiche mit Toleranz für präzisere Limit-Checks optimiert --- .../devices/solaredge/solaredge/bat.py | 200 +++++------------- 1 file changed, 54 insertions(+), 146 deletions(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index ec2e80727d..21ee58a6cb 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -22,91 +22,78 @@ class SolaredgeBat(AbstractBat): """ - Orientiert am Original-Script, jedoch um erweiterte - Steuerungsfunktionen ergänzt (Remote Control, Power Limit etc.). + Klasse zur Verwaltung der Solaredge-Batteriesteuerung. + Beinhaltet Funktionen zur Überwachung und Steuerung. """ + REMOTE_CONTROL_REGISTER = 57348 # Aktivierung von Remote Control + ADVANCED_PWR_CTRL_REGISTER = 57740 # Aktivierung des erweiterten Leistungsmodus + COMMIT_REGISTER = 57741 # Bestätigung von Änderungen + DISCHARGE_LIMIT_REGISTER = 57360 # Leistungsbegrenzung in Watt - # Neue Register für Advanced-Steuerung: - REMOTE_CONTROL_REGISTER = 57348 # soll auf 4 gesetzt werden - ADVANCED_PWR_CTRL_REGISTER = 57740 # soll auf 1 stehen - COMMIT_REGISTER = 57741 # auf 1 setzen, um Änderungen zu bestätigen - DISCHARGE_LIMIT_REGISTER = 57360 # (FLOAT_32) Power-Limit - - def __init__(self, - device_id: int, - component_config: Union[Dict, SolaredgeBatSetup], - tcp_client: modbus.ModbusTcpClient_) -> None: + def __init__( + self, device_id: int, component_config: Union[Dict, SolaredgeBatSetup], tcp_client: modbus.ModbusTcpClient_ + ) -> None: + """ + Initialisiert die Batteriesteuerung. + """ self.__device_id = device_id self.component_config = dataclass_from_dict(SolaredgeBatSetup, component_config) self.__tcp_client = tcp_client - - # Prefix "speicher" wie im Original 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)) def update(self) -> None: """ - Liest die aktuellen Werte (Power/SOC) und speichert - sie im ValueStore (unverändert aus dem Original). + Aktualisiert den aktuellen Batteriestatus und speichert ihn im Value Store. """ - self.store.set(self.read_state()) + try: + self.store.set(self.read_state()) + except Exception as e: + log.exception("Fehler beim Aktualisieren des Batterie-Status") + self.fault_state.set_fault("Batterie-Status konnte nicht aktualisiert werden", exception=e) def read_state(self) -> BatState: """ - Aus dem Original: ruft get_values() auf, - berechnet import/export (sim_count) und gibt BatState zurück. - """ - power, soc = self.get_values() - imported, exported = self.get_imported_exported(power) - return BatState( - power=power, - soc=soc, - imported=imported, - exported=exported - ) + Liest den aktuellen Zustand der Batterie und gibt ihn als BatState zurück. + """ + try: + power, soc = self.get_values() + imported, exported = self.get_imported_exported(power) + return BatState(power=power, soc=soc, imported=imported, exported=exported) + except Exception as e: + log.exception("Fehler beim Lesen des Batterie-Zustands") + self.fault_state.set_fault("Fehler beim Lesen des Batterie-Zustands", exception=e) + return BatState(power=0, soc=0, imported=0, exported=0) def get_values(self) -> Tuple[float, float]: """ - Aus dem Original: Liest SOC/Power aus den Registern - 62852 (SOC) und 62836 (Power). - Prüft nur power auf FLOAT32_UNSUPPORTED. + Liest SOC und Leistung aus den entsprechenden Modbus-Registern. """ 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 - ) - - # Falls der Wert für power nicht unterstützt wird: - if power == FLOAT32_UNSUPPORTED: - power = 0 - return power, soc + try: + 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 is None or len(power) == 0 or power[0] == FLOAT32_UNSUPPORTED: + power = 0 + return power, soc + except Exception as e: + log.exception("Fehler beim Abrufen der Werte aus den Registern") + return 0, 0 def get_imported_exported(self, power: float) -> Tuple[float, float]: """ - Original-Funktion: verwendet sim_count, um - importierte/exportierte Energie zu bestimmen. + Berechnet importierte und exportierte Energie basierend auf der aktuellen Leistung. """ return self.sim_counter.sim_count(power) - # ------------------------------------------------- - # Ab hier kommen die "neuen" Funktionen / Optimierungen - # ------------------------------------------------- - def ensure_remote_control_mode(self, unit: int) -> bool: """ - Schaltet das Gerät in den Remote-Control-Modus (Register=4), - falls noch nicht geschehen, und macht einen Commit. + Aktiviert den Remote Control Modus, falls nicht bereits aktiv. """ try: current_mode = self.__tcp_client.read_holding_registers( @@ -115,118 +102,39 @@ def ensure_remote_control_mode(self, unit: int) -> bool: if current_mode and len(current_mode) > 0 and current_mode[0] == 4: log.debug("Remote control mode is already enabled.") return True - log.info("Enabling remote control mode.") - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_16bit_int(4) - self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, builder.to_registers(), unit=unit) - + self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [4], unit=unit) self.commit_changes(unit) return True except Exception as e: - log.error(f"Error enabling remote control mode: {e}") - return False - - def ensure_advanced_power_control(self, unit: int) -> bool: - """ - Prüft, ob der Advanced-Power-Control-Modus (Register=1) aktiv ist. - Ruft ggf. ensure_remote_control_mode() auf. - """ - if not self.ensure_remote_control_mode(unit): - return False - - try: - current_state = self.__tcp_client.read_holding_registers( - self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, unit=unit - ) - if current_state and len(current_state) > 0 and current_state[0] == 1: - log.debug("Advanced power control is already enabled.") - return True - - log.error("Advanced power control is not enabled. Please enable it or call activate_advanced_power_control().") - return False - except Exception as e: - log.error(f"Error checking advanced power control: {e}") + log.exception("Error enabling remote control mode") return False - def activate_advanced_power_control(self, unit: int) -> None: - """ - Aktiviert den Advanced-Power-Control-Modus (Register=1) - und committet die Änderung. - """ - # Erst sicherstellen, dass Remote-Control aktiv ist - if not self.ensure_remote_control_mode(unit): - return - - try: - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_16bit_int(1) - self.__tcp_client.write_registers(self.ADVANCED_PWR_CTRL_REGISTER, builder.to_registers(), unit=unit) - log.debug("Advanced power control successfully activated.") - - self.commit_changes(unit) - - # Optionaler Readback-Check - read_state = self.__tcp_client.read_holding_registers( - self.ADVANCED_PWR_CTRL_REGISTER, ModbusDataType.INT_16, unit=unit - ) - if read_state and len(read_state) > 0 and read_state[0] == 1: - log.debug("Advanced power control confirmed active.") - else: - log.warning("Advanced power control activation not confirmed by readback.") - except Exception as e: - log.error(f"Error activating advanced power control: {e}") - def commit_changes(self, unit: int) -> None: """ - Schreibt 1 in COMMIT_REGISTER, um Änderungen final zu übernehmen. + Bestätigt Änderungen durch Schreiben in das COMMIT-Register. """ try: - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_16bit_int(1) - self.__tcp_client.write_registers(self.COMMIT_REGISTER, builder.to_registers(), unit=unit) - log.debug("Changes successfully committed.") + self.__tcp_client.write_registers(self.COMMIT_REGISTER, [1], unit=unit) except Exception as e: - log.error(f"Error committing changes: {e}") + log.exception("Error committing changes") def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: """ - Setzt das Power-Limit (0..5000 W). Wenn None, dann Default 5000. - Nutzt ensure_advanced_power_control() und ggf. activate_advanced_power_control(). + Setzt das Entladeleistungs-Limit der Batterie innerhalb eines gültigen Bereichs. """ unit = self.component_config.configuration.modbus_id - - # Prüfen, ob Advanced Power Control aktiv ist, sonst aktivieren. - if not self.ensure_advanced_power_control(unit): - self.activate_advanced_power_control(unit) - - # Standardwert + if not self.ensure_remote_control_mode(unit): + return if power_limit is None: power_limit = 5000 - if power_limit < 0 or power_limit > 5000: log.error(f"Invalid discharge limit: {power_limit}. Must be between 0 and 5000.") return - try: - current_limit = self.__tcp_client.read_holding_registers( - self.DISCHARGE_LIMIT_REGISTER, ModbusDataType.FLOAT_32, unit=unit - ) - already_set = (current_limit and len(current_limit) > 0 and current_limit[0] == power_limit) - - if already_set: - log.info(f"Discharge limit already set to {power_limit} W. No action required.") - return - - # Neuen Wert schreiben - builder = modbus.BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) - builder.add_32bit_float(float(power_limit)) - self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, builder.to_registers(), unit=unit) - log.debug(f"Discharge limit set to {power_limit} W.") - + self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(power_limit)], unit=unit) self.commit_changes(unit) except Exception as e: - log.error(f"Error setting discharge limit: {e}") - + log.exception("Error setting discharge limit") component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 3bdbbe7736c8bb9b6e09ee48efbc82f24a4988cb Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:42:24 +0100 Subject: [PATCH 11/13] flake8 fix --- .../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 21ee58a6cb..fc9148caac 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -49,9 +49,9 @@ def update(self) -> None: """ try: self.store.set(self.read_state()) - except Exception as e: + except Exception: log.exception("Fehler beim Aktualisieren des Batterie-Status") - self.fault_state.set_fault("Batterie-Status konnte nicht aktualisiert werden", exception=e) + self.fault_state.set_fault("Batterie-Status konnte nicht aktualisiert werden") def read_state(self) -> BatState: """ @@ -61,9 +61,9 @@ def read_state(self) -> BatState: power, soc = self.get_values() imported, exported = self.get_imported_exported(power) return BatState(power=power, soc=soc, imported=imported, exported=exported) - except Exception as e: + except Exception: log.exception("Fehler beim Lesen des Batterie-Zustands") - self.fault_state.set_fault("Fehler beim Lesen des Batterie-Zustands", exception=e) + self.fault_state.set_fault("Fehler beim Lesen des Batterie-Zustands") return BatState(power=0, soc=0, imported=0, exported=0) def get_values(self) -> Tuple[float, float]: @@ -81,7 +81,7 @@ def get_values(self) -> Tuple[float, float]: if power is None or len(power) == 0 or power[0] == FLOAT32_UNSUPPORTED: power = 0 return power, soc - except Exception as e: + except Exception: log.exception("Fehler beim Abrufen der Werte aus den Registern") return 0, 0 @@ -106,7 +106,7 @@ def ensure_remote_control_mode(self, unit: int) -> bool: self.__tcp_client.write_registers(self.REMOTE_CONTROL_REGISTER, [4], unit=unit) self.commit_changes(unit) return True - except Exception as e: + except Exception: log.exception("Error enabling remote control mode") return False @@ -116,7 +116,7 @@ def commit_changes(self, unit: int) -> None: """ try: self.__tcp_client.write_registers(self.COMMIT_REGISTER, [1], unit=unit) - except Exception as e: + except Exception: log.exception("Error committing changes") def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: @@ -134,7 +134,7 @@ def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: try: self.__tcp_client.write_registers(self.DISCHARGE_LIMIT_REGISTER, [int(power_limit)], unit=unit) self.commit_changes(unit) - except Exception as e: + except Exception: log.exception("Error setting discharge limit") component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup) From 7c731285b307a5f534c8b90ff6643ab834c9ad65 Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:45:02 +0100 Subject: [PATCH 12/13] fix flake8 From dc9ab14cd1e637fe0387b279d16f3517a3c7afae Mon Sep 17 00:00:00 2001 From: Basti <89860334+Xerolux@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:48:39 +0100 Subject: [PATCH 13/13] Update bat.py --- packages/modules/devices/solaredge/solaredge/bat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/solaredge/solaredge/bat.py b/packages/modules/devices/solaredge/solaredge/bat.py index fc9148caac..f4d1bc572a 100644 --- a/packages/modules/devices/solaredge/solaredge/bat.py +++ b/packages/modules/devices/solaredge/solaredge/bat.py @@ -22,9 +22,10 @@ class SolaredgeBat(AbstractBat): """ - Klasse zur Verwaltung der Solaredge-Batteriesteuerung. + Klasse zur Verwaltung der Solaredge-Batteriesteuerung. Beinhaltet Funktionen zur Überwachung und Steuerung. """ + REMOTE_CONTROL_REGISTER = 57348 # Aktivierung von Remote Control ADVANCED_PWR_CTRL_REGISTER = 57740 # Aktivierung des erweiterten Leistungsmodus COMMIT_REGISTER = 57741 # Bestätigung von Änderungen @@ -137,4 +138,5 @@ def set_power_limit(self, power_limit: Optional[Union[int, float]]) -> None: except Exception: log.exception("Error setting discharge limit") + component_descriptor = ComponentDescriptor(configuration_factory=SolaredgeBatSetup)