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: 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/config.py b/packages/modules/consumers/shelly/shelly_relais/config.py new file mode 100644 index 0000000000..abb0569f7b --- /dev/null +++ b/packages/modules/consumers/shelly/shelly_relais/config.py @@ -0,0 +1,32 @@ +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, + channel: int = 0, + username: Optional[str] = None, + password: Optional[str] = None) -> None: + self.ip_address = ip_address + 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..12551b8189 --- /dev/null +++ b/packages/modules/consumers/shelly/shelly_relais/consumer.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +from modules.common import req +from typing import Optional, Dict +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_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, 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 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" + 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" + req.get_http_session().get(url, auth=auth, timeout=3) + + def update() -> ConsumerState: + status = request_status(config.configuration.ip_address, generation, auth=auth) + state = parse_state(status) + return ConsumerState( + state=state + ) + + def parse_state(status: Dict) -> bool: + try: + 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, + initializer=initializer, + error_handler=error_handler, + switch_on=switch_on, + switch_off=switch_off, + update=update,) + + +device_descriptor = DeviceDescriptor(configuration_factory=ShellyRelais) diff --git a/packages/modules/devices/shelly/shelly/status_handler.py b/packages/modules/devices/shelly/shelly/status_handler.py index 42ec365d8b..2f1cf69eef 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, HTTPBasicAuth, HTTPDigestAuth from modules.common import req @@ -23,12 +24,20 @@ def get_generation(address: str) -> Tuple[int, str]: return generation, model -def request_status(address: str, generation: Optional[int]) -> Dict: +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" 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]]: