Small Rust Modbus library with typed request/response PDUs and a reusable Modbus TCP transport.
- Typed
ModbusRequestandModbusResponseenums for the common Modbus function codes - Request serialization and response parsing for:
- read coils
- read discrete inputs
- read holding registers
- read input registers
- write single coil
- write single register
- write multiple coils
- write multiple registers
- Modbus TCP transport with:
- lazy connect or explicit
connect() - reusable connections
- retry of transient I/O failures
- per-phase timeouts for connect, write, and read
- request/response validation for transaction ID, protocol ID, unit ID, and echoed payloads
- lazy connect or explicit
- Support for talking to multiple unit IDs through one Modbus TCP connection handle
- Optional CLI example behind the
clifeature
Add the crate to your project:
[dependencies]
tiny-mb = "0.1.0"Create a typed request and send it over Modbus TCP:
use std::net::{IpAddr, Ipv4Addr};
use tiny_mb::tcp::ModbusTcpConnection;
use tiny_mb::{ModbusRequest, ModbusResponse};
#[tokio::main]
async fn main() -> Result<(), tiny_mb::ModbusError> {
let connection = ModbusTcpConnection::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 502, 1, 1);
let response = connection
.send_message(&ModbusRequest::ReadHoldingRegisters {
starting_address: 100,
quantity: 2,
})
.await?;
match response {
ModbusResponse::ReadHoldingRegisters { registers } => {
println!("registers: {registers:?}");
}
other => {
println!("unexpected response: {other:?}");
}
}
Ok(())
}The TCP client opens connections lazily, retries short-lived I/O failures, and can reuse the same socket across multiple requests.
Use send_message_with_unit_id or with_unit_id(...) when talking to multiple devices behind one
Modbus TCP gateway.
ModbusTcpConnection::new(...) uses sensible defaults for connect, write, and read timeouts.
If you need custom limits, construct the connection with with_timeouts(...).
use std::net::{IpAddr, Ipv4Addr};
use std::time::Duration;
use tiny_mb::tcp::{ModbusTcpConnection, ModbusTcpTimeouts};
let connection = ModbusTcpConnection::with_timeouts(
IpAddr::V4(Ipv4Addr::LOCALHOST),
502,
1,
1,
ModbusTcpTimeouts {
connect_timeout: Duration::from_secs(2),
write_timeout: Duration::from_secs(2),
read_timeout: Duration::from_secs(2),
},
);Transport and protocol failures are reported with ModbusError, including:
- connect, write, and read errors/timeouts
- malformed or invalid responses
- Modbus exception responses
- transaction ID, protocol ID, and unit ID mismatches
- request/response mismatches
- request validation errors
The repository includes a modbus_cli example for interactive Modbus TCP reads and writes across
all supported function codes.
Enable the cli feature when building or running it so the extra CLI-only dependencies are not
pulled into library-only builds.
Show the CLI help:
cargo run --features cli --example modbus_cli -- --helpThe CLI is organized as nested read/write subcommands:
modbus_cli [OPTIONS] read <coils|discrete|holding|input> <ADDRESS> <QUANTITY>
modbus_cli [OPTIONS] write <coil|register|coils|registers> <ADDRESS> <VALUE...>
Examples:
cargo run --features cli --example modbus_cli -- read coils 0 8
cargo run --features cli --example modbus_cli -- --address 192.168.1.10 read holding 100 4
cargo run --features cli --example modbus_cli -- write coil 12 on
cargo run --features cli --example modbus_cli -- --output hex write register 200 4660
cargo run --features cli --example modbus_cli -- write coils 16 1 0 1 1
cargo run --features cli --example modbus_cli -- write registers 300 10 20 30
cargo run --features cli --example modbus_cli -- -a 192.168.0.89 write register 5004 6000The CLI accepts:
- coil values as
1,0,true,false,on, oroff - register output in
decimalorhex - global connection options such as
--address,--port,--unit-id, and--transaction-id