From ce26bbc022860169357ef3289085c51081138e77 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Wed, 16 Sep 2026 14:47:09 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Ger=C3=A4te=20mit=20gemeinsamer=20Verbindun?= =?UTF-8?q?g:=20Fehlerzustand=20korreliert=20setzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RCT, Kostal Piko, generic/http, solar_view, sonnenBatterie, Discovergy, Powerfox, smart-me lesen Zähler/Wechselrichter/Speicher alle über dieselbe Verbindung/Session desselben physischen Geräts aus, verwendeten dafür aber IndependentComponentUpdater - dadurch bekam jede Komponente einen eigenen, unabhängigen Fehlerzustand statt eines gemeinsamen, obwohl ein Verbindungs- ausfall alle betrifft. - Umgestellt auf MultiComponentUpdater: schlägt die gemeinsame Verbindung fehl, werden jetzt alle Komponenten des Geräts als fehlerhaft markiert, nicht nur die zuerst geprüfte. - RCT zusätzlich so umgebaut, dass sich alle drei Komponenten eine einzige TCP-Verbindung teilen statt je eine eigene zu öffnen. - Shelly und Tasmota bewusst nicht angepasst: technisch möglich, kombinierte Zähler+WR+Speicher-Konfiguration auf einem Gerät anzulegen, aber keine reale Anwendung bei diesen Einzelzweck-Sensoren. --- .../devices/discovergy/discovergy/device.py | 12 +++++++++-- .../modules/devices/generic/http/device.py | 11 ++++++++-- .../devices/kostal/kostal_piko/device.py | 13 ++++++++++-- .../devices/powerfox/powerfox/device.py | 12 +++++++++-- packages/modules/devices/rct/rct/device.py | 20 ++++++++++--------- .../devices/smart_me/smart_me/device.py | 12 +++++++++-- .../devices/solar_view/solar_view/device.py | 17 +++++++++++----- .../devices/sonnen/sonnenbatterie/device.py | 16 +++++++++++++-- 8 files changed, 87 insertions(+), 26 deletions(-) diff --git a/packages/modules/devices/discovergy/discovergy/device.py b/packages/modules/devices/discovergy/discovergy/device.py index 75d237ab71..c78f05fa23 100644 --- a/packages/modules/devices/discovergy/discovergy/device.py +++ b/packages/modules/devices/discovergy/discovergy/device.py @@ -1,7 +1,8 @@ import logging +from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.common.req import get_http_session from modules.devices.discovergy.discovergy import counter, inverter from modules.devices.discovergy.discovergy.config import ( @@ -26,11 +27,18 @@ def initializer(): session = get_http_session() session.auth = (device_config.configuration.user, device_config.configuration.password) + def update_components(components: Iterable[Union[counter.DiscovergyCounter, inverter.DiscovergyInverter]]): + # Zähler und Wechselrichter teilen sich Login/Session beim selben Discovergy-Konto - ist die Anmeldung + # ungültig oder die API nicht erreichbar, muss das für beide gelten, nicht nur für die zuerst geprüfte + # Komponente. + for component in components: + component.update(session) + return ConfigurableDevice( device_config=device_config, initializer=initializer, component_factory=ComponentFactoryByType(counter=create_counter_component, inverter=create_inverter_component), - component_updater=IndependentComponentUpdater(lambda component: component.update(session)), + component_updater=MultiComponentUpdater(update_components), ) diff --git a/packages/modules/devices/generic/http/device.py b/packages/modules/devices/generic/http/device.py index 89a67d4ebc..422cf5fc66 100644 --- a/packages/modules/devices/generic/http/device.py +++ b/packages/modules/devices/generic/http/device.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 import logging +from typing import Iterable, Union from modules.common import req from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.devices.generic.http.bat import HttpBat from modules.devices.generic.http.config import (HTTP, HttpBatSetup, HttpCounterSetup, HttpInverterSetup) @@ -35,6 +36,12 @@ def initializer(): nonlocal session session = req.get_http_session() + def update_components(components: Iterable[Union[HttpBat, HttpCounter, HttpInverter]]): + # Zähler, Wechselrichter und Speicher werden alle über dieselbe URL ausgelesen - ist das Backend nicht + # erreichbar, muss das für alle drei gelten, nicht nur für die zuerst geprüfte Komponente. + for component in components: + component.update(session) + return ConfigurableDevice( device_config=device_config, initializer=initializer, @@ -43,7 +50,7 @@ def initializer(): counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=IndependentComponentUpdater(lambda component: component.update(session)) + component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/kostal/kostal_piko/device.py b/packages/modules/devices/kostal/kostal_piko/device.py index eb77305343..162a94dcfd 100644 --- a/packages/modules/devices/kostal/kostal_piko/device.py +++ b/packages/modules/devices/kostal/kostal_piko/device.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 import logging +from typing import Iterable, Union -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.abstract_device import DeviceDescriptor from modules.devices.kostal.kostal_piko import counter from modules.devices.kostal.kostal_piko import inverter @@ -27,6 +28,14 @@ def create_bat_component(component_config: KostalPikoBatSetup): device_id=device_config.id, ip_address=device_config.configuration.ip_address) + def update_components( + components: Iterable[Union[counter.KostalPikoCounter, inverter.KostalPikoInverter, bat.KostalPikoBat]] + ): + # Zähler, Wechselrichter und Speicher werden alle über dieselbe IP-Adresse ausgelesen - ist das Gerät nicht + # erreichbar, muss das für alle drei gelten, nicht nur für die zuerst geprüfte Komponente. + for component in components: + component.update() + return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( @@ -34,7 +43,7 @@ def create_bat_component(component_config: KostalPikoBatSetup): inverter=create_inverter_component, bat=create_bat_component ), - component_updater=IndependentComponentUpdater(lambda component: component.update()) + component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/powerfox/powerfox/device.py b/packages/modules/devices/powerfox/powerfox/device.py index 55290bb598..fcd323cb5d 100644 --- a/packages/modules/devices/powerfox/powerfox/device.py +++ b/packages/modules/devices/powerfox/powerfox/device.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import logging +from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.req import get_http_session from modules.devices.powerfox.powerfox.counter import PowerfoxCounter from modules.devices.powerfox.powerfox.config import Powerfox, PowerfoxCounterSetup, PowerfoxInverterSetup @@ -24,6 +25,13 @@ def initializer(): session = get_http_session() session.auth = (device_config.configuration.user, device_config.configuration.password) + def update_components(components: Iterable[Union[PowerfoxCounter, PowerfoxInverter]]): + # Zähler und Wechselrichter teilen sich Login/Session beim selben Powerfox-Konto - ist die Anmeldung + # ungültig oder die API nicht erreichbar, muss das für beide gelten, nicht nur für die zuerst geprüfte + # Komponente. + for component in components: + component.update(session) + return ConfigurableDevice( device_config=device_config, initializer=initializer, @@ -31,7 +39,7 @@ def initializer(): counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=IndependentComponentUpdater(lambda component: component.update(session)) + component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/rct/rct/device.py b/packages/modules/devices/rct/rct/device.py index 2a483e857a..d4e5b01ff8 100644 --- a/packages/modules/devices/rct/rct/device.py +++ b/packages/modules/devices/rct/rct/device.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 import logging import time -from typing import Callable +from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.devices.rct.rct import rct_lib from modules.devices.rct.rct.bat import RctBat from modules.devices.rct.rct.config import Rct, RctBatSetup, RctCounterSetup, RctInverterSetup @@ -24,13 +24,15 @@ def create_counter_component(component_config: RctCounterSetup): def create_inverter_component(component_config: RctInverterSetup): return RctInverter(component_config) - def update_component(update_func: Callable[[rct_lib.RCT], None]): + def update_components(components: Iterable[Union[RctBat, RctCounter, RctInverter]]): + # Zähler, Wechselrichter und Speicher teilen sich eine physische Verbindung zum RCT-Gerät - eine + # fehlgeschlagene Verbindung muss daher für alle drei den Fehlerzustand setzen, nicht nur für die + # Komponente, die zufällig zuerst dran war. + rct = rct_lib.RCT(device_config.configuration.ip_address) try: - rct = rct_lib.RCT(device_config.configuration.ip_address) - if rct.connect_to_server(): - update_func(rct) - except Exception: - raise + rct.connect_to_server() + for component in components: + component.update(rct) finally: rct.close() time.sleep(0.5) @@ -42,7 +44,7 @@ def update_component(update_func: Callable[[rct_lib.RCT], None]): counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=IndependentComponentUpdater(lambda component: update_component(component.update)), + component_updater=MultiComponentUpdater(update_components), ) diff --git a/packages/modules/devices/smart_me/smart_me/device.py b/packages/modules/devices/smart_me/smart_me/device.py index 042bf7c096..c959deabb8 100644 --- a/packages/modules/devices/smart_me/smart_me/device.py +++ b/packages/modules/devices/smart_me/smart_me/device.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import logging +from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.req import get_http_session from modules.devices.smart_me.smart_me.counter import SmartMeCounter from modules.devices.smart_me.smart_me.config import SmartMe, SmartMeCounterSetup, SmartMeInverterSetup @@ -25,6 +26,13 @@ def initializer(): session = get_http_session() session.auth = (device_config.configuration.user, device_config.configuration.password) + def update_components(components: Iterable[Union[SmartMeCounter, SmartMeInverter]]): + # Zähler und Wechselrichter teilen sich Login/Session beim selben smart-me-Konto - ist die Anmeldung + # ungültig oder die API nicht erreichbar, muss das für beide gelten, nicht nur für die zuerst geprüfte + # Komponente. + for component in components: + component.update(session) + return ConfigurableDevice( device_config=device_config, initializer=initializer, @@ -32,7 +40,7 @@ def initializer(): counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=IndependentComponentUpdater(lambda component: component.update(session)) + component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/solar_view/solar_view/device.py b/packages/modules/devices/solar_view/solar_view/device.py index b9c805f94e..0ad38f2afc 100644 --- a/packages/modules/devices/solar_view/solar_view/device.py +++ b/packages/modules/devices/solar_view/solar_view/device.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import logging +from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.solar_view.solar_view.counter import SolarViewCounter from modules.devices.solar_view.solar_view.config import SolarView, SolarViewCounterSetup, SolarViewInverterSetup from modules.devices.solar_view.solar_view.inverter import SolarViewInverter @@ -16,16 +17,22 @@ def create_counter_component(component_config: SolarViewCounterSetup): def create_inverter_component(component_config: SolarViewInverterSetup): return SolarViewInverter(component_config) + def update_components(components: Iterable[Union[SolarViewCounter, SolarViewInverter]]): + # Zähler und Wechselrichter werden über dieselbe IP-Adresse ausgelesen - ist das Gerät nicht erreichbar, + # muss das für beide gelten, nicht nur für die zuerst geprüfte Komponente. + for component in components: + component.update( + device_config.configuration.ip_address, + device_config.configuration.port, + device_config.configuration.timeout) + return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=IndependentComponentUpdater(lambda component: component.update( - device_config.configuration.ip_address, - device_config.configuration.port, - device_config.configuration.timeout)) + component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/sonnen/sonnenbatterie/device.py b/packages/modules/devices/sonnen/sonnenbatterie/device.py index 1dc9d408f1..727223e458 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/device.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/device.py @@ -2,9 +2,10 @@ """ Modul zum Auslesen von SonnenBatterie Speichern. """ import logging +from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.sonnen.sonnenbatterie.bat import SonnenbatterieBat from modules.devices.sonnen.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, @@ -48,6 +49,17 @@ def create_inverter_component(component_config: SonnenbatterieInverterSetup): device_variant=device_config.configuration.variant, device_api_v2_token=device_config.configuration.api_v2_token) + def update_components( + components: Iterable[ + Union[SonnenbatterieBat, SonnenbatterieCounter, SonnenbatterieConsumptionCounter, + SonnenbatterieInverter]] + ): + # Speicher, Zähler und Wechselrichter werden alle über dieselbe Adresse/denselben API-Token derselben + # SonnenBatterie ausgelesen - ist das Gerät nicht erreichbar, muss das für alle vier gelten, nicht nur + # für die zuerst geprüfte Komponente. + for component in components: + component.update() + return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( @@ -56,7 +68,7 @@ def create_inverter_component(component_config: SonnenbatterieInverterSetup): counter_consumption=create_consumption_counter_component, inverter=create_inverter_component, ), - component_updater=IndependentComponentUpdater(lambda component: component.update()) + component_updater=MultiComponentUpdater(update_components) ) From 221de266fadb5735ab745455b5024a53abfe6dd9 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Wed, 23 Sep 2026 16:22:53 +0200 Subject: [PATCH 2/3] Fehlerzustand pro Komponente isolieren statt bei Fehler alle zu markieren MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wie von Lena vorgeschlagen: MultiComponentUpdater deckt weiterhin Verbindungsfehler ab (alle Komponenten betroffen), zusätzlich SingleComponentUpdateContext pro Komponente wie bei den Modbus-Modulen - ein Fehler bei nur einer Komponente (falsches Register/Endpoint) markiert nicht mehr fälschlich die anderen als defekt. --- .../modules/devices/discovergy/discovergy/device.py | 8 ++++---- packages/modules/devices/generic/http/device.py | 7 ++++--- .../modules/devices/kostal/kostal_piko/device.py | 7 ++++--- .../modules/devices/powerfox/powerfox/device.py | 8 ++++---- packages/modules/devices/rct/rct/device.py | 8 ++++---- .../modules/devices/smart_me/smart_me/device.py | 8 ++++---- .../modules/devices/solar_view/solar_view/device.py | 13 +++++++------ .../modules/devices/sonnen/sonnenbatterie/device.py | 8 ++++---- 8 files changed, 35 insertions(+), 32 deletions(-) diff --git a/packages/modules/devices/discovergy/discovergy/device.py b/packages/modules/devices/discovergy/discovergy/device.py index c78f05fa23..ac1a0db62e 100644 --- a/packages/modules/devices/discovergy/discovergy/device.py +++ b/packages/modules/devices/discovergy/discovergy/device.py @@ -2,6 +2,7 @@ from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.common.req import get_http_session from modules.devices.discovergy.discovergy import counter, inverter @@ -28,11 +29,10 @@ def initializer(): session.auth = (device_config.configuration.user, device_config.configuration.password) def update_components(components: Iterable[Union[counter.DiscovergyCounter, inverter.DiscovergyInverter]]): - # Zähler und Wechselrichter teilen sich Login/Session beim selben Discovergy-Konto - ist die Anmeldung - # ungültig oder die API nicht erreichbar, muss das für beide gelten, nicht nur für die zuerst geprüfte - # Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update(session) + with SingleComponentUpdateContext(component.fault_state): + component.update(session) return ConfigurableDevice( device_config=device_config, diff --git a/packages/modules/devices/generic/http/device.py b/packages/modules/devices/generic/http/device.py index 422cf5fc66..97286b3fc6 100644 --- a/packages/modules/devices/generic/http/device.py +++ b/packages/modules/devices/generic/http/device.py @@ -4,6 +4,7 @@ from modules.common import req from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.devices.generic.http.bat import HttpBat from modules.devices.generic.http.config import (HTTP, HttpBatSetup, HttpCounterSetup, @@ -37,10 +38,10 @@ def initializer(): session = req.get_http_session() def update_components(components: Iterable[Union[HttpBat, HttpCounter, HttpInverter]]): - # Zähler, Wechselrichter und Speicher werden alle über dieselbe URL ausgelesen - ist das Backend nicht - # erreichbar, muss das für alle drei gelten, nicht nur für die zuerst geprüfte Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update(session) + with SingleComponentUpdateContext(component.fault_state): + component.update(session) return ConfigurableDevice( device_config=device_config, diff --git a/packages/modules/devices/kostal/kostal_piko/device.py b/packages/modules/devices/kostal/kostal_piko/device.py index 162a94dcfd..bfa5e66c0b 100644 --- a/packages/modules/devices/kostal/kostal_piko/device.py +++ b/packages/modules/devices/kostal/kostal_piko/device.py @@ -2,6 +2,7 @@ import logging from typing import Iterable, Union +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.abstract_device import DeviceDescriptor from modules.devices.kostal.kostal_piko import counter @@ -31,10 +32,10 @@ def create_bat_component(component_config: KostalPikoBatSetup): def update_components( components: Iterable[Union[counter.KostalPikoCounter, inverter.KostalPikoInverter, bat.KostalPikoBat]] ): - # Zähler, Wechselrichter und Speicher werden alle über dieselbe IP-Adresse ausgelesen - ist das Gerät nicht - # erreichbar, muss das für alle drei gelten, nicht nur für die zuerst geprüfte Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update() + with SingleComponentUpdateContext(component.fault_state): + component.update() return ConfigurableDevice( device_config=device_config, diff --git a/packages/modules/devices/powerfox/powerfox/device.py b/packages/modules/devices/powerfox/powerfox/device.py index fcd323cb5d..201b4bab64 100644 --- a/packages/modules/devices/powerfox/powerfox/device.py +++ b/packages/modules/devices/powerfox/powerfox/device.py @@ -3,6 +3,7 @@ from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.req import get_http_session from modules.devices.powerfox.powerfox.counter import PowerfoxCounter @@ -26,11 +27,10 @@ def initializer(): session.auth = (device_config.configuration.user, device_config.configuration.password) def update_components(components: Iterable[Union[PowerfoxCounter, PowerfoxInverter]]): - # Zähler und Wechselrichter teilen sich Login/Session beim selben Powerfox-Konto - ist die Anmeldung - # ungültig oder die API nicht erreichbar, muss das für beide gelten, nicht nur für die zuerst geprüfte - # Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update(session) + with SingleComponentUpdateContext(component.fault_state): + component.update(session) return ConfigurableDevice( device_config=device_config, diff --git a/packages/modules/devices/rct/rct/device.py b/packages/modules/devices/rct/rct/device.py index d4e5b01ff8..b297de108c 100644 --- a/packages/modules/devices/rct/rct/device.py +++ b/packages/modules/devices/rct/rct/device.py @@ -4,6 +4,7 @@ from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.devices.rct.rct import rct_lib from modules.devices.rct.rct.bat import RctBat @@ -25,14 +26,13 @@ def create_inverter_component(component_config: RctInverterSetup): return RctInverter(component_config) def update_components(components: Iterable[Union[RctBat, RctCounter, RctInverter]]): - # Zähler, Wechselrichter und Speicher teilen sich eine physische Verbindung zum RCT-Gerät - eine - # fehlgeschlagene Verbindung muss daher für alle drei den Fehlerzustand setzen, nicht nur für die - # Komponente, die zufällig zuerst dran war. + # Verbindungsfehler betrifft alle Komponenten, ein Fehler pro Komponente nur diese eine. rct = rct_lib.RCT(device_config.configuration.ip_address) try: rct.connect_to_server() for component in components: - component.update(rct) + with SingleComponentUpdateContext(component.fault_state): + component.update(rct) finally: rct.close() time.sleep(0.5) diff --git a/packages/modules/devices/smart_me/smart_me/device.py b/packages/modules/devices/smart_me/smart_me/device.py index c959deabb8..6a1c5681b1 100644 --- a/packages/modules/devices/smart_me/smart_me/device.py +++ b/packages/modules/devices/smart_me/smart_me/device.py @@ -3,6 +3,7 @@ from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.req import get_http_session from modules.devices.smart_me.smart_me.counter import SmartMeCounter @@ -27,11 +28,10 @@ def initializer(): session.auth = (device_config.configuration.user, device_config.configuration.password) def update_components(components: Iterable[Union[SmartMeCounter, SmartMeInverter]]): - # Zähler und Wechselrichter teilen sich Login/Session beim selben smart-me-Konto - ist die Anmeldung - # ungültig oder die API nicht erreichbar, muss das für beide gelten, nicht nur für die zuerst geprüfte - # Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update(session) + with SingleComponentUpdateContext(component.fault_state): + component.update(session) return ConfigurableDevice( device_config=device_config, diff --git a/packages/modules/devices/solar_view/solar_view/device.py b/packages/modules/devices/solar_view/solar_view/device.py index 0ad38f2afc..e2611698bc 100644 --- a/packages/modules/devices/solar_view/solar_view/device.py +++ b/packages/modules/devices/solar_view/solar_view/device.py @@ -3,6 +3,7 @@ from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.solar_view.solar_view.counter import SolarViewCounter from modules.devices.solar_view.solar_view.config import SolarView, SolarViewCounterSetup, SolarViewInverterSetup @@ -18,13 +19,13 @@ def create_inverter_component(component_config: SolarViewInverterSetup): return SolarViewInverter(component_config) def update_components(components: Iterable[Union[SolarViewCounter, SolarViewInverter]]): - # Zähler und Wechselrichter werden über dieselbe IP-Adresse ausgelesen - ist das Gerät nicht erreichbar, - # muss das für beide gelten, nicht nur für die zuerst geprüfte Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update( - device_config.configuration.ip_address, - device_config.configuration.port, - device_config.configuration.timeout) + with SingleComponentUpdateContext(component.fault_state): + component.update( + device_config.configuration.ip_address, + device_config.configuration.port, + device_config.configuration.timeout) return ConfigurableDevice( device_config=device_config, diff --git a/packages/modules/devices/sonnen/sonnenbatterie/device.py b/packages/modules/devices/sonnen/sonnenbatterie/device.py index 727223e458..8fd8aa9a62 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/device.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/device.py @@ -5,6 +5,7 @@ from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.sonnen.sonnenbatterie.bat import SonnenbatterieBat from modules.devices.sonnen.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, @@ -54,11 +55,10 @@ def update_components( Union[SonnenbatterieBat, SonnenbatterieCounter, SonnenbatterieConsumptionCounter, SonnenbatterieInverter]] ): - # Speicher, Zähler und Wechselrichter werden alle über dieselbe Adresse/denselben API-Token derselben - # SonnenBatterie ausgelesen - ist das Gerät nicht erreichbar, muss das für alle vier gelten, nicht nur - # für die zuerst geprüfte Komponente. + # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. for component in components: - component.update() + with SingleComponentUpdateContext(component.fault_state): + component.update() return ConfigurableDevice( device_config=device_config, From 19aa0b015b6c441fca3b73e0272e01d3fb276d09 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Tue, 29 Sep 2026 14:18:02 +0200 Subject: [PATCH 3/3] =?UTF-8?q?MultiComponentUpdater=20nur=20f=C3=BCr=20RC?= =?UTF-8?q?T,=20restliche=20Module=20zur=C3=BCck=20auf=20IndependentCompon?= =?UTF-8?q?entUpdater?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devices/discovergy/discovergy/device.py | 12 ++---------- .../modules/devices/generic/http/device.py | 12 ++---------- .../devices/kostal/kostal_piko/device.py | 14 ++------------ .../devices/powerfox/powerfox/device.py | 12 ++---------- .../devices/smart_me/smart_me/device.py | 12 ++---------- .../devices/solar_view/solar_view/device.py | 18 +++++------------- .../devices/sonnen/sonnenbatterie/device.py | 16 ++-------------- 7 files changed, 17 insertions(+), 79 deletions(-) diff --git a/packages/modules/devices/discovergy/discovergy/device.py b/packages/modules/devices/discovergy/discovergy/device.py index ac1a0db62e..75d237ab71 100644 --- a/packages/modules/devices/discovergy/discovergy/device.py +++ b/packages/modules/devices/discovergy/discovergy/device.py @@ -1,9 +1,7 @@ import logging -from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater from modules.common.req import get_http_session from modules.devices.discovergy.discovergy import counter, inverter from modules.devices.discovergy.discovergy.config import ( @@ -28,17 +26,11 @@ def initializer(): session = get_http_session() session.auth = (device_config.configuration.user, device_config.configuration.password) - def update_components(components: Iterable[Union[counter.DiscovergyCounter, inverter.DiscovergyInverter]]): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update(session) - return ConfigurableDevice( device_config=device_config, initializer=initializer, component_factory=ComponentFactoryByType(counter=create_counter_component, inverter=create_inverter_component), - component_updater=MultiComponentUpdater(update_components), + component_updater=IndependentComponentUpdater(lambda component: component.update(session)), ) diff --git a/packages/modules/devices/generic/http/device.py b/packages/modules/devices/generic/http/device.py index 97286b3fc6..89a67d4ebc 100644 --- a/packages/modules/devices/generic/http/device.py +++ b/packages/modules/devices/generic/http/device.py @@ -1,11 +1,9 @@ #!/usr/bin/env python3 import logging -from typing import Iterable, Union from modules.common import req from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater from modules.devices.generic.http.bat import HttpBat from modules.devices.generic.http.config import (HTTP, HttpBatSetup, HttpCounterSetup, HttpInverterSetup) @@ -37,12 +35,6 @@ def initializer(): nonlocal session session = req.get_http_session() - def update_components(components: Iterable[Union[HttpBat, HttpCounter, HttpInverter]]): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update(session) - return ConfigurableDevice( device_config=device_config, initializer=initializer, @@ -51,7 +43,7 @@ def update_components(components: Iterable[Union[HttpBat, HttpCounter, HttpInver counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=MultiComponentUpdater(update_components) + component_updater=IndependentComponentUpdater(lambda component: component.update(session)) ) diff --git a/packages/modules/devices/kostal/kostal_piko/device.py b/packages/modules/devices/kostal/kostal_piko/device.py index bfa5e66c0b..eb77305343 100644 --- a/packages/modules/devices/kostal/kostal_piko/device.py +++ b/packages/modules/devices/kostal/kostal_piko/device.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 import logging -from typing import Iterable, Union -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater from modules.common.abstract_device import DeviceDescriptor from modules.devices.kostal.kostal_piko import counter from modules.devices.kostal.kostal_piko import inverter @@ -29,14 +27,6 @@ def create_bat_component(component_config: KostalPikoBatSetup): device_id=device_config.id, ip_address=device_config.configuration.ip_address) - def update_components( - components: Iterable[Union[counter.KostalPikoCounter, inverter.KostalPikoInverter, bat.KostalPikoBat]] - ): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update() - return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( @@ -44,7 +34,7 @@ def update_components( inverter=create_inverter_component, bat=create_bat_component ), - component_updater=MultiComponentUpdater(update_components) + component_updater=IndependentComponentUpdater(lambda component: component.update()) ) diff --git a/packages/modules/devices/powerfox/powerfox/device.py b/packages/modules/devices/powerfox/powerfox/device.py index 201b4bab64..55290bb598 100644 --- a/packages/modules/devices/powerfox/powerfox/device.py +++ b/packages/modules/devices/powerfox/powerfox/device.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import logging -from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater from modules.common.req import get_http_session from modules.devices.powerfox.powerfox.counter import PowerfoxCounter from modules.devices.powerfox.powerfox.config import Powerfox, PowerfoxCounterSetup, PowerfoxInverterSetup @@ -26,12 +24,6 @@ def initializer(): session = get_http_session() session.auth = (device_config.configuration.user, device_config.configuration.password) - def update_components(components: Iterable[Union[PowerfoxCounter, PowerfoxInverter]]): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update(session) - return ConfigurableDevice( device_config=device_config, initializer=initializer, @@ -39,7 +31,7 @@ def update_components(components: Iterable[Union[PowerfoxCounter, PowerfoxInvert counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=MultiComponentUpdater(update_components) + component_updater=IndependentComponentUpdater(lambda component: component.update(session)) ) diff --git a/packages/modules/devices/smart_me/smart_me/device.py b/packages/modules/devices/smart_me/smart_me/device.py index 6a1c5681b1..042bf7c096 100644 --- a/packages/modules/devices/smart_me/smart_me/device.py +++ b/packages/modules/devices/smart_me/smart_me/device.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import logging -from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater from modules.common.req import get_http_session from modules.devices.smart_me.smart_me.counter import SmartMeCounter from modules.devices.smart_me.smart_me.config import SmartMe, SmartMeCounterSetup, SmartMeInverterSetup @@ -27,12 +25,6 @@ def initializer(): session = get_http_session() session.auth = (device_config.configuration.user, device_config.configuration.password) - def update_components(components: Iterable[Union[SmartMeCounter, SmartMeInverter]]): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update(session) - return ConfigurableDevice( device_config=device_config, initializer=initializer, @@ -40,7 +32,7 @@ def update_components(components: Iterable[Union[SmartMeCounter, SmartMeInverter counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=MultiComponentUpdater(update_components) + component_updater=IndependentComponentUpdater(lambda component: component.update(session)) ) diff --git a/packages/modules/devices/solar_view/solar_view/device.py b/packages/modules/devices/solar_view/solar_view/device.py index e2611698bc..b9c805f94e 100644 --- a/packages/modules/devices/solar_view/solar_view/device.py +++ b/packages/modules/devices/solar_view/solar_view/device.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import logging -from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater from modules.devices.solar_view.solar_view.counter import SolarViewCounter from modules.devices.solar_view.solar_view.config import SolarView, SolarViewCounterSetup, SolarViewInverterSetup from modules.devices.solar_view.solar_view.inverter import SolarViewInverter @@ -18,22 +16,16 @@ def create_counter_component(component_config: SolarViewCounterSetup): def create_inverter_component(component_config: SolarViewInverterSetup): return SolarViewInverter(component_config) - def update_components(components: Iterable[Union[SolarViewCounter, SolarViewInverter]]): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update( - device_config.configuration.ip_address, - device_config.configuration.port, - device_config.configuration.timeout) - return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( counter=create_counter_component, inverter=create_inverter_component, ), - component_updater=MultiComponentUpdater(update_components) + component_updater=IndependentComponentUpdater(lambda component: component.update( + device_config.configuration.ip_address, + device_config.configuration.port, + device_config.configuration.timeout)) ) diff --git a/packages/modules/devices/sonnen/sonnenbatterie/device.py b/packages/modules/devices/sonnen/sonnenbatterie/device.py index 8fd8aa9a62..1dc9d408f1 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/device.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/device.py @@ -2,11 +2,9 @@ """ Modul zum Auslesen von SonnenBatterie Speichern. """ import logging -from typing import Iterable, Union from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater from modules.devices.sonnen.sonnenbatterie.bat import SonnenbatterieBat from modules.devices.sonnen.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, @@ -50,16 +48,6 @@ def create_inverter_component(component_config: SonnenbatterieInverterSetup): device_variant=device_config.configuration.variant, device_api_v2_token=device_config.configuration.api_v2_token) - def update_components( - components: Iterable[ - Union[SonnenbatterieBat, SonnenbatterieCounter, SonnenbatterieConsumptionCounter, - SonnenbatterieInverter]] - ): - # Fehler pro Komponente isoliert, statt bei einem Fehler alle als defekt zu markieren. - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update() - return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( @@ -68,7 +56,7 @@ def update_components( counter_consumption=create_consumption_counter_component, inverter=create_inverter_component, ), - component_updater=MultiComponentUpdater(update_components) + component_updater=IndependentComponentUpdater(lambda component: component.update()) )