modbus-sim · open source

Test your Modbus integration without the devices.

modbus-sim simulates Modbus devices that behave. Below are three of them, built from the published register maps of real products: an energy meter, a temperature controller and a PV inverter. Read them, write to them, break them. The real simulator runs right here in your browser.

SDM630 (simulated) energy meter · unit 1 · float32
L1– V– A– W
L2– V– A– W
L3– V– A– W
total– import– frequency– power factor–

powering up…

E5CC (simulated) temperature controller · unit 2 · int16 × 0.1 COMM W MANU RUN
– – –
set point– output–

powering up…

SunSpec inverter (simulated) 10 kW PV · unit 3 · a day in 12 min –
AC power– energy– DC voltage– heat sink–
–:–

powering up…

starting…

Try these in order:

Break something:

What you just used

your SCADA, gateway, Node-RED flow or Python code Modbus TCP :5020 → modbus-sim instead of the real devices

Every command above became a real Modbus TCP frame (the tx and rx lines) and went through the same code that answers on a network port. modbus-sim normally runs on your laptop or in CI and speaks Modbus TCP, RTU over TCP and RTU on a serial line. The devices behave: the meter's counters count, the controller heats towards its set point, the inverter follows the sun. And they keep the details that break integrations: word order, scale factors, gaps in the map, write protection, exception codes.

The three profiles follow public documentation: the Eastron SDM630 Modbus protocol, Omron's E5CC communications manual and the SunSpec model definitions. Where they simplify, the README says so.

The same thing from Python

pip install modbus-sim
modbus-sim serve --bench real-devices.yaml     # the bench from this page
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)          # SDM630: L1 voltage
print(c.convert_from_registers(r.registers, c.DATATYPE.FLOAT32))   # 230.1

r = c.write_register(0x2103, 1500, device_id=2)               # E5CC: set point 150.0
print(r.exception_code)                                       # 4: writing is off
c.write_register(0x0000, 0x0001, device_id=2)                 # communications writing on
c.write_register(0x2103, 1500, device_id=2)                   # now it works

The bench file is examples/real-devices.yaml.

What else it does