Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mqtt, powerdog, saxpower, siemens, lg #1754

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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