Skip to content

Commit

Permalink
mqtt, powerdog, saxpower, siemens, lg (#1754)
Browse files Browse the repository at this point in the history
* mqtt, powerdog, saxpower, siemens

* lg
  • Loading branch information
LKuemmel authored Jul 16, 2024
1 parent 5d5b7cc commit a12090b
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 426 deletions.
193 changes: 61 additions & 132 deletions packages/modules/devices/lg/device.py
Original file line number Diff line number Diff line change
@@ -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)
22 changes: 13 additions & 9 deletions packages/modules/devices/lg/lg_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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


Expand All @@ -38,15 +43,15 @@ 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))
mock_counter_value_store = Mock()
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)
Expand All @@ -66,16 +71,15 @@ 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))
mock_counter_value_store = Mock()
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
Expand Down
68 changes: 25 additions & 43 deletions packages/modules/devices/mqtt/device.py
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit a12090b

Please sign in to comment.