# 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.

    **REMARK** This code is experimental and not integrated into production.

    """

    from pymodbus.simulator import DataType, SimData, SimDevice

    def define_datamodel():
        """Define register groups.

        Coils and discrete inputs are modeled as bits representing a relay in the device.
        There are no real difference between coils and discrete inputs, but historically
        they have been divided. Please be aware the coils and discrete 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, 10, 17, DataType.REGISTERS) == SimData(
            address=5, values=17, count=10, datatype=DataType.REGISTERS
        )

        # Define a group of coils/discrete inputs non-shared (address=15..31 each 1 bit)
        # block1 = SimData(address=15, count=16, values=True, datatype=DataType.BITS)
        # Define a group of coils/discrete inputs shared (address=15..31 each 16 bit)
        # block2 = SimData(address=15, count=16, values=0xFFFF, datatype=DataType.BITS)

        # Define a group of holding/input registers (remark NO difference between shared and non-shared)
        # block3 = SimData(10, 1, 123.4, datatype=DataType.FLOAT32)
        # block4 = SimData(17, count=5, values=123, datatype=DataType.INT64)
        block5 = SimData(1027, 1, "Hello ", datatype=DataType.STRING)

        block_def = SimData(0, count=1000, datatype=DataType.REGISTERS)

        # SimDevice can be instantiated with positional or optional parameters:
        assert SimDevice(
            5,
            [block_def, block5],
        ) == SimDevice(id=5, simdata=[block_def, block5])

        # SimDevice can define either a shared or a non-shared register model
        SimDevice(id=1, simdata=[block_def, block5])
        # SimDevice(2, False,
        #          block_coil=[block1],
        #          block_discrete=[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()

## Datastore definitions

class pymodbus.simulator.DataType(*values)

  Register types, used to define 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.

  INVALID = 1

    1 register

  INT16 = 2

    1 integer == 1 register

  UINT16 = 3

    1 positive integer == 1 register

  INT32 = 4

    1 integer == 2 registers

  UINT32 = 5

    1 positive integer == 2 registers

  INT64 = 6

    1 integer == 4 registers

  UINT64 = 7

    1 positive integer == 4 register

  FLOAT32 = 8

    1 float == 2 registers

  FLOAT64 = 9

    1 float == 4 registers

  STRING = 10

    1 string == (len(string) / 2) registers

  BITS = 11

    16 bits == 1 register

  REGISTERS = 12

    Registers == 2 bytes (identical to UINT16)

class pymodbus.simulator.SimData(address: int, count: int = 1, values: int | float | str | bytes | list[int] | list[float] | list[str] | list[bytes] | list[bool] = 0, datatype: DataType = DataType.INVALID, string_encoding: str = 'utf-8', readonly: bool = False)

  Bases: `object`

  Configure a group of continuous identical values/registers.

  Examples:

      SimData(
          address=100,
          count=5,
          values=12345678
          datatype=DataType.INT32
      )
      SimData(
          address=100,
          values=[1, 2, 3, 4, 5]
          datatype=DataType.INT32
      )

  Each SimData defines 5 INT32 in total 10 registers (address 100-109)

      SimData(
          address=0,
          count=1000,
          values=0x1234
          datatype=DataType.REGISTERS
      )

  Defines a range of registers (addresses) 0..999 each with the value 0x1234.

      SimData(
          address=0,
          count=1000,
          datatype=DataType.INVALID
      )

  Defines a range of registers (addresses) 0..999 each marked as invalid.

      SimData(
          address=100,
          count=16,
          values=True
          datatype=DataType.BITS
      )
      SimData(
          address=100,
          values=[True] * 16
          datatype=DataType.BITS
      )
      SimData(
          address=100,
          values=0xffff,
          datatype=DataType.REGISTERS
      )
      SimData(
          address=100,
          values=[0xffff],
          datatype=DataType.REGISTERS
      )

  Each SimData defines 16 BITS (coils), with value True.

  Value are stored in registers (16bit is 1 register).

  In shared mode (coil and discrete inputs requests):

    - address refers to the register, containing individual bits, Individual bits within the register cannot be addressed, unless “use_bit_as_address” is set on the device.

  In non-shared mode (coil and discrete inputs requests)

    - address refers to the bit.

  address: int

    Address of first register, starting with 0 (identical to the requests)

  count: int = 1

    Count of datatype e.g.

    > - count=3 datatype=DataType.REGISTERS is 3 registers.

    > - count=3 datatype=DataType.INT32 is 6 registers.

    > - count=1 datatype=DataType.STRING, values=”ABCD” is 2 registers

    > - count=2 datatype=DataType.STRING, values=”ABCD” is 4 registers

    if values= is a list, count will be applied to the whole list, e.g.

    > - count=3 datatype=DataType.REGISTERS values=[3,2] is 6 registers.

    > - count=3 datatype=DataType.INT32 values=[3,2] is 12 registers.

    > - count=2 datatype=DataType.STRING, values=[“ABCD”, ‘EFGH’] is 8 registers

  values: int | float | str | bytes | list[int] | list[float] | list[str] | list[bytes] | list[bool] = 0

    Value/Values of datatype, will automatically be converted to registers, according to datatype.

  datatype: DataType = 1

    Used to check access and convert value to/from registers or mark as invalid.

  string_encoding: str = 'utf-8'

    String encoding

    Used to convert a SimData(DataType.STRING) to registers.

  readonly: bool = False

    Mark register(s) as readonly.

  build_registers_bits_block() → list[bool]

    Convert values= to registers from bits (1 bit in each register).

  build_registers_bits_shared() → list[int]

    Convert values= to registers from bits (16 bits in each register).

  build_registers_string() → list[int]

    Convert values= to registers from string(s).

  build_registers(block_bits: bool) → list[int] | list[bool]

    Convert values= to registers.

class pymodbus.simulator.SimDevice(id: int, simdata: SimData | list[SimData] | tuple[list[SimData], list[SimData], list[SimData], list[SimData]], use_bit_addressing: bool | None = None, identity: ModbusDeviceIdentification | None = None, action: Callable[[int, int, int, int, list[int], list[int] | list[bool] | None], Awaitable[None | ExcCodes]] | None = None)

  Bases: `object`

  Configure a device with parameters and registers.

  Registers are defined as a list of SimData objects (block).

  Some old devices uses 4 distinct blocks instead of a shared block, to support these devices, define the 4 blocks and add them as a set.

  When using distinct blocks, coils and discrete inputs are addressed differently, each register represent 1 coil/relay

  Device with shared registers:

      SimDevice(
          id=1,
          simdata=[SimData(...)]
      )

  Device with non-shared registers:

      SimDevice(
          id=1,
          simdata=([SimData(...)], [SimData(...)], [SimData(...)], [SimData(...)]),
      )

  A server can be configured with either a single `SimDevice` or a list of `SimDevice` to simulate a multipoint line.

  id: int

    Address/id of device

    id=0 means all devices, except those specifically defined.

  simdata: SimData | list[SimData] | tuple[list[SimData], list[SimData], list[SimData], list[SimData]]

    List of register blocks (shared registers) or a tuple with 4 lists of register blocks (non-shared registers)

    The tuple is defined as:

      (<coils>, <discrete inputs>, <holding registers>, <input registers>)

    ..tip:: addresses not defined are invalid and will produce an ExceptionResponse

  use_bit_addressing: bool | None = None

    Define coil/discrete input addressing in shared mode.

    False, means the register is addressed, and single bits cannot be addressed. True, means single bit is being addressed. effictive address is register_address * 16 + bit_offset.

    Example:

      SimData(200, value=True, datatype=DataType.BITS)

    with use_bit_addressing=False:

      read_coils(200) returns [True] + [False] * 7 read_coils(200, count=16) returns [True] + [False] * 15

    with use_bit_addressing=True:

      read_coils(200*16+15) returns [True] + [False] * 7 read_coils(200*16, count=16) returns [False] * 15 + [True]

  identity: ModbusDeviceIdentification | None = None

    Set device identity

  action: Callable[[int, int, int, int, list[int], list[int] | list[bool] | None], Awaitable[None | ExcCodes]] | None = None

    action can: - update registers (affect the current and future responses) - update set_values (affect the register update) - return an ExceptionResponse.

    Tip

    use functools.partial to add extra parameters if needed.

  build_device() → tuple[int, list[int], list[int]] | dict[str, tuple[int, list[int], list[int]]]

    Check simdata and built runtime structure.