Simulator
WORK IN PROGRESS, do NOT use
The simulator is a full fledged modbus server/simulator.
The purpose of the simulator is to provide support for client application test harnesses with end-to-end testing simulating real life modbus devices.
The simulator allows the user to (all automated):
simulate a modbus device by adding a simple configuration,
simulate a multipoint line, but adding multiple device configurations,
simulate devices that are not conforming to the protocol,
simulate communication problems (data loss etc),
test how a client handles modbus response and exceptions,
test a client apps correct use of the simulated device.
The web interface (activated optionally) allows the user to:
introduce modbus errors (like e.g. wrong length),
introduce communication errors (like splitting a message),
monitor requests/responses,
see/Change values online.
inject modbus errors like malicious a response,
run your test server in the cloud,
The REST API allow the test process to be automated
spin up a test server in your test harness,
set expected responses with a simple REST API command,
check the result with a simple REST API command,
test your client app in a true end-to-end fashion.
The web server uses the REST API internally, which helps to ensure that it actually works.
Data model configuration
Warning
from v3.9.0 this is available as a “normal” datastore model.
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
should be used to mirror the physical device.
SimDevice
defines device parameters and a list of SimData
.
The list of SimData
can added as shared registers or as the 4 blocks, defined in modbus.
SimDevice
can be used to simulate a single device, while a list of
SimDevice
simulates a multipoint line (simulating a rs485 line or a tcp based serial forwarder).
A server consist of communication parameters and a device or a list of devices
SimDataType
is a helper class that defines legal datatypes.
SimActions
is a helper class that defines built in actions.
examples/simulator_datamodel.py contains usage examples.
SimData
- class pymodbus.simulator.SimData(start_register: int, value: int | float | str | bool | bytes = 0, count: int = 1, datatype: SimDataType = SimDataType.REGISTERS, action: Callable[[int, int | float | str | bool | bytes], int | float | str | bool | bytes] | None = None)
Bases:
object
Configure a group of continuous identical registers.
Example:
SimData( start_register=100, count=5, value=-123456 datatype=SimDataType.INT32 )
The above code defines 5 INT32, each with the value -123456, in total 20 registers.
Tip
use SimDatatype.DEFAULT to define register limits:
SimData( start_register=0, # First legal registers count=1000, # last legal register is start_register+count-1 value=0x1234 # Default register value datatype=SimDataType.DEFAULT )
The above code sets the range of legal registers to 0..9999 all with the value 0x1234. Accessing non-defined registers will cause an exception response.
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.
- action: Callable[[int, int | float | str | bool | bytes], 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: int | float | str | bool | bytes ) -> int | float | str | bool | bytes: return value + 1
Tip
use functools.partial to add extra parameters if needed.
- count: int = 1
Count of datatype e.g. count=3 datatype=SimdataType.INT32 is 6 registers.
SimdataType.STR is special:
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
- start_register: int
Address of first register, starting with 0.
Caution
No default, must be defined.
- 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.
SimDevice
- 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:
object
Configure 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. coils to be read as a holding register (except if type_checking is True).
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.
Device with shared registers:
SimDevice( id=0, block_shared=[SimData(...)] )
Device with non-shared registers:
SimDevice( id=0, block_coil=[SimData(...)], block_direct=[SimData(...)], block_holding=[SimData(...)], block_input=[SimData(...)], )
A server can contain either a single
SimDevice
or list ofSimDevice
to simulate a multipoint line.- 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 direct 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
- block_holding: list[SimData] | None = None
Use this block for non-shared registers (very old devices).
In this block an address is a register.
Request of type read/write_holding accesses this block.
Tip
block_coil/direct/holding/input must all be defined
- block_input: list[SimData] | None = None
Use this block for non-shared registers (very old devices).
In this block an address is a register.
Request of type read/write_input accesses this block.
Tip
block_coil/direct/holding/input must all be defined
Use this block for shared registers (Modern devices).
Requests accesses all registers in this block.
Warning
cannot be used together with other block_* parameters!
- id: int = 0
Address of device
Default 0 means accept all devices, except those defined in the same server.
Warning
A server with a single device id=0 accept all requests.
- type_check: bool = False
Enforce type checking, if True access are controlled to be conform with datatypes.
Used to control that read_coils do not access a register defined as holding and visaversa
SimDataType
- class pymodbus.simulator.SimDataType(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)
Bases:
Enum
Register types, used to define group of registers.
This is the types pymodbus recognizes, actually the modbus standard do NOT define e.g. INT32, but since nearly every device have e.g. INT32 as part of its register map, it was decided to include it in pymodbus, with automatic conversions to/from registers.
- BITS = 10
Shared mode: 16 bits == 1 register else 1 bit == 1 “register” (address)
- DEFAULT = 12
Raw registers, but also sets register address limits.
Tip
It a single but special register, and therefore improves speed and memory usage compared to REGISTERS.
- FLOAT32 = 7
1 float == 2 registers
- FLOAT64 = 8
1 float == 4 registers
- INT16 = 1
1 integer == 1 register
- INT32 = 3
1 integer == 2 registers
- INT64 = 5
1 integer == 4 registers
- REGISTERS = 11
Raw registers
Warning
Do not use as default, since it fills the memory and block other registrations.
- STRING = 9
1 string == len(string) / 2 registers
Tip
String length must be a multiple of 2 (corresponding to registers).
- UINT16 = 2
1 positive integer == 1 register
- UINT32 = 4
1 positive integer == 2 register2
- UINT64 = 6
1 positive integer == 4 register
Simulator server
Note
This is a v4.0.0 functionality currently not available, please see the 3x simulator server.
Web frontend
Note
This is a v4.0.0 functionality currently not available, please see the 3x simulator server.
REST API
Note
This is a v4.0.0 functionality currently not available, please see the 3x simulator server.