# 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 of `SimDevice` 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

  block_shared: list[SimData] | None = None

    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.