From d099e3ac92cd5769d7a5321ce0b16d9814f85ff0 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Wed, 30 Sep 2026 15:05:18 +0200 Subject: [PATCH 1/8] add shelly relais --- .../modules/common/configurable_consumer.py | 4 +- .../consumers/shelly/shelly_relais/config.py | 34 +++++++++ .../shelly/shelly_relais/consumer.py | 73 +++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 packages/modules/consumers/shelly/shelly_relais/config.py create mode 100644 packages/modules/consumers/shelly/shelly_relais/consumer.py diff --git a/packages/modules/common/configurable_consumer.py b/packages/modules/common/configurable_consumer.py index f1d4113ddf..7d39976463 100644 --- a/packages/modules/common/configurable_consumer.py +++ b/packages/modules/common/configurable_consumer.py @@ -77,8 +77,8 @@ def update(self): if self.module_updater is not None: consumer_state = self.module_updater() imported, exported = self.peak_filter.check_values( - consumer_state.power, - consumer_state.imported, + consumer_state.power if consumer_state.power is not None else 0, + consumer_state.imported if consumer_state.imported is not None else 0, consumer_state.exported if consumer_state.exported is not None else 0) if imported != consumer_state.imported or exported != consumer_state.exported: consumer_state.imported = imported diff --git a/packages/modules/consumers/shelly/shelly_relais/config.py b/packages/modules/consumers/shelly/shelly_relais/config.py new file mode 100644 index 0000000000..fe52b2eb99 --- /dev/null +++ b/packages/modules/consumers/shelly/shelly_relais/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 ShellyConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + factor: int = -1, + channel: int = 0, + username: Optional[str] = None, + password: Optional[str] = None) -> None: + self.ip_address = ip_address + self.factor = factor + self.channel = channel + self.username = username + self.password = password + + +@auto_str +class ShellyRelais(ConsumerSetup[ShellyConfiguration]): + def __init__(self, + name: str = "Shelly Relais (Schalten)", + type: str = "shelly_relais", + id: int = 0, + configuration: Optional[ShellyConfiguration] = None, + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF,), + **kwargs) -> None: + super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( + ).type, configuration=configuration or ShellyConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/shelly/shelly_relais/consumer.py b/packages/modules/consumers/shelly/shelly_relais/consumer.py new file mode 100644 index 0000000000..2fd8de0bad --- /dev/null +++ b/packages/modules/consumers/shelly/shelly_relais/consumer.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +from modules.common import req +from typing import Optional, Dict +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 +from modules.common.simcount._simcounter import SimCounterConsumer +from modules.consumers.shelly.shelly_relais.config import ShellyRelais +from modules.devices.shelly.shelly.status_handler import get_generation, request_status + + +def create_consumer(config: ShellyRelais): + sim_counter: Optional[SimCounterConsumer] = None + model: Optional[str] = None + generation: int = 1 + + def initializer(): + nonlocal sim_counter, generation, model + sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) + generation, model = get_generation(config.configuration.ip_address) + + def error_handler() -> None: + initializer() + + def switch_on() -> None: + if generation == 1: + url = f"http://{config.configuration.ip_address}/relay/{config.configuration.channel}?turn=on" + else: + chan = config.configuration.channel + # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true + url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=true" + if config.configuration.username and config.configuration.password: + auth = (config.configuration.username, config.configuration.password) + else: + auth = None + req.get_http_session().get(url, auth=auth, timeout=3) + + def switch_off() -> None: + if generation == 1: + url = f"http://{config.configuration.ip_address}/relay/{config.configuration.channel}?turn=off" + else: + chan = config.configuration.channel + # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true + url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=false" + if config.configuration.username and config.configuration.password: + auth = (config.configuration.username, config.configuration.password) + else: + auth = None + req.get_http_session().get(url, auth=auth, timeout=3) + + def update() -> ConsumerState: + status = request_status(config.configuration.ip_address, generation) + state = parse_state(status) + return ConsumerState( + state=state + ) + + def parse_state(status: Dict) -> bool: + try: + return status["switch:0"]["output"] + except (KeyError, TypeError): + raise Exception("Schaltstatus des Relais nicht lesbar.") + + return ConfigurableConsumer(consumer_config=config, + initializer=initializer, + error_handler=error_handler, + switch_on=switch_on, + switch_off=switch_off, + update=update,) + + +device_descriptor = DeviceDescriptor(configuration_factory=ShellyRelais) From c3507cfb300d34a193958c04420de69dd783fd15 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Wed, 30 Sep 2026 15:13:07 +0200 Subject: [PATCH 2/8] remove factor --- packages/modules/consumers/shelly/shelly_relais/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/modules/consumers/shelly/shelly_relais/config.py b/packages/modules/consumers/shelly/shelly_relais/config.py index fe52b2eb99..abb0569f7b 100644 --- a/packages/modules/consumers/shelly/shelly_relais/config.py +++ b/packages/modules/consumers/shelly/shelly_relais/config.py @@ -10,12 +10,10 @@ class ShellyConfiguration: def __init__(self, ip_address: Optional[str] = None, - factor: int = -1, channel: int = 0, username: Optional[str] = None, password: Optional[str] = None) -> None: self.ip_address = ip_address - self.factor = factor self.channel = channel self.username = username self.password = password From 0c455000b72491cf2f3a8b61996f17cb1205e7fd Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Wed, 30 Sep 2026 15:05:18 +0200 Subject: [PATCH 3/8] add shelly relais --- .../consumers/shelly/shelly_relais/config.py | 34 +++++++++ .../shelly/shelly_relais/consumer.py | 73 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 packages/modules/consumers/shelly/shelly_relais/config.py create mode 100644 packages/modules/consumers/shelly/shelly_relais/consumer.py diff --git a/packages/modules/consumers/shelly/shelly_relais/config.py b/packages/modules/consumers/shelly/shelly_relais/config.py new file mode 100644 index 0000000000..fe52b2eb99 --- /dev/null +++ b/packages/modules/consumers/shelly/shelly_relais/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 ShellyConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + factor: int = -1, + channel: int = 0, + username: Optional[str] = None, + password: Optional[str] = None) -> None: + self.ip_address = ip_address + self.factor = factor + self.channel = channel + self.username = username + self.password = password + + +@auto_str +class ShellyRelais(ConsumerSetup[ShellyConfiguration]): + def __init__(self, + name: str = "Shelly Relais (Schalten)", + type: str = "shelly_relais", + id: int = 0, + configuration: Optional[ShellyConfiguration] = None, + usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF,), + **kwargs) -> None: + super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory( + ).type, configuration=configuration or ShellyConfiguration(), usage=usage, **kwargs) diff --git a/packages/modules/consumers/shelly/shelly_relais/consumer.py b/packages/modules/consumers/shelly/shelly_relais/consumer.py new file mode 100644 index 0000000000..2fd8de0bad --- /dev/null +++ b/packages/modules/consumers/shelly/shelly_relais/consumer.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +from modules.common import req +from typing import Optional, Dict +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 +from modules.common.simcount._simcounter import SimCounterConsumer +from modules.consumers.shelly.shelly_relais.config import ShellyRelais +from modules.devices.shelly.shelly.status_handler import get_generation, request_status + + +def create_consumer(config: ShellyRelais): + sim_counter: Optional[SimCounterConsumer] = None + model: Optional[str] = None + generation: int = 1 + + def initializer(): + nonlocal sim_counter, generation, model + sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) + generation, model = get_generation(config.configuration.ip_address) + + def error_handler() -> None: + initializer() + + def switch_on() -> None: + if generation == 1: + url = f"http://{config.configuration.ip_address}/relay/{config.configuration.channel}?turn=on" + else: + chan = config.configuration.channel + # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true + url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=true" + if config.configuration.username and config.configuration.password: + auth = (config.configuration.username, config.configuration.password) + else: + auth = None + req.get_http_session().get(url, auth=auth, timeout=3) + + def switch_off() -> None: + if generation == 1: + url = f"http://{config.configuration.ip_address}/relay/{config.configuration.channel}?turn=off" + else: + chan = config.configuration.channel + # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true + url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=false" + if config.configuration.username and config.configuration.password: + auth = (config.configuration.username, config.configuration.password) + else: + auth = None + req.get_http_session().get(url, auth=auth, timeout=3) + + def update() -> ConsumerState: + status = request_status(config.configuration.ip_address, generation) + state = parse_state(status) + return ConsumerState( + state=state + ) + + def parse_state(status: Dict) -> bool: + try: + return status["switch:0"]["output"] + except (KeyError, TypeError): + raise Exception("Schaltstatus des Relais nicht lesbar.") + + return ConfigurableConsumer(consumer_config=config, + initializer=initializer, + error_handler=error_handler, + switch_on=switch_on, + switch_off=switch_off, + update=update,) + + +device_descriptor = DeviceDescriptor(configuration_factory=ShellyRelais) From 3729974c61dc98a3706df64dc4678f2314c7ec83 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Wed, 30 Sep 2026 15:13:07 +0200 Subject: [PATCH 4/8] remove factor --- packages/modules/consumers/shelly/shelly_relais/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/modules/consumers/shelly/shelly_relais/config.py b/packages/modules/consumers/shelly/shelly_relais/config.py index fe52b2eb99..abb0569f7b 100644 --- a/packages/modules/consumers/shelly/shelly_relais/config.py +++ b/packages/modules/consumers/shelly/shelly_relais/config.py @@ -10,12 +10,10 @@ class ShellyConfiguration: def __init__(self, ip_address: Optional[str] = None, - factor: int = -1, channel: int = 0, username: Optional[str] = None, password: Optional[str] = None) -> None: self.ip_address = ip_address - self.factor = factor self.channel = channel self.username = username self.password = password From 5d56b298f932f0fdf00f4b0083b215350cc2aca9 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Tue, 3 Nov 2026 13:25:59 +0100 Subject: [PATCH 5/8] adjust component update --- .../modules/common/configurable_consumer.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/modules/common/configurable_consumer.py b/packages/modules/common/configurable_consumer.py index bc056e40fd..18f203714f 100644 --- a/packages/modules/common/configurable_consumer.py +++ b/packages/modules/common/configurable_consumer.py @@ -75,18 +75,25 @@ def update(self): with SingleComponentUpdateContext(self.fault_state): if self.module_updater is not None: consumer_state = self.module_updater() - imported, exported = self.peak_filter.check_values( - consumer_state.power, - consumer_state.imported, - consumer_state.exported) - if consumer_state.imported is None or consumer_state.exported is None: - simulated_imported, simulated_exported = self.sim_counter.sim_count(consumer_state.power) + if consumer_state.power is None: + consumer_state.power = 0 if consumer_state.imported is None: - imported = simulated_imported + consumer_state.imported = 0 if consumer_state.exported is None: - exported = simulated_exported - consumer_state.imported = imported - consumer_state.exported = exported + consumer_state.exported = 0 + else: + imported, exported = self.peak_filter.check_values( + consumer_state.power, + consumer_state.imported, + consumer_state.exported) + if consumer_state.imported is None or consumer_state.exported is None: + simulated_imported, simulated_exported = self.sim_counter.sim_count(consumer_state.power) + if consumer_state.imported is None: + imported = simulated_imported + if consumer_state.exported is None: + exported = simulated_exported + consumer_state.imported = imported + consumer_state.exported = exported self.store.set(consumer_state) def set_power_limit(self, power_limit: float, data: SetLimitData) -> None: From c53ad0bde325519682721853ecc10b2781a0edf2 Mon Sep 17 00:00:00 2001 From: ndrsnhs Date: Thu, 5 Nov 2026 11:56:45 +0100 Subject: [PATCH 6/8] handle generations and channels --- packages/modules/consumers/shelly/shelly_relais/consumer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/modules/consumers/shelly/shelly_relais/consumer.py b/packages/modules/consumers/shelly/shelly_relais/consumer.py index 2fd8de0bad..cf9495ffab 100644 --- a/packages/modules/consumers/shelly/shelly_relais/consumer.py +++ b/packages/modules/consumers/shelly/shelly_relais/consumer.py @@ -58,8 +58,10 @@ def update() -> ConsumerState: def parse_state(status: Dict) -> bool: try: - return status["switch:0"]["output"] - except (KeyError, TypeError): + if generation == 1: + return status["relays"][config.configuration.channel]["ison"] + return status[f"switch:{config.configuration.channel}"]["output"] + except (KeyError, TypeError, IndexError): raise Exception("Schaltstatus des Relais nicht lesbar.") return ConfigurableConsumer(consumer_config=config, From afb97e2f66fa364f78772d881918230fac35b750 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 11 Nov 2026 11:43:41 +0100 Subject: [PATCH 7/8] fix auth --- .../shelly/shelly_relais/consumer.py | 24 ++++++++++--------- .../devices/shelly/shelly/status_handler.py | 5 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/modules/consumers/shelly/shelly_relais/consumer.py b/packages/modules/consumers/shelly/shelly_relais/consumer.py index cf9495ffab..d732d8837e 100644 --- a/packages/modules/consumers/shelly/shelly_relais/consumer.py +++ b/packages/modules/consumers/shelly/shelly_relais/consumer.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from modules.common import req from typing import Optional, Dict +from requests.auth import AuthBase, HTTPBasicAuth, HTTPDigestAuth from modules.common.abstract_device import DeviceDescriptor from modules.common.component_state import ConsumerState from modules.common.component_type import ComponentType @@ -23,6 +24,15 @@ def initializer(): def error_handler() -> None: initializer() + def get_auth() -> Optional[AuthBase]: + if not config.configuration.password: + return None + if generation == 1: + if config.configuration.username: + return HTTPBasicAuth(config.configuration.username, config.configuration.password) + return None + return HTTPDigestAuth("admin", config.configuration.password) + def switch_on() -> None: if generation == 1: url = f"http://{config.configuration.ip_address}/relay/{config.configuration.channel}?turn=on" @@ -30,11 +40,7 @@ def switch_on() -> None: chan = config.configuration.channel # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=true" - if config.configuration.username and config.configuration.password: - auth = (config.configuration.username, config.configuration.password) - else: - auth = None - req.get_http_session().get(url, auth=auth, timeout=3) + req.get_http_session().get(url, auth=get_auth(), timeout=3) def switch_off() -> None: if generation == 1: @@ -43,14 +49,10 @@ def switch_off() -> None: chan = config.configuration.channel # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=false" - if config.configuration.username and config.configuration.password: - auth = (config.configuration.username, config.configuration.password) - else: - auth = None - req.get_http_session().get(url, auth=auth, timeout=3) + req.get_http_session().get(url, auth=get_auth(), timeout=3) def update() -> ConsumerState: - status = request_status(config.configuration.ip_address, generation) + status = request_status(config.configuration.ip_address, generation, auth=get_auth()) state = parse_state(status) return ConsumerState( state=state diff --git a/packages/modules/devices/shelly/shelly/status_handler.py b/packages/modules/devices/shelly/shelly/status_handler.py index 42ec365d8b..93d11430ca 100644 --- a/packages/modules/devices/shelly/shelly/status_handler.py +++ b/packages/modules/devices/shelly/shelly/status_handler.py @@ -1,6 +1,7 @@ import logging from typing import Dict from typing import Tuple, List, Optional +from requests.auth import AuthBase from modules.common import req @@ -23,12 +24,12 @@ def get_generation(address: str) -> Tuple[int, str]: return generation, model -def request_status(address: str, generation: Optional[int]) -> Dict: +def request_status(address: str, generation: Optional[int], auth: Optional[AuthBase] = None) -> Dict: if generation == 1: status_url = "http://" + address + "/status" else: status_url = "http://" + address + "/rpc/Shelly.GetStatus" - return req.get_http_session().get(status_url, timeout=3).json() + return req.get_http_session().get(status_url, auth=auth, timeout=3).json() def parse_temperatures(status: Dict, generation: int) -> Optional[List[float]]: From 57c36829a96ecae40c2277692fdbf58295c4a1c4 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 11 Nov 2026 11:59:26 +0100 Subject: [PATCH 8/8] Review --- .../consumers/shelly/shelly_em/consumer.py | 11 ++++++--- .../consumers/shelly/shelly_pm/consumer.py | 18 ++++++--------- .../shelly/shelly_relais/consumer.py | 23 +++++++------------ .../devices/shelly/shelly/status_handler.py | 10 +++++++- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/modules/consumers/shelly/shelly_em/consumer.py b/packages/modules/consumers/shelly/shelly_em/consumer.py index 06949070fa..cd2fa0262a 100644 --- a/packages/modules/consumers/shelly/shelly_em/consumer.py +++ b/packages/modules/consumers/shelly/shelly_em/consumer.py @@ -1,23 +1,28 @@ #!/usr/bin/env python3 +from typing import Optional +from requests.auth import AuthBase from modules.common.abstract_device import DeviceDescriptor from modules.common.component_state import ConsumerState from modules.common.configurable_consumer import ConfigurableConsumer from modules.consumers.shelly.shelly_em.config import ShellyEM -from modules.devices.shelly.shelly.status_handler import get_generation, request_status, parse_data, parse_temperatures +from modules.devices.shelly.shelly.status_handler import ( + get_auth, get_generation, request_status, parse_data, parse_temperatures) def create_consumer(config: ShellyEM): generation: int = 1 + auth: Optional[AuthBase] = None def initializer(): - nonlocal generation + nonlocal generation, auth generation, _ = get_generation(config.configuration.ip_address) + auth = get_auth(generation, config.configuration.username, config.configuration.password) def error_handler() -> None: initializer() def update() -> ConsumerState: - status = request_status(config.configuration.ip_address, generation) + status = request_status(config.configuration.ip_address, generation, auth=auth) powers, voltages, currents, _, power, _ = parse_data(1, config.configuration.factor, status) consumer_state = ConsumerState( power=power, diff --git a/packages/modules/consumers/shelly/shelly_pm/consumer.py b/packages/modules/consumers/shelly/shelly_pm/consumer.py index 048296f4b0..4a66c58461 100644 --- a/packages/modules/consumers/shelly/shelly_pm/consumer.py +++ b/packages/modules/consumers/shelly/shelly_pm/consumer.py @@ -1,20 +1,24 @@ #!/usr/bin/env python3 from modules.common import req from typing import Optional +from requests.auth import AuthBase from modules.common.abstract_device import DeviceDescriptor from modules.common.component_state import ConsumerState from modules.common.configurable_consumer import ConfigurableConsumer from modules.consumers.shelly.shelly_pm.config import ShellyPM -from modules.devices.shelly.shelly.status_handler import get_generation, request_status, parse_data, parse_temperatures +from modules.devices.shelly.shelly.status_handler import ( + get_auth, get_generation, request_status, parse_data, parse_temperatures) def create_consumer(config: ShellyPM): model: Optional[str] = None generation: int = 1 + auth: Optional[AuthBase] = None def initializer(): - nonlocal generation, model + nonlocal generation, model, auth generation, model = get_generation(config.configuration.ip_address) + auth = get_auth(generation, config.configuration.username, config.configuration.password) def error_handler() -> None: initializer() @@ -31,10 +35,6 @@ def switch_on() -> None: chan = config.configuration.channel # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=true" - if config.configuration.username and config.configuration.password: - auth = (config.configuration.username, config.configuration.password) - else: - auth = None req.get_http_session().get(url, auth=auth, timeout=3) def switch_off() -> None: @@ -49,14 +49,10 @@ def switch_off() -> None: chan = config.configuration.channel # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=false" - if config.configuration.username and config.configuration.password: - auth = (config.configuration.username, config.configuration.password) - else: - auth = None req.get_http_session().get(url, auth=auth, timeout=3) def update() -> ConsumerState: - status = request_status(config.configuration.ip_address, generation) + status = request_status(config.configuration.ip_address, generation, auth=auth) powers, voltages, currents, _, power, _ = parse_data(1, config.configuration.factor, status) return ConsumerState( power=power, diff --git a/packages/modules/consumers/shelly/shelly_relais/consumer.py b/packages/modules/consumers/shelly/shelly_relais/consumer.py index d732d8837e..12551b8189 100644 --- a/packages/modules/consumers/shelly/shelly_relais/consumer.py +++ b/packages/modules/consumers/shelly/shelly_relais/consumer.py @@ -1,38 +1,31 @@ #!/usr/bin/env python3 from modules.common import req from typing import Optional, Dict -from requests.auth import AuthBase, HTTPBasicAuth, HTTPDigestAuth +from requests.auth import AuthBase 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 from modules.common.simcount._simcounter import SimCounterConsumer from modules.consumers.shelly.shelly_relais.config import ShellyRelais -from modules.devices.shelly.shelly.status_handler import get_generation, request_status +from modules.devices.shelly.shelly.status_handler import get_auth, get_generation, request_status def create_consumer(config: ShellyRelais): sim_counter: Optional[SimCounterConsumer] = None model: Optional[str] = None generation: int = 1 + auth: Optional[AuthBase] = None def initializer(): - nonlocal sim_counter, generation, model + nonlocal sim_counter, generation, model, auth sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER) generation, model = get_generation(config.configuration.ip_address) + auth = get_auth(generation, config.configuration.username, config.configuration.password) def error_handler() -> None: initializer() - def get_auth() -> Optional[AuthBase]: - if not config.configuration.password: - return None - if generation == 1: - if config.configuration.username: - return HTTPBasicAuth(config.configuration.username, config.configuration.password) - return None - return HTTPDigestAuth("admin", config.configuration.password) - def switch_on() -> None: if generation == 1: url = f"http://{config.configuration.ip_address}/relay/{config.configuration.channel}?turn=on" @@ -40,7 +33,7 @@ def switch_on() -> None: chan = config.configuration.channel # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=true" - req.get_http_session().get(url, auth=get_auth(), timeout=3) + req.get_http_session().get(url, auth=auth, timeout=3) def switch_off() -> None: if generation == 1: @@ -49,10 +42,10 @@ def switch_off() -> None: chan = config.configuration.channel # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true url = f"http://{config.configuration.ip_address}/rpc/Switch.Set?id={chan}&on=false" - req.get_http_session().get(url, auth=get_auth(), timeout=3) + req.get_http_session().get(url, auth=auth, timeout=3) def update() -> ConsumerState: - status = request_status(config.configuration.ip_address, generation, auth=get_auth()) + status = request_status(config.configuration.ip_address, generation, auth=auth) state = parse_state(status) return ConsumerState( state=state diff --git a/packages/modules/devices/shelly/shelly/status_handler.py b/packages/modules/devices/shelly/shelly/status_handler.py index 93d11430ca..2f1cf69eef 100644 --- a/packages/modules/devices/shelly/shelly/status_handler.py +++ b/packages/modules/devices/shelly/shelly/status_handler.py @@ -1,7 +1,7 @@ import logging from typing import Dict from typing import Tuple, List, Optional -from requests.auth import AuthBase +from requests.auth import AuthBase, HTTPBasicAuth, HTTPDigestAuth from modules.common import req @@ -24,6 +24,14 @@ def get_generation(address: str) -> Tuple[int, str]: return generation, model +def get_auth(generation: int, username: Optional[str], password: Optional[str]) -> Optional[AuthBase]: + if not password: + return None + if generation == 1: + return HTTPBasicAuth(username, password) if username else None + return HTTPDigestAuth("admin", password) + + def request_status(address: str, generation: Optional[int], auth: Optional[AuthBase] = None) -> Dict: if generation == 1: status_url = "http://" + address + "/status"