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
20 changes: 19 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

class UpdateConfig:

DATASTORE_VERSION = 143
DATASTORE_VERSION = 144

valid_topic = [
"^openWB/bat/config/bat_control_activated$",
Expand Down Expand Up @@ -3706,3 +3706,21 @@ def add_consumer_dict(file: str) -> None:
with ThreadPoolExecutor() as executor:
executor.map(add_consumer_dict, files)
self._append_datastore_version(143)

def upgrade_datastore_144(self) -> None:
def upgrade(topic: str, payload) -> Optional[dict]:
if re.search("^openWB/system/device/[0-9]+/config$", topic) is not None:
payload_device = decode_payload(payload)
if payload_device.get("type") == "growatt" and \
payload_device.get("configuration", {}).get("version") == "MAX":
payload_device["configuration"]["version"] = "SPH"
pub_system_message(
payload_device,
"Die Growatt-Geräte-Konfiguration wurde aktualisiert: 'MAX Series' hieß "
"in Wirklichkeit 'SPH/SPA Hybrid mit Speicher' und wurde entsprechend "
"umbenannt. Außerdem wurde ein Vorzeichenfehler bei der Speicherleistung "
"korrigiert - Laden/Entladen wurden bisher vertauscht angezeigt.",
MessageType.WARNING)
return {topic: payload_device}
self._loop_all_received_topics(upgrade)
self._append_datastore_version(144)
55 changes: 37 additions & 18 deletions packages/modules/devices/growatt/growatt/bat.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,49 @@ def initialize(self) -> None:
self.peak_filter = PeakFilter(ComponentType.BAT, self.component_config.id, self.fault_state)

def update(self) -> None:
if self.version == GrowattVersion.max_series:
power_in = self.client.read_input_registers(
1011, ModbusDataType.UINT_32, unit=self.__modbus_id) * 0.1
power_out = self.client.read_input_registers(
1009, ModbusDataType.UINT_32, unit=self.__modbus_id) * -0.1
if self.version == GrowattVersion.vpp:
# Quelle: Growatt VPP Protocol V2.03, Input 31200/31201 (int32, 0.1W).
# Dort gilt: positiv = Laden -> für openWB-Konvention invertieren.
power = self.client.read_input_registers(31200, ModbusDataType.INT_32,
unit=self.__modbus_id) * -0.1
soc = self.client.read_input_registers(31217, ModbusDataType.UINT_16,
unit=self.__modbus_id)
imported = self.client.read_input_registers(31204, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
exported = self.client.read_input_registers(31208, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100

elif self.version == GrowattVersion.sph:
# Quelle: Protocol II V1.39, Storage-Block Input 1000-1249 (SPH/SPA-Hybrid).
# KORREKTUR ggü. bisherigem Stand ("max_series"): Vorzeichen von
# Pcharge1/Pdischarge1 war vertauscht (Laden fälschlich positiv statt negativ) -
# widersprach der eigenen Konvention im tlx-Zweig unten und in counter.py, wo
# beide Zweige konsistent sind.
power_in = self.client.read_input_registers(1011, ModbusDataType.UINT_32,
unit=self.__modbus_id) * -0.1 # Pcharge1
power_out = self.client.read_input_registers(1009, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 0.1 # Pdischarge1
power = power_in + power_out

soc = self.client.read_input_registers(1014, ModbusDataType.UINT_16, unit=self.__modbus_id)
imported = self.client.read_input_registers(
1058, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
exported = self.client.read_input_registers(
1054, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
else:
power_in = self.client.read_input_registers(
3180, ModbusDataType.UINT_32, unit=self.__modbus_id) * -0.1
power_out = self.client.read_input_registers(
3178, ModbusDataType.UINT_32, unit=self.__modbus_id) * 0.1
imported = self.client.read_input_registers(1058, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
exported = self.client.read_input_registers(1054, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100

else: # GrowattVersion.tlx
# Quelle: Protocol II V1.39, BDC1-Block Input 3160-3233 (TL-X/TL-XH/TL3-XH inkl. MOD-XH+APX).
power_in = self.client.read_input_registers(3180, ModbusDataType.UINT_32,
unit=self.__modbus_id) * -0.1 # Pchr
power_out = self.client.read_input_registers(3178, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 0.1 # Pdischr
power = power_in + power_out

soc = self.client.read_input_registers(3171, ModbusDataType.UINT_16, unit=self.__modbus_id)
imported = self.client.read_input_registers(
3131, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
exported = self.client.read_input_registers(
3127, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
imported = self.client.read_input_registers(3131, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
exported = self.client.read_input_registers(3127, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100

imported, exported = self.peak_filter.check_values(power, imported, exported)
bat_state = BatState(
Expand Down
2 changes: 1 addition & 1 deletion packages/modules/devices/growatt/growatt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class GrowattConfiguration:
def __init__(self, modbus_id: int = 1,
ip_address: Optional[str] = None,
port: int = 502,
version: GrowattVersion = GrowattVersion.max_series):
version: GrowattVersion = GrowattVersion.tlx):
self.modbus_id = modbus_id
self.ip_address = ip_address
self.port = port
Expand Down
79 changes: 50 additions & 29 deletions packages/modules/devices/growatt/growatt/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,41 +33,62 @@ def initialize(self) -> None:
self.peak_filter = PeakFilter(ComponentType.COUNTER, self.component_config.id, self.fault_state)

def update(self) -> None:
if self.version == GrowattVersion.max_series:
power_in = self.client.read_input_registers(1021, ModbusDataType.UINT_32, unit=self.__modbus_id) * 0.1
power_out = self.client.read_input_registers(1029, ModbusDataType.UINT_32, unit=self.__modbus_id) * -0.1
if self.version == GrowattVersion.vpp:
# Quelle: Growatt VPP Protocol V2.03, Input 31112 (int32, 0.1W).
# Dort bereits: positiv = Bezug, negativ = Einspeisung - keine Invertierung nötig.
power = self.client.read_input_registers(31112, ModbusDataType.INT_32,
unit=self.__modbus_id) * 0.1
# VPP liefert keine separaten Phasenleistungen am Zähler (nur Summe 31112)
powers = None

exported = self.client.read_input_registers(31124, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
imported = self.client.read_input_registers(31120, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100

elif self.version == GrowattVersion.sph:
# Quelle: Protocol II V1.39, Storage-Block Input 1000-1249 (SPH/SPA-Hybrid).
power_in = self.client.read_input_registers(1021, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 0.1
power_out = self.client.read_input_registers(1029, ModbusDataType.UINT_32,
unit=self.__modbus_id) * -0.1
power = power_in + power_out

powers = [
self.client.read_input_registers(
40, ModbusDataType.INT_32, unit=self.__modbus_id) / 10,
self.client.read_input_registers(
44, ModbusDataType.INT_32, unit=self.__modbus_id) / 10,
self.client.read_input_registers(
48, ModbusDataType.INT_32, unit=self.__modbus_id) / 10]

# Einheit 0.1 kWh
exported = self.client.read_input_registers(1050, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
imported = self.client.read_input_registers(1046, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100

# TL-X Dokumentation hat die gleichen Register wie die MAX Serie,
# zusätzlich sind aber auch unten abweichende enthalten
else:
power_in = self.client.read_input_registers(3041, ModbusDataType.UINT_32, unit=self.__modbus_id) * 0.1
power_out = self.client.read_input_registers(3043, ModbusDataType.UINT_32, unit=self.__modbus_id) * -0.1
self.client.read_input_registers(40, ModbusDataType.INT_32,
unit=self.__modbus_id) / 10,
self.client.read_input_registers(44, ModbusDataType.INT_32,
unit=self.__modbus_id) / 10,
self.client.read_input_registers(48, ModbusDataType.INT_32,
unit=self.__modbus_id) / 10]

exported = self.client.read_input_registers(1050, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
imported = self.client.read_input_registers(1046, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100

else: # GrowattVersion.tlx
# Quelle: Protocol II V1.39, Bereich 3000-3124 (TL-X/TL-XH/TL3-XH).
# TL-X Dokumentation hat die gleichen Register wie die MAX Serie,
# zusätzlich sind aber auch unten abweichende enthalten
power_in = self.client.read_input_registers(3041, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 0.1
power_out = self.client.read_input_registers(3043, ModbusDataType.UINT_32,
unit=self.__modbus_id) * -0.1
power = power_in + power_out

powers = [
self.client.read_input_registers(
3028, ModbusDataType.INT_32, unit=self.__modbus_id) / 10,
self.client.read_input_registers(
3032, ModbusDataType.INT_32, unit=self.__modbus_id) / 10,
self.client.read_input_registers(
3036, ModbusDataType.INT_32, unit=self.__modbus_id) / 10]

# Einheit 0.1 kWh
exported = self.client.read_input_registers(3073, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
imported = self.client.read_input_registers(3069, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
self.client.read_input_registers(3028, ModbusDataType.INT_32,
unit=self.__modbus_id) / 10,
self.client.read_input_registers(3032, ModbusDataType.INT_32,
unit=self.__modbus_id) / 10,
self.client.read_input_registers(3036, ModbusDataType.INT_32,
unit=self.__modbus_id) / 10]

exported = self.client.read_input_registers(3073, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
imported = self.client.read_input_registers(3069, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100

imported, exported = self.peak_filter.check_values(power, imported, exported)
counter_state = CounterState(
Expand Down
1 change: 1 addition & 0 deletions packages/modules/devices/growatt/growatt/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def create_counter_component(component_config: GrowattCounterSetup):

def create_inverter_component(component_config: GrowattInverterSetup):
return GrowattInverter(component_config=component_config,
device_id=device_config.id,
modbus_id=device_config.configuration.modbus_id,
version=GrowattVersion(device_config.configuration.version),
client=client)
Expand Down
40 changes: 28 additions & 12 deletions packages/modules/devices/growatt/growatt/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from modules.common.component_state import InverterState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.simcount import SimCounter
from modules.common.utils.peak_filter import PeakFilter
from modules.common.modbus import ModbusDataType, ModbusTcpClient_
from modules.common.store import get_component_value_store
Expand All @@ -14,6 +15,7 @@


class KwargsDict(TypedDict):
device_id: int
modbus_id: int
version: GrowattVersion
client: ModbusTcpClient_
Expand All @@ -25,28 +27,42 @@ def __init__(self, component_config: GrowattInverterSetup, **kwargs: Any) -> Non
self.kwargs: KwargsDict = kwargs

def initialize(self) -> None:
self.__device_id: int = self.kwargs['device_id']
self.__modbus_id: int = self.kwargs['modbus_id']
self.version: GrowattVersion = self.kwargs['version']
self.client: ModbusTcpClient_ = self.kwargs['client']
self.store = get_component_value_store(self.component_config.type, self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.peak_filter = PeakFilter(ComponentType.INVERTER, self.component_config.id, self.fault_state)
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, self.component_config.type)

def update(self) -> None:
if self.version == GrowattVersion.max_series:
power = self.client.read_input_registers(
1, ModbusDataType.UINT_32, unit=self.__modbus_id) / -10
exported = self.client.read_input_registers(
91, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100
else:
power = self.client.read_input_registers(
3001, ModbusDataType.UINT_32, unit=self.__modbus_id) / -10
exported = self.client.read_input_registers(
3053, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100

_, exported = self.peak_filter.check_values(power, None, exported)
if self.version == GrowattVersion.vpp:
# Quelle: Growatt VPP Protocol V2.03, Input 31058 (PV-Gesamtleistung, int32, 0.1W).
power = self.client.read_input_registers(31058, ModbusDataType.INT_32,
unit=self.__modbus_id) / -10
self.peak_filter.check_values(power)
imported, exported = self.sim_counter.sim_count(power)
elif self.version == GrowattVersion.sph:
# Quelle: Protocol II V1.39, Basisblock Input 0-124 (gemeinsam für alle Modellfamilien).
power = self.client.read_input_registers(1, ModbusDataType.UINT_32,
unit=self.__modbus_id) / -10
exported = self.client.read_input_registers(91, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
_, exported = self.peak_filter.check_values(power, None, exported)
imported, _ = self.sim_counter.sim_count(power)
else: # GrowattVersion.tlx
# Quelle: Protocol II V1.39, Bereich 3000-3124 (TL-X/TL-XH/TL3-XH).
power = self.client.read_input_registers(3001, ModbusDataType.UINT_32,
unit=self.__modbus_id) / -10
exported = self.client.read_input_registers(3053, ModbusDataType.UINT_32,
unit=self.__modbus_id) * 100
_, exported = self.peak_filter.check_values(power, None, exported)
imported, _ = self.sim_counter.sim_count(power)

inverter_state = InverterState(
power=power,
imported=imported,
exported=exported
)
self.store.set(inverter_state)
Expand Down
13 changes: 12 additions & 1 deletion packages/modules/devices/growatt/growatt/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,16 @@


class GrowattVersion(Enum):
"""
Growatt-Registerkarten:
- tlx: Protocol II V1.39, Bereich 3000-3374 (TL-X/TL-XH/TL3-XH inkl. MOD-XH mit BDC-Batterie).
- sph: Protocol II V1.39, Bereich 0-124 (WR/Zähler) + 1000-1249 (Speicher) - SPH/SPA-Hybrid.
Vormals fälschlich "MAX" genannt - die echte MAX-Serie hat laut Growatt keine
Batterieregister.
- vpp: Growatt VPP Communication Protocol V2.01/V2.03, Bereich 30000-31499. Neueres,
paralleles Protokoll auf denselben Geräten (MOD/MIN-TL-XH/SPH/WIT), per Device Type
Code (Holding 30000, ggf. 43 für Legacy-only) erkennbar. Signierte 32-Bit-Register.
"""
tlx = "TL-X"
max_series = "MAX"
sph = "SPH"
vpp = "VPP"