Command-line tool
modbusctl is a command-line tool for Modbus over TCP, UDP and serial lines (RTU and ASCII). It is built on go-modbus and covers the whole path from “what is this device?” to a hardware-free replay of it. Output is text, a table or JSON, with stable exit codes for scripts and CI.
Install
Section titled “Install”Pre-built binaries for Linux (x86-64, ARM64, ARMv7), macOS (Apple Silicon, Intel) and Windows (x86-64, ARM64) are on GitHub Releases. Each release has a modbusctl-<version>-checksums.txt file with SHA-256 sums. No Go toolchain is needed.
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/otfabric/modbusctl/releases/latest | sed 's|.*/v||')case "$(uname -m)" in x86_64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; armv7l) ARCH=armv7 ;;esacFILE="modbusctl-${VERSION}-linux-${ARCH}.tar.gz"BASE="https://github.com/otfabric/modbusctl/releases/download/v${VERSION}"curl -fsSLO "${BASE}/${FILE}"curl -fsSLO "${BASE}/modbusctl-${VERSION}-checksums.txt"grep "${FILE}" "modbusctl-${VERSION}-checksums.txt" | sha256sum -c -mkdir -p modbusctl-dl && tar -xzf "${FILE}" -C modbusctl-dlsudo install -m 0755 "$(find modbusctl-dl -type f -name 'modbusctl*' | head -n1)" /usr/local/bin/modbusctlSame steps with darwin in the file name and shasum -a 256 -c - to verify. If macOS refuses a binary downloaded with a browser, run xattr -d com.apple.quarantine /usr/local/bin/modbusctl.
Requires Go 1.25 or later.
git clone https://github.com/otfabric/modbusctl.gitcd modbusctlmake build # writes bin/modbusctlThe README also has a PowerShell script for Windows that verifies the checksum and adds the binary to your PATH. Check the install with modbusctl version.
Connecting
Section titled “Connecting”Every client command takes either --url or --ip with an optional --port (Modbus TCP). The two styles are mutually exclusive.
| URL | Transport |
|---|---|
tcp://host:port |
Modbus TCP (same as --ip host --port port) |
udp://host:port |
Modbus TCP framing over UDP |
rtu://<device> |
Modbus RTU on a serial line |
ascii://<device> |
Modbus ASCII on a serial line |
rtuovertcp://host:port |
RTU framing through a serial-to-Ethernet bridge |
rtuoverudp://host:port |
RTU framing over UDP |
asciiovertcp://host:port |
ASCII framing through a serial-to-Ethernet bridge |
The serial flags apply to rtu:// and ascii:// and are ignored for network URLs:
| Flag | Values | Default |
|---|---|---|
--baud |
any rate the adapter supports | 19200 |
--data-bits |
5, 6, 7, 8 | 8 for RTU, 7 for ASCII |
--parity |
none, even, odd |
none |
--stop-bits |
1, 2 | 2 without parity, 1 with |
# RTU at 9600 8E1, unit 3modbusctl client read --url rtu:///dev/ttyUSB0 --baud 9600 --parity even --unit 3 --start 0 --count 4
# RTU device behind a serial-to-Ethernet bridgemodbusctl client scan --url rtuovertcp://192.168.1.50:4001 --unit 7 --start 0 --end 200 --output scan.mcap--unit accepts a single ID, a range (1-10), a list (1,5,25), a mix (1-10,255) or all on the commands that probe several units. Each absent unit costs one --timeout, so use a short timeout (for example --timeout 300) when sweeping a bus. On serial lines --parallel is ignored.
Command tree
Section titled “Command tree”modbusctl├── client│ ├── identify FC43 Read Device Identification (+ optional FC17)│ ├── fingerprint Probe supported read functions per unit│ ├── diagnostic FC08 Diagnostics│ ├── reportserverid FC17 Report Server ID│ ├── read Read registers (FC01/FC02/FC03/FC04)│ ├── decode Read and decode registers via a codec (FC03/FC04)│ ├── codecs List available register codecs│ ├── poll Poll coils/registers at an interval│ ├── write Write coils (FC05/FC15) or holding registers (FC06/FC16)│ ├── scan Scan for readable register blocks│ ├── record Record registers over time to MCAP│ └── sunspec detect | models | map | probe├── server│ ├── static Serve scanned MCAP data│ └── replay Replay recorded MCAP data├── mcap convert | decode | extract | info | strings | frequencies├── discover Discover Modbus devices on subnets├── completion bash | zsh | fish | powershell└── versionA first session, read-only
Section titled “A first session, read-only”-
Identify the device and check which read functions it answers:
Terminal window modbusctl client identify --ip 192.168.1.10 --server-idmodbusctl client fingerprint --ip 192.168.1.10 --unit 1-10 -
Find the readable register blocks. Start gently:
Terminal window modbusctl client scan --ip 192.168.1.10 --start 0 --end 1000 --delay 100 --output scan.mcap -
Decode a value you found:
Terminal window modbusctl client decode --ip 192.168.1.10 --start 100 --codec uint32/layout:4321 --count 8 --format json -
Watch it change:
Terminal window modbusctl client poll --ip 192.168.1.10 --function 4 --start 0 --codec float32/layout:4321 --interval 500 --polls 20
Scan algorithms
Section titled “Scan algorithms”client scan --algo selects the strategy. The default is safe.
| Algorithm | Use it for |
|---|---|
safe |
Fragile devices and first passes. Linear, descending block sizes, fixed delay, no retry storms. |
smart |
Unknown devices. Adaptive interval splitting. The README calls it the best default. |
deep |
smart plus refinement around block edges. Lab work and reverse engineering. |
stepped |
Cheap triage of a huge range at --step positions. |
linear |
Long contiguous maps, such as PLCs. |
boundary |
Grow a known-good read (--seed-start, --seed-count) to its full extent. |
sunspec |
Find the SunSpec base and capture every model to MCAP. |
ALGOS.md specifies each algorithm, including worst-case request counts.
Codecs
Section titled “Codecs”client decode, client poll and client write share the go-modbus codec registry. modbusctl client codecs lists the IDs, with filters such as --family float or --registers 2. Examples: float32/layout:4321 (big-endian float), uint32/layout:2143 (word-swapped), ascii/registers:8, ip_addr.
Writing
Section titled “Writing”# One holding register (FC06)modbusctl client write --ip 192.168.1.10 --start 100 --values 1234
# Switch coil 8 on (FC05)modbusctl client write --ip 192.168.1.10 --function 5 --start 8 --values on
# A big-endian float32 setpoint: two registers, FC16modbusctl client write --ip 192.168.1.10 --start 200 --codec float32/layout:4321 --values 21.5The safety model is explicit:
--startis required. There is no default address for a write.- Values, codec, quantity limits and the address range are all validated before anything is sent. A rejected command exits with code 3 and has sent nothing.
- A write is sent exactly once and never retried. If it times out, read the value back before sending it again.
- The documentation describes no dry-run mode and no confirmation prompt. Rehearse against a simulator instead.
Output and exit codes
Section titled “Output and exit codes”--format text|json|table (or MODBUSCTL_OUTPUT_FORMAT) controls the final result on stdout for the client commands. Progress for scan and record goes to stderr, so stdout stays a single parseable result. client poll --format json writes one JSON object per poll (JSON Lines). With --format json, a fatal error is one JSON object with kind, code and message.
mcap convert --format is a different flag: it chooses csv or json export (environment variable MODBUSCTL_FORMAT).
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Usage error (reserved) |
| 3 | Invalid input, flags or configuration |
| 4 | Transport: TCP or UDP dial failure, or a serial port that cannot be opened |
| 5 | Timeout (documented as reserved for typed timeouts; a timed-out client write exits 5) |
| 6 | Protocol error or Modbus exception |
| 7 | Partial success: valid output, but some units, scan requests or polls failed |
| 10 | Output, internal or unknown error |
Match on the exit code or the JSON code, not on message text.
Environment variables
Section titled “Environment variables”Most flags have a MODBUSCTL_* equivalent, for example MODBUSCTL_URL, MODBUSCTL_IP, MODBUSCTL_UNIT, MODBUSCTL_TIMEOUT (milliseconds; 0 means 10000), MODBUSCTL_BAUD, MODBUSCTL_PARITY, MODBUSCTL_CODEC and MODBUSCTL_ALGO. Note that client read, decode, poll and write take their start address from MODBUSCTL_ADDRESS, while client scan uses MODBUSCTL_START.
MODBUSCTL_URL=rtu:///dev/ttyUSB0 MODBUSCTL_BAUD=9600 MODBUSCTL_PARITY=even MODBUSCTL_UNIT=3 \ modbusctl client poll --start 0 --count 4Record, replay and simulate
Section titled “Record, replay and simulate”MCAP (Modbus Capture) is modbusctl’s own file format, inspired by PCAP: a JSON header with the target and function code, followed by binary records with timestamps, addresses and raw register data. client scan and client record write it; the mcap commands analyse it.
# Turn a scan into a block list, then record those blocks every second for a minutemodbusctl mcap extract --input scan.mcap --output register-ranges.jsonmodbusctl client record --ip 192.168.1.10 --blocks-file register-ranges.json --interval 1000 --duration 60000 --output session.mcap
# Serve the data as a Modbus TCP device: fixed values, or replayed over timemodbusctl server static --input session.mcap --port 1502modbusctl server replay --input session.mcap --port 1502 --loops 0
# Look for device names and ~50 Hz frequency readingsmodbusctl mcap strings --input session.mcapmodbusctl mcap frequencies --input session.mcapserver and discover are Modbus TCP only. The servers listen on all interfaces and accept any client, so run them on trusted networks. discover caps large subnets unless you pass --force-large-scan, and --resolve-mac uses raw sockets that may need CAP_NET_RAW.
SunSpec
Section titled “SunSpec”client sunspec does transport-level SunSpec discovery: the SunS marker, the base address and the model headers. It does not decode points; use sunspecctl for that.
modbusctl client sunspec detect --url tcp://192.168.1.10:502 --unit 1modbusctl client sunspec models --ip 192.168.1.10 --unit 1 --base 40000 --format jsonmodbusctl client sunspec probe --url tcp://192.168.1.10:502 --unit 1