Repository navigation
Releases: otfabric/modbusctl
Release list
v2.4.1
Date: 2026-10-11
Previous release: v2.4.0
Summary
Patch release: adopts go-modbus v1.3.0, which hardens the client against lost, slow and out-of-step connections. No changes to commands, flags or JSON output.
Changes
- go-modbus: Requires
github.com/otfabric/go-modbusv1.3.0 (was v1.2.1). - No stale answers after a timeout (TCP/TLS): After a request times out or a response is malformed, the connection is replaced before the next request. A late response to the timed-out request can no longer be taken for the answer to the next one, which matters most for
client poll,client recordandclient scanagainst slow devices. - Reconnects by itself: A long-running
client pollorclient recordwhose connection drops fails that one sample and continues on a new connection with the next. - Ctrl-C and
--timeouttake effect immediately: A request in flight is interrupted instead of running to its timeout.
v2.4.0
Date: 2026-10-09
Previous release: v2.3.0
Summary
Closes the capability gap with classic poll-and-write tools. modbusctl now speaks Modbus RTU and ASCII on serial lines, Modbus UDP, and RTU/ASCII framing through serial-to-Ethernet bridges; it can write coils and holding registers (client write) and poll continuously at an interval without recording (client poll). Every existing client command works over the new transports. No breaking changes to flags or JSON output.
Highlights
- Transports (all client commands):
--urlnow acceptsrtu://<device>,ascii://<device>,udp://host:port,rtuovertcp://,rtuoverudp://andasciiovertcp://in addition totcp://. Serial devices are written asrtu:///dev/ttyUSB0(Linux/macOS) orrtu://COM3(Windows). - Serial settings: New
--baud,--data-bits,--parity(none/even/odd) and--stop-bitsflags on every client command, withMODBUSCTL_BAUD,MODBUSCTL_DATA_BITS,MODBUSCTL_PARITYandMODBUSCTL_STOP_BITS. Defaults: 19200 baud, 8 data bits for RTU and 7 for ASCII, no parity, 2 stop bits without parity or 1 with. They are ignored for network URLs. client write(new): Writes coils (FC05, FC15) and holding registers (FC06, FC16).--valuestakes one value or a comma-separated list; with--functionomitted, one register uses FC06 and several use FC16.--codecencodes typed values (floats, 32/64-bit integers, strings, IP addresses, timestamps) with the same codec registry asclient decode.--startis mandatory, everything is validated before anything is sent, and a write is sent exactly once (never retried).client poll(new): Reads the same block of coils, discrete inputs or registers every--intervalms and prints each sample as it is read — one line (text), one JSON object per line (json) or one row (table) — until--pollssamples or Ctrl-C. Optional--codecdecoding. Failed polls are reported in-stream and polling continues (reconnecting when needed); exit code 7 when any poll failed. Nothing is written to disk;client recordremains the MCAP capture command.- Serial buses: On
rtu://andascii://,identifyandreportserveridignore--paralleland query units one after the other, since a serial line carries one request at a time. - MCAP: Captures made over a serial line store the device path in the header
ipfield with port 0. The format is unchanged. - Completion: Tab completion for
--parityand forclient write --function(5, 6, 15, 16). - go-modbus: Requires
github.com/otfabric/go-modbusv1.2.0, which adds the Modbus ASCII transport and corrects function probing.
Fixes
- Silent empty result after a dropped connection:
client read,decodeandscancould report success with no data when the connection was reset during the request: the reconnect succeeded and its result overwrote the read error. The read error is now reported (and counted as a transport failure byscan). client scan --algo safe(the default) lost registers: After finding a block that followed unreadable addresses, the scan skipped one register (for example the last register of a short block), and could read one register below--start. Both are fixed.client scanwith--end 65535(the default):--algo smartand--algo deepnever finished building their work list and ran out of memory, and--algo safecould wrap around instead of finishing. Position arithmetic no longer overflows at the top of the address space.client scan --algo linear: No longer loops forever when the range reaches address 65535, and no longer issues zero-register reads for the full range 0–65535.client scan --algo boundary: Finds the left edge of blocks of any size (it used to stop after the first failed read), never reads more than 125 registers at once, and handles blocks and seeds that reach address 65535.client fingerprintandclient sunspec probe: A function is reported as supported only when the device implements it. Previously any exception counted, so a device answering Illegal Function — or a gateway answering for an absent unit — appeared to support every probed function; and a device that correctly echoed FC08 was reported as not supporting diagnostics. (Fixed in go-modbus v1.2.0.)- MCAP files cut short: A capture truncated exactly between two fields of a record was read as complete, silently dropping the damaged record. Every
mcapcommand now reports it as an unexpected end of file. A record too large for the format is rejected instead of corrupting the file. server static/server replaywith coils or discrete inputs: A request for more than one coil returned wrong values for every coil after the first. Multi-coil reads now return the recorded bits.server replay: Fixed a data race between the startup message and the replay loop.client sunspeccommands:--timeoutnow bounds every read. Previously a device that did not answer blocked for the whole operation budget (10 s or more) on the first base address.client identify/client reportserveridwith several units: Ctrl-C now stops at the next unit. Previously every remaining unit still waited for its full timeout.- Error classification: A connection closed by the device is reported as a transport error (exit code 4), not an internal error (10).
--format table: The rule line under the header no longer ends in trailing spaces.- Validation message: An
--outputpath whose parent is a file reported%!w(<nil>); it now says the output directory is not a directory. - Error text: Connection failures are reported as
connection error: …instead ofTCP connection error: …, since the transport may be a serial port. The JSONkind(transport) andcode(transport_connect_failed) are unchanged. - Documentation:
float32/layout:1234was described as big-endian in examples; it is little-endian. Examples now usefloat32/layout:4321(big-endian).client readreadsMODBUSCTL_ADDRESS(notMODBUSCTL_START) for--start; the help example and README now say so.
Quality
- Coverage: Test coverage raised from 51% to 96% of the whole module, with a floor of 85% enforced by
make coverage(COVERAGE_MIN) and a Codecov project target; CI now tests thecmdpackages too. A Code Coverage badge replaces the retired Go Report Card badge. - Hardware-free tests: New
internal/mbtestin-memory Modbus device served over TCP, UDP, RTU/ASCII over TCP, and RTU/ASCII on a pseudo-terminal, so serial transports are exercised end to end in CI. - Makefile: Sets
GOWORK=offso a parentgo.workcannot interfere.
Upgrading
- Scripts matching error text: Replace matches on
TCP connection errorwith the JSONcode(transport_connect_failed) or the exit code (4). - Completion users: Regenerate and re-source your shell completion script to pick up the new commands and flags.
- Float examples: If you copied
float32/layout:1234from earlier documentation expecting big-endian values, switch tofloat32/layout:4321. Codec behaviour itself has not changed.
Release workflow
- Unchanged from v2.3.0: reusable
go-binary-releaseworkflow and link-time version metadata.
v2.4.0
Date: 2026-10-09
Previous release: v2.3.0
Summary
Closes the capability gap with classic poll-and-write tools. modbusctl now speaks Modbus RTU and ASCII on serial lines, Modbus UDP, and RTU/ASCII framing through serial-to-Ethernet bridges; it can write coils and holding registers (client write) and poll continuously at an interval without recording (client poll). Every existing client command works over the new transports. No breaking changes to flags or JSON output.
Highlights
- Transports (all client commands):
--urlnow acceptsrtu://<device>,ascii://<device>,udp://host:port,rtuovertcp://,rtuoverudp://andasciiovertcp://in addition totcp://. Serial devices are written asrtu:///dev/ttyUSB0(Linux/macOS) orrtu://COM3(Windows). - Serial settings: New
--baud,--data-bits,--parity(none/even/odd) and--stop-bitsflags on every client command, withMODBUSCTL_BAUD,MODBUSCTL_DATA_BITS,MODBUSCTL_PARITYandMODBUSCTL_STOP_BITS. Defaults: 19200 baud, 8 data bits for RTU and 7 for ASCII, no parity, 2 stop bits without parity or 1 with. They are ignored for network URLs. client write(new): Writes coils (FC05, FC15) and holding registers (FC06, FC16).--valuestakes one value or a comma-separated list; with--functionomitted, one register uses FC06 and several use FC16.--codecencodes typed values (floats, 32/64-bit integers, strings, IP addresses, timestamps) with the same codec registry asclient decode.--startis mandatory, everything is validated before anything is sent, and a write is sent exactly once (never retried).client poll(new): Reads the same block of coils, discrete inputs or registers every--intervalms and prints each sample as it is read — one line (text), one JSON object per line (json) or one row (table) — until--pollssamples or Ctrl-C. Optional--codecdecoding. Failed polls are reported in-stream and polling continues (reconnecting when needed); exit code 7 when any poll failed. Nothing is written to disk;client recordremains the MCAP capture command.- Serial buses: On
rtu://andascii://,identifyandreportserveridignore--paralleland query units one after the other, since a serial line carries one request at a time. - MCAP: Captures made over a serial line store the device path in the header
ipfield with port 0. The format is unchanged. - Completion: Tab completion for
--parityand forclient write --function(5, 6, 15, 16). - go-modbus: Requires
github.com/otfabric/go-modbusv1.2.0, which adds the Modbus ASCII transport and corrects function probing.
Fixes
- Silent empty result after a dropped connection:
client read,decodeandscancould report success with no data when the connection was reset during the request: the reconnect succeeded and its result overwrote the read error. The read error is now reported (and counted as a transport failure byscan). client scan --algo safe(the default) lost registers: After finding a block that followed unreadable addresses, the scan skipped one register (for example the last register of a short block), and could read one register below--start. Both are fixed.client scanwith--end 65535(the default):--algo smartand--algo deepnever finished building their work list and ran out of memory, and--algo safecould wrap around instead of finishing. Position arithmetic no longer overflows at the top of the address space.client scan --algo linear: No longer loops forever when the range reaches address 65535, and no longer issues zero-register reads for the full range 0–65535.client scan --algo boundary: Finds the left edge of blocks of any size (it used to stop after the first failed read), never reads more than 125 registers at once, and handles blocks and seeds that reach address 65535.client fingerprintandclient sunspec probe: A function is reported as supported only when the device implements it. Previously any exception counted, so a device answering Illegal Function — or a gateway answering for an absent unit — appeared to support every probed function; and a device that correctly echoed FC08 was reported as not supporting diagnostics. (Fixed in go-modbus v1.2.0.)- MCAP files cut short: A capture truncated exactly between two fields of a record was read as complete, silently dropping the damaged record. Every
mcapcommand now reports it as an unexpected end of file. A record too large for the format is rejected instead of corrupting the file. server static/server replaywith coils or discrete inputs: A request for more than one coil returned wrong values for every coil after the first. Multi-coil reads now return the recorded bits.server replay: Fixed a data race between the startup message and the replay loop.client sunspeccommands:--timeoutnow bounds every read. Previously a device that did not answer blocked for the whole operation budget (10 s or more) on the first base address.client identify/client reportserveridwith several units: Ctrl-C now stops at the next unit. Previously every remaining unit still waited for its full timeout.- Error classification: A connection closed by the device is reported as a transport error (exit code 4), not an internal error (10).
--format table: The rule line under the header no longer ends in trailing spaces.- Validation message: An
--outputpath whose parent is a file reported%!w(<nil>); it now says the output directory is not a directory. - Error text: Connection failures are reported as
connection error: …instead ofTCP connection error: …, since the transport may be a serial port. The JSONkind(transport) andcode(transport_connect_failed) are unchanged. - Documentation:
float32/layout:1234was described as big-endian in examples; it is little-endian. Examples now usefloat32/layout:4321(big-endian).client readreadsMODBUSCTL_ADDRESS(notMODBUSCTL_START) for--start; the help example and README now say so.
Quality
- Coverage: Test coverage raised from 51% to 96% of the whole module, with a floor of 85% enforced by
make coverage(COVERAGE_MIN) and a Codecov project target; CI now tests thecmdpackages too. A Code Coverage badge replaces the retired Go Report Card badge. - Hardware-free tests: New
internal/mbtestin-memory Modbus device served over TCP, UDP, RTU/ASCII over TCP, and RTU/ASCII on a pseudo-terminal, so serial transports are exercised end to end in CI. - Makefile: Sets
GOWORK=offso a parentgo.workcannot interfere.
Upgrading
- Scripts matching error text: Replace matches on
TCP connection errorwith the JSONcode(transport_connect_failed) or the exit code (4). - Completion users: Regenerate and re-source your shell completion script to pick up the new commands and flags.
- Float examples: If you copied
float32/layout:1234from earlier documentation expecting big-endian values, switch tofloat32/layout:4321. Codec behaviour itself has not changed.
Release workflow
- Unchanged from v2.3.0: reusable
go-binary-releaseworkflow and link-time version metadata.
v2.3.0
Date: 2026-07-06
Previous release: v2.2.4
Summary
Adds typed codec decoding to the client: a new client decode command reads holding/input registers and decodes them into typed values (integers, floats, strings, BCD, time, IP, MAC, …) using the github.com/otfabric/go-modbus/codec registry, plus a client codecs command to discover available codecs. As part of this, the ad-hoc --ascii and --byteswap flags are removed from client read, which now focuses on raw capture and MCAP output. This is a breaking change for anyone using those two read flags.
Highlights
client decode(new): Reads holding (FC3) or input (FC4) registers and decodes them with a single--codec(e.g.float32/layout:1234,ascii/registers:8,uint32/layout:4321,ip_addr,datetime_iec870_utc). The codec sets the per-value register width; omit--countto decode one value, or pass a multiple of the codec width to decode an array of values. Output honors--format text|json|table; JSON emits a typedvaluesarray. No MCAP side effect—decode is a read-and-interpret command.client codecs(new): Lists every registered codec (id, name, family, value kind, registers, bytes), with optional--family,--value-kind, and--registersfilters and--format text|json|table. Mirrors the codec catalog exposed by the Modbus simulator server.- Codecs source: The codec registry ships inside the already-depended-on
github.com/otfabric/go-modbus(v1.1.0) under thecodecsubpackage—no new dependency and no version bump were required. - Completion: New
--codecflag supports tab completion (registered codec IDs);--functioncompletion is reused ondecode. - Config / env: New
MODBUSCTL_CODECenvironment variable maps to--codec;MODBUSCTL_COUNTgains decode semantics (a multiple of the codec width;0= one value). client read(breaking): Removed--ascii/MODBUSCTL_ASCIIand--byteswap/MODBUSCTL_BYTESWAP. ThereadJSON DTO dropspre_swap_hex,bytes_swapped, andascii_decoded;readstill captures raw bytes (raw_data_hex) and writes MCAP.
Upgrading
- Replace
read --ascii: Useclient decode --codec ascii/registers:N(orascii_fixed/ascii_reverse/utf16be/utf16levariants) for text decoding. - Replace
read --byteswap: Use a layout-aware numeric codec viaclient decode(e.g.--codec uint32/layout:2143) instead of swapping bytes on a raw read. - Scripts parsing
read --format json: Stop relying onpre_swap_hex,bytes_swapped, andascii_decoded; move typed interpretation toclient decodeand read itsvaluesarray. - Completion users: Regenerate and re-source your shell completion script so the new
decode/codecscommands and--codeccandidates are available.
Release workflow
- Unchanged from v2.2.4: reusable
go-binary-releaseworkflow and link-time version metadata.
v2.2.4
Date: 2026-03-28
Previous release: v2.2.3
Summary
Maintenance release: internal package boundaries, validation and tests, and Makefile/CI alignment with full-module typecheck. No intentional user-facing CLI or output-format breaks.
Highlights
- Packages: MCAP binary codec, load, and CSV/JSON/blocks exports live in
internal/mcap; client stdout (text/json/table) and MCAP-derived strings / frequencies helpers stay ininternal/format. Command wiring and formatted runs useinternal/runner; typed CLI errors and exit mapping useinternal/errs. - Config & discover: Modbus URL and SunSpec base parsing are split into focused
internal/confighelpers (with tests). Discover adds--force-large-scanandMODBUSCTL_DISCOVER_FORCE_LARGEto allow probing beyond the default unique-host safety cap. - Modbus & MCAP: Capture path resolution, cancel-aware sleeps, clearer layered address validation, multi-unit pooling where applicable; MCAP export paths share load/stitch/sort; fingerprint partial vs failed outcomes and related types are clearer.
- Server: Static handler FC gating and replay comments tightened.
- Tooling:
make compile(go build ./...) feedslint/lint-ci;checkruns fmt, lint, lint-ci, vet, test, and coverage;check-legacyhelps catch duplicate root packages. golangci-lint usesissues.new: falseandrun.tests: trueso the linter sees the same typecheck surface as a full build (see.golangci.yml). - Tests & docs: More unit tests across config, modbus, types, format/mcap, and CLI fatals; exit codes remain documented in
docs/exitcodes.md.
Upgrading
- Same CLI and
--formatcontract as v2.2.3 unless you rely on discover host-count limits—in which case use--force-large-scanwhen you intentionally need a larger probe.
v2.2.3
Date: 2026-03-27
Previous release: v2.2.2
Summary
This release standardizes client command output behind a shared --format flag (text, json, table) and stable JSON DTOs, separates scan/record progress (stderr) from the final summary (stdout), migrates SunSpec off standalone --json to --format json, and improves shell completion for enum-like flags via a small generic helper in internal/cli.
Highlights
- Client output layer: Results for identify, reportserverid, read, fingerprint, diagnostic, scan, record, and client sunspec are produced through
internal/format(format.Write) with typed payloads ininternal/types(*_output.go).internal/modbuscollects data and performs MCAP side effects; it does not print final user-facing results to stdout. --formatand env:MODBUSCTL_OUTPUT_FORMAT/--formaton client commands accept text (default), json, or table. This is distinct frommcap convert, which still uses--format csv|jsonandMODBUSCTL_FORMAT.- JSON contract: Field naming conventions for scripts are documented in
docs/json-output.md;--format jsonis treated as a stable scripting surface (discipline in release notes for intentional changes). - Scan / record: Live progress, debug lines, and worst-case hints go to stderr only; stdout emits one formatted summary after the run—important for
--format jsonpipelines. - SunSpec: Subcommands use the same
--formatmechanism. A hidden parent--jsonremains as a deprecated alias mapping to--format json. - Shell completion: Generic
cli.RegisterEnumFlagCompletion/RegisterEnumFlagCompletionWithDescriptions; canonical value lists live with their domains (e.g.format.Values()for client stdout formats,config.ScanAlgorithms(),ConvertFormats(), etc.).--formatis registered next to flags on each command (no deferred command-tree walk). - Tests: Golden-style checks for representative text output and JSON structure; config/format completion maps stay aligned with allowed values via small tests.
Upgrading
- Scripts parsing client stdout: Prefer
--format jsonand the documented JSON fields; default text output is intended to stay close to prior UX where practical. - SunSpec: Replace
--jsonwith--format json; stop relying onMODBUSCTL_JSON(removed in favor ofMODBUSCTL_OUTPUT_FORMAT). - Scan/record automation: If you previously assumed progress on stdout, capture stderr for logs and stdout for the final summary (or use
--format jsonon stdout only). - Completion users: Regenerate and re-source your shell completion script after upgrading so new
--formatcandidates and descriptions are picked up.
Release workflow
- Unchanged from v2.2.2: reusable
go-binary-releaseworkflow and link-time version metadata.
v2.2.2
Date: 2026-03-23
Previous release: v2.2.1
Summary
This release migrates the Modbus stack to github.com/otfabric/go-modbus, improves release and local builds (including Windows artifacts), and standardizes version metadata via link-time flags. Behavior of the CLI should remain the same for typical use; see Upgrading if you embed scripts or build from source.
Highlights
- Modbus library: Switched from
github.com/otfabric/modbustogithub.com/otfabric/go-modbus(SunSpec helpers live ingo-modbus/sunspec). Device identification, Report Server ID, register I/O, and server APIs were updated to match the new module. - Client defaults: Outbound clients now set
DialTimeout, useNopLogger(orNewStdLoggerwhen--debugis set on read/scan/record), and runValidateConfigbefore connecting. - GitHub releases: Published archives now include Windows builds:
modbusctl-<version>-windows-amd64.zipandmodbusctl-<version>-windows-arm64.zip(each contains the corresponding.exe). Linux and Darwin remain.tar.gzas before. - Version output:
modbusctl versionprints version, tag, commit, and build date, injected at link time (-X main.version=…etc.). Theversion.txtfile is no longer used for versioning. - Local builds:
make build/make build-nocheckwrite the binary tobin/modbusctl(not the repository root). Docker image build copies from that path.
Release workflow
- Releases are driven by the reusable
go-binary-releaseworkflow (see.github/workflows/build-release.yml), with ldflags aligned to themain.*variables above.
Upgrading
- Prebuilt binaries: Download the same platform archive as before; add Windows zips if you deploy on Windows.
- From source: Use
make buildand run./bin/modbusctl, or adjust any scripts that assumed./modbusctlin the repo root. - Go modules: Run
go mod tidyafter pulling; direct importers of the oldotfabric/modbuspath should depend onotfabric/go-modbusinstead.