Data model configuration
The simulator data model represent the registers and parameters of the simulated devices.
The data model is defined using SimData and SimDevice before starting the
server and cannot be changed without restarting the server.
SimData defines a group of continuous identical registers. This is the basis of the model,
multiple SimData are used to mirror the physical device.
SimDevice defines device parameters and a list of SimData. The
list of SimData can be added as shared registers or as 4 separate blocks as defined in modbus.
SimDevice are used to simulate a single device, while a list of
SimDevice simulates a multipoint line (rs485 line) or a serial forwarder.
A server consist of communication parameters and a list of SimDevice
Usage examples
#!/usr/bin/env python3
"""Pymodbus server datamodel examples.
This file shows examples of how to configure the datamodel for the server/simulator.
There are different examples showing the flexibility of the datamodel.
"""
from pymodbus.simulator import SimData, SimDataType, SimDevice
def define_datamodel():
"""Define register groups.
Coils and direct inputs are modeled as bits representing a relay in the device.
There are no real difference between coils and direct inputs, but historically
they have been divided. Please be aware the coils and direct inputs are addressed differently
in shared vs non-shared models.
- In a non-shared model the address is the bit directly.
It can be thought of as if 1 register == 1 bit.
- In a shared model the address is the register containing the bits.
1 register == 16bit, so a single bit CANNOT be addressed directly.
Holding registers and input registers are modeled as int/float/string representing a sensor in the device.
There are no real difference between holding registers and input registers, but historically they have
been divided.
Please be aware that 1 sensor might be modeled as several register because it needs more than
16 bit for accuracy (e.g. a INT32).
"""
# SimData can be instantiated with positional or optional parameters:
assert SimData(
5, 17, 10, SimDataType.REGISTERS
) == SimData(
address=5, value=17, count=10, datatype=SimDataType.REGISTERS
)
# Define a group of coils/direct inputs non-shared (address=15..31 each 1 bit)
block1 = SimData(address=15, value=True, count=16, datatype=SimDataType.BITS)
# Define a group of coils/direct inputs shared (address=15..31 each 16 bit)
block2 = SimData(address=15, value=0xFFFF, count=16, datatype=SimDataType.BITS)
# Define a group of holding/input registers (remark NO difference between shared and non-shared)
block3 = SimData(10, 123.4, datatype=SimDataType.FLOAT32)
block4 = SimData(17, value=123, count=5, datatype=SimDataType.INT64)
block5 = SimData(27, "Hello ", datatype=SimDataType.STRING)
# Please use SimDataType.DEFAULT to define register limits.
# this datatype only uses 1 object, whereas SimDataType.REGISTERS uses <count> objects,
# mean SimDataType.DEFAULT is factors more efficient and much less memory consuming
block_def = SimData(0, count=1000, datatype=SimDataType.DEFAULT)
# SimDevice can be instantiated with positional or optional parameters:
assert SimDevice(
5,False, [block_def, block5]
) == SimDevice(
id=5, type_check=False, block_shared=[block_def, block5]
)
# SimDevice can define either a shared or a non-shared register model
SimDevice(1, False, block_shared=[block_def, block5])
SimDevice(2, False,
block_coil=[block1],
block_direct=[block1],
block_holding=[block2],
block_input=[block3, block4])
# Remark: it is legal to reuse SimData, the object is only used for configuration,
# not for runtime.
# id=0 in a SimDevice act as a "catch all". Requests to an unknown id is executed in this SimDevice.
SimDevice(0, block_shared=[block2])
def main():
"""Combine setup and run."""
define_datamodel()
if __name__ == "__main__":
main()
Class definitions
- class pymodbus.simulator.SimData(address: int, value: int | float | str | bool | bytes = 0, count: int = 1, datatype: SimDataType = SimDataType.REGISTERS, action: Callable[[int | float | str | bool | bytes], int | float | str | bool | bytes] | Awaitable[int | float | str | bool | bytes] | None = None)
Bases:
objectConfigure a group of continuous identical registers.
Examples:
SimData( address=100, count=5, value=-123456 datatype=SimDataType.INT32 )
The above code defines 5 INT32, each with the value -123456, in total 10 registers (address 100-109)
SimData( address=100, count=17, value=-True datatype=SimDataType.BITS )
The above code defines 17 BITS (coils), each with the value True. In non-shared mode addresses are 100-115.
in shared mode BITS are stored in registers (16bit is one register), the address refer to the register, addresses are 100-101 (with register 101 being padded with 15 bits)
Tip
use SimDatatype.DEFAULT to define register limits:
SimData( address=0, # First legal registers count=1000, # last legal register is r+count-1 value=0x1234 # Default register value datatype=SimDataType.DEFAULT )
The above code sets the range of legal registers to 0..999 all with the value 0x1234. Accessing non-defined registers will cause an exception response.
Remark that DEFAULT can be overwritten with other definitions:
SimData( address=0, # First legal registers count=1000, # last legal register is r+count-1 value=0x1234 # Default register value datatype=SimDataType.DEFAULT ) SimData( address=6, count=1, value=117 datatype=SimDataType.INT32 )
Is a legal and normal combination.
Attention
Using SimDataType.DEFAULT is a LOT more efficient to define all registers, than the other datatypes. This is because default registers are not created unless written to, whereas the registers of other datatypes are each created as objects.
- address: int
Address of first register, starting with 0.
- value: int | float | str | bool | bytes = 0
Value of datatype, to initialize the registers (repeated with count, apart from string).
Depending on in which block the object is used some value types are not legal e.g. float cannot be used to define coils.
- count: int = 1
Count of datatype e.g. count=3 datatype=SimdataType.INT32 is 6 registers.
SimdataType.STR is special, the value string is copied “count” times.
count=1, value=”ABCD” is 2 registers
count=3, value=”ABCD” is 6 registers, with “ABCD” repeated 3 times.
- datatype: SimDataType = 11
Datatype, used to check access and calculate register count.
Note
Default is SimDataType.REGISTERS
- action: Callable[[int | float | str | bool | bytes], int | float | str | bool | bytes] | Awaitable[int | float | str | bool | bytes] | None = None
Optional function to call when registers are being read/written.
Example function:
def my_action( addr: int, value: SimValueType) -> SimValueType: return value + 1 async def my_action( addr: int, value: SimValueType) -> SimValueType: return value + 1
Tip
use functools.partial to add extra parameters if needed.
- class pymodbus.simulator.SimDevice(id: int = 0, type_check: bool = False, block_shared: list[SimData] | None = None, block_coil: list[SimData] | None = None, block_direct: list[SimData] | None = None, block_holding: list[SimData] | None = None, block_input: list[SimData] | None = None)
Bases:
objectConfigure a device with parameters and registers.
Registers can be defined as shared or as 4 separate blocks.
shared_block means all requests access the same registers, allowing e.g. input registers to be read with read_holding_register.
Warning
Shared mode cannot be mixed with non-shared mode !
In shared mode, individual coils/direct input cannot be addressed directly ! Instead the register address is used with count. In non-shared mode coils/direct input can be addressed directly individually.
Device with shared registers:
SimDevice( id=1, block_shared=[SimData(...)] )
Device with non-shared registers:
SimDevice( id=1, block_coil=[SimData(...)], block_direct=[SimData(...)], block_holding=[SimData(...)], block_input=[SimData(...)], )
A server can contain either a single
SimDeviceor list ofSimDeviceto simulate a multipoint line.- id: int = 0
Address of device
Default 0 means accept all devices, except those specifically defined.
- type_check: bool = False
Enforce type checking, if True access are controlled to be conform with datatypes.
Used to control that e.g. INT32 are not read as INT16.
Use this block for shared registers (Modern devices).
Requests accesses all registers in this block.
Warning
cannot be used together with other block_* parameters!
- block_coil: list[SimData] | None = None
Use this block for non-shared registers (very old devices).
In this block an address is a single coil, there are no registers.
Request of type read/write_coil accesses this block.
Tip
block_coil/direct/holding/input must all be defined
- block_direct: list[SimData] | None = None
Use this block for non-shared registers (very old devices).
In this block an address is a single relay, there are no registers.
Request of type read/write_direct_input accesses this block.
Tip
block_coil/direct/holding/input must all be defined
- class pymodbus.simulator.SimDataType(value)
Bases:
EnumRegister types, used to type of a group of registers.
This is the types pymodbus recognizes, actually the modbus standard do NOT define e.g. INT32, but since nearly every device contain e.g. values of type INT32, it is available in pymodbus, with automatic conversions to/from registers.
- INT16 = 1
1 integer == 1 register
- UINT16 = 2
1 positive integer == 1 register
- INT32 = 3
1 integer == 2 registers
- UINT32 = 4
1 positive integer == 2 registers
- INT64 = 5
1 integer == 4 registers
- UINT64 = 6
1 positive integer == 4 register
- FLOAT32 = 7
1 float == 2 registers
- FLOAT64 = 8
1 float == 4 registers
- STRING = 9
1 string == len(string) / 2 registers
Tip
String length must be a multiple of 2 (corresponding to registers).
- BITS = 10
Shared mode: 16 bits == 1 register else 1 bit == 1 “register” (address)
- REGISTERS = 11
Raw registers
Warning
Do not use as default because it fills the memory and block other registrations.
- DEFAULT = 12
Define register address limits and default values
Tip
Implemented a single but special register, and therefore improves speed and memory usage compared to REGISTERS.
- pymodbus.simulator.SimValueType
alias of
int|float|str|bool|bytes
- pymodbus.simulator.SimAction
alias of
Callable[[int|float|str|bool|bytes],int|float|str|bool|bytes] |Awaitable[int|float|str|bool|bytes]