diff --git a/packages/modules/devices/lg/device.py b/packages/modules/devices/lg/device.py index 0a55eac6ab..93a01be397 100644 --- a/packages/modules/devices/lg/device.py +++ b/packages/modules/devices/lg/device.py @@ -1,146 +1,75 @@ #!/usr/bin/env python3 import json import logging -import os -from typing import Dict, Union, Optional, List +from typing import Dict, Iterable, Union from requests import HTTPError, Session -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import req -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext -from modules.devices.lg.config import LG, LgBatSetup, LgConfiguration, LgCounterSetup, LgInverterSetup -from modules.devices.lg import bat, counter, inverter +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.lg.bat import LgBat +from modules.devices.lg.config import LG, LgBatSetup, LgCounterSetup, LgInverterSetup +from modules.devices.lg.counter import LgCounter +from modules.devices.lg.inverter import LgInverter log = logging.getLogger(__name__) -lg_component_classes = Union[bat.LgBat, counter.LgCounter, inverter.LgInverter] - - -class Device(AbstractDevice): - """Beispiel JSON-Objekte liegen im Ordner lgessv1/JSON-Beispiele.txt - lg_ess_url: IP/URL des LG ESS V1.0 - lg_ess_pass: Passwort, um sich in den LG ESS V1.0 einzuloggen. - Das Passwort ist standardmäßig die Registrierungsnummer, - die sich auf dem PCS (dem Hybridwechselrichter und - Batteriemanagementsystem) befindet (Aufkleber!). Alter- - nativ findet man die Registrierungsnummer in der App unter - dem Menüpunkt "Systeminformationen". - Mit der Registrierungsnummer kann man sich dann in der - Rolle "installer" einloggen.""" - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.LgBat, - "counter": counter.LgCounter, - "inverter": inverter.LgInverter - } - - def __init__(self, device_config: Union[Dict, LG]) -> None: - self.components = {} # type: Dict[str, lg_component_classes] - self.session_key = " " - try: - self.device_config = dataclass_from_dict(LG, device_config) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, LgBatSetup, LgCounterSetup, LgInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, - component_config)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - with MultiComponentUpdateContext(self.components): - session = req.get_http_session() - try: - response = self._request_data(session) - except HTTPError: - self._update_session_key(session) - response = self._request_data(session) - - for component in self.components: - self.components[component].update(response) - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - def _update_session_key(self, session: Session): - try: - headers = {'Content-Type': 'application/json', } - data = json.dumps({"password": self.device_config.configuration.password}) - response = session.put("https://"+self.device_config.configuration.ip_address+'/v1/login', headers=headers, - data=data, verify=False, timeout=5).json() - self.session_key = response["auth_key"] - except (HTTPError, KeyError) as e: - e.args += ("login failed! check password!", ) - raise e - - def _request_data(self, session: Session) -> Dict: +def _update_session_key(session: Session, ip_address: str, password: str) -> str: + try: headers = {'Content-Type': 'application/json', } - data = json.dumps({"auth_key": self.session_key}) - return session.post("https://"+self.device_config.configuration.ip_address + "/v1/user/essinfo/home", - headers=headers, - data=data, - verify=False, - timeout=5).json() - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip: str, password: str, num: Optional[int] = None) -> None: - dev = Device(LG(configuration=LgConfiguration(ip_address=ip, password=password))) - - if os.path.isfile("/var/www/html/openWB/ramdisk/ess_session_key"): - with open("/var/www/html/openWB/ramdisk/ess_session_key", "r") as f: - # erste Zeile ohne Zeilenumbruch lesen - old_session_key = f.readline().strip() - dev.session_key = old_session_key - else: - old_session_key = dev.session_key - - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - if component_type == "bat" or component_type == "counter": - num = None - component_config.id = num - dev.add_component(component_config) - log.debug('LG ESS V1.0 IP: ' + ip) - log.debug('LG ESS V1.0 password: ' + password) - dev.update() - - if dev.session_key != old_session_key: - with open("/var/www/html/openWB/ramdisk/ess_session_key", "w") as f: - f.write(str(dev.session_key)) - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + data = json.dumps({"password": password}) + response = session.put(f"https://{ip_address}/v1/login", headers=headers, + data=data, verify=False, timeout=5).json() + return response["auth_key"] + except (HTTPError, KeyError) as e: + e.args += ("login failed! check password!", ) + raise e + + +def _request_data(session: Session, session_key: str, ip_address: str) -> Dict: + headers = {'Content-Type': 'application/json', } + data = json.dumps({"auth_key": session_key}) + return session.post(f"https://{ip_address}/v1/user/essinfo/home", + headers=headers, + data=data, + verify=False, + timeout=5).json() + + +def create_device(device_config: LG): + def create_bat_component(component_config: LgBatSetup): + return LgBat(device_config.id, component_config) + + def create_counter_component(component_config: LgCounterSetup): + return LgCounter(device_config.id, component_config) + + def create_inverter_component(component_config: LgInverterSetup): + return LgInverter(device_config.id, component_config) + + def update_components(components: Iterable[Union[LgBat, LgCounter, LgInverter]]): + nonlocal session_key + session = req.get_http_session() + try: + response = _request_data(session, session_key, device_config.configuration.ip_address) + except HTTPError: + session_key = _update_session_key( + session, device_config.configuration.ip_address, device_config.configuration.password) + response = _request_data(session, session_key, device_config.configuration.ip_address) + + for component in components: + component.update(response) + + session_key = " " + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=LG) diff --git a/packages/modules/devices/lg/lg_test.py b/packages/modules/devices/lg/lg_test.py index 5bea372f85..58808a0f8a 100644 --- a/packages/modules/devices/lg/lg_test.py +++ b/packages/modules/devices/lg/lg_test.py @@ -3,7 +3,11 @@ from unittest.mock import Mock from modules.common.component_state import BatState, CounterState, InverterState -from modules.devices.lg import bat, counter, device, inverter +from modules.common.configurable_device import ConfigurableDevice +from modules.devices.lg import bat, counter, inverter +from modules.devices.lg import device +from modules.devices.lg.device import create_device + from modules.devices.lg.config import LG, LgConfiguration from test_utils.mock_ramdisk import MockRamdisk @@ -14,9 +18,10 @@ def mock_ramdisk(monkeypatch): @pytest.fixture -def dev() -> device.Device: - dev = device.Device(LG(configuration=LgConfiguration(ip_address=API_URL, password="some password"))) - dev.session_key = "67567d76-0c83-11ea-8a59-d84fb802005a" +def dev(monkeypatch) -> ConfigurableDevice: + dev = create_device(LG(configuration=LgConfiguration(ip_address=API_URL, password="some password"))) + mock_session_key = Mock(return_value="67567d76-0c83-11ea-8a59-d84fb802005a") + monkeypatch.setattr(device, "_update_session_key", mock_session_key) return dev @@ -38,7 +43,7 @@ def assert_inverter_state_correct(state: InverterState): assert state.exported == 200 -def test_valid_login(monkeypatch, dev: device.Device): +def test_valid_login(monkeypatch, dev: ConfigurableDevice): # setup mock_bat_value_store = Mock() monkeypatch.setattr(bat, "get_bat_value_store", Mock(return_value=mock_bat_value_store)) @@ -46,7 +51,7 @@ def test_valid_login(monkeypatch, dev: device.Device): monkeypatch.setattr(counter, "get_counter_value_store", Mock(return_value=mock_counter_value_store)) mock_inverter_value_store = Mock() monkeypatch.setattr(inverter, "get_inverter_value_store", Mock(return_value=mock_inverter_value_store)) - monkeypatch.setattr(device.Device, "_request_data", Mock(return_value=sample_auth_key_valid)) + monkeypatch.setattr(device, "_request_data", Mock(return_value=sample_auth_key_valid)) component_config = bat.component_descriptor.configuration_factory() component_config.id = None dev.add_component(component_config) @@ -66,7 +71,7 @@ def test_valid_login(monkeypatch, dev: device.Device): assert_inverter_state_correct(mock_inverter_value_store.set.call_args[0][0]) -def test_update_session_key(monkeypatch, dev: device.Device): +def test_update_session_key(monkeypatch, dev: ConfigurableDevice): # setup mock_bat_value_store = Mock() monkeypatch.setattr(bat, "get_bat_value_store", Mock(return_value=mock_bat_value_store)) @@ -74,8 +79,7 @@ def test_update_session_key(monkeypatch, dev: device.Device): monkeypatch.setattr(counter, "get_counter_value_store", Mock(return_value=mock_counter_value_store)) mock_inverter_value_store = Mock() monkeypatch.setattr(inverter, "get_inverter_value_store", Mock(return_value=mock_inverter_value_store)) - monkeypatch.setattr(device.Device, "_update_session_key", Mock()) - monkeypatch.setattr(device.Device, "_request_data", Mock( + monkeypatch.setattr(device, "_request_data", Mock( side_effect=[HTTPError, sample_auth_key_valid])) component_config = bat.component_descriptor.configuration_factory() component_config.id = None diff --git a/packages/modules/devices/mqtt/device.py b/packages/modules/devices/mqtt/device.py index dfbe0dc6fd..5bf377ed2b 100644 --- a/packages/modules/devices/mqtt/device.py +++ b/packages/modules/devices/mqtt/device.py @@ -1,55 +1,37 @@ #!/usr/bin/env python3 -from typing import Dict, Union +from typing import Iterable, Union import logging -from dataclass_utils import dataclass_from_dict -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.mqtt import bat, counter, inverter from modules.devices.mqtt.config import Mqtt, MqttBatSetup, MqttCounterSetup, MqttInverterSetup log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.MqttBat, - "counter": counter.MqttCounter, - "inverter": inverter.MqttInverter - } - COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter - } - - def __init__(self, device_config: Union[Dict, Mqtt]) -> None: - self.components = {} - try: - self.device_config = dataclass_from_dict(Mqtt, device_config) - except Exception: - log.exception("Fehler im Modul " + self.device_config.name) - - def add_component(self, component_config: Union[Dict, MqttBatSetup, MqttCounterSetup, MqttInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(self.COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id) - ] = (self.COMPONENT_TYPE_TO_CLASS[component_type](component_config)) - - def update(self) -> None: - if self.components: - with MultiComponentUpdateContext(self.components): - log.debug("MQTT-Module müssen nicht ausgelesen werden.") - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) +def create_device(device_config: Mqtt): + def create_bat_component(component_config: MqttBatSetup): + return bat.MqttBat(component_config) + + def create_counter_component(component_config: MqttCounterSetup): + return counter.MqttCounter(component_config) + + def create_inverter_component(component_config: MqttInverterSetup): + return inverter.MqttInverter(component_config) + + def update_components(components: Iterable[Union[bat.MqttBat, counter.MqttCounter, inverter.MqttInverter]]): + log.debug("MQTT-Module müssen nicht ausgelesen werden.") + + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Mqtt) diff --git a/packages/modules/devices/powerdog/device.py b/packages/modules/devices/powerdog/device.py index a0a1935814..ee027fd814 100644 --- a/packages/modules/devices/powerdog/device.py +++ b/packages/modules/devices/powerdog/device.py @@ -1,112 +1,64 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Optional, List +from typing import Iterable, Union -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext, SingleComponentUpdateContext -from modules.devices.powerdog import counter -from modules.devices.powerdog import inverter -from modules.devices.powerdog.config import Powerdog, PowerdogConfiguration, PowerdogCounterSetup, PowerdogInverterSetup +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.powerdog.config import Powerdog, PowerdogCounterSetup, PowerdogInverterSetup +from modules.devices.powerdog.counter import PowerdogCounter +from modules.devices.powerdog.inverter import PowerdogInverter log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "counter": counter.PowerdogCounter, - "inverter": inverter.PowerdogInverter - } +def create_device(device_config: Powerdog): + def create_counter_component(component_config: PowerdogCounterSetup): + return PowerdogCounter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def __init__(self, device_config: Union[Dict, Powerdog]) -> None: - self.components = {} # type: Dict[str, Union[counter.PowerdogCounter, inverter.PowerdogInverter]] - try: - self.device_config = dataclass_from_dict(Powerdog, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) + def create_inverter_component(component_config: PowerdogInverterSetup): + return PowerdogInverter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def add_component(self, component_config: Union[Dict, PowerdogCounterSetup, PowerdogInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = ( - self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, - self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - with MultiComponentUpdateContext(self.components): - if len(self.components) == 1: - for component in self.components: - if isinstance(self.components[component], inverter.PowerdogInverter): - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() + def update_components(components: Iterable[Union[PowerdogCounter, PowerdogInverter]]): + with client: + if len(components) == 1: + for component in components: + if isinstance(components[component], PowerdogInverter): + with SingleComponentUpdateContext(components[component].fault_state): + components[component].update() else: raise Exception( "Wenn ein EVU-Zähler konfiguriert wurde, muss immer auch ein WR konfiguriert sein.") - elif len(self.components) == 2: - for component in self.components: - if isinstance(self.components[component], inverter.PowerdogInverter): - inverter_power = self.components[component].update() + elif len(components) == 2: + for component in components: + if isinstance(components[component], PowerdogInverter): + inverter_power = components[component].update() break else: inverter_power = 0 - for component in self.components: - if isinstance(self.components[component], counter.PowerdogCounter): - self.components[component].update(inverter_power) + for component in components: + if isinstance(components[component], PowerdogCounter): + components[component].update(inverter_power) else: log.warning( - self.device_config.name + + device_config.name + ": Es konnten keine Werte gelesen werden, da noch keine oder zu viele Komponenten konfiguriert " + "wurden." ) - -COMPONENT_TYPE_TO_MODULE = { - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - dev = Device(Powerdog(configuration=PowerdogConfiguration(ip_address=ip_address))) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - # Wenn der EVU-Zähler ausgelesen werden soll, wird auch noch der Inverter benötigt. - if component_type in COMPONENT_TYPE_TO_MODULE and component_type == "counter": - inverter_config = PowerdogInverterSetup() - inverter_config.id = 1 - dev.add_component(inverter_config) - - log.debug('Powerdog IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Powerdog) diff --git a/packages/modules/devices/saxpower/device.py b/packages/modules/devices/saxpower/device.py index 12e59c1734..727be7ed67 100644 --- a/packages/modules/devices/saxpower/device.py +++ b/packages/modules/devices/saxpower/device.py @@ -1,88 +1,38 @@ #!/usr/bin/env python3 import logging -from typing import Dict, List, Union +from typing import Iterable -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext -from modules.devices.saxpower import bat +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.saxpower.bat import SaxpowerBat from modules.devices.saxpower.config import Saxpower, SaxpowerBatSetup log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.SaxpowerBat - } - - def __init__(self, device_config: Union[Dict, Saxpower]) -> None: - self.components = {} # type: Dict[str, bat.SaxpowerBat] - try: - self.device_config = dataclass_from_dict(Saxpower, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, SaxpowerBatSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat -} - - -def read_legacy(component_type: str, ip_address: str) -> None: - device_config = Saxpower() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = None - dev.add_component(component_config) - - log.debug('Saxpower IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) +def create_device(device_config: Saxpower): + def create_bat_component(component_config: SaxpowerBatSetup): + return SaxpowerBat(device_config.id, component_config, client, device_config.configuration.modbus_id) + + def update_components(components: Iterable[SaxpowerBat]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Saxpower) diff --git a/packages/modules/devices/siemens/device.py b/packages/modules/devices/siemens/device.py index 03f4ca2b4a..720c87dbe6 100644 --- a/packages/modules/devices/siemens/device.py +++ b/packages/modules/devices/siemens/device.py @@ -1,99 +1,51 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Optional, List +from typing import Iterable, Union -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext -from modules.devices.siemens import bat, counter, inverter +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.siemens.bat import SiemensBat from modules.devices.siemens.config import Siemens, SiemensBatSetup, SiemensCounterSetup, SiemensInverterSetup +from modules.devices.siemens.counter import SiemensCounter +from modules.devices.siemens.inverter import SiemensInverter log = logging.getLogger(__name__) -siemens_component_classes = Union[bat.SiemensBat, counter.SiemensCounter, inverter.SiemensInverter] +siemens_component_classes = Union[SiemensBat, SiemensCounter, SiemensInverter] -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.SiemensBat, - "counter": counter.SiemensCounter, - "inverter": inverter.SiemensInverter - } +def create_device(device_config: Siemens): + def create_bat_component(component_config: SiemensBatSetup): + return SiemensBat(device_config.id, component_config, client, device_config.configuration.modbus_id) - def __init__(self, device_config: Union[Dict, Siemens]) -> None: - self.components = {} # type: Dict[str, siemens_component_classes] - try: - self.device_config = dataclass_from_dict(Siemens, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) + def create_counter_component(component_config: SiemensCounterSetup): + return SiemensCounter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def add_component(self, component_config: Union[Dict, - SiemensBatSetup, - SiemensCounterSetup, - SiemensInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, - self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) + def create_inverter_component(component_config: SiemensInverterSetup): + return SiemensInverter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def update(self) -> None: - log.debug("Start device reading" + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) + def update_components(components: Iterable[siemens_component_classes]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - device_config = Siemens() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - log.debug('Siemens IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Siemens)