diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 76a54a5533..a46bdd05a3 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -58,7 +58,7 @@ class UpdateConfig: - DATASTORE_VERSION = 143 + DATASTORE_VERSION = 144 valid_topic = [ "^openWB/bat/config/bat_control_activated$", @@ -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) diff --git a/packages/modules/devices/growatt/growatt/bat.py b/packages/modules/devices/growatt/growatt/bat.py index 2edf6eddf9..e6351d2076 100644 --- a/packages/modules/devices/growatt/growatt/bat.py +++ b/packages/modules/devices/growatt/growatt/bat.py @@ -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( diff --git a/packages/modules/devices/growatt/growatt/config.py b/packages/modules/devices/growatt/growatt/config.py index f37c2e80c8..b405451c8d 100644 --- a/packages/modules/devices/growatt/growatt/config.py +++ b/packages/modules/devices/growatt/growatt/config.py @@ -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 diff --git a/packages/modules/devices/growatt/growatt/counter.py b/packages/modules/devices/growatt/growatt/counter.py index c03763e0fc..f40eac48fe 100644 --- a/packages/modules/devices/growatt/growatt/counter.py +++ b/packages/modules/devices/growatt/growatt/counter.py @@ -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( diff --git a/packages/modules/devices/growatt/growatt/device.py b/packages/modules/devices/growatt/growatt/device.py index c6f95a2ae5..7d453d96ad 100644 --- a/packages/modules/devices/growatt/growatt/device.py +++ b/packages/modules/devices/growatt/growatt/device.py @@ -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) diff --git a/packages/modules/devices/growatt/growatt/inverter.py b/packages/modules/devices/growatt/growatt/inverter.py index da8edc87d4..3122ddf1c1 100644 --- a/packages/modules/devices/growatt/growatt/inverter.py +++ b/packages/modules/devices/growatt/growatt/inverter.py @@ -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 @@ -14,6 +15,7 @@ class KwargsDict(TypedDict): + device_id: int modbus_id: int version: GrowattVersion client: ModbusTcpClient_ @@ -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) diff --git a/packages/modules/devices/growatt/growatt/version.py b/packages/modules/devices/growatt/growatt/version.py index d1044824b0..bafa0739b0 100644 --- a/packages/modules/devices/growatt/growatt/version.py +++ b/packages/modules/devices/growatt/growatt/version.py @@ -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"