Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/modules/devices/sma/sma_sunny_boy/bat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
import logging
from typing import Dict, Union

from dataclass_utils import dataclass_from_dict
Expand All @@ -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
Expand Down Expand Up @@ -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())
Expand Down
129 changes: 112 additions & 17 deletions packages/modules/devices/sma/sma_sunny_boy/bat_smart_energy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
from typing import Dict, Union
import logging
from typing import Dict, Union, Optional

from dataclass_utils import dataclass_from_dict
from modules.common.abstract_device import AbstractBat
Expand All @@ -10,12 +11,28 @@
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
import pymodbus


log = logging.getLogger(__name__)


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],
Expand All @@ -26,37 +43,115 @@ def __init__(self,
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
self.store = get_bat_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.last_mode = 'Undefined'
self.inverter_type = None

def update(self) -> None:
self.store.set(self.read())

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
registers_to_read = [
"Battery_SoC",
"Battery_ChargePower",
"Battery_DischargePower",
"Battery_ChargedEnergy",
"Battery_DischargedEnergy"
]

if self.inverter_type is None: # Only read Inverter_Type if not already set
registers_to_read.append("Inverter_Type")

if soc == self.SMA_UINT32_NAN:
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.
soc = 0
values["Battery_SoC"] = 0
power = 0
else:
power = current*voltage
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)
if values["Battery_ChargePower"] > 5:
power = values["Battery_ChargePower"]
else:
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.'
)

return BatState(
bat_state = BatState(
power=power,
soc=soc,
imported=imported,
exported=exported
soc=values["Battery_SoC"],
exported=values["Battery_DischargedEnergy"],
imported=values["Battery_ChargedEnergy"]
)
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:
unit = self.component_config.configuration.modbus_id

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.")
values_to_write = {
"Externe_Steuerung": 802,
"Wirkleistungsvorgabe": power_limit
}
self._write_registers(values_to_write, unit)
self.last_mode = 'limited'

def _read_registers(self, register_names: list, unit: int) -> Dict[str, Union[int, float]]:
values = {}
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(f"Bat raw values {self.__tcp_client.address}: {values}")
return values

def _write_registers(self, values_to_write: Dict[str, Union[int, float]], unit: int) -> None:
for key, value in values_to_write.items():
address, data_type = self.REGISTERS[key]
encoded_value = self._encode_value(value, data_type)
self.__tcp_client.write_registers(address, encoded_value, unit=unit)
log.debug(f"Neuer Wert {encoded_value} in Register {address} geschrieben.")

def _encode_value(self, value: Union[int, float], data_type: ModbusDataType) -> list:
builder = pymodbus.payload.BinaryPayloadBuilder(
byteorder=pymodbus.constants.Endian.Big,
wordorder=pymodbus.constants.Endian.Big
)
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}")

return builder.to_registers()


component_descriptor = ComponentDescriptor(configuration_factory=SmaSunnyBoySmartEnergyBatSetup)
5 changes: 4 additions & 1 deletion packages/modules/devices/sma/sma_sunny_boy/bat_tesvolt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
import logging

from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
Expand All @@ -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,
Expand All @@ -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)


Expand Down