From 73e8b063f2bb43b7bdd39eee0d6ee70f5051054c Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 18 Sep 2026 14:22:20 +0200 Subject: [PATCH 1/3] Add M-TEC, Weishaupt and Xtherma consumer modules --- packages/modules/consumers/mtec/__init__.py | 0 .../modules/consumers/mtec/mtec/__init__.py | 0 .../modules/consumers/mtec/mtec/config.py | 33 ++++++++++ .../modules/consumers/mtec/mtec/consumer.py | 48 +++++++++++++++ packages/modules/consumers/mtec/vendor.py | 14 +++++ .../modules/consumers/weishaupt/__init__.py | 0 .../modules/consumers/weishaupt/vendor.py | 14 +++++ .../weishaupt/weishaupt_wwp/__init__.py | 0 .../weishaupt/weishaupt_wwp/config.py | 34 +++++++++++ .../weishaupt/weishaupt_wwp/consumer.py | 48 +++++++++++++++ .../modules/consumers/xtherma/__init__.py | 0 packages/modules/consumers/xtherma/vendor.py | 14 +++++ .../consumers/xtherma/xtherma/__init__.py | 0 .../consumers/xtherma/xtherma/config.py | 31 ++++++++++ .../consumers/xtherma/xtherma/consumer.py | 60 +++++++++++++++++++ 15 files changed, 296 insertions(+) create mode 100644 packages/modules/consumers/mtec/__init__.py create mode 100644 packages/modules/consumers/mtec/mtec/__init__.py create mode 100644 packages/modules/consumers/mtec/mtec/config.py create mode 100644 packages/modules/consumers/mtec/mtec/consumer.py create mode 100644 packages/modules/consumers/mtec/vendor.py create mode 100644 packages/modules/consumers/weishaupt/__init__.py create mode 100644 packages/modules/consumers/weishaupt/vendor.py create mode 100644 packages/modules/consumers/weishaupt/weishaupt_wwp/__init__.py create mode 100644 packages/modules/consumers/weishaupt/weishaupt_wwp/config.py create mode 100644 packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py create mode 100644 packages/modules/consumers/xtherma/__init__.py create mode 100644 packages/modules/consumers/xtherma/vendor.py create mode 100644 packages/modules/consumers/xtherma/xtherma/__init__.py create mode 100644 packages/modules/consumers/xtherma/xtherma/config.py create mode 100644 packages/modules/consumers/xtherma/xtherma/consumer.py diff --git a/packages/modules/consumers/mtec/__init__.py b/packages/modules/consumers/mtec/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/mtec/mtec/__init__.py b/packages/modules/consumers/mtec/mtec/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/mtec/mtec/config.py b/packages/modules/consumers/mtec/mtec/config.py new file mode 100644 index 0000000000..c0da618a18 --- /dev/null +++ b/packages/modules/consumers/mtec/mtec/config.py @@ -0,0 +1,33 @@ +from typing import Optional, Tuple + +from control.consumer.consumer_data import ConsumerUsage +from helpermodules.auto_str import auto_str +from modules.common.consumer_setup import ConsumerSetup +from ..vendor import vendor_descriptor + + +@auto_str +class MtecConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1): + self.ip_address = ip_address + self.port = port + self.modbus_id = modbus_id + + +@auto_str +class Mtec(ConsumerSetup[MtecConfiguration]): + def __init__(self, + name: str = "M-TEC Wärmepumpe", + type: str = "mtec", + id: int = 0, + configuration: Optional[MtecConfiguration] = None, + # Am Wärmepumpenregler muss "Modul Type" auf "Virtual" und "WMZ Modus" auf + # "Analog" gestellt werden, siehe M-TEC-Anleitung zur PV-Überschussnutzung. + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE, + ConsumerUsage.METER_ONLY), + **kwargs) -> None: + super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( + ).type, configuration=configuration or MtecConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/mtec/mtec/consumer.py b/packages/modules/consumers/mtec/mtec/consumer.py new file mode 100644 index 0000000000..1cc2d6afec --- /dev/null +++ b/packages/modules/consumers/mtec/mtec/consumer.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Optional + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_state import ConsumerState +from modules.common.component_type import ComponentType +from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount._simcounter import SimCounterConsumer +from modules.consumers.mtec.mtec.config import Mtec + +log = logging.getLogger(__name__) + + +def create_consumer(config: Mtec): + client: Optional[ModbusTcpClient_] = None + sim_counter: Optional[SimCounterConsumer] = None + + def initializer(): + nonlocal client, sim_counter + client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port) + sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) + + def update() -> ConsumerState: + # Reg 707: elektrische Leistungsaufnahme [W]. UNVERIFIZIERT: keine offizielle + # Registerliste gefunden (im Gegensatz zu Reg 1000). + power = client.read_holding_registers(707, ModbusDataType.INT_16, unit=config.configuration.modbus_id) + imported, exported = sim_counter.sim_count(power) + + return ConsumerState( + power=power, + imported=imported, + exported=exported + ) + + def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None: + # Reg 1000: PV-Überschussleistung [W] + value = 0 if power_limit is None else max(round(power_limit), 0) + client.write_register(1000, value, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id) + + return ConfigurableConsumer(consumer_config=config, + initializer=initializer, + update=update, + set_power_limit=set_power_limit) + + +device_descriptor = DeviceDescriptor(configuration_factory=Mtec) diff --git a/packages/modules/consumers/mtec/vendor.py b/packages/modules/consumers/mtec/vendor.py new file mode 100644 index 0000000000..eba3646172 --- /dev/null +++ b/packages/modules/consumers/mtec/vendor.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from modules.common.abstract_device import DeviceDescriptor +from modules.devices.vendors import VendorGroup + + +class Vendor: + def __init__(self): + self.type = Path(__file__).parent.name + self.vendor = "M-TEC" + self.group = VendorGroup.VENDORS.value + + +vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) diff --git a/packages/modules/consumers/weishaupt/__init__.py b/packages/modules/consumers/weishaupt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/weishaupt/vendor.py b/packages/modules/consumers/weishaupt/vendor.py new file mode 100644 index 0000000000..3623816d5c --- /dev/null +++ b/packages/modules/consumers/weishaupt/vendor.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from modules.common.abstract_device import DeviceDescriptor +from modules.devices.vendors import VendorGroup + + +class Vendor: + def __init__(self): + self.type = Path(__file__).parent.name + self.vendor = "Weishaupt" + self.group = VendorGroup.VENDORS.value + + +vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) diff --git a/packages/modules/consumers/weishaupt/weishaupt_wwp/__init__.py b/packages/modules/consumers/weishaupt/weishaupt_wwp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py b/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py new file mode 100644 index 0000000000..0cb16a3967 --- /dev/null +++ b/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py @@ -0,0 +1,34 @@ +from typing import Optional, Tuple + +from control.consumer.consumer_data import ConsumerUsage +from helpermodules.auto_str import auto_str +from modules.common.consumer_setup import ConsumerSetup +from ..vendor import vendor_descriptor + + +@auto_str +class WeishauptConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1): + self.ip_address = ip_address + self.port = port + self.modbus_id = modbus_id + + +@auto_str +class Weishaupt(ConsumerSetup[WeishauptConfiguration]): + def __init__(self, + name: str = "Weishaupt Wärmepumpe (AEROBLOCK/BIBLOCK/SPLITBLOCK/GEOBLOCK)", + type: str = "weishaupt_wwp", + id: int = 0, + configuration: Optional[WeishauptConfiguration] = None, + # kein SUSPENDABLE_ONOFF: die SG-Ready-Eingänge sind auf diesen Geräten laut + # Weishaupt-Datenpunktliste (Modbus TCP WWP) nur lesbar, die Leistungsvorgabe + # SollwertPV übersteuert SG-Ready ohnehin. + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE, + ConsumerUsage.METER_ONLY), + **kwargs) -> None: + super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( + ).type, configuration=configuration or WeishauptConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py b/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py new file mode 100644 index 0000000000..79aeb58da8 --- /dev/null +++ b/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Optional + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_state import ConsumerState +from modules.common.component_type import ComponentType +from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount._simcounter import SimCounterConsumer +from modules.consumers.weishaupt.weishaupt_wwp.config import Weishaupt + +log = logging.getLogger(__name__) + + +def create_consumer(config: Weishaupt): + client: Optional[ModbusTcpClient_] = None + sim_counter: Optional[SimCounterConsumer] = None + + def initializer(): + nonlocal client, sim_counter + client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port) + sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) + + def update() -> ConsumerState: + # Reg 33126: El. Leistungsaufnahme [W] + power = client.read_input_registers(33126, ModbusDataType.UINT_16, unit=config.configuration.modbus_id) + imported, exported = sim_counter.sim_count(power) + + return ConsumerState( + power=power, + imported=imported, + exported=exported + ) + + def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None: + # Reg 40002: SollwertPV [W], 0 gibt die Regelung an die Wärmepumpe zurück (0..65535, + # daher clamp statt negativer Werte). + value = 0 if power_limit is None else min(max(round(power_limit), 0), 65535) + client.write_register(40002, value, data_type=ModbusDataType.UINT_16, unit=config.configuration.modbus_id) + + return ConfigurableConsumer(consumer_config=config, + initializer=initializer, + update=update, + set_power_limit=set_power_limit) + + +device_descriptor = DeviceDescriptor(configuration_factory=Weishaupt) diff --git a/packages/modules/consumers/xtherma/__init__.py b/packages/modules/consumers/xtherma/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/xtherma/vendor.py b/packages/modules/consumers/xtherma/vendor.py new file mode 100644 index 0000000000..a311eb1574 --- /dev/null +++ b/packages/modules/consumers/xtherma/vendor.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from modules.common.abstract_device import DeviceDescriptor +from modules.devices.vendors import VendorGroup + + +class Vendor: + def __init__(self): + self.type = Path(__file__).parent.name + self.vendor = "Xtherma" + self.group = VendorGroup.VENDORS.value + + +vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) diff --git a/packages/modules/consumers/xtherma/xtherma/__init__.py b/packages/modules/consumers/xtherma/xtherma/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/xtherma/xtherma/config.py b/packages/modules/consumers/xtherma/xtherma/config.py new file mode 100644 index 0000000000..31ae8ab5dd --- /dev/null +++ b/packages/modules/consumers/xtherma/xtherma/config.py @@ -0,0 +1,31 @@ +from typing import Optional, Tuple + +from control.consumer.consumer_data import ConsumerUsage +from helpermodules.auto_str import auto_str +from modules.common.consumer_setup import ConsumerSetup +from ..vendor import vendor_descriptor + + +@auto_str +class XthermaConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502, + modbus_id: int = 1): + self.ip_address = ip_address + self.port = port + self.modbus_id = modbus_id + + +@auto_str +class Xtherma(ConsumerSetup[XthermaConfiguration]): + def __init__(self, + name: str = "Xtherma Wärmepumpe", + type: str = "xtherma", + id: int = 0, + configuration: Optional[XthermaConfiguration] = None, + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE, + ConsumerUsage.METER_ONLY), + **kwargs) -> None: + super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( + ).type, configuration=configuration or XthermaConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/xtherma/xtherma/consumer.py b/packages/modules/consumers/xtherma/xtherma/consumer.py new file mode 100644 index 0000000000..f08f6bc387 --- /dev/null +++ b/packages/modules/consumers/xtherma/xtherma/consumer.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import logging +from typing import Optional + +from helpermodules import timecheck +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_state import ConsumerState +from modules.common.component_type import ComponentType +from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount._simcounter import SimCounterConsumer +from modules.consumers.xtherma.xtherma.config import Xtherma + +log = logging.getLogger(__name__) + + +def create_consumer(config: Xtherma): + client: Optional[ModbusTcpClient_] = None + sim_counter: Optional[SimCounterConsumer] = None + last_write: Optional[float] = None + + def initializer(): + nonlocal client, sim_counter + client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port) + sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) + + def update() -> ConsumerState: + # Reg 177 (in_total): elektrische Leistungsaufnahme, Auflösung 10W + power = client.read_holding_registers( + 177, ModbusDataType.UINT_16, unit=config.configuration.modbus_id) * 10 + imported, exported = sim_counter.sim_count(power) + + return ConsumerState( + power=power, + imported=imported, + exported=exported + ) + + def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None: + # Reg 70/71 dürfen laut Xtherma-Protokoll höchstens 1x/min beschrieben werden. + nonlocal last_write + if last_write is not None and timecheck.check_timestamp(last_write, 60): + return + last_write = timecheck.create_timestamp() + unit = config.configuration.modbus_id + if power_limit is None: + client.write_register(70, 0, data_type=ModbusDataType.UINT_16, unit=unit) + else: + # Reg 71: Überschuss [W], 0..100000 + client.write_register( + 71, min(max(round(power_limit), 0), 100000), data_type=ModbusDataType.UINT_16, unit=unit) + client.write_register(70, 1, data_type=ModbusDataType.UINT_16, unit=unit) + + return ConfigurableConsumer(consumer_config=config, + initializer=initializer, + update=update, + set_power_limit=set_power_limit) + + +device_descriptor = DeviceDescriptor(configuration_factory=Xtherma) From dfcc4947971a937bc5fef0bf4980b32d4ce1ab5c Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 25 Sep 2026 07:29:48 +0200 Subject: [PATCH 2/3] =?UTF-8?q?M-TEC:=20Reg=20707=20durch=20evcc-Quelle=20?= =?UTF-8?q?best=C3=A4tigt,=20Kommentar=20aktualisiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reg 707 (elektrische Leistungsaufnahme) ist in evcc's mtec.yaml als holding register mit scale 1 gelistet, identisch zu unserer Implementierung. --- packages/modules/consumers/mtec/mtec/consumer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/modules/consumers/mtec/mtec/consumer.py b/packages/modules/consumers/mtec/mtec/consumer.py index 1cc2d6afec..277c18dc57 100644 --- a/packages/modules/consumers/mtec/mtec/consumer.py +++ b/packages/modules/consumers/mtec/mtec/consumer.py @@ -23,8 +23,7 @@ def initializer(): sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) def update() -> ConsumerState: - # Reg 707: elektrische Leistungsaufnahme [W]. UNVERIFIZIERT: keine offizielle - # Registerliste gefunden (im Gegensatz zu Reg 1000). + # Reg 707: elektrische Leistungsaufnahme [W] power = client.read_holding_registers(707, ModbusDataType.INT_16, unit=config.configuration.modbus_id) imported, exported = sim_counter.sim_count(power) From 7042432ef24acc09eb75f68eb2b05c0a0d99fe1d Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 25 Sep 2026 07:35:37 +0200 Subject: [PATCH 3/3] =?UTF-8?q?Weishaupt:=20Reg=2033126=20entfernt=20(nich?= =?UTF-8?q?t=20verifizierbar),=20Xtherma:=20SG-Ready=20on/off=20erg=C3=A4n?= =?UTF-8?q?zt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Weishaupt: Reg 33126 (el. Leistungsaufnahme) ist weder in der offiziellen Datenpunktliste (83807301) noch in einer ausgereiften Community-Implementierung (OStrama/weishaupt_modbus) zu finden - update()/METER_ONLY entfernt, SollwertPV (Reg 40002, offiziell dokumentiert) bleibt unverändert. Xtherma: Reg 60 (SG-Ready-Modus) ergänzt switch_on/switch_off (SUSPENDABLE_ONOFF), eigener 30min-Throttle laut Protokoll. --- .../weishaupt/weishaupt_wwp/config.py | 3 +-- .../weishaupt/weishaupt_wwp/consumer.py | 22 ++++--------------- .../consumers/xtherma/xtherma/config.py | 1 + .../consumers/xtherma/xtherma/consumer.py | 22 ++++++++++++++++++- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py b/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py index 0cb16a3967..dbc4bdef2d 100644 --- a/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py +++ b/packages/modules/consumers/weishaupt/weishaupt_wwp/config.py @@ -27,8 +27,7 @@ def __init__(self, # kein SUSPENDABLE_ONOFF: die SG-Ready-Eingänge sind auf diesen Geräten laut # Weishaupt-Datenpunktliste (Modbus TCP WWP) nur lesbar, die Leistungsvorgabe # SollwertPV übersteuert SG-Ready ohnehin. - usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE, - ConsumerUsage.METER_ONLY), + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE,), **kwargs) -> None: super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( ).type, configuration=configuration or WeishauptConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py b/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py index 79aeb58da8..39ee68b701 100644 --- a/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py +++ b/packages/modules/consumers/weishaupt/weishaupt_wwp/consumer.py @@ -3,11 +3,8 @@ from typing import Optional from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_state import ConsumerState -from modules.common.component_type import ComponentType from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData from modules.common.modbus import ModbusDataType, ModbusTcpClient_ -from modules.common.simcount._simcounter import SimCounterConsumer from modules.consumers.weishaupt.weishaupt_wwp.config import Weishaupt log = logging.getLogger(__name__) @@ -15,23 +12,10 @@ def create_consumer(config: Weishaupt): client: Optional[ModbusTcpClient_] = None - sim_counter: Optional[SimCounterConsumer] = None def initializer(): - nonlocal client, sim_counter + nonlocal client client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port) - sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) - - def update() -> ConsumerState: - # Reg 33126: El. Leistungsaufnahme [W] - power = client.read_input_registers(33126, ModbusDataType.UINT_16, unit=config.configuration.modbus_id) - imported, exported = sim_counter.sim_count(power) - - return ConsumerState( - power=power, - imported=imported, - exported=exported - ) def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None: # Reg 40002: SollwertPV [W], 0 gibt die Regelung an die Wärmepumpe zurück (0..65535, @@ -39,9 +23,11 @@ def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None: value = 0 if power_limit is None else min(max(round(power_limit), 0), 65535) client.write_register(40002, value, data_type=ModbusDataType.UINT_16, unit=config.configuration.modbus_id) + # Keine update()-Funktion: die Datenpunktliste (83807301) enthält keine elektrische + # Leistungsaufnahme, nur Leistungsanforderung in % (Reg 33103). Wer den Verbrauch erfassen + # will, braucht eine separate Leistungsmessung (zB Zwischenzähler/Smart-Plug). return ConfigurableConsumer(consumer_config=config, initializer=initializer, - update=update, set_power_limit=set_power_limit) diff --git a/packages/modules/consumers/xtherma/xtherma/config.py b/packages/modules/consumers/xtherma/xtherma/config.py index 31ae8ab5dd..8ecf8e7a84 100644 --- a/packages/modules/consumers/xtherma/xtherma/config.py +++ b/packages/modules/consumers/xtherma/xtherma/config.py @@ -25,6 +25,7 @@ def __init__(self, id: int = 0, configuration: Optional[XthermaConfiguration] = None, usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE, + ConsumerUsage.SUSPENDABLE_ONOFF, ConsumerUsage.METER_ONLY), **kwargs) -> None: super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( diff --git a/packages/modules/consumers/xtherma/xtherma/consumer.py b/packages/modules/consumers/xtherma/xtherma/consumer.py index f08f6bc387..e1456fd1d4 100644 --- a/packages/modules/consumers/xtherma/xtherma/consumer.py +++ b/packages/modules/consumers/xtherma/xtherma/consumer.py @@ -18,6 +18,7 @@ def create_consumer(config: Xtherma): client: Optional[ModbusTcpClient_] = None sim_counter: Optional[SimCounterConsumer] = None last_write: Optional[float] = None + last_sgready_write: Optional[float] = None def initializer(): nonlocal client, sim_counter @@ -51,10 +52,29 @@ def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None: 71, min(max(round(power_limit), 0), 100000), data_type=ModbusDataType.UINT_16, unit=unit) client.write_register(70, 1, data_type=ModbusDataType.UINT_16, unit=unit) + def switch_on() -> None: + # Reg 60: SG-Ready-Modus, darf laut Xtherma-Protokoll höchstens 1x/30min beschrieben + # werden. 1 = Normalbetrieb. + nonlocal last_sgready_write + if last_sgready_write is not None and timecheck.check_timestamp(last_sgready_write, 1800): + return + last_sgready_write = timecheck.create_timestamp() + client.write_register(60, 1, data_type=ModbusDataType.UINT_16, unit=config.configuration.modbus_id) + + def switch_off() -> None: + # Reg 60: SG-Ready-Modus, 2 = Sperre. + nonlocal last_sgready_write + if last_sgready_write is not None and timecheck.check_timestamp(last_sgready_write, 1800): + return + last_sgready_write = timecheck.create_timestamp() + client.write_register(60, 2, data_type=ModbusDataType.UINT_16, unit=config.configuration.modbus_id) + return ConfigurableConsumer(consumer_config=config, initializer=initializer, update=update, - set_power_limit=set_power_limit) + set_power_limit=set_power_limit, + switch_on=switch_on, + switch_off=switch_off) device_descriptor = DeviceDescriptor(configuration_factory=Xtherma)