From 8167c8a4a1fa9d4c5fdd2fc52ebfdf6af937e379 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Thu, 29 Oct 2026 20:57:12 +0100 Subject: [PATCH 1/2] Tasmota: neues Verbrauchermodul --- .../modules/consumers/tasmota/__init__.py | 0 .../consumers/tasmota/tasmota/__init__.py | 0 .../consumers/tasmota/tasmota/config.py | 26 ++++++++++ .../consumers/tasmota/tasmota/consumer.py | 48 +++++++++++++++++++ packages/modules/consumers/tasmota/vendor.py | 14 ++++++ 5 files changed, 88 insertions(+) create mode 100644 packages/modules/consumers/tasmota/__init__.py create mode 100644 packages/modules/consumers/tasmota/tasmota/__init__.py create mode 100644 packages/modules/consumers/tasmota/tasmota/config.py create mode 100644 packages/modules/consumers/tasmota/tasmota/consumer.py create mode 100644 packages/modules/consumers/tasmota/vendor.py diff --git a/packages/modules/consumers/tasmota/__init__.py b/packages/modules/consumers/tasmota/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/tasmota/tasmota/__init__.py b/packages/modules/consumers/tasmota/tasmota/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/consumers/tasmota/tasmota/config.py b/packages/modules/consumers/tasmota/tasmota/config.py new file mode 100644 index 0000000000..e4ae377dc6 --- /dev/null +++ b/packages/modules/consumers/tasmota/tasmota/config.py @@ -0,0 +1,26 @@ +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 TasmotaConfiguration: + def __init__(self, ip_address: Optional[str] = None): + self.ip_address = ip_address + + +@auto_str +class Tasmota(ConsumerSetup[TasmotaConfiguration]): + def __init__(self, + name: str = "Tasmota", + type: str = "tasmota", + id: int = 0, + configuration: Optional[TasmotaConfiguration] = None, + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF, + ConsumerUsage.METER_ONLY), + **kwargs) -> None: + super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( + ).type, configuration=configuration or TasmotaConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/tasmota/tasmota/consumer.py b/packages/modules/consumers/tasmota/tasmota/consumer.py new file mode 100644 index 0000000000..03f312dc0b --- /dev/null +++ b/packages/modules/consumers/tasmota/tasmota/consumer.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Optional + +from modules.common import req +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_state import ConsumerState +from modules.common.configurable_consumer import ConfigurableConsumer +from modules.consumers.tasmota.tasmota.config import Tasmota + +log = logging.getLogger(__name__) + + +def create_consumer(config: Tasmota): + session: Optional[req.CustomSession] = None + + def initializer(): + nonlocal session + session = req.get_http_session() + + def error_handler() -> None: + initializer() + + def update() -> ConsumerState: + # Status 0 liefert Relais- und Energiemesswerte in einer Antwort. + url = f"http://{config.configuration.ip_address}/cm?cmnd=Status%200" + response = session.get(url, timeout=5).json() + state = bool(response["Status"]["Power"]) + energy = response["StatusSNS"].get("ENERGY") + # Ohne Energiemessung (reines Schaltrelais) bleibt power 0. + power = float(energy["Power"]) if energy else 0.0 + return ConsumerState(power=power, state=state) + + def switch_on() -> None: + session.get(f"http://{config.configuration.ip_address}/cm?cmnd=Power%20on", timeout=5) + + def switch_off() -> None: + session.get(f"http://{config.configuration.ip_address}/cm?cmnd=Power%20off", timeout=5) + + return ConfigurableConsumer(consumer_config=config, + initializer=initializer, + error_handler=error_handler, + update=update, + switch_on=switch_on, + switch_off=switch_off) + + +device_descriptor = DeviceDescriptor(configuration_factory=Tasmota) diff --git a/packages/modules/consumers/tasmota/vendor.py b/packages/modules/consumers/tasmota/vendor.py new file mode 100644 index 0000000000..2b5266d933 --- /dev/null +++ b/packages/modules/consumers/tasmota/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 = "Tasmota" + self.group = VendorGroup.VENDORS.value + + +vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) From bfced9f084457490b4a826a5d48b14ed4129e6f1 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Sat, 31 Oct 2026 13:58:40 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Tasmota:=20CONTINUOUS=20erg=C3=A4nzen,=20Ta?= =?UTF-8?q?smotaParser=20nutzen=20(voltages/currents/powers)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/consumers/tasmota/tasmota/config.py | 3 ++- .../modules/consumers/tasmota/tasmota/consumer.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/modules/consumers/tasmota/tasmota/config.py b/packages/modules/consumers/tasmota/tasmota/config.py index e4ae377dc6..9ccf3807fa 100644 --- a/packages/modules/consumers/tasmota/tasmota/config.py +++ b/packages/modules/consumers/tasmota/tasmota/config.py @@ -19,7 +19,8 @@ def __init__(self, type: str = "tasmota", id: int = 0, configuration: Optional[TasmotaConfiguration] = None, - usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF, + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.CONTINUOUS, + ConsumerUsage.SUSPENDABLE_ONOFF, ConsumerUsage.METER_ONLY), **kwargs) -> None: super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( diff --git a/packages/modules/consumers/tasmota/tasmota/consumer.py b/packages/modules/consumers/tasmota/tasmota/consumer.py index 03f312dc0b..c841ed8d30 100644 --- a/packages/modules/consumers/tasmota/tasmota/consumer.py +++ b/packages/modules/consumers/tasmota/tasmota/consumer.py @@ -7,6 +7,7 @@ from modules.common.component_state import ConsumerState from modules.common.configurable_consumer import ConfigurableConsumer from modules.consumers.tasmota.tasmota.config import Tasmota +from modules.devices.tasmota.tasmota.parser import TasmotaParser log = logging.getLogger(__name__) @@ -25,11 +26,14 @@ def update() -> ConsumerState: # Status 0 liefert Relais- und Energiemesswerte in einer Antwort. url = f"http://{config.configuration.ip_address}/cm?cmnd=Status%200" response = session.get(url, timeout=5).json() - state = bool(response["Status"]["Power"]) - energy = response["StatusSNS"].get("ENERGY") - # Ohne Energiemessung (reines Schaltrelais) bleibt power 0. - power = float(energy["Power"]) if energy else 0.0 - return ConsumerState(power=power, state=state) + parser = TasmotaParser(response) + return ConsumerState( + power=parser.power, + state=parser.state, + voltages=parser.voltages, + currents=parser.currents, + powers=parser.powers, + ) def switch_on() -> None: session.get(f"http://{config.configuration.ip_address}/cm?cmnd=Power%20on", timeout=5)