Skip to content

About

Read registers from any Modbus device over TCP or RTU (RS-485) – read-only by design, raw words always shown, addresses never converted. Single Go binary, no device knowledge built in.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

modbusread

Read registers from any Modbus device over TCP or RS-485 – read-only, raw words always shown.

CI Release License: MIT Go

🇩🇪 Deutsche Kurzfassung

A universal, read-only Modbus reader – address, register and type in, value out. No device knowledge in the tool, no built-in register map; it works with any Modbus device. Meant for checking register maps from manuals or community reverse engineering against the real device.

Speaks Modbus TCP and Modbus RTU (serial). It calls no write function of the Modbus library at all – a tool without a write path cannot write by accident.

modbusread <target> <address> <type> [flags]

Address in decimal (42082) or hex (0xA462), type raw, uint16, int16, uint32, int32, float32, float64 or string.

The target decides the transport, without an extra flag:

Input Transport
192.168.1.50, plc.local:1502 Modbus TCP (port defaults to 502)
/dev/ttyUSB0, /dev/tty.usbserial-…, COM3 Modbus RTU over the serial line
rtu://…, tcp://…, udp://…, rtuovertcp://…, rtuoverudp://… explicit, overrides the detection

tcp+tls:// is rejected outright rather than silently ignored – the library could do it, but it needs certificates this tool does not manage, and offering it untested would be a promise with nothing behind it.

$ modbusread 192.168.1.50 42082 uint16
addr     raw                  value
42082    0x0064               100

# A float with swapped words (high word in the second register)
$ modbusread 192.168.1.50 40574 float32 --word-order low

# Dump a range to find unknown registers
$ modbusread 192.168.1.50 40520 raw --count 120 --out hex

# Find out which register reacts to a change on the device
$ modbusread 192.168.1.50 40520 raw --count 120 --interval 1s --on-change

The addresses in these examples come from the EcoFlow PowerOcean, the device the tool was first written for; its register map lives in womat/ecoflow.

Important: addresses are never converted – they go on the wire exactly as typed (0-based). Many register maps are documented 1-based, some are not, and often it is not clear which. Converting is therefore left to the human, so the tool does not hide an assumption.

With a serial target the line parameters come in – --baud (19200), --databits (8), --parity (none) and --stopbits. For the latter, 0 follows the Modbus rule: two stop bits without parity, one with. They have to match the device exactly, otherwise you get garbage or nothing at all. A bus typically has several devices on it, so --unit is no longer a formality there:

$ modbusread /dev/ttyUSB0 40069 uint16 --baud 9600 --parity even --unit 3

On a TCP target these flags are rejected rather than ignored – a baud rate that silently has no effect sends you off debugging the wiring.

More flags: --unit, --fc holding|input, --byte-order, --timeout, --json, --samples. modbusread --help shows everything. --samples and --on-change belong to --interval and are rejected without it, for the same reason as the serial flags.

The raw words are always printed, even when a value was decoded – when reverse engineering a register map, the raw value matters more than the interpretation. A register that could not be read shows - there (null in --json) next to the error, never 0x0000: a zero would look like something the device sent. A float that is NaN or infinite is null in --json, since JSON has no number for it; its bits are in raw.

The exit code is non-zero when not a single value could be read – a uint32 with one of its two registers missing counts as not read. In a range, one readable value is enough.

Ctrl-C ends a read after the request in flight. A device that does not answer makes every request wait for --timeout, so that is at most one timeout, not one per register. Connecting over the network is the exception: the Modbus library waits up to 5 s for it whatever --timeout says, and Ctrl-C takes effect only after that.

Installation

With a Go toolchain:

go install github.com/womat/modbusread@latest

For machines without Go – such as a Raspberry Pi next to the device – ready-made binaries are under Releases. They are statically linked (CGO_ENABLED=0), so there is nothing to install: unpack and run.

VERSION=0.8.0    # without the v - see the releases page for the latest
ARCH=linux_arm64 # see the table below

curl -LO "https://github.com/womat/modbusread/releases/download/v$VERSION/modbusread_${VERSION}_$ARCH.tar.gz"
tar -xzf "modbusread_${VERSION}_$ARCH.tar.gz" modbusread
./modbusread --version
Machine ARCH
Raspberry Pi 3/4/5/Zero 2 W with a 64-bit OS linux_arm64
Raspberry Pi 2/3/4/5/Zero 2 W with a 32-bit OS linux_armv7
Raspberry Pi 1 and Zero (1st gen) linux_armv6
ordinary Linux PC/server, NAS linux_amd64
Mac with Apple Silicon darwin_arm64
Mac with Intel darwin_amd64
Windows windows_amd64 (.zip)

The download can be checked against the checksums.txt of the same release:

curl -LO "https://github.com/womat/modbusread/releases/download/v$VERSION/checksums.txt"
sha256sum -c checksums.txt --ignore-missing

Releases

Every release on the releases page carries archives for Linux (including all Raspberry Pi architectures), macOS and Windows with the binary, README.md and LICENSE, plus a checksums.txt and a changelog. Versions follow semantic versioning.

modbusread --version reports the release a binary was built from. A local build reports the commit instead, with +dirty when the working tree had changes – which is how the two are told apart.

History

Up to v0.6.0 modbusread lived in womat/ecoflow as cmd/modbusread; those releases stay there, and go install github.com/womat/ecoflow/cmd/modbusread@v0.6.0 still works. From v0.7.0 on it is released here. The version numbers continue.

Development

Building from source needs Go and make; make help lists the targets:

make test       # go test -race ./... - no hardware: the integration tests start a Modbus server
make lint       # gofmt, go fix -diff, go mod tidy -diff, go vet, golangci-lint, govulncheck
make build      # ./modbusread for this machine
make snapshot   # all release archives into ./dist, without publishing (needs goreleaser)

A release is a tag vX.Y.Z on main, made with make release TAG=vX.Y.Z; the workflow tests the tagged commit again and GoReleaser publishes the archives.

License

modbusread is released under the MIT License – see LICENSE for the full text.

Third-party licenses

The source tree contains no third-party code, but a compiled binary statically links the modules below. Their terms apply to anyone distributing that binary, not to the sources here.

Module License
github.com/simonvetter/modbus MIT
github.com/goburrow/serial MIT

Both are permissive.

About

Read registers from any Modbus device over TCP or RTU (RS-485) – read-only by design, raw words always shown, addresses never converted. Single Go binary, no device knowledge built in.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages