A lightweight Modbus server simulator that supports both RTU (serial) and TCP (network) communication modes. Available as a CLI tool and a GUI application (tkinter). Useful for testing Modbus master/client software, PLC integrations, and SCADA systems without needing real hardware.
Disclaimer: This software is provided "as is", without warranty of any kind. Use it at your own risk. The authors are not responsible for any damage, data loss, or issues caused by the use of this software. It is intended for development and testing purposes only — do not use it as a substitute for certified industrial equipment in production environments. See the LICENSE file for full terms.
- RTU mode — communicates over a serial port (RS-485/RS-232)
- TCP mode — communicates over a network socket
- All four primary Modbus function codes:
FC Name Data Type Access 1 Read Coils Boolean (1 bit) Read/Write 2 Read Discrete Inputs Boolean (1 bit) Read-Only 3 Read Holding Registers 16-bit registers Read/Write 4 Read Input Registers 16-bit registers Read-Only - Multiple data types for FC 3 & 4:
Type Size Range Float32 2 registers IEEE 754 float Int16 1 register -32768 to 32767 UInt16 1 register 0 to 65535 Int32 2 registers -2147483648 to 2147483647 UInt32 2 registers 0 to 4294967295 - Live value editing — change register/coil values while the server is running; Modbus clients see the update immediately
- CSV import/export — bulk-load or save data point configurations
- Live PDU tracing — logs every Modbus request/response for debugging
- Quiet mode (CLI only) — suppress all output for headless/automated use
- Single-binary distribution — built with PyInstaller for Linux and Windows, no Python needed on target
- Python 3.10+
- Dependencies listed in
requirements.txt:pymodbus(>= 3.12, < 4.0)pyserial(>= 3.5, < 4.0)
# Run the setup script — creates a .venv and installs dependencies
./setup_venv.sh
# Activate the environment
source .venv/bin/activateStandalone binaries are available in dist/ (Linux) and dist_win/ (Windows). Copy them anywhere and run directly — no Python installation needed.
# CLI version
./dist/modbus_server_simul --help
# GUI version
./dist/modbus_server_simul_gui # Linux
dist_win\modbus_server_simul_gui.exe # WindowsThe CLI simulator (modbus_server_simul.py) is configured entirely via command-line flags. The only required flag is -D (data point), which can be repeated.
-D FC:ADDRESS:VALUE[:TYPE]
| Field | Description |
|---|---|
FC |
Function code: 1 (Coils), 2 (Discrete Inputs), 3 (Holding Registers), 4 (Input Registers) |
ADDRESS |
Modbus register/coil address (non-negative integer) |
VALUE |
Value to serve — 0/1 for FC 1 & 2, numeric for FC 3 & 4 |
TYPE |
(optional, FC 3/4 only) Data type: float32 (default), int16, uint16, int32, uint32 |
RTU mode — simulate a device on serial port with multiple data types:
./modbus_server_simul -i 100 -S /dev/ttyUSB0 -b 19200 \
-D 1:99:1 \
-D 2:10:1 \
-D 3:2100:123.45 \
-D 4:2000:98.765This creates a server with:
- Unit ID
100on/dev/ttyUSB0at 19200 baud - Coil 99 = ON (FC 1)
- Discrete Input 10 = ON (FC 2)
- Holding Register 2100 = 123.45 as Float32 (FC 3)
- Input Register 2000 = 98.765 as Float32 (FC 4)
TCP mode with mixed data types — combine different register types:
./modbus_server_simul -m tcp -H 0.0.0.0 -p 5020 -i 1 \
-D 1:0:1 \
-D 2:5:0 \
-D 3:0:3.14:float32 \
-D 3:2:42:int16 \
-D 3:3:1000:uint16 \
-D 3:4:-100000:int32 \
-D 3:6:4000000000:uint32This starts a TCP Modbus server on port 5020 with mixed data types across contiguous holding registers. When the type is omitted (e.g. -D 3:0:42.0), it defaults to float32.
Quiet mode — suppress all output:
./modbus_server_simul -q -i 69 -D 3:100:3.14| Flag | Description | Default |
|---|---|---|
-m, --mode |
Communication mode: rtu or tcp |
rtu |
-D, --data |
Data point (repeatable): FC:ADDRESS:VALUE[:TYPE] |
required |
-i, --id |
Slave/Unit ID (1–247) | 69 |
-q, --quiet |
Suppress informational output | off |
-V, --version |
Show version and exit | |
| RTU Settings | ||
-S, --port |
Serial port path | /dev/ttyUSB0 |
-b, --baudrate |
Baud rate | 19200 |
-P, --parity |
Parity: N, E, O |
N |
-d, --bytesize |
Data bits: 7 or 8 |
8 |
-s, --stopbits |
Stop bits: 1 or 2 |
1 |
| TCP Settings | ||
-H, --host |
Bind address | 0.0.0.0 |
-p, --tcp-port |
TCP port | 5020 |
The graphical interface (modbus_server_simul_gui.py) provides the same functionality as the CLI with a point-and-click interface built on tkinter.
# From source (requires virtual environment)
source .venv/bin/activate
python modbus_server_simul_gui.py
# Or use the pre-built binary
./dist/modbus_server_simul_gui # Linux
dist_win\modbus_server_simul_gui.exe # Windows- Server Configuration (top) — select RTU or TCP mode, set Unit ID, and configure serial/network settings. Irrelevant settings are greyed out based on the selected mode.
- Data Points table (middle) — the central table showing all configured data points with columns for Function Code, Data Type, Address, and Value.
- Start / Stop (control bar) — start or stop the Modbus server. Shows a live status indicator.
- Log (bottom) — timestamped console showing PDU traffic and server events. Click Clear Log to reset.
Use the input fields below the table:
- Select a Function Code from the dropdown (FC1–FC4).
- Select a Data Type (Float32, Int16, UInt16, Int32, UInt32). For FC 1/2 this is automatically set to Bit.
- Enter the Address (register/coil number).
- Enter the Value.
- Click Add or press Enter.
To remove rows, select them in the table and click Remove (or press Delete).
Double-click any cell in the table to edit it inline:
- Function Code and Data Type cells open a dropdown selector.
- Address and Value cells open a text editor — press Enter to save, Escape to cancel.
- Coil/Discrete values (FC 1/2) open a 0 / 1 dropdown for quick toggling.
While the server is running, you can double-click the Value column to change values on the fly. The change is pushed to the server's in-memory registers immediately — any Modbus client reading that address will see the new value on the next request. The FC, Type, and Address columns are locked while the server is running since changing them would require a server restart.
Import — click Import CSV to load data points from a CSV file. The expected format:
FC,Type,Address,Value
1,Bit,99,1
2,Bit,10,0
3,Float32,2100,123.45
3,Int16,200,-1234
3,UInt16,300,65000
3,Int32,400,-100000
3,UInt32,500,3000000000
4,Float32,0,98.765The FC column accepts multiple formats: 1, FC1, or FC1 - Coils (same for FC 2/3/4). The header row is optional. For FC 1/2, the Type column can be omitted or set to Bit. A 3-column format (FC, Address, Value) is also supported — the type defaults to Float32 for FC 3/4.
Export — click Export CSV to save all current data points to a CSV file with the same 4-column format. Useful for backing up a configuration or sharing it between machines.
A sample CSV file (example_data.csv) is included in the project.
The project uses PyInstaller to create standalone executables.
# Activate the virtual environment
source .venv/bin/activate
# Install build dependencies
pip install pyinstaller
# Build the CLI (Linux)
pyinstaller --onefile --clean --name modbus_server_simul modbus_server_simul.py
# Build the GUI (Linux)
pyinstaller --onefile --clean --name modbus_server_simul_gui modbus_server_simul_gui.pyWindows builds are done via Wine with a Windows Python installation:
# Install Windows Python under Wine (one-time setup)
wine python-3.13.4-amd64.exe /quiet InstallAllUsers=0 PrependPath=1
# Install dependencies in Windows Python
wine ~/.wine/drive_c/users/$USER/AppData/Local/Programs/Python/Python313/python.exe \
-m pip install pymodbus pyserial pyinstaller
# Build the GUI with the banana icon
wine ~/.wine/drive_c/users/$USER/AppData/Local/Programs/Python/Python313/python.exe \
-m PyInstaller --onefile --clean --name modbus_server_simul_gui --icon=banana.ico \
--distpath dist_win --workpath build_win --specpath build_win modbus_server_simul_gui.pyThe project follows Semantic Versioning (MAJOR.MINOR.PATCH):
- MAJOR — breaking changes (CLI flags renamed, CSV format changed, etc.)
- MINOR — new features (new data types, new GUI controls, etc.)
- PATCH — bug fixes and small improvements
The version number lives in version.py and is displayed in:
- The GUI title bar
- The CLI via
--version/-V
All changes are tracked in CHANGELOG.md following the Keep a Changelog format.
modbus_server_simul/
LICENSE # BSD 3-Clause License
modbus_server_simul.py # CLI simulator — Modbus RTU/TCP server
modbus_server_simul_gui.py # GUI simulator — tkinter graphical interface
version.py # Single source of truth for the version number
CHANGELOG.md # Release history and change tracking
example_data.csv # Sample CSV for importing data points
banana.ico # Application icon (Windows builds)
requirements.txt # Python dependencies
setup_venv.sh # Virtual environment setup script
dist/ # Built Linux binaries
dist_win/ # Built Windows executables (.exe)
- Argument parsing — the CLI parses
-Dflags into(function_code, address, value, data_type)tuples. The GUI collects the same data from the table. Both validate inputs (address range, value range, overlapping addresses) before proceeding. - Device building — each data point is converted into a
SimDataentry in the appropriate data store (coils, discrete inputs, holding registers, or input registers) using the native pymodbus data type (e.g.DataType.FLOAT32,DataType.INT16). pymodbus handles encoding values into 16-bit registers internally. - Server startup — pymodbus starts either a serial (RTU) or TCP server with the configured device. In the GUI, the server runs in a background thread with its own asyncio event loop so the interface stays responsive.
- Request handling — when a Modbus master sends a request, pymodbus looks up the value in the data store and responds. The PDU trace handler logs every transaction to the console or GUI log panel.
- Live editing (GUI only) — value changes are written directly into the server's in-memory register arrays using a thread lock to prevent torn reads. Coils and discrete inputs are stored as packed bitmasks (16 bits per register), so toggling a single coil uses bit-level manipulation at the correct offset.
This project is licensed under the BSD 3-Clause License — a permissive open-source license that allows free use, modification, and redistribution with minimal restrictions. See the LICENSE file for the full text.
USE AT YOUR OWN RISK. This software comes with no warranties. The authors disclaim all liability for any damages arising from its use. It is designed for development, testing, and educational purposes. It should not be used in safety-critical or production industrial environments without proper validation.