# modbus-rtu
[](https://github.com/im-jababa/rust-modbus-rtu)
[](https://crates.io/crates/modbus-rtu)
[](https://docs.rs/modbus-rtu)
[](https://github.com/im-jababa/rust-modbus-rtu/actions?query=branch%3Amain)
This crate provides helpers for building and decoding standard Modbus RTU request and response packets.
It now ships with a synchronous `Master` that can talk to a serial port directly, while still
exposing the lower-level building blocks for applications that prefer to manage framing themselves.
---
# Usage
## High-level master (auto write/read)
```rust
use modbus_rtu::{Function, Master, Request};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut master = Master::new_rs485("/dev/ttyUSB0", 19_200)?;
let func = Function::ReadHoldingRegisters { starting_address: 0x0000, quantity: 2 };
let request = Request::new(0x01, &func, std::time::Duration::from_millis(200));
let response = master.send(&request)?;
println!("response: {response:?}");
Ok(())
}
```
The master enforces the Modbus RTU silent interval (T3.5) before/after each transmission,
flushes the TX buffer, reads until the slave stops talking, and automatically decodes the reply.
---
## Opting out of the master to shrink binaries
The synchronous master and its `serialport` dependency are enabled by default. If you only need the
packet-building utilities, disable default features in your `Cargo.toml`:
```toml
[dependencies]
modbus-rtu = { version = "1.1", default-features = false }
```
You can always re-enable the high-level API with `features = ["master"]` when needed.
---
## Manual packet construction
First, construct the function you want to issue.
The following example reads four input registers starting at address `0x1234`.
```rust
use modbus_rtu::Function;
let starting_address: u16 = 0x1234;
let quantity: usize = 4;
let function = Function::ReadInputRegisters { starting_address, quantity };
```
Next, build the request with the target device information and timeout.
```rust
use modbus_rtu::{Function, Request};
...
let modbus_id: u8 = 1;
let timeout: std::time::Duration = std::time::Duration::from_millis(100);
let request = Request::new(1, &function, timeout);
```
Finally, convert the request into a Modbus RTU frame.
```rust
...
let packet: Box<[u8]> = request.to_bytes().expect("Failed to build request packet");
```
You can now write `packet` through any transport of your choice (UART, TCP tunnel, etc.).
---
## Receiving
With the original request available, attempt to decode the response bytes as shown below.
```rust
use modbus_rtu::Response;
...
let bytes: &[u8] = ... ; // user-implemented receive logic
let response = Response::from_bytes(&request, bytes).expect("Failed to analyze response packet");
match response {
Response::Value(value) => {
let _ = value[0]; // value at address 0x1234
let _ = value[1]; // value at address 0x1235
let _ = value[2]; // value at address 0x1236
let _ = value[3]; // value at address 0x1237
},
Response::Exception(e) => {
eprintln!("device responded with exception: {e}");
},
_ => unreachable!(),
}
```