11#!/usr/bin/env python3
2- from typing import Dict , Union
2+ import logging
3+ from typing import Dict , Union , Optional
34
45from dataclass_utils import dataclass_from_dict
56from modules .common .abstract_device import AbstractBat
1011from modules .common .simcount import SimCounter
1112from modules .common .store import get_bat_value_store
1213from modules .devices .sma .sma_sunny_boy .config import SmaSunnyBoySmartEnergyBatSetup
14+ import pymodbus
15+
16+
17+ log = logging .getLogger (__name__ )
1318
1419
1520class SunnyBoySmartEnergyBat (AbstractBat ):
1621 SMA_UINT32_NAN = 0xFFFFFFFF # SMA uses this value to represent NaN
1722 SMA_UINT_64_NAN = 0xFFFFFFFFFFFFFFFF # SMA uses this value to represent NaN
1823
24+ # Define all possible registers with their data types
25+ REGISTERS = {
26+ "Battery_SoC" : (30845 , ModbusDataType .UINT_32 ),
27+ "Battery_ChargePower" : (31393 , ModbusDataType .INT_32 ),
28+ "Battery_DischargePower" : (31395 , ModbusDataType .INT_32 ),
29+ "Battery_ChargedEnergy" : (31397 , ModbusDataType .UINT_64 ),
30+ "Battery_DischargedEnergy" : (31401 , ModbusDataType .UINT_64 ),
31+ "Inverter_Type" : (30053 , ModbusDataType .UINT_32 ),
32+ "Externe_Steuerung" : (40151 , ModbusDataType .UINT_32 ),
33+ "Wirkleistungsvorgabe" : (40149 , ModbusDataType .UINT_32 ),
34+ }
35+
1936 def __init__ (self ,
2037 device_id : int ,
2138 component_config : Union [Dict , SmaSunnyBoySmartEnergyBatSetup ],
@@ -26,37 +43,115 @@ def __init__(self,
2643 self .sim_counter = SimCounter (self .__device_id , self .component_config .id , prefix = "speicher" )
2744 self .store = get_bat_value_store (self .component_config .id )
2845 self .fault_state = FaultState (ComponentInfo .from_component_config (self .component_config ))
46+ self .last_mode = 'Undefined'
47+ self .inverter_type = None
2948
3049 def update (self ) -> None :
3150 self .store .set (self .read ())
3251
3352 def read (self ) -> BatState :
3453 unit = self .component_config .configuration .modbus_id
3554
36- soc = self .__tcp_client .read_holding_registers (30845 , ModbusDataType .UINT_32 , unit = unit )
37- current = self .__tcp_client .read_holding_registers (30843 , ModbusDataType .INT_32 , unit = unit )/ - 1000
38- voltage = self .__tcp_client .read_holding_registers (30851 , ModbusDataType .INT_32 , unit = unit )/ 100
55+ registers_to_read = [
56+ "Battery_SoC" ,
57+ "Battery_ChargePower" ,
58+ "Battery_DischargePower" ,
59+ "Battery_ChargedEnergy" ,
60+ "Battery_DischargedEnergy"
61+ ]
62+
63+ if self .inverter_type is None : # Only read Inverter_Type if not already set
64+ registers_to_read .append ("Inverter_Type" )
3965
40- if soc == self .SMA_UINT32_NAN :
66+ values = self ._read_registers (registers_to_read , unit )
67+
68+ if values ["Battery_SoC" ] == self .SMA_UINT32_NAN :
4169 # If the storage is empty and nothing is produced on the DC side, the inverter does not supply any values.
42- soc = 0
70+ values [ "Battery_SoC" ] = 0
4371 power = 0
4472 else :
45- power = current * voltage
46- exported = self .__tcp_client .read_holding_registers (31401 , ModbusDataType .UINT_64 , unit = 3 )
47- imported = self .__tcp_client .read_holding_registers (31397 , ModbusDataType .UINT_64 , unit = 3 )
73+ if values ["Battery_ChargePower" ] > 5 :
74+ power = values ["Battery_ChargePower" ]
75+ else :
76+ power = values ["Battery_DischargePower" ] * - 1
4877
49- if exported == self .SMA_UINT_64_NAN or imported == self .SMA_UINT_64_NAN :
50- raise ValueError (f'Batterie lieferte nicht plausible Werte. Export: { exported } , Import: { imported } . ' ,
51- 'Sobald die Batterie geladen/entladen wird sollte sich dieser Wert ändern, ' ,
52- 'andernfalls kann ein Defekt vorliegen.' )
78+ if (values ["Battery_ChargedEnergy" ] == self .SMA_UINT_64_NAN or
79+ values ["Battery_DischargedEnergy" ] == self .SMA_UINT_64_NAN ):
80+ raise ValueError (
81+ f'Batterie lieferte nicht plausible Werte. Geladene Energie: { values ["Battery_ChargedEnergy" ]} , '
82+ f'Entladene Energie: { values ["Battery_DischargedEnergy" ]} . ' ,
83+ 'Sobald die Batterie geladen/entladen wird sollte sich dieser Wert ändern, ' ,
84+ 'andernfalls kann ein Defekt vorliegen.'
85+ )
5386
54- return BatState (
87+ bat_state = BatState (
5588 power = power ,
56- soc = soc ,
57- imported = imported ,
58- exported = exported
89+ soc = values [ "Battery_SoC" ] ,
90+ exported = values [ "Battery_DischargedEnergy" ] ,
91+ imported = values [ "Battery_ChargedEnergy" ]
5992 )
93+ if self .inverter_type is None :
94+ self .inverter_type = values ["Inverter_Type" ]
95+ log .debug (f"Inverter Type: { self .inverter_type } " )
96+ log .debug (f"Bat { self .__tcp_client .address } : { bat_state } " )
97+ return bat_state
98+
99+ def set_power_limit (self , power_limit : Optional [int ]) -> None :
100+ unit = self .component_config .configuration .modbus_id
101+
102+ if power_limit is None :
103+ if self .last_mode is not None :
104+ # Kein Powerlimit gefordert, externe Steuerung war aktiv, externe Steuerung deaktivieren
105+ log .debug ("Keine Batteriesteuerung gefordert, deaktiviere externe Steuerung." )
106+ values_to_write = {
107+ "Externe_Steuerung" : 803 ,
108+ "Wirkleistungsvorgabe" : 0 ,
109+ }
110+ self ._write_registers (values_to_write , unit )
111+ self .last_mode = None
112+ else :
113+ # Powerlimit gefordert, externe Steuerung aktivieren, Limit setzen
114+ log .debug ("Aktive Batteriesteuerung vorhanden. Setze externe Steuerung." )
115+ values_to_write = {
116+ "Externe_Steuerung" : 802 ,
117+ "Wirkleistungsvorgabe" : power_limit
118+ }
119+ self ._write_registers (values_to_write , unit )
120+ self .last_mode = 'limited'
121+
122+ def _read_registers (self , register_names : list , unit : int ) -> Dict [str , Union [int , float ]]:
123+ values = {}
124+ for key in register_names :
125+ address , data_type = self .REGISTERS [key ]
126+ values [key ] = self .__tcp_client .read_holding_registers (address , data_type , unit = unit )
127+ log .debug (f"Bat raw values { self .__tcp_client .address } : { values } " )
128+ return values
129+
130+ def _write_registers (self , values_to_write : Dict [str , Union [int , float ]], unit : int ) -> None :
131+ for key , value in values_to_write .items ():
132+ address , data_type = self .REGISTERS [key ]
133+ encoded_value = self ._encode_value (value , data_type )
134+ self .__tcp_client .write_registers (address , encoded_value , unit = unit )
135+ log .debug (f"Neuer Wert { encoded_value } in Register { address } geschrieben." )
136+
137+ def _encode_value (self , value : Union [int , float ], data_type : ModbusDataType ) -> list :
138+ builder = pymodbus .payload .BinaryPayloadBuilder (
139+ byteorder = pymodbus .constants .Endian .Big ,
140+ wordorder = pymodbus .constants .Endian .Big
141+ )
142+ encode_methods = {
143+ ModbusDataType .UINT_32 : builder .add_32bit_uint ,
144+ ModbusDataType .INT_32 : builder .add_32bit_int ,
145+ ModbusDataType .UINT_16 : builder .add_16bit_uint ,
146+ ModbusDataType .INT_16 : builder .add_16bit_int ,
147+ }
148+
149+ if data_type in encode_methods :
150+ encode_methods [data_type ](int (value ))
151+ else :
152+ raise ValueError (f"Unsupported data type: { data_type } " )
153+
154+ return builder .to_registers ()
60155
61156
62157component_descriptor = ComponentDescriptor (configuration_factory = SmaSunnyBoySmartEnergyBatSetup )
0 commit comments