From 2502dd903700276a15a28598b3fcbc9578f8b78a Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Mon, 20 Jan 2025 08:16:23 +0100 Subject: [PATCH 01/14] read bat power from modbus, remove calculation from voltage and current --- .../devices/sma/sma_sunny_boy/bat_smart_energy.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index b5b5d61845..32efd19756 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -34,15 +34,18 @@ def read(self) -> BatState: unit = self.component_config.configuration.modbus_id soc = self.__tcp_client.read_holding_registers(30845, ModbusDataType.UINT_32, unit=unit) - current = self.__tcp_client.read_holding_registers(30843, ModbusDataType.INT_32, unit=unit)/-1000 - voltage = self.__tcp_client.read_holding_registers(30851, ModbusDataType.INT_32, unit=unit)/100 + imp = self.__tcp_client.read_holding_registers(31393, ModbusDataType.INT_32, unit=unit) + exp = self.__tcp_client.read_holding_registers(31395, ModbusDataType.INT_32, unit=unit) if soc == self.SMA_UINT32_NAN: # If the storage is empty and nothing is produced on the DC side, the inverter does not supply any values. soc = 0 power = 0 else: - power = current*voltage + if imp > 5: + power = imp + else: + power = exp * -1 exported = self.__tcp_client.read_holding_registers(31401, ModbusDataType.UINT_64, unit=3) imported = self.__tcp_client.read_holding_registers(31397, ModbusDataType.UINT_64, unit=3) From 0102a898904545a6127f14efc64a7077eae5b498 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Sat, 1 Feb 2025 16:00:29 +0000 Subject: [PATCH 02/14] improve logging --- packages/modules/devices/sma/sma_sunny_boy/bat.py | 7 ++++++- .../modules/devices/sma/sma_sunny_boy/bat_smart_energy.py | 7 ++++++- packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat.py b/packages/modules/devices/sma/sma_sunny_boy/bat.py index 99624e1295..61ba5f2193 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import logging from typing import Dict, Union from dataclass_utils import dataclass_from_dict @@ -10,6 +11,8 @@ from modules.common.store import get_bat_value_store from modules.devices.sma.sma_sunny_boy.config import SmaSunnyBoyBatSetup +log = logging.getLogger(__name__) + class SunnyBoyBat(AbstractBat): SMA_UINT_64_NAN = 0xFFFFFFFFFFFFFFFF # SMA uses this value to represent NaN @@ -42,12 +45,14 @@ def read(self) -> BatState: 'Sobald die Batterie geladen/entladen wird sollte sich dieser Wert ändern, ', 'andernfalls kann ein Defekt vorliegen.') - return BatState( + bat_state = BatState( power=power, soc=soc, imported=imported, exported=exported ) + log.debug("Bat {}: {}".format(self.tcp_client.address, bat_state)) + return bat_state def update(self) -> None: self.store.set(self.read()) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 32efd19756..1eacb86d1f 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import logging from typing import Dict, Union from dataclass_utils import dataclass_from_dict @@ -11,6 +12,8 @@ from modules.common.store import get_bat_value_store from modules.devices.sma.sma_sunny_boy.config import SmaSunnyBoySmartEnergyBatSetup +log = logging.getLogger(__name__) + class SunnyBoySmartEnergyBat(AbstractBat): SMA_UINT32_NAN = 0xFFFFFFFF # SMA uses this value to represent NaN @@ -54,12 +57,14 @@ def read(self) -> BatState: 'Sobald die Batterie geladen/entladen wird sollte sich dieser Wert ändern, ', 'andernfalls kann ein Defekt vorliegen.') - return BatState( + bat_state = BatState( power=power, soc=soc, imported=imported, exported=exported ) + log.debug("Bat {}: {}".format(self.tcp_client.address, bat_state)) + return bat_state component_descriptor = ComponentDescriptor(configuration_factory=SmaSunnyBoySmartEnergyBatSetup) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py b/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py index 9fe38a8ed1..1abb40f038 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import logging from modules.common.abstract_device import AbstractBat from modules.common.component_state import BatState @@ -9,6 +10,8 @@ from modules.common.store import get_bat_value_store from modules.devices.sma.sma_sunny_boy.config import SmaTesvoltBatSetup +log = logging.getLogger(__name__) + class TesvoltBat(AbstractBat): def __init__(self, @@ -32,7 +35,7 @@ def update(self) -> None: imported=imported, exported=exported ) - + log.debug("Bat {}: {}".format(self.tcp_client.address, bat_state)) self.store.set(bat_state) From 791f23b11fce054ab85c671a9bc584a4b7e8b087 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Sun, 2 Feb 2025 13:31:31 +0000 Subject: [PATCH 03/14] refactor read modbus values, add logging --- .../sma/sma_sunny_boy/bat_smart_energy.py | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 1eacb86d1f..245440cc25 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union +from typing import Dict, Union, Tuple from dataclass_utils import dataclass_from_dict from modules.common.abstract_device import AbstractBat @@ -36,35 +36,55 @@ def update(self) -> None: def read(self) -> BatState: unit = self.component_config.configuration.modbus_id - soc = self.__tcp_client.read_holding_registers(30845, ModbusDataType.UINT_32, unit=unit) - imp = self.__tcp_client.read_holding_registers(31393, ModbusDataType.INT_32, unit=unit) - exp = self.__tcp_client.read_holding_registers(31395, ModbusDataType.INT_32, unit=unit) + # Define the required registers + registers = { + "Battery_SoC": (30845, ModbusDataType.UINT_32), + "Battery_ChargePower": (31393, ModbusDataType.INT_32), + "Battery_DischargePower": (31395, ModbusDataType.INT_32), + "Battery_ChargedEnergy": (31401, ModbusDataType.UINT_64), + "Battery_DischargedEnergy": (31397, ModbusDataType.UINT_64), + "Inverter_Type": (30053, ModbusDataType.UINT_32) + } - if soc == self.SMA_UINT32_NAN: + # Read all values + values = self.read_registers(registers, unit) + + if values["Battery_SoC"] == self.SMA_UINT32_NAN: # If the storage is empty and nothing is produced on the DC side, the inverter does not supply any values. - soc = 0 + values["Battery_SoC"] = 0 power = 0 else: - if imp > 5: - power = imp + if values["Battery_ChargePower"] > 5: + power = values["Battery_ChargePower"] else: - power = exp * -1 - exported = self.__tcp_client.read_holding_registers(31401, ModbusDataType.UINT_64, unit=3) - imported = self.__tcp_client.read_holding_registers(31397, ModbusDataType.UINT_64, unit=3) + power = values["Battery_DischargePower"] * -1 - if exported == self.SMA_UINT_64_NAN or imported == self.SMA_UINT_64_NAN: - raise ValueError(f'Batterie lieferte nicht plausible Werte. Export: {exported}, Import: {imported}. ', - 'Sobald die Batterie geladen/entladen wird sollte sich dieser Wert ändern, ', - 'andernfalls kann ein Defekt vorliegen.') + if (values["Battery_ChargedEnergy"] == self.SMA_UINT_64_NAN or + values["Battery_DischargedEnergy"] == self.SMA_UINT_64_NAN): + raise ValueError( + f'Batterie lieferte nicht plausible Werte. Geladene Energie: {values["Battery_ChargedEnergy"]}, ' + f'Entladene Energie: {values["Battery_DischargedEnergy"]}. ', + 'Sobald die Batterie geladen/entladen wird sollte sich dieser Wert ändern, ', + 'andernfalls kann ein Defekt vorliegen.' + ) bat_state = BatState( power=power, - soc=soc, - imported=imported, - exported=exported + soc=values["Battery_SoC"], + exported=values["Battery_ChargedEnergy"], + imported=values["Battery_DischargedEnergy"] ) - log.debug("Bat {}: {}".format(self.tcp_client.address, bat_state)) + log.debug("Bat {}: {}".format(self.__tcp_client.address, bat_state)) return bat_state + def read_registers( + self, registers: Dict[str, Tuple[int, ModbusDataType]], unit: int + ) -> Dict[str, Union[int, float]]: + values = {} + for key, (address, data_type) in registers.items(): + values[key] = self.__tcp_client.read_holding_registers(address, data_type, unit=unit) + log.debug("Bat raw values {}: {}".format(self.__tcp_client.address, values)) + return values + component_descriptor = ComponentDescriptor(configuration_factory=SmaSunnyBoySmartEnergyBatSetup) From 6fdeb85a44c201a0c2ebfee946679d1eccee0f48 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 6 Feb 2025 08:55:06 +0000 Subject: [PATCH 04/14] fix typo --- .../modules/devices/sma/sma_sunny_boy/bat_smart_energy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 245440cc25..f2d7b983bb 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -41,8 +41,8 @@ def read(self) -> BatState: "Battery_SoC": (30845, ModbusDataType.UINT_32), "Battery_ChargePower": (31393, ModbusDataType.INT_32), "Battery_DischargePower": (31395, ModbusDataType.INT_32), - "Battery_ChargedEnergy": (31401, ModbusDataType.UINT_64), - "Battery_DischargedEnergy": (31397, ModbusDataType.UINT_64), + "Battery_ChargedEnergy": (31397, ModbusDataType.UINT_64), + "Battery_DischargedEnergy": (31401, ModbusDataType.UINT_64), "Inverter_Type": (30053, ModbusDataType.UINT_32) } @@ -71,8 +71,8 @@ def read(self) -> BatState: bat_state = BatState( power=power, soc=values["Battery_SoC"], - exported=values["Battery_ChargedEnergy"], - imported=values["Battery_DischargedEnergy"] + exported=values["Battery_DischargedEnergy"], + imported=values["Battery_ChargedEnergy"] ) log.debug("Bat {}: {}".format(self.__tcp_client.address, bat_state)) return bat_state From f7f377dba6ecf5e3b50d2753876846e6e6043565 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Mon, 10 Feb 2025 08:59:39 +0000 Subject: [PATCH 05/14] trigger build From 51ab1c3715539c8a79825ce2ca94dde8d3d6c087 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Feb 2025 08:03:32 +0000 Subject: [PATCH 06/14] refactor SMA Sunny Boy battery module: consolidate register definitions and add power limit functionality --- .../sma/sma_sunny_boy/bat_smart_energy.py | 84 +++++++++++++++---- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index f2d7b983bb..6c218b5b84 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Tuple +from typing import Dict, Union, Optional from dataclass_utils import dataclass_from_dict from modules.common.abstract_device import AbstractBat @@ -11,6 +11,8 @@ from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.sma.sma_sunny_boy.config import SmaSunnyBoySmartEnergyBatSetup +from pymodbus.payload import BinaryPayloadBuilder +from pymodbus.constants import Endian log = logging.getLogger(__name__) @@ -19,6 +21,18 @@ class SunnyBoySmartEnergyBat(AbstractBat): SMA_UINT32_NAN = 0xFFFFFFFF # SMA uses this value to represent NaN SMA_UINT_64_NAN = 0xFFFFFFFFFFFFFFFF # SMA uses this value to represent NaN + # Define all possible registers with their data types + REGISTERS = { + "Battery_SoC": (30845, ModbusDataType.UINT_32), + "Battery_ChargePower": (31393, ModbusDataType.INT_32), + "Battery_DischargePower": (31395, ModbusDataType.INT_32), + "Battery_ChargedEnergy": (31397, ModbusDataType.UINT_64), + "Battery_DischargedEnergy": (31401, ModbusDataType.UINT_64), + "Inverter_Type": (30053, ModbusDataType.UINT_32), + "Externe_Steuerung": (40151, ModbusDataType.UINT_32), + "Wirkleistungsvorgabe": (40149, ModbusDataType.UINT_32), + } + def __init__(self, device_id: int, component_config: Union[Dict, SmaSunnyBoySmartEnergyBatSetup], @@ -36,18 +50,18 @@ def update(self) -> None: def read(self) -> BatState: unit = self.component_config.configuration.modbus_id - # Define the required registers - registers = { - "Battery_SoC": (30845, ModbusDataType.UINT_32), - "Battery_ChargePower": (31393, ModbusDataType.INT_32), - "Battery_DischargePower": (31395, ModbusDataType.INT_32), - "Battery_ChargedEnergy": (31397, ModbusDataType.UINT_64), - "Battery_DischargedEnergy": (31401, ModbusDataType.UINT_64), - "Inverter_Type": (30053, ModbusDataType.UINT_32) - } + # List the required registers to read + registers_to_read = [ + "Battery_SoC", + "Battery_ChargePower", + "Battery_DischargePower", + "Battery_ChargedEnergy", + "Battery_DischargedEnergy", + "Inverter_Type" + ] # Read all values - values = self.read_registers(registers, unit) + values = self.read_registers(registers_to_read, unit) if values["Battery_SoC"] == self.SMA_UINT32_NAN: # If the storage is empty and nothing is produced on the DC side, the inverter does not supply any values. @@ -77,14 +91,54 @@ def read(self) -> BatState: log.debug("Bat {}: {}".format(self.__tcp_client.address, bat_state)) return bat_state - def read_registers( - self, registers: Dict[str, Tuple[int, ModbusDataType]], unit: int - ) -> Dict[str, Union[int, float]]: + def set_power_limit(self, power_limit: Optional[int]) -> None: + unit = self.component_config.configuration.modbus_id + + if power_limit is None: + log.debug("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") + values_to_write = { + "Externe_Steuerung": 803, + "Wirkleistungsvorgabe": 0, + } + else: + log.debug("Aktive Batteriesteuerung vorhanden. Setze externe Steuerung.") + values_to_write = { + "Externe_Steuerung": 802, + "Wirkleistungsvorgabe": power_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, (address, data_type) in registers.items(): + for key in register_names: + address, data_type = self.REGISTERS[key] values[key] = self.__tcp_client.read_holding_registers(address, data_type, unit=unit) log.debug("Bat raw values {}: {}".format(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 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) + + if data_type == ModbusDataType.UINT_32: + builder.add_32bit_uint(value) + elif data_type == ModbusDataType.INT_32: + builder.add_32bit_int(value) + elif data_type == ModbusDataType.UINT_16: + builder.add_16bit_uint(value) + elif data_type == ModbusDataType.INT_16: + builder.add_16bit_int(value) + else: + raise ValueError(f"Unsupported data type: {data_type}") + + return builder.to_registers() + component_descriptor = ComponentDescriptor(configuration_factory=SmaSunnyBoySmartEnergyBatSetup) From 7cf13011bb6debc01ca6b571f5bda81451d09404 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Feb 2025 08:08:19 +0000 Subject: [PATCH 07/14] fix attribute error --- packages/modules/devices/sma/sma_sunny_boy/bat.py | 2 +- packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat.py b/packages/modules/devices/sma/sma_sunny_boy/bat.py index 61ba5f2193..3ab2a720bc 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat.py @@ -51,7 +51,7 @@ def read(self) -> BatState: imported=imported, exported=exported ) - log.debug("Bat {}: {}".format(self.tcp_client.address, bat_state)) + log.debug("Bat {}: {}".format(self.__tcp_client.address, bat_state)) return bat_state def update(self) -> None: diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py b/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py index 1abb40f038..21fec4b06d 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py @@ -35,7 +35,7 @@ def update(self) -> None: imported=imported, exported=exported ) - log.debug("Bat {}: {}".format(self.tcp_client.address, bat_state)) + log.debug("Bat {}: {}".format(self.__tcp_client.address, bat_state)) self.store.set(bat_state) From 96c08b44a15390d1e067da7afa8cdeba489ea611 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Feb 2025 14:56:14 +0000 Subject: [PATCH 08/14] refactor for readability, skip unecessary modbus writes when there is no change --- .../sma/sma_sunny_boy/bat_smart_energy.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 6c218b5b84..e09b3e7466 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -43,6 +43,7 @@ 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 = None def update(self) -> None: self.store.set(self.read()) @@ -61,7 +62,7 @@ def read(self) -> BatState: ] # Read all values - values = self.read_registers(registers_to_read, unit) + values = self._read_registers(registers_to_read, unit) if values["Battery_SoC"] == self.SMA_UINT32_NAN: # If the storage is empty and nothing is produced on the DC side, the inverter does not supply any values. @@ -94,22 +95,26 @@ def read(self) -> BatState: def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id - if power_limit is None: + if power_limit is None and self.last_mode is not None: + # Kein Powerlimit gefordert, externe Steuerung war aktiv, externe Steuerung deaktivieren log.debug("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") values_to_write = { "Externe_Steuerung": 803, "Wirkleistungsvorgabe": 0, } + self._write_registers(values_to_write, unit) + self.last_mode = None else: + # Powerlimit gefordert, externe Steuerung aktivieren, Limit setzen log.debug("Aktive Batteriesteuerung vorhanden. Setze externe Steuerung.") values_to_write = { "Externe_Steuerung": 802, "Wirkleistungsvorgabe": power_limit } + self._write_registers(values_to_write, unit) + self.last_mode = 'limited' - self.write_registers(values_to_write, unit) - - def read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: + def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]: values = {} for key in register_names: address, data_type = self.REGISTERS[key] @@ -117,24 +122,24 @@ def read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int log.debug("Bat raw values {}: {}".format(self.__tcp_client.address, values)) return values - def write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: int) -> None: + 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) + 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: + def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> list: builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) if data_type == ModbusDataType.UINT_32: - builder.add_32bit_uint(value) + builder.add_32bit_uint(int(value)) elif data_type == ModbusDataType.INT_32: - builder.add_32bit_int(value) + builder.add_32bit_int(int(value)) elif data_type == ModbusDataType.UINT_16: - builder.add_16bit_uint(value) + builder.add_16bit_uint(int(value)) elif data_type == ModbusDataType.INT_16: - builder.add_16bit_int(value) + builder.add_16bit_int(int(value)) else: raise ValueError(f"Unsupported data type: {data_type}") From ed205ea6a888ade903ca0e0a189c2aba8a2e62c7 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Feb 2025 15:12:14 +0000 Subject: [PATCH 09/14] fix pytest --- .../devices/sma/sma_sunny_boy/bat_smart_energy.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index e09b3e7466..3ed452460d 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -11,8 +11,8 @@ from modules.common.simcount import SimCounter from modules.common.store import get_bat_value_store from modules.devices.sma.sma_sunny_boy.config import SmaSunnyBoySmartEnergyBatSetup -from pymodbus.payload import BinaryPayloadBuilder -from pymodbus.constants import Endian +import pymodbus + log = logging.getLogger(__name__) @@ -130,8 +130,10 @@ def _write_registers(self, values_to_write: Dict[str, Union[int, float]], 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 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big) - + builder = pymodbus.payload.BinaryPayloadBuilder( + byteorder=pymodbus.constants.Endian.Big, + wordorder=pymodbus.constants.Endian.Big + ) if data_type == ModbusDataType.UINT_32: builder.add_32bit_uint(int(value)) elif data_type == ModbusDataType.INT_32: From 6154f3d4db7de5ac444b1375ae7f602b71bcb589 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Feb 2025 21:04:40 +0000 Subject: [PATCH 10/14] refactor SunnyBoySmartEnergyBat: optimize register reading and improve logging --- .../sma/sma_sunny_boy/bat_smart_energy.py | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 3ed452460d..882af1c221 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -44,6 +44,7 @@ def __init__(self, self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) self.last_mode = None + self.Inverter_Type = None def update(self) -> None: self.store.set(self.read()) @@ -51,17 +52,17 @@ def update(self) -> None: def read(self) -> BatState: unit = self.component_config.configuration.modbus_id - # List the required registers to read registers_to_read = [ "Battery_SoC", "Battery_ChargePower", "Battery_DischargePower", "Battery_ChargedEnergy", - "Battery_DischargedEnergy", - "Inverter_Type" + "Battery_DischargedEnergy" ] - # Read all values + if self.inverter_type is None: # Only read Inverter_Type if not already set + registers_to_read.append("Inverter_Type") + values = self._read_registers(registers_to_read, unit) if values["Battery_SoC"] == self.SMA_UINT32_NAN: @@ -89,7 +90,10 @@ def read(self) -> BatState: exported=values["Battery_DischargedEnergy"], imported=values["Battery_ChargedEnergy"] ) - log.debug("Bat {}: {}".format(self.__tcp_client.address, bat_state)) + if self.inverter_type is None: + self.Inverter_Type = values["Inverter_Type"] + log.debug(f"Inverter Type: {self.Inverter_Type}") + log.debug(f"Bat {self.__tcp_client.address}: {bat_state}") return bat_state def set_power_limit(self, power_limit: Optional[int]) -> None: @@ -119,7 +123,7 @@ def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[in for key in register_names: address, data_type = self.REGISTERS[key] values[key] = self.__tcp_client.read_holding_registers(address, data_type, unit=unit) - log.debug("Bat raw values {}: {}".format(self.__tcp_client.address, values)) + 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: @@ -131,17 +135,18 @@ def _write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: 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.Big + byteorder=pymodbus.constants.Endian.Big, + wordorder=pymodbus.constants.Endian.Big ) - if data_type == ModbusDataType.UINT_32: - builder.add_32bit_uint(int(value)) - elif data_type == ModbusDataType.INT_32: - builder.add_32bit_int(int(value)) - elif data_type == ModbusDataType.UINT_16: - builder.add_16bit_uint(int(value)) - elif data_type == ModbusDataType.INT_16: - builder.add_16bit_int(int(value)) + 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, + } + + if data_type in encode_methods: + encode_methods[data_type](int(value)) else: raise ValueError(f"Unsupported data type: {data_type}") From bcaf43c253831ae7c45a5787552b5b526d9ae277 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Sun, 16 Mar 2025 19:42:33 +0000 Subject: [PATCH 11/14] fix logic for last_mode --- .../sma/sma_sunny_boy/bat_smart_energy.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 882af1c221..1bfe91b35f 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -43,7 +43,7 @@ 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 = None + self.last_mode = 'Undefined' self.Inverter_Type = None def update(self) -> None: @@ -98,16 +98,18 @@ def read(self) -> BatState: def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id - - if power_limit is None and self.last_mode is not None: - # Kein Powerlimit gefordert, externe Steuerung war aktiv, externe Steuerung deaktivieren - log.debug("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") - values_to_write = { - "Externe_Steuerung": 803, - "Wirkleistungsvorgabe": 0, - } - self._write_registers(values_to_write, unit) - self.last_mode = None + log.debug(f'last_mode: {self.last_mode}') + + if power_limit is None: + if self.last_mode is not None: + # Kein Powerlimit gefordert, externe Steuerung war aktiv, externe Steuerung deaktivieren + log.debug("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung.") + values_to_write = { + "Externe_Steuerung": 803, + "Wirkleistungsvorgabe": 0, + } + self._write_registers(values_to_write, unit) + self.last_mode = None else: # Powerlimit gefordert, externe Steuerung aktivieren, Limit setzen log.debug("Aktive Batteriesteuerung vorhanden. Setze externe Steuerung.") From 96fe55f47ba5bacf87442c3c2a3b5abc70fae248 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Wed, 19 Mar 2025 13:00:00 +0000 Subject: [PATCH 12/14] fix inverter_type --- .../modules/devices/sma/sma_sunny_boy/bat_smart_energy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 1bfe91b35f..95147479bc 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -44,7 +44,7 @@ def __init__(self, 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.Inverter_Type = None + self.inverter_type = None def update(self) -> None: self.store.set(self.read()) @@ -60,7 +60,7 @@ def read(self) -> BatState: "Battery_DischargedEnergy" ] - if self.inverter_type is None: # Only read Inverter_Type if not already set + if self.Inverter_Type is None: # Only read Inverter_Type if not already set registers_to_read.append("Inverter_Type") values = self._read_registers(registers_to_read, unit) @@ -91,8 +91,8 @@ def read(self) -> BatState: imported=values["Battery_ChargedEnergy"] ) if self.inverter_type is None: - self.Inverter_Type = values["Inverter_Type"] - log.debug(f"Inverter Type: {self.Inverter_Type}") + self.inverter_type = values["Inverter_Type"] + log.debug(f"Inverter Type: {self.inverter_type}") log.debug(f"Bat {self.__tcp_client.address}: {bat_state}") return bat_state From db1600817c49f4f52baa3283a95d7d9dbaf23365 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Wed, 19 Mar 2025 13:00:23 +0000 Subject: [PATCH 13/14] remove debug loggig --- packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 95147479bc..5126fae2d6 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -98,7 +98,6 @@ def read(self) -> BatState: def set_power_limit(self, power_limit: Optional[int]) -> None: unit = self.component_config.configuration.modbus_id - log.debug(f'last_mode: {self.last_mode}') if power_limit is None: if self.last_mode is not None: From 618e357606c9e8bd422966dd7c1a8005fbaeddd4 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Mar 2025 11:36:29 +0100 Subject: [PATCH 14/14] fix inverter_type --- packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py index 5126fae2d6..8690b70866 100644 --- a/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py +++ b/packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py @@ -60,7 +60,7 @@ def read(self) -> BatState: "Battery_DischargedEnergy" ] - if self.Inverter_Type is None: # Only read Inverter_Type if not already set + if self.inverter_type is None: # Only read Inverter_Type if not already set registers_to_read.append("Inverter_Type") values = self._read_registers(registers_to_read, unit)