rodbus 0.1.1

A high-performance implementation of the Modbus protocol using Tokio and async/await
Documentation
rodbus-0.1.1 has been yanked.

A high-performance implementation of the Modbus protocol using Tokio and Rust's async/await syntax.

Features

  • Panic-free parsing
  • Focus on maximal correctness and compliance to the specification
  • Automatic connection management with configurable reconnect strategy
  • Scalable performance using Tokio's multi-threaded executor
  • async (futures), callbacks, and synchronous API modes
  • Idiomatic C API for integration with legacy codebases

Supported modes

  • TCP client and server

Supported Functions

  • Read Coils
  • Read Discrete Inputs
  • Read Holding Registers
  • Read Input Registers
  • Write Single Coil
  • Write Single Register
  • Write Multiple Coils
  • Write Multiple Registers

Future support

  • TLS Client / TLS Server + Modbus X.509 extensions using Rustls
  • Additional function code support
  • Modbus RTU over serial

Example Client

A simple client application that periodically polls for some Coils

use rodbus::prelude::*;

use std::net::SocketAddr;
use std::time::Duration;
use std::str::FromStr;


use tokio::time::delay_for;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

let channel = spawn_tcp_client_task(
SocketAddr::from_str("127.0.0.1:502")?,
10,
strategy::default()
);

let mut session = channel.create_session(
UnitId::new(0x02),
Duration::from_secs(1)
);

// try to poll for some coils every 3 seconds
loop {
match session.read_coils(AddressRange::new(0, 5)).await {
Ok(values) => {
for x in values {
println!("index: {} value: {}", x.index, x.value)
}
},
Err(err) => println!("Error: {:?}", err)
}

delay_for(std::time::Duration::from_secs(3)).await
}
}

Example Server

use std::net::SocketAddr;
use std::str::FromStr;

use tokio::net::TcpListener;
use rodbus::prelude::*;

struct CoilsOnlyHandler {
pub coils: [bool; 10]
}

impl CoilsOnlyHandler {
fn new() -> Self {
Self {
coils: [false; 10]
}
}
}

impl ServerHandler for CoilsOnlyHandler {
fn read_coils(&mut self, range: AddressRange) -> Result<&[bool], details::ExceptionCode> {
Self::get_range_of(self.coils.as_ref(), range)
}
}

#[tokio::main(threaded_scheduler)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

let handler = CoilsOnlyHandler::new().wrap();

// map unit ids to a handler for processing requests
let map = ServerHandlerMap::single(UnitId::new(1), handler.clone());

// spawn a server to handle connections onto its own task
rodbus::server::spawn_tcp_server_task(
1,
TcpListener::bind(SocketAddr::from_str("127.0.0.1:502")?).await?,
map,
);

let mut next = tokio::time::Instant::now();

// toggle all coils every couple of seconds
loop {
next += tokio::time::Duration::from_secs(2);
{
let mut guard = handler.lock().await;
for c in &mut guard.coils {
*c = !*c;
}
}
tokio::time::delay_until(next).await;
}
}