diff --git a/packages/modules/devices/fox_ess/fox_ess/config.py b/packages/modules/devices/fox_ess/fox_ess/config.py index 328ccbde4d..e5d7445831 100644 --- a/packages/modules/devices/fox_ess/fox_ess/config.py +++ b/packages/modules/devices/fox_ess/fox_ess/config.py @@ -15,7 +15,7 @@ def __init__(self, class FoxEss: def __init__(self, - name: str = "FoxESS", + name: str = "FoxESS H3", type: str = "fox_ess", id: int = 0, configuration: FoxEssConfiguration = None) -> None: @@ -35,7 +35,7 @@ def __init__(self, modbus_id: int = 1): @auto_str class FoxEssBatSetup(ComponentSetup[FoxEssBatConfiguration]): def __init__(self, - name: str = "FoxESS Speicher", + name: str = "FoxESS H3 Speicher", type: str = "bat", id: int = 0, configuration: FoxEssBatConfiguration = None, @@ -52,7 +52,7 @@ def __init__(self, modbus_id: int = 1): @auto_str class FoxEssCounterSetup(ComponentSetup[FoxEssCounterConfiguration]): def __init__(self, - name: str = "FoxESS Zähler", + name: str = "FoxESS H3 Zähler", type: str = "counter", id: int = 0, configuration: FoxEssCounterConfiguration = None, @@ -69,7 +69,7 @@ def __init__(self, modbus_id: int = 1): @auto_str class FoxEssInverterSetup(ComponentSetup[FoxEssInverterConfiguration]): def __init__(self, - name: str = "FoxESS Wechselrichter", + name: str = "FoxESS H3 Wechselrichter", type: str = "inverter", id: int = 0, configuration: FoxEssInverterConfiguration = None, diff --git a/packages/modules/devices/fox_ess/fox_ess_h3_smart/bat.py b/packages/modules/devices/fox_ess/fox_ess_h3_smart/bat.py new file mode 100644 index 0000000000..d732099a74 --- /dev/null +++ b/packages/modules/devices/fox_ess/fox_ess_h3_smart/bat.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import logging +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_component_value_store +from modules.devices.fox_ess.fox_ess_h3_smart.config import FoxEssH3SmartBatSetup +from modules.common.utils.peak_filter import PeakFilter +from modules.common.component_type import ComponentType + +log = logging.getLogger(__name__) + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class FoxEssH3SmartBat(AbstractBat): + def __init__(self, component_config: FoxEssH3SmartBatSetup, **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.client: ModbusTcpClient_ = self.kwargs['client'] + self.store = get_component_value_store(self.component_config.type, self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.peak_filter = PeakFilter(ComponentType.BAT, self.component_config.id, self.fault_state) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, self.component_config.type) + + def update(self) -> None: + unit = self.component_config.configuration.modbus_id + + power = self.client.read_holding_registers(39237, ModbusDataType.INT_32, unit=unit) + soc = self.client.read_holding_registers(37612, ModbusDataType.INT_16, unit=unit) / 100 + + self.peak_filter.check_values(power) + 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=FoxEssH3SmartBatSetup) diff --git a/packages/modules/devices/fox_ess/fox_ess_h3_smart/config.py b/packages/modules/devices/fox_ess/fox_ess_h3_smart/config.py new file mode 100644 index 0000000000..2e4e0fb212 --- /dev/null +++ b/packages/modules/devices/fox_ess/fox_ess_h3_smart/config.py @@ -0,0 +1,77 @@ +from typing import Optional + +from helpermodules.auto_str import auto_str +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class FoxEssH3SmartConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502): + self.ip_address = ip_address + self.port = port + + +class FoxEssH3Smart: + def __init__(self, + name: str = "FoxEss H3 Smart", + type: str = "fox_ess_h3_smart", + id: int = 0, + configuration: FoxEssH3SmartConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or FoxEssH3SmartConfiguration() + + +@auto_str +class FoxEssH3SmartBatConfiguration: + def __init__(self, modbus_id: int = 247): + self.modbus_id = modbus_id + + +@auto_str +class FoxEssH3SmartBatSetup(ComponentSetup[FoxEssH3SmartBatConfiguration]): + def __init__(self, + name: str = "FoxEss H3 Smart Speicher", + type: str = "bat", + id: int = 0, + configuration: FoxEssH3SmartBatConfiguration = None, + **kwargs) -> None: + super().__init__(name, type, id, configuration or FoxEssH3SmartBatConfiguration(), **kwargs) + + +@auto_str +class FoxEssH3SmartCounterConfiguration: + def __init__(self, modbus_id: int = 247): + self.modbus_id = modbus_id + + +@auto_str +class FoxEssH3SmartCounterSetup(ComponentSetup[FoxEssH3SmartCounterConfiguration]): + def __init__(self, + name: str = "FoxEss H3 Smart Zähler", + type: str = "counter", + id: int = 0, + configuration: FoxEssH3SmartCounterConfiguration = None, + **kwargs) -> None: + super().__init__(name, type, id, configuration or FoxEssH3SmartCounterConfiguration(), **kwargs) + + +@auto_str +class FoxEssH3SmartInverterConfiguration: + def __init__(self, modbus_id: int = 247): + self.modbus_id = modbus_id + + +@auto_str +class FoxEssH3SmartInverterSetup(ComponentSetup[FoxEssH3SmartInverterConfiguration]): + def __init__(self, + name: str = "FoxEss H3 Smart Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: FoxEssH3SmartInverterConfiguration = None, + **kwargs) -> None: + super().__init__(name, type, id, configuration or FoxEssH3SmartInverterConfiguration(), **kwargs) diff --git a/packages/modules/devices/fox_ess/fox_ess_h3_smart/counter.py b/packages/modules/devices/fox_ess/fox_ess_h3_smart/counter.py new file mode 100644 index 0000000000..73082fe21e --- /dev/null +++ b/packages/modules/devices/fox_ess/fox_ess_h3_smart/counter.py @@ -0,0 +1,52 @@ +#!/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_component_value_store +from modules.devices.fox_ess.fox_ess_h3_smart.config import FoxEssH3SmartCounterSetup +from modules.common.utils.peak_filter import PeakFilter +from modules.common.component_type import ComponentType + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class FoxEssH3SmartCounter(AbstractCounter): + def __init__(self, component_config: FoxEssH3SmartCounterSetup, **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.client: ModbusTcpClient_ = self.kwargs['client'] + self.store = get_component_value_store(self.component_config.type, self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.peak_filter = PeakFilter(ComponentType.COUNTER, self.component_config.id, self.fault_state) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, self.component_config.type) + + def update(self) -> None: + unit = self.component_config.configuration.modbus_id + + power = self.client.read_holding_registers(38814, ModbusDataType.INT_32, unit=unit) * -0.1 + powers = self.client.read_holding_registers(38816, [ModbusDataType.INT_32]*3, unit=unit) + powers = [value * -0.1 for value in powers] + + self.peak_filter.check_values(power) + imported, exported = self.sim_counter.sim_count(power) + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + powers=powers + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=FoxEssH3SmartCounterSetup) diff --git a/packages/modules/devices/fox_ess/fox_ess_h3_smart/device.py b/packages/modules/devices/fox_ess/fox_ess_h3_smart/device.py new file mode 100644 index 0000000000..80ee200f5d --- /dev/null +++ b/packages/modules/devices/fox_ess/fox_ess_h3_smart/device.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +import logging +from typing import Iterable, Union + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.modbus import ModbusTcpClient_ +from modules.devices.fox_ess.fox_ess_h3_smart.bat import FoxEssH3SmartBat +from modules.devices.fox_ess.fox_ess_h3_smart.counter import FoxEssH3SmartCounter +from modules.devices.fox_ess.fox_ess_h3_smart.inverter import FoxEssH3SmartInverter +from modules.devices.fox_ess.fox_ess_h3_smart.config import FoxEssH3Smart, FoxEssH3SmartBatSetup +from modules.devices.fox_ess.fox_ess_h3_smart.config import FoxEssH3SmartCounterSetup, FoxEssH3SmartInverterSetup + +log = logging.getLogger(__name__) + + +def create_device(device_config: FoxEssH3Smart): + client = None + + def create_bat_component(component_config: FoxEssH3SmartBatSetup): + return FoxEssH3SmartBat(component_config, device_id=device_config.id, client=client) + + def create_counter_component(component_config: FoxEssH3SmartCounterSetup): + return FoxEssH3SmartCounter(component_config, device_id=device_config.id, client=client) + + def create_inverter_component(component_config: FoxEssH3SmartInverterSetup): + return FoxEssH3SmartInverter(component_config, device_id=device_config.id, client=client) + + def update_components(components: Iterable[Union[FoxEssH3SmartBat, FoxEssH3SmartCounter, FoxEssH3SmartInverter]]): + 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) + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + 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=FoxEssH3Smart) diff --git a/packages/modules/devices/fox_ess/fox_ess_h3_smart/inverter.py b/packages/modules/devices/fox_ess/fox_ess_h3_smart/inverter.py new file mode 100644 index 0000000000..b3aeed3106 --- /dev/null +++ b/packages/modules/devices/fox_ess/fox_ess_h3_smart/inverter.py @@ -0,0 +1,49 @@ +#!/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.utils.peak_filter import PeakFilter +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_component_value_store +from modules.devices.fox_ess.fox_ess_h3_smart.config import FoxEssH3SmartInverterSetup +from modules.common.component_type import ComponentType + + +class KwargsDict(TypedDict): + device_id: int + client: ModbusTcpClient_ + + +class FoxEssH3SmartInverter(AbstractInverter): + def __init__(self, component_config: FoxEssH3SmartInverterSetup, **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.client: ModbusTcpClient_ = self.kwargs['client'] + self.store = get_component_value_store(self.component_config.type, self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.peak_filter = PeakFilter(ComponentType.INVERTER, self.component_config.id, self.fault_state) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, self.component_config.type) + + def update(self) -> None: + unit = self.component_config.configuration.modbus_id + + pv_strings = self.client.read_holding_registers(39279, [ModbusDataType.INT_32]*6, unit=unit) + power = sum(pv_strings) * -1 + + self.peak_filter.check_values(power) + _, exported = self.sim_counter.sim_count(power) + inverter_state = InverterState( + power=power, + exported=exported, + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=FoxEssH3SmartInverterSetup)