Skip to content

Getting started

Ralf Steinert edited this page Nov 1, 2026 · 1 revision

Install

modbus-sim needs Python 3.10 or newer; its only dependency is PyYAML.

pip install modbus-sim
pip install 'modbus-sim[serial]'     # also for real serial ports (pyserial)
modbus-sim --version                 # modbus-sim 0.2.0

One device

modbus-sim profiles                  # what is bundled
modbus-sim map eastron-sdm630        # its register map
modbus-sim serve eastron-sdm630      # unit 1, Modbus TCP on 127.0.0.1:5020

In a second terminal, read phase 1 voltage — register 30001 in Eastron's manual, input register address 0 on the wire:

$ modbus-sim read input 0 -t float32
input 0: 230.091  [4366 1741]

The hex in brackets is what the device sent: 4366 1741 is 230.09 as an IEEE 754 float, most significant word first.

modbus-sim serve binds to 127.0.0.1 by default. Use --host 0.0.0.0 to reach it from other machines (a PLC, a gateway, a VM), and --port for another port. Port 502 needs root on Linux, so the default is 5020.

Several devices: a bench

A bench file puts several devices behind one server, each with its own unit ID, like a Modbus TCP gateway:

devices:
  - {profile: eastron-sdm630, unit: 1, name: meter}
  - {profile: omron-e5cc, unit: 2, name: controller,
     model_params: {time_scale: 10}}          # heats ten times faster than real time
  - {profile: sunspec-inverter, unit: 3, name: inverter,
     model_params: {time_scale: 120}}         # a day in 12 minutes
modbus-sim serve --bench examples/real-devices.yaml
modbus-sim read holding 40000 -c 2 -t string -u 3      # 'SunS'
modbus-sim list                                        # devices, faults, request counts

This is the bench of the Browser demo.

From Python

Any Modbus master works. With pymodbus (3.10 or newer):

from pymodbus.client import ModbusTcpClient

c = ModbusTcpClient("127.0.0.1", port=5020)
c.connect()
r = c.read_input_registers(0, count=2, device_id=1)
print(c.convert_from_registers(r.registers, c.DATATYPE.FLOAT32))   # 230.1
c.close()

modbus-sim also has a minimal client of its own, handy in tests:

from modbus_sim.client import ModbusClient

with ModbusClient("127.0.0.1", 5020) as c:
    print(c.read_holding_registers(40002, 2, unit=3))   # [1, 66]: SunSpec model 1

RTU

modbus-sim serve --bench examples/real-devices.yaml --rtu-over-tcp   # RTU frames over TCP
modbus-sim serve --bench examples/real-devices.yaml --pty            # prints e.g. /dev/pts/7
modbus-sim serve eastron-sdm630 --serial /dev/ttyUSB0 --baud 9600 --parity N

With --pty (Linux/macOS), point your RTU master at the printed device. On Windows, use --serial COM5 with a virtual null-modem pair such as com0com.

Change something while it runs

modbus-sim inject meter phase_loss phase=2     # device fault
modbus-sim inject controller no_response       # the next request gets no answer
modbus-sim get inverter ac_power operating_state
modbus-sim clear meter                         # all faults of the meter

These go to the control port (5021, loopback only). See Fault injection recipes for using them from tests.

Your own device

A profile is a YAML file with points; modbus-sim map checks it and prints the result. The format is in the README:

name: tank
word_order: CDAB
points:
  - {name: level, table: input, address: 0, type: float32, unit: m,
     source: [{type: sine, offset: 2.0, amplitude: 0.5, period: 120}]}
  - {name: pump, table: coil, address: 0}
modbus-sim map tank.yaml
modbus-sim serve tank.yaml

Clone this wiki locally