diff --git a/packages/modules/devices/huawei/huawei_emma/bat.py b/packages/modules/devices/huawei/huawei_emma/bat.py new file mode 100644 index 0000000000..cdf43d8a13 --- /dev/null +++ b/packages/modules/devices/huawei/huawei_emma/bat.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +from modules.common.abstract_device import AbstractBat +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +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.huawei.huawei_emma.config import Huawei_EmmaBatSetup + + +class KwargsDict(TypedDict): + device_id: int + modbus_id: int + client: ModbusTcpClient_ + + +class Huawei_EmmaBat(AbstractBat): + def __init__(self, component_config: Huawei_EmmaBatSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__device_id: int = self.kwargs['device_id'] + self.modbus_id: int = self.kwargs['modbus_id'] + self.client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self) -> None: + power = self.client.read_holding_registers(30360, ModbusDataType.INT_32, unit=self.modbus_id) + soc = self.client.read_holding_registers(30368, ModbusDataType.UINT_16, unit=self.modbus_id) * 0.01 + + 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=Huawei_EmmaBatSetup) diff --git a/packages/modules/devices/huawei/huawei_emma/config.py b/packages/modules/devices/huawei/huawei_emma/config.py new file mode 100644 index 0000000000..45b3c0a9da --- /dev/null +++ b/packages/modules/devices/huawei/huawei_emma/config.py @@ -0,0 +1,68 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class Huawei_EmmaConfiguration: + def __init__(self, modbus_id: int = 0, + ip_address: Optional[str] = None, + port: int = 502): + self.modbus_id = modbus_id + self.ip_address = ip_address + self.port = port + + +class Huawei_Emma: + def __init__(self, + name: str = "Huawei EMMA", + type: str = "huawei_emma", + id: int = 0, + configuration: Huawei_EmmaConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or Huawei_EmmaConfiguration() + + +class Huawei_EmmaBatConfiguration: + def __init__(self): + pass + + +class Huawei_EmmaBatSetup(ComponentSetup[Huawei_EmmaBatConfiguration]): + def __init__(self, + name: str = "Huawei EMMA Speicher", + type: str = "bat", + id: int = 0, + configuration: Huawei_EmmaBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or Huawei_EmmaBatConfiguration()) + + +class Huawei_EmmaCounterConfiguration: + def __init__(self): + pass + + +class Huawei_EmmaCounterSetup(ComponentSetup[Huawei_EmmaCounterConfiguration]): + def __init__(self, + name: str = "Huawei EMMA Zähler", + type: str = "counter", + id: int = 0, + configuration: Huawei_EmmaCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or Huawei_EmmaCounterConfiguration()) + + +class Huawei_EmmaInverterConfiguration: + def __init__(self): + pass + + +class Huawei_EmmaInverterSetup(ComponentSetup[Huawei_EmmaInverterConfiguration]): + def __init__(self, + name: str = "Huawei EMMA Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: Huawei_EmmaInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or Huawei_EmmaInverterConfiguration()) diff --git a/packages/modules/devices/huawei/huawei_emma/counter.py b/packages/modules/devices/huawei/huawei_emma/counter.py new file mode 100644 index 0000000000..912f70a97e --- /dev/null +++ b/packages/modules/devices/huawei/huawei_emma/counter.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +from modules.common.abstract_device import AbstractCounter +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.huawei.huawei_emma.config import Huawei_EmmaCounterSetup + + +class KwargsDict(TypedDict): + device_id: int + modbus_id: int + client: ModbusTcpClient_ + + +class Huawei_EmmaCounter(AbstractCounter): + def __init__(self, component_config: Huawei_EmmaCounterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__device_id: int = self.kwargs['device_id'] + self.modbus_id: int = self.kwargs['modbus_id'] + self.client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self) -> None: + currents = self.client.read_holding_registers(31651, [ModbusDataType.INT_32]*3, unit=self.modbus_id) + currents = [val * 0.1 for val in currents] + power = self.client.read_holding_registers(31657, ModbusDataType.INT_32, unit=self.modbus_id) + + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + currents=currents, + imported=imported, + exported=exported, + power=power + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=Huawei_EmmaCounterSetup) diff --git a/packages/modules/devices/huawei/huawei_emma/device.py b/packages/modules/devices/huawei/huawei_emma/device.py new file mode 100644 index 0000000000..3ccd4e04b1 --- /dev/null +++ b/packages/modules/devices/huawei/huawei_emma/device.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import logging +from pathlib import Path +from typing import Iterable, Union + +from helpermodules.utils.run_command import run_command +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.common.modbus import ModbusTcpClient_ +from modules.devices.huawei.huawei_emma.bat import Huawei_EmmaBat +from modules.devices.huawei.huawei_emma.config import Huawei_Emma, Huawei_EmmaBatSetup +from modules.devices.huawei.huawei_emma.config import Huawei_EmmaCounterSetup, Huawei_EmmaInverterSetup +from modules.devices.huawei.huawei_emma.counter import Huawei_EmmaCounter +from modules.devices.huawei.huawei_emma.inverter import Huawei_EmmaInverter + +log = logging.getLogger(__name__) + + +def create_device(device_config: Huawei_Emma): + client = None + + def create_bat_component(component_config: Huawei_EmmaBatSetup): + nonlocal client + return Huawei_EmmaBat(component_config, + device_id=device_config.id, + modbus_id=device_config.configuration.modbus_id, + client=client) + + def create_counter_component(component_config: Huawei_EmmaCounterSetup): + nonlocal client + return Huawei_EmmaCounter(component_config, + device_id=device_config.id, + modbus_id=device_config.configuration.modbus_id, + client=client) + + def create_inverter_component(component_config: Huawei_EmmaInverterSetup): + nonlocal client + return Huawei_EmmaInverter(component_config, + device_id=device_config.id, + modbus_id=device_config.configuration.modbus_id, + client=client) + + def update_components(components: Iterable[Union[Huawei_EmmaBat, Huawei_EmmaCounter, Huawei_EmmaInverter]]): + nonlocal client + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = ModbusTcpClient_(device_config.configuration.ip_address, + device_config.configuration.port) + + def error_handler(): + run_command(f"{Path(__file__).resolve().parents[4]}/modules/common/restart_protoss_admin") + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + error_handler=error_handler, + 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=Huawei_Emma) diff --git a/packages/modules/devices/huawei/huawei_emma/inverter.py b/packages/modules/devices/huawei/huawei_emma/inverter.py new file mode 100644 index 0000000000..7e66d36ed0 --- /dev/null +++ b/packages/modules/devices/huawei/huawei_emma/inverter.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +from modules.common.abstract_device import AbstractInverter +from modules.common.component_state import InverterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +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.huawei.huawei_emma.config import Huawei_EmmaInverterSetup + + +class KwargsDict(TypedDict): + device_id: int + modbus_id: int + client: ModbusTcpClient_ + + +class Huawei_EmmaInverter(AbstractInverter): + def __init__(self, component_config: Huawei_EmmaInverterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.__device_id: int = self.kwargs['device_id'] + self.modbus_id: int = self.kwargs['modbus_id'] + self.client: ModbusTcpClient_ = self.kwargs['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.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self) -> None: + power = self.client.read_holding_registers(30354, ModbusDataType.INT_32, unit=self.modbus_id) * -1 + exported = self.client.read_holding_registers(30344, ModbusDataType.UINT_32, unit=self.modbus_id) * 0.01 + + inverter_state = InverterState( + power=power, + exported=exported + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=Huawei_EmmaInverterSetup)