From bfea4e2a067aa30d8cceba8c97000cdd0b85e3d1 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 24 Jul 2023 15:48:06 +0200 Subject: [PATCH] add solarmax bat --- packages/helpermodules/update_config.py | 20 ++++- packages/modules/devices/solarmax/bat.py | 34 ++++++++ packages/modules/devices/solarmax/config.py | 19 ++++- packages/modules/devices/solarmax/device.py | 79 ++++++++----------- packages/modules/devices/solarmax/inverter.py | 15 ++-- 5 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 packages/modules/devices/solarmax/bat.py diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 2e757ec4e7..247bb37477 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -24,7 +24,7 @@ class UpdateConfig: - DATASTORE_VERSION = 16 + DATASTORE_VERSION = 17 valid_topic = [ "^openWB/bat/config/configured$", "^openWB/bat/set/charging_power_left$", @@ -778,3 +778,21 @@ def upgrade_datastore_15(self) -> None: except Exception: log.exception(f"Logfile {file} konnte nicht konvertiert werden.") Pub().pub("openWB/system/datastore_version", 16) + + def upgrade_datastore_16(self) -> None: + for topic_device, payload in self.all_received_topics.items(): + if re.search("openWB/system/device/[0-9]+/config", topic_device) is not None: + payload_device = decode_payload(payload) + index = get_index(topic_device) + if payload_device["type"] == "solarmax": + for topic_component, payload_component in self.all_received_topics.items(): + if re.search(f"^openWB/system/device/{index}/component/[0-9]+/config$", + topic_component) is not None: + payload_inverter = decode_payload(payload_component) + if payload_inverter["type"] == "inverter": + payload_inverter["configuration"]["modbus_id"] = payload_device["configuration"][ + "modbus_id"] + payload_device["configuration"].pop("modbus_id") + Pub().pub(topic_device.replace("openWB/", "openWB/set/"), payload_device) + Pub().pub(topic_component.replace("openWB/", "openWB/set/"), payload_inverter) + Pub().pub("openWB/system/datastore_version", 17) diff --git a/packages/modules/devices/solarmax/bat.py b/packages/modules/devices/solarmax/bat.py new file mode 100644 index 0000000000..7b419a24d7 --- /dev/null +++ b/packages/modules/devices/solarmax/bat.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.solarmax.config import SolarmaxBatSetup + + +class SolarmaxBat: + def __init__(self, device_id: int, component_config: SolarmaxBatSetup) -> None: + self.__device_id = device_id + self.component_config = dataclass_from_dict(SolarmaxBatSetup, component_config) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) + self.component_info = ComponentInfo.from_component_config(self.component_config) + + def update(self, client: ModbusTcpClient_) -> None: + power = client.read_holding_registers(114, ModbusDataType.INT_32, unit=1) + soc = client.read_holding_registers(122, ModbusDataType.INT_16, unit=1) + imported, exported = self.sim_counter.sim_count(power) + + bat_state = BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + self.store.set(bat_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolarmaxBatSetup) diff --git a/packages/modules/devices/solarmax/config.py b/packages/modules/devices/solarmax/config.py index ec62a33ad4..3893987d6d 100644 --- a/packages/modules/devices/solarmax/config.py +++ b/packages/modules/devices/solarmax/config.py @@ -4,9 +4,8 @@ class SolarmaxConfiguration: - def __init__(self, ip_address: Optional[str] = None, modbus_id: int = 1): + def __init__(self, ip_address: Optional[str] = None): self.ip_address = ip_address - self.modbus_id = modbus_id class Solarmax: @@ -21,11 +20,25 @@ def __init__(self, self.configuration = configuration or SolarmaxConfiguration() -class SolarmaxInverterConfiguration: +class SolarmaxBatConfiguration: def __init__(self): pass +class SolarmaxBatSetup(ComponentSetup[SolarmaxBatConfiguration]): + def __init__(self, + name: str = "Solarmax MAX.STORAGE / MAX.STORAGE Ultimate Speicher", + type: str = "bat", + id: int = 0, + configuration: SolarmaxBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SolarmaxBatConfiguration()) + + +class SolarmaxInverterConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + class SolarmaxInverterSetup(ComponentSetup[SolarmaxInverterConfiguration]): def __init__(self, name: str = "Solarmax Wechselrichter", diff --git a/packages/modules/devices/solarmax/device.py b/packages/modules/devices/solarmax/device.py index fff2755dbe..6e936c3b4e 100644 --- a/packages/modules/devices/solarmax/device.py +++ b/packages/modules/devices/solarmax/device.py @@ -1,61 +1,44 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Optional, List, Union +from typing import Iterable, Optional, List, 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.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.solarmax import inverter -from modules.devices.solarmax.config import Solarmax, SolarmaxConfiguration +from modules.devices.solarmax.bat import SolarmaxBat +from modules.devices.solarmax.config import Solarmax, SolarmaxBatSetup, SolarmaxConfiguration, SolarmaxInverterSetup log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "inverter": inverter.SolarmaxInverter - } - - def __init__(self, device_config: Union[Dict, Solarmax]) -> None: - self.components = {} - try: - self.device_config = dataclass_from_dict(Solarmax, device_config) - self.client = modbus.ModbusTcpClient_(self.device_config.configuration.ip_address, 502) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: dict) -> 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].component_info): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine oder zu viele Komponenten konfiguriert wurden." - ) +def create_device(device_config: Solarmax): + def create_bat_component(component_config: SolarmaxBatSetup): + return SolarmaxBat(device_config.id, component_config) + + def create_inverter_component(component_config: SolarmaxInverterSetup): + return inverter.SolarmaxInverter(device_config.id, component_config) + + def update_components(components: Iterable[Union[SolarmaxBat, inverter.SolarmaxInverter]]): + with client as c: + for component in components: + with SingleComponentUpdateContext(component.component_info): + component.update(c) + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, 502) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) COMPONENT_TYPE_TO_MODULE = { @@ -64,7 +47,7 @@ def update(self) -> None: def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - dev = Device(Solarmax(configuration=SolarmaxConfiguration(ip_address=ip_address))) + dev = create_device(Solarmax(configuration=SolarmaxConfiguration(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: diff --git a/packages/modules/devices/solarmax/inverter.py b/packages/modules/devices/solarmax/inverter.py index f30c674f72..5ef8106cf1 100644 --- a/packages/modules/devices/solarmax/inverter.py +++ b/packages/modules/devices/solarmax/inverter.py @@ -2,11 +2,10 @@ from typing import Dict, Union from dataclass_utils import dataclass_from_dict -from modules.common import modbus from modules.common.component_state import InverterState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo -from modules.common.modbus import ModbusDataType +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ from modules.common.simcount import SimCounter from modules.common.store import get_inverter_value_store from modules.devices.solarmax.config import SolarmaxInverterSetup @@ -15,20 +14,16 @@ class SolarmaxInverter: def __init__(self, device_id: int, - component_config: Union[Dict, SolarmaxInverterSetup], - tcp_client: modbus.ModbusTcpClient_, - modbus_id: int) -> None: + component_config: Union[Dict, SolarmaxInverterSetup]) -> None: self.__device_id = device_id - self.__modbus_id = modbus_id self.component_config = dataclass_from_dict(SolarmaxInverterSetup, component_config) - self.__tcp_client = tcp_client self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") self.store = get_inverter_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) - def update(self) -> None: - with self.__tcp_client: - power = self.__tcp_client.read_holding_registers(4151, ModbusDataType.UINT_32, unit=self.__modbus_id) * -1 + def update(self, client: ModbusTcpClient_) -> None: + power = client.read_holding_registers(4151, ModbusDataType.UINT_32, + unit=self.component_config.configuration.modbus_id) * -1 power = power / 10 _, exported = self.sim_counter.sim_count(power) inverter_state = InverterState(