From d689965d2f1082278e3166774dc8da2aaf6d3ed4 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 02:38:07 +0100 Subject: [PATCH 1/9] add modbus udp functionality --- packages/modules/common/modbus.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/modules/common/modbus.py b/packages/modules/common/modbus.py index 9bdf3e73dd..89f555231d 100644 --- a/packages/modules/common/modbus.py +++ b/packages/modules/common/modbus.py @@ -11,7 +11,7 @@ from typing import Any, Callable, Iterable, Optional, Union, overload, List import pymodbus -from pymodbus.client.sync import ModbusTcpClient, ModbusSerialClient +from pymodbus.client.sync import ModbusTcpClient, ModbusUdpClient, ModbusSerialClient from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder from pymodbus.transaction import ModbusSocketFramer @@ -50,7 +50,7 @@ def __init__(self, bits: int, encoding_method: str, decoding_method: str): class ModbusClient: def __init__(self, - delegate: Union[ModbusSerialClient, ModbusTcpClient], + delegate: Union[ModbusSerialClient, ModbusTcpClient, ModbusUdpClient], address: str, port: int = 502, sleep_after_connect: Optional[int] = 0): self._delegate = delegate @@ -307,6 +307,17 @@ def __init__(self, port = parsed_url.port super().__init__(ModbusTcpClient(host, port, framer, **kwargs), address, port, sleep_after_connect) +class ModbusUdpClient_(ModbusClient): + def __init__(self, + address: str, + port: int = 502, + sleep_after_connect: Optional[int] = 0, + **kwargs): + parsed_url = parse_url(address) + host = parsed_url.host + if parsed_url.port is not None: + port = parsed_url.port + super().__init__(ModbusUdpClient(host, port, **kwargs), address, port, sleep_after_connect) class ModbusSerialClient_(ModbusClient): def __init__(self, From abc906180edce943522df548516a0f88119dd951 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 02:51:49 +0100 Subject: [PATCH 2/9] add Victron 3P75CT functionality --- .../victron/victron_3p75ct/__init__.py | 0 .../devices/victron/victron_3p75ct/config.py | 38 +++++++++ .../devices/victron/victron_3p75ct/counter.py | 80 +++++++++++++++++++ .../devices/victron/victron_3p75ct/device.py | 44 ++++++++++ 4 files changed, 162 insertions(+) create mode 100644 packages/modules/devices/victron/victron_3p75ct/__init__.py create mode 100644 packages/modules/devices/victron/victron_3p75ct/config.py create mode 100644 packages/modules/devices/victron/victron_3p75ct/counter.py create mode 100644 packages/modules/devices/victron/victron_3p75ct/device.py diff --git a/packages/modules/devices/victron/victron_3p75ct/__init__.py b/packages/modules/devices/victron/victron_3p75ct/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/devices/victron/victron_3p75ct/config.py b/packages/modules/devices/victron/victron_3p75ct/config.py new file mode 100644 index 0000000000..06a59db944 --- /dev/null +++ b/packages/modules/devices/victron/victron_3p75ct/config.py @@ -0,0 +1,38 @@ +from typing import Optional + +from modules.common.component_setup import ComponentSetup +from ..vendor import vendor_descriptor + + +class VictronConfiguration: + def __init__(self, ip_address: Optional[str] = None, port: int = 502): + self.ip_address = ip_address + self.port = port + + +class Victron: + def __init__(self, + name: str = "Victron 3P75CT", + type: str = "victron_3p75ct", + id: int = 0, + configuration: VictronConfiguration = None) -> None: + self.name = name + self.type = type + self.vendor = vendor_descriptor.configuration_factory().type + self.id = id + self.configuration = configuration or VictronConfiguration() + + +class VictronCounterConfiguration: + def __init__(self, energy_meter: bool = True, modbus_id: int = 1): + self.energy_meter = energy_meter + self.modbus_id = modbus_id + + +class VictronCounterSetup(ComponentSetup[VictronCounterConfiguration]): + def __init__(self, + name: str = "Victron Zähler", + type: str = "counter", + id: int = 0, + configuration: VictronCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or VictronCounterConfiguration()) diff --git a/packages/modules/devices/victron/victron_3p75ct/counter.py b/packages/modules/devices/victron/victron_3p75ct/counter.py new file mode 100644 index 0000000000..d7d6af499c --- /dev/null +++ b/packages/modules/devices/victron/victron_3p75ct/counter.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +from typing import TypedDict, Any + +from modules.common import modbus +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 +from modules.common.simcount import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.victron.victron_3p75ct.config import VictronCounterSetup + + +class KwargsDict(TypedDict): + device_id: int + client: modbus.ModbusUdpClient_ + + +class VictronCounter(AbstractCounter): + def __init__(self, component_config: VictronCounterSetup, **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.__udp_client = 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): + unit = self.component_config.configuration.modbus_id + energy_meter = self.component_config.configuration.energy_meter + with self.__udp_client: + if energy_meter: + powers = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / 1 + for reg in [0x3082, 0x3086, 0x308A]] #4 byte abstand + currents = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + for reg in [0x3041, 0x3049, 0x3051]] #8 byte abstand + voltages = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + for reg in [0x3040, 0x3048, 0x3050]] #8 byte abstand + power_factors = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 1000 + for reg in [0x3047, 0x304F, 0x3057]] + power = sum(powers) + frequency = self.__udp_client.read_holding_registers(0x3032, ModbusDataType.UINT_16, unit=unit) / 100 + else: + powers = self.__udp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) + power = sum(powers) + + self.__udp_client.read_holding_registers_bulk() + + imported, exported = self.sim_counter.sim_count(power) + + if energy_meter: + counter_state = CounterState( + voltages=voltages, + currents=currents, + powers=powers, + power_factors=power_factors, + imported=imported, + exported=exported, + power=power, + frequency=frequency + ) + else: + counter_state = CounterState( + powers=powers, + imported=imported, + exported=exported, + power=power + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=VictronCounterSetup) diff --git a/packages/modules/devices/victron/victron_3p75ct/device.py b/packages/modules/devices/victron/victron_3p75ct/device.py new file mode 100644 index 0000000000..d84331b343 --- /dev/null +++ b/packages/modules/devices/victron/victron_3p75ct/device.py @@ -0,0 +1,44 @@ +#!/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 ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.common.modbus import ModbusUdpClient_ +from modules.devices.victron.victron_3p75ct.config import Victron, VictronCounterSetup +from modules.devices.victron.victron_3p75ct.counter import VictronCounter + +log = logging.getLogger(__name__) + + +def create_device(device_config: Victron): + client = None + + def create_counter_component(component_config: VictronCounterSetup): + nonlocal client + return VictronCounter(component_config, device_id=device_config.id, client=client) + + + def update_components(components: Iterable[VictronCounter]): + nonlocal client + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + def initializer(): + nonlocal client + client = ModbusUdpClient_(device_config.configuration.ip_address, device_config.configuration.port, timeout=10) + + return ConfigurableDevice( + device_config=device_config, + initializer=initializer, + component_factory=ComponentFactoryByType( + counter=create_counter_component + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=Victron) From 5824de8fc5fd92b0b60dbed4f61cb0532aca898e Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 02:56:27 +0100 Subject: [PATCH 3/9] remaining line removed --- packages/modules/devices/victron/victron_3p75ct/counter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/modules/devices/victron/victron_3p75ct/counter.py b/packages/modules/devices/victron/victron_3p75ct/counter.py index d7d6af499c..2f7a9303c8 100644 --- a/packages/modules/devices/victron/victron_3p75ct/counter.py +++ b/packages/modules/devices/victron/victron_3p75ct/counter.py @@ -52,8 +52,6 @@ def update(self): powers = self.__udp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) power = sum(powers) - self.__udp_client.read_holding_registers_bulk() - imported, exported = self.sim_counter.sim_count(power) if energy_meter: From f2322e4cae9d77ec48ac9648a56342e439fa14ac Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 03:16:14 +0100 Subject: [PATCH 4/9] add real counter data --- packages/modules/devices/victron/victron_3p75ct/counter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/victron/victron_3p75ct/counter.py b/packages/modules/devices/victron/victron_3p75ct/counter.py index 2f7a9303c8..fb28ae6509 100644 --- a/packages/modules/devices/victron/victron_3p75ct/counter.py +++ b/packages/modules/devices/victron/victron_3p75ct/counter.py @@ -48,12 +48,16 @@ def update(self): for reg in [0x3047, 0x304F, 0x3057]] power = sum(powers) frequency = self.__udp_client.read_holding_registers(0x3032, ModbusDataType.UINT_16, unit=unit) / 100 + imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) / 100 + exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) / 100 + else: powers = self.__udp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) power = sum(powers) + imported, exported = self.sim_counter.sim_count(power) - imported, exported = self.sim_counter.sim_count(power) + if energy_meter: counter_state = CounterState( voltages=voltages, From 2acc47c0ddd89c939e5245b921366fc20705b8f1 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 03:22:38 +0100 Subject: [PATCH 5/9] fix counter scaler --- packages/modules/devices/victron/victron_3p75ct/counter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/victron/victron_3p75ct/counter.py b/packages/modules/devices/victron/victron_3p75ct/counter.py index fb28ae6509..a6f2a544f6 100644 --- a/packages/modules/devices/victron/victron_3p75ct/counter.py +++ b/packages/modules/devices/victron/victron_3p75ct/counter.py @@ -48,8 +48,8 @@ def update(self): for reg in [0x3047, 0x304F, 0x3057]] power = sum(powers) frequency = self.__udp_client.read_holding_registers(0x3032, ModbusDataType.UINT_16, unit=unit) / 100 - imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) / 100 - exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) / 100 + imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) *10 + exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) *10 else: powers = self.__udp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) From 39dd8b10076827d39b7a168697b48d9efd375e8f Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 03:23:02 +0100 Subject: [PATCH 6/9] increase client timeout to 30s --- packages/modules/devices/victron/victron_3p75ct/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/devices/victron/victron_3p75ct/device.py b/packages/modules/devices/victron/victron_3p75ct/device.py index d84331b343..9d4bb14739 100644 --- a/packages/modules/devices/victron/victron_3p75ct/device.py +++ b/packages/modules/devices/victron/victron_3p75ct/device.py @@ -29,7 +29,7 @@ def update_components(components: Iterable[VictronCounter]): def initializer(): nonlocal client - client = ModbusUdpClient_(device_config.configuration.ip_address, device_config.configuration.port, timeout=10) + client = ModbusUdpClient_(device_config.configuration.ip_address, device_config.configuration.port, timeout=30) return ConfigurableDevice( device_config=device_config, From 472ca48cbd3f247cb5e94a8fa6e3da9b79f434fd Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Fri, 13 Feb 2026 17:15:34 +0100 Subject: [PATCH 7/9] remove config options, since usage implies standalone mode --- .../devices/victron/victron_3p75ct/config.py | 5 +- .../devices/victron/victron_3p75ct/counter.py | 76 +++++++------------ 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/packages/modules/devices/victron/victron_3p75ct/config.py b/packages/modules/devices/victron/victron_3p75ct/config.py index 06a59db944..bd367b223f 100644 --- a/packages/modules/devices/victron/victron_3p75ct/config.py +++ b/packages/modules/devices/victron/victron_3p75ct/config.py @@ -24,9 +24,8 @@ def __init__(self, class VictronCounterConfiguration: - def __init__(self, energy_meter: bool = True, modbus_id: int = 1): - self.energy_meter = energy_meter - self.modbus_id = modbus_id + def __init__(self): + pass class VictronCounterSetup(ComponentSetup[VictronCounterConfiguration]): diff --git a/packages/modules/devices/victron/victron_3p75ct/counter.py b/packages/modules/devices/victron/victron_3p75ct/counter.py index a6f2a544f6..7d505453c8 100644 --- a/packages/modules/devices/victron/victron_3p75ct/counter.py +++ b/packages/modules/devices/victron/victron_3p75ct/counter.py @@ -7,7 +7,6 @@ from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo, FaultState from modules.common.modbus import ModbusDataType -from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.devices.victron.victron_3p75ct.config import VictronCounterSetup @@ -23,59 +22,42 @@ def __init__(self, component_config: VictronCounterSetup, **kwargs: Any) -> None self.kwargs: KwargsDict = kwargs def initialize(self) -> None: - self.__device_id: int = self.kwargs['device_id'] self.__udp_client = 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): - unit = self.component_config.configuration.modbus_id - energy_meter = self.component_config.configuration.energy_meter + unit = 1 # Modbus ID ist immer 1 da Standalone Produkt with self.__udp_client: - if energy_meter: - powers = [ - self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / 1 - for reg in [0x3082, 0x3086, 0x308A]] #4 byte abstand - currents = [ - self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 - for reg in [0x3041, 0x3049, 0x3051]] #8 byte abstand - voltages = [ - self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 - for reg in [0x3040, 0x3048, 0x3050]] #8 byte abstand - power_factors = [ - self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 1000 - for reg in [0x3047, 0x304F, 0x3057]] - power = sum(powers) - frequency = self.__udp_client.read_holding_registers(0x3032, ModbusDataType.UINT_16, unit=unit) / 100 - imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) *10 - exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) *10 - - else: - powers = self.__udp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) - power = sum(powers) - imported, exported = self.sim_counter.sim_count(power) + powers = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / 1 + for reg in [0x3082, 0x3086, 0x308A]] + currents = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + for reg in [0x3041, 0x3049, 0x3051]] + voltages = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + for reg in [0x3040, 0x3048, 0x3050]] + power_factors = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 1000 + for reg in [0x3047, 0x304F, 0x3057]] + + power = sum(powers) + frequency = self.__udp_client.read_holding_registers(0x3032, ModbusDataType.UINT_16, unit=unit) / 100 + imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) *10 + exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) *10 + + counter_state = CounterState( + voltages=voltages, + currents=currents, + powers=powers, + power_factors=power_factors, + imported=imported, + exported=exported, + power=power, + frequency=frequency + ) - - - if energy_meter: - counter_state = CounterState( - voltages=voltages, - currents=currents, - powers=powers, - power_factors=power_factors, - imported=imported, - exported=exported, - power=power, - frequency=frequency - ) - else: - counter_state = CounterState( - powers=powers, - imported=imported, - exported=exported, - power=power - ) self.store.set(counter_state) From c7e814fc42923cb865d7e8292143e3c299a8ad53 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Tue, 24 Feb 2026 17:43:21 +0100 Subject: [PATCH 8/9] flake8 formatting --- packages/modules/common/modbus.py | 2 ++ .../devices/victron/victron_3p75ct/counter.py | 12 ++++++------ .../modules/devices/victron/victron_3p75ct/device.py | 3 +-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/modules/common/modbus.py b/packages/modules/common/modbus.py index 89f555231d..9f5fc13e50 100644 --- a/packages/modules/common/modbus.py +++ b/packages/modules/common/modbus.py @@ -307,6 +307,7 @@ def __init__(self, port = parsed_url.port super().__init__(ModbusTcpClient(host, port, framer, **kwargs), address, port, sleep_after_connect) + class ModbusUdpClient_(ModbusClient): def __init__(self, address: str, @@ -319,6 +320,7 @@ def __init__(self, port = parsed_url.port super().__init__(ModbusUdpClient(host, port, **kwargs), address, port, sleep_after_connect) + class ModbusSerialClient_(ModbusClient): def __init__(self, port: int, diff --git a/packages/modules/devices/victron/victron_3p75ct/counter.py b/packages/modules/devices/victron/victron_3p75ct/counter.py index 7d505453c8..87fea94375 100644 --- a/packages/modules/devices/victron/victron_3p75ct/counter.py +++ b/packages/modules/devices/victron/victron_3p75ct/counter.py @@ -27,7 +27,7 @@ def initialize(self) -> None: self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self): - unit = 1 # Modbus ID ist immer 1 da Standalone Produkt + unit = 1 # Modbus ID ist immer 1 da Standalone Produkt with self.__udp_client: powers = [ self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / 1 @@ -37,16 +37,16 @@ def update(self): for reg in [0x3041, 0x3049, 0x3051]] voltages = [ self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 - for reg in [0x3040, 0x3048, 0x3050]] + for reg in [0x3040, 0x3048, 0x3050]] power_factors = [ self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 1000 for reg in [0x3047, 0x304F, 0x3057]] - + power = sum(powers) frequency = self.__udp_client.read_holding_registers(0x3032, ModbusDataType.UINT_16, unit=unit) / 100 - imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) *10 - exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) *10 - + imported = self.__udp_client.read_holding_registers(0x3034, ModbusDataType.UINT_32, unit=unit) * 10 + exported = self.__udp_client.read_holding_registers(0x3036, ModbusDataType.UINT_32, unit=unit) * 10 + counter_state = CounterState( voltages=voltages, currents=currents, diff --git a/packages/modules/devices/victron/victron_3p75ct/device.py b/packages/modules/devices/victron/victron_3p75ct/device.py index 9d4bb14739..ac98f9624a 100644 --- a/packages/modules/devices/victron/victron_3p75ct/device.py +++ b/packages/modules/devices/victron/victron_3p75ct/device.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Iterable, Union +from typing import Iterable from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext @@ -19,7 +19,6 @@ def create_counter_component(component_config: VictronCounterSetup): nonlocal client return VictronCounter(component_config, device_id=device_config.id, client=client) - def update_components(components: Iterable[VictronCounter]): nonlocal client with client: From 306c1c3773ea827936a9cb75b1a56faffa0fee30 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Tue, 24 Feb 2026 17:45:12 +0100 Subject: [PATCH 9/9] added modbus udp to pytests --- packages/modules/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/modules/conftest.py b/packages/modules/conftest.py index c5e6f1f617..9733696c81 100644 --- a/packages/modules/conftest.py +++ b/packages/modules/conftest.py @@ -23,6 +23,7 @@ module = type(sys)('pymodbus.client.sync') module.ModbusSerialClient = Mock() module.ModbusTcpClient = Mock() +module.ModbusUdpClient = Mock() sys.modules['pymodbus.client.sync'] = module module = type(sys)('pymodbus.constants')