Use this command to install Universal Modbus Diagnostic Tool:
winget install --id=KevinBralten.UMDT -e
Universal Modbus Diagnostic Tool (UMDT) is a comprehensive toolkit designed for engineers, developers, and testers working with Modbus (RTU/TCP) devices. It simplifies tasks such as diagnosing device behavior, simulating virtual devices, bridging communication between transports, and capturing traffic for analysis.
Key Features:
Diagnosing: Read/write registers, scan address ranges, and probe networks to identify devices.
Simulating: Create virtual Modbus slaves with configurable behaviors, including fault injection and scripting for realistic testing scenarios.
Bridging: Route traffic between different transports (e.g., TCP to Serial) and modify packets on the fly for seamless communication.
Sniffing: Capture and analyze Modbus traffic in real-time or offline using PCAP-compatible files.
Audience & Benefit:
Ideal for engineers, developers, and testers working with industrial automation systems. UMDT helps reduce downtime by diagnosing connectivity issues, accelerate development by simulating complex scenarios, and improve testing reliability through fault injection and scripting capabilities. It supports Modbus RTU/TCP protocols and can be used alongside RS485 communication setups.
UMDT can be installed via winget for easy setup on compatible systems.
README
Universal Modbus Diagnostic Tool (UMDT)
Overview
UMDT is a comprehensive Python-based toolkit for diagnosing, simulating, and bridging Modbus devices (RTU/TCP).
It has evolved into four distinct tools:
Interactive Tool: A CLI and GUI for reading/writing registers, scanning addresses, probing networks, and live monitoring.
Mock Server: A configurable simulation environment for creating virtual Modbus devices with fault injection and scripting.
Bridge: A soft-gateway for routing Modbus traffic between TCP and Serial (RTU) transports, with PCAP logging and scripting hooks.
Sniff: Lightweight CLI and GUI sniffers for capturing Modbus traffic (TCP/RTU) to PCAP files compatible with the UMDT Wireshark wrapper.
Documentation
For detailed user guides, installation instructions, and advanced topics, please refer to the UMDT User Guide.
1. Interactive Tool (CLI & GUI)
The interactive tool is designed for direct communication with Modbus devices.
CLI (main_cli.py)
The CLI provides a suite of commands for quick diagnostics and scripting.
Common Options:
Connection: --serial COMx --baud 9600 or --host x.x.x.x --port 502
The mock server includes a lightweight ScriptEngine for custom behavior and automation. Scripts are Python-based and can be attached via the server configuration or loaded at runtime from the CLI/GUI.
What scripts can do:
Provide custom request/response handlers to implement non-standard device logic.
Generate dynamic register values (timers, counters, or procedurally generated data).
Inject faults (delays, dropped responses, exception replies) for testing resilience.
React to events (on-start, on-stop, on-write) and emit state changes to the GUI/CLI.
Deployment:
Add script entries to the server config to load at startup, or use the --script CLI/GUI controls to load/unload during a running session.
Scripts run in a restricted, async-friendly environment exposed by the ScriptEngine API (request/response objects, scheduler utilities, logging). See server.md and the umdt/mock_server/scripts/ examples for patterns.
This makes the mock server suitable for realistic simulation, regression tests, and automated E2E scenarios.
Hooks & ScriptEngine API
The Mock Server exposes a small set of hook entry points that scripts can implement to intercept or synthesize traffic and lifecycle events. Hooks are executed inside the ScriptEngine and are async-friendly.
Common hook signatures:
async def on_request(request, context) — called when a request arrives; may return a Response to short-circuit handling or None to continue normal processing.
async def on_response(response, context) — called when a response is about to be sent (after handler logic).
async def on_write(unit_id, address, value, context) — notified when a write request changes register state.
async def on_timer(name, context) — called by scheduled timers registered via the ScriptEngine scheduler.
Capabilities provided by the ScriptEngine:
Scheduler utilities for one-shot or periodic timers.
Access to the server's register map and metadata (read/write helpers with concurrency control).
Helpers for injecting faults (latency, exceptions, packet drops) and emitting events to the GUI/CLI.
Logging and metrics hooks scoped to the script instance.
Example script (simple counter):
# scripts/counter.py
from asyncio import sleep
async def on_start(ctx):
ctx.log.info("counter script started")
ctx.state["count"] = 0
async def tick():
while True:
ctx.state["count"] += 1
# write to register 1000 for unit 1
await ctx.write_register(unit=1, address=1000, value=ctx.state["count"])
await sleep(1)
ctx.schedule_task(tick())
async def on_request(request, ctx):
# short-circuit a read for a special address
if request.function_code == 3 and request.address == 9999:
return ctx.make_response_exception(request, exception_code=1)
return None
Config / CLI:
Add to YAML config:
scripts:
- path: scripts/counter.py
enabled: true
Or load at runtime via CLI/GUI: mock_server_cli.py start --script scripts/counter.py.
See umdt/mock_server/scripts/ for additional examples demonstrating fault injection, timers, and custom protocol behavior.
GUI (mock_server_gui.py)
A control panel for the mock server to visualize state, modify values on-the-fly, and control fault injection sliders.
Configuration
Configs are YAML/JSON files defining register maps and initial state. See server.md for details.
3. Bridge (Soft-Gateway)
A transparent bridge for routing Modbus traffic between different transports (e.g., TCP Master to RTU Slave). It supports protocol conversion and multiple concurrent upstream clients.
Bridge → Slave (logged by egress_hook, direction: OUTBOUND)
Slave → Bridge (logged by response_hook, direction: INBOUND)
The PCAP records use DLT_USER0 (147) with a 4-byte UMDT metadata header: byte 0 = direction (1=inbound, 2=outbound), byte 1 = protocol hint (1=MODBUS_RTU, 2=MODBUS_TCP), bytes 2-3 reserved. The provided Wireshark Lua wrapper scripts will strip this metadata and decode Modbus frames.
Wireshark Lua plugin
We provide two Lua scripts to make UMDT PCAPs decode nicely in Wireshark:
umdt_modbus_wrapper.lua — a wrapper that strips the 4-byte UMDT metadata header, converts Modbus-RTU frames to MBAP-like TVBs (removing CRC when present), sets Src/Dst to client/server, and populates the Info column with the Modbus Unit/Function summary.
umdt_mbap.lua — a simple MBAP dissector used by the wrapper to decode MBAP PDUs (function codes, byte counts, registers) and to detect Modbus exceptions. Exceptions are added as expert-error items so Wireshark highlights them.
You may need to provide the full path instead of relative paths to the Lua scripts when using the -X option, remember to add lua_script: before the path.
Or install the scripts permanently by copying them to your personal Wireshark plugins directory:
Windows (per-user): %APPDATA%\Wireshark\plugins\
Windows (system): C:\Program Files\Wireshark\plugins\
Notes
UMDT PCAP record format: first 4 bytes are metadata — byte 0 = direction (1=inbound, 2=outbound), byte 1 = protocol hint (1=MODBUS_RTU, 2=MODBUS_TCP), bytes 2-3 reserved.
The wrapper will automatically strip that metadata for decoding. For RTU frames it will attempt CRC detection and remove the CRC before wrapping into an MBAP-like TVB.
The umdt_mbap.lua dissector tags Modbus exception responses (function >= 0x80) and adds expert-error entries so they appear highlighted in Wireshark.
If you prefer to decode the PCAP manually, set "Decode As" → USER0 (147) to umdt_modbus (or load the wrapper script) so Wireshark uses the wrapper for these records.
Bridge Scripting: Logic Engine and Hooks
The Bridge includes a Logic Engine that lets you run small, async-friendly Python scripts to inspect, modify, or react to Modbus traffic as it flows through the pipeline. Scripts are registered as hooks and executed without blocking I/O.
egress_hook(request, context) — outbound requests to downstream (Bridge → Slave)
response_hook(response, context) — responses from downstream (Slave → Bridge)
upstream_response_hook(response, context) — responses forwarded to upstream (Bridge → Master)
Typical uses:
Transform addresses, function codes, or payloads for protocol adaptation.
Implement filtering, access-control, or mapping rules.
Inject or correct fields to emulate gateway logic or vendor quirks.
Schedule background tasks, metrics, or conditional faults.
Enabling scripts:
Configure scripts via the bridge config or load them at runtime using CLI/management commands; the bridge supports reloadable hooks for iterative development.
See umdt/bridge/scripts/ for examples and the bridge.py help text for available runtime flags.
4. Sniff (Traffic Capture)
UMDT includes lightweight "sniff" programs for capturing and inspecting Modbus traffic from TCP or serial/RTU transports. There are both CLI and GUI frontends:
CLI: sniff_cli.py — command-line packet capture and on-disk PCAP writer.
GUI: sniff_gui.py — a small PySide6 front-end for live capture, playback, and quick decoding.
Purpose:
Capture Modbus conversations for forensic analysis, debugging, and replay.
Produce PCAP files compatible with the UMDT Wireshark Lua wrapper (see above) so Modbus frames decode cleanly in Wireshark.
Common features:
Capture from a TCP endpoint (host:port) or a serial device (COM/tty, baud rate).
Save captures to PCAP, with the same UMDT 4-byte metadata header (direction, protocol hint) used by the bridge.
Optionally split upstream/downstream streams into separate files for easier analysis.
Live decoding using the built-in simple displayer (CLI) or the GUI's live view.
The GUI offers: Start/Stop capture, file naming, basic live decoding, and an option to open captures in Wireshark with the UMDT Lua wrapper.
Building the sniff binaries with PyInstaller:
build_dist.py will produce umdt_sniff and umdt_sniff_gui if the corresponding entry points exist. Run:
python build_dist.py
and look for umdt_sniff / umdt_sniff_gui in ./dist/.
Notes & tips:
Use the Wireshark wrapper scripts (umdt_modbus_wrapper.lua + umdt_mbap.lua) to decode the PCAPs produced by the sniff tools.
When capturing RTU frames, ensure the sniff tool or the environment exposes the serial line in raw mode so CRCs are preserved for the wrapper to strip appropriately.
Development Notes
Setup
Install dependencies:
pip install -r requirements.txt
(Optional) Install dev dependencies for testing/building:
pip install -r requirements-dev.txt
Project Layout
main_cli.py / main_gui.py: Interactive tools.
mock_server_cli.py / mock_server_gui.py: Mock server tools.
bridge.py: Bridge entry point.
umdt/: Core package source.
tests/: Pytest suite.
Testing
Run unit tests:
pytest
End-to-end (E2E) Docker tests
Requirements: Docker (and docker compose) installed. On Windows, WSL2 is recommended for CI-like environments.
Start the test environment and run the E2E suite:
docker compose -f tests/e2e/docker-compose.yml up --build --abort-on-container-exit
# in another shell (or after containers are up) run the E2E pytest suite
pytest tests/e2e -q
# OR run only E2E-marked tests
pytest -m e2e
UI tests
UI tests exercise the PySide6 GUI and require the GUI runtime and pytest-qt (or equivalent fixtures).
Notes: Running GUI tests on headless CI typically requires an X server or virtual framebuffer (e.g., xvfb) or using a Windows-native test runner. For quick local iteration on Windows, run tests directly in the desktop session.