Skip to content
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
31 changes: 31 additions & 0 deletions docs/samples/sample_modbus/sample_modbus/bat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
from enum import IntEnum
from typing import Optional, TypedDict, Any
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
Expand All @@ -15,7 +16,23 @@ class KwargsDict(TypedDict):
client: ModbusTcpClient_


class Register(IntEnum):
CURRENT_L1 = 0x06
POWER = 0x0C
SOC = 0x46
IMPORTED = 0x48
EXPORTED = 0x4A


class SampleBat(AbstractBat):
REG_MAPPING = (
(Register.CURRENT_L1, [ModbusDataType.FLOAT_32]*3),
(Register.POWER, [ModbusDataType.FLOAT_32]*3),
(Register.SOC, ModbusDataType.FLOAT_32),
(Register.IMPORTED, ModbusDataType.FLOAT_32),
(Register.EXPORTED, ModbusDataType.FLOAT_32),
)

def __init__(self, component_config: SampleBatSetup, **kwargs: Any) -> None:
self.component_config = component_config
self.kwargs: KwargsDict = kwargs
Expand All @@ -29,6 +46,20 @@ def initialize(self) -> None:

def update(self) -> None:
unit = self.component_config.configuration.modbus_id
# Modbus-Bulk reader, liest einen Block von Registern und gibt ein Dictionary mit den Werten zurück
# read_input_registers_bulk benötigit als Parameter das Startregister, die Anzahl der Register,
# Register-Mapping und die Modbus-ID
resp = self.client.read_input_registers_bulk(
Register.CURRENT_L1, 70, mapping=self.REG_MAPPING, unit=self.id)
bat_state = BatState(
power=resp[Register.POWER],
soc=resp[Register.SOC],
imported=resp[Register.IMPORTED],
exported=resp[Register.EXPORTED],
)
self.store.set(bat_state)

# Einzelregister lesen (dauert länger, bei sehr weit >100 auseinanderliegenden Registern sinnvoll)
power = self.client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit)
soc = self.client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit)
imported, exported = self.sim_counter.sim_count(power)
Expand Down
39 changes: 39 additions & 0 deletions docs/samples/sample_modbus/sample_modbus/counter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
from enum import IntEnum
from typing import TypedDict, Any
from modules.common.abstract_device import AbstractCounter
from modules.common.component_state import CounterState
Expand All @@ -15,7 +16,27 @@ class KwargsDict(TypedDict):
client: ModbusTcpClient_


class Register(IntEnum):
VOLTAGE_L1 = 0x00
CURRENT_L1 = 0x06
POWER_L1 = 0x0C
POWER_FACTOR_L1 = 0x1E
FREQUENCY = 0x46
IMPORTED = 0x48
EXPORTED = 0x4A


class SampleCounter(AbstractCounter):
REG_MAPPING = (
(Register.VOLTAGE_L1, [ModbusDataType.FLOAT_32]*3),
(Register.CURRENT_L1, [ModbusDataType.FLOAT_32]*3),
(Register.POWER_L1, [ModbusDataType.FLOAT_32]*3),
(Register.POWER_FACTOR_L1, [ModbusDataType.FLOAT_32]*3),
(Register.FREQUENCY, ModbusDataType.FLOAT_32),
(Register.IMPORTED, ModbusDataType.FLOAT_32),
(Register.EXPORTED, ModbusDataType.FLOAT_32),
)

def __init__(self, component_config: SampleCounterSetup, **kwargs: Any) -> None:
self.component_config = component_config
self.kwargs: KwargsDict = kwargs
Expand All @@ -29,6 +50,24 @@ def initialize(self) -> None:

def update(self):
unit = self.component_config.configuration.modbus_id
# Modbus-Bulk reader, liest einen Block von Registern und gibt ein Dictionary mit den Werten zurück
# read_input_registers_bulk benötigit als Parameter das Startregister, die Anzahl der Register,
# Register-Mapping und die Modbus-ID
resp = self.client.read_input_registers_bulk(
Register.VOLTAGE_L1, 76, mapping=self.REG_MAPPING, unit=self.id)
counter_state = CounterState(
imported=resp[Register.IMPORTED],
exported=resp[Register.EXPORTED],
power=sum(resp[Register.POWER_L1]),
voltages=resp[Register.VOLTAGE_L1],
currents=resp[Register.CURRENT_L1],
powers=resp[Register.POWER_L1],
power_factors=resp[Register.POWER_FACTOR_L1],
frequency=resp[Register.FREQUENCY],
)
self.store.set(counter_state)

# Einzelregister lesen (dauert länger, bei sehr weit >100 auseinanderliegenden Registern sinnvoll)
power = self.client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit)
imported, exported = self.sim_counter.sim_count(power)

Expand Down
29 changes: 29 additions & 0 deletions docs/samples/sample_modbus/sample_modbus/inverter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
from enum import IntEnum
from typing import TypedDict, Any
from modules.common.abstract_device import AbstractInverter
from modules.common.component_state import InverterState
Expand All @@ -15,7 +16,21 @@ class KwargsDict(TypedDict):
client: ModbusTcpClient_


class Register(IntEnum):
CURRENT_L1 = 0x06
POWER = 0x0C
DC_POWER = 0x48
EXPORTED = 0x4A


class SampleInverter(AbstractInverter):
REG_MAPPING = (
(Register.CURRENT_L1, [ModbusDataType.FLOAT_32]*3),
(Register.POWER, [ModbusDataType.FLOAT_32]*3),
(Register.DC_POWER, ModbusDataType.FLOAT_32),
(Register.EXPORTED, ModbusDataType.FLOAT_32),
)

def __init__(self, component_config: SampleInverterSetup, **kwargs: Any) -> None:
self.component_config = component_config
self.kwargs: KwargsDict = kwargs
Expand All @@ -29,6 +44,20 @@ def initialize(self) -> None:

def update(self) -> None:
unit = self.component_config.configuration.modbus_id
# Modbus-Bulk reader, liest einen Block von Registern und gibt ein Dictionary mit den Werten zurück
# read_input_registers_bulk benötigit als Parameter das Startregister, die Anzahl der Register,
# Register-Mapping und die Modbus-ID
resp = self.client.read_input_registers_bulk(
Register.CURRENT_L1, 70, mapping=self.REG_MAPPING, unit=self.id)
inverter_state = InverterState(
power=resp[Register.POWER],
currents=resp[Register.CURRENT_L1],
dc_power=resp[Register.DC_POWER],
exported=resp[Register.EXPORTED],
)
self.store.set(inverter_state)

# Einzelregister lesen (dauert länger, bei sehr weit >100 auseinanderliegenden Registern sinnvoll)
power = self.client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit)
exported = self.sim_counter.sim_count(power)[1]

Expand Down
71 changes: 71 additions & 0 deletions packages/modules/common/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,77 @@ def write_registers(self, address: int, value: Any, **kwargs):
def write_single_coil(self, address: int, value: Any, **kwargs):
self._delegate.write_coil(address, value, **kwargs)

def __read_bulk(self,
read_register_method: Callable,
start_address: int,
count: int,
mapping: list[tuple[int, Union[ModbusDataType, Iterable[ModbusDataType]]]],
byteorder: Endian = Endian.Big, wordorder: Endian = Endian.Big, **kwargs):
"""
Liest einen Registerbereich und gibt ein dict mit reg als Key und dekodiertem Wert als Value zurück.
mapping: Liste von Tupeln (reg, ModbusDataType)
"""
if self.is_socket_open() is False:
self.connect()
try:
response = read_register_method(start_address, count, **kwargs)
if response.isError():
raise Exception(__name__+" "+str(response))
decoder = BinaryPayloadDecoder.fromRegisters(response.registers, byteorder, wordorder)
results = {}
for register_address, data_type in mapping:
multiple_register_requested = isinstance(data_type, Iterable)
if not multiple_register_requested:
data_type = [data_type]
offset = register_address - start_address
decoder.reset()
decoder.skip_bytes(offset * 2)
val = [struct.unpack(">e", struct.pack(">H", decoder.decode_16bit_uint())) if t ==
ModbusDataType.FLOAT_16 else getattr(decoder, t.decoding_method)() for t in data_type]
results[register_address] = val if multiple_register_requested else val[0]
return results
except pymodbus.exceptions.ConnectionException as e:
self.close()
e.args += (NO_CONNECTION.format(self.address, self.port),)
raise e
except pymodbus.exceptions.ModbusIOException as e:
self.close()
e.args += (NO_VALUES.format(self.address, self.port),)
raise e
except Exception as e:
self.close()
raise Exception(__name__+" "+str(type(e))+" " + str(e)) from e

def read_input_registers_bulk(self,
start_address: int,
count: int,
mapping: list[tuple[int, Union[ModbusDataType, Iterable[ModbusDataType]]]],
byteorder: Endian = Endian.Big,
wordorder: Endian = Endian.Big,
**kwargs):
return self.__read_bulk(self._delegate.read_input_registers,
start_address,
count,
mapping,
byteorder,
wordorder,
**kwargs)

def read_holding_registers_bulk(self,
start_address: int,
count: int,
mapping: list[tuple[int, Union[ModbusDataType, Iterable[ModbusDataType]]]],
byteorder: Endian = Endian.Big,
wordorder: Endian = Endian.Big,
**kwargs):
return self.__read_bulk(self._delegate.read_holding_registers,
start_address,
count,
mapping,
byteorder,
wordorder,
**kwargs)


class ModbusTcpClient_(ModbusClient):
def __init__(self,
Expand Down
Loading