rmodbus - Modbus for Rust

A framework to build fast and reliable Modbus-powered applications.

Cargo crate: https://crates.io/crates/rmodbus

What is rmodbus

rmodbus is not a yet another Modbus server. rmodbus is a set of tools to quickly build Modbus-powered applications.

Why yet another Modbus lib?

So the server isn't included?

Yes, there's no server included. You build the server by your own. You choose protocol, technology and everything else. rmodbus just process frames and works with Modbus context.

Here's an example of a simple TCP blocking server:

```rust use std::io::{Read, Write}; use std::net::TcpListener; use std::thread;

use rmodbus::server::{ModbusFrame, ModbusProto, process_frame};

pub fn tcpserver(unit: u8, listen: &str) { let listener = TcpListener::bind(listen).unwrap(); println!("listening started, ready to accept"); for stream in listener.incoming() { thread::spawn(move || { println!("client connected"); let mut stream = stream.unwrap(); loop { let mut buf: ModbusFrame = [0; 256]; let mut response = Vec::new(); // for nostd use FixedVec with alloc [u8;256] if stream.read(&mut buf).unwrapor(0) == 0 { return; } if processframe(unit, &buf, ModbusProto::TcpUdp, &mut response).iserr() { println!("server error"); return; } println!("{:x?}", response.asslice()); if !response.isempty() { if stream.write(response.asslice()).is_err() { return; } } } }); } } ```

There are also examples for Serial-RTU and UDP in examples folder (if you're reading this text somewhere else, visit rmodbus project repository.

Modbus context

The rule is simple: one standard Modbus context per application. 10k+10k 16-bit registers and 10k+10k coils are usually more than enough. This takes about 43Kbytes of RAM, but if you need to reduce context size, download library source and change CONTEXT_SIZE constant in "context.rs".

rmodbus server context is thread-safe, easy to use and has a lot of functions.

The context is created automatically, as soon as the library is imported. No additional action is required.

Every time Modbus context is accessed, a context mutex must be locked. This slows down a performance, but guarantees that the context always has valid data after bulk-sets or after 32-bit data types were written. So make sure your application locks context only when required and only for a short period time.

There are two groups of context functions:

Take a look at simple PLC example:

```rust use rmodbus::server::context; use std::fs::File; use std::io::prelude::*;

fn looping() { loop { // READ WORK MODES ETC let mut ctx = context::CONTEXT.lock().unwrap(); let param1 = context::get(1000, &ctx.holdings).unwrap(); let _param2 = context::getf32(1100, &ctx.holdings).unwrap(); // ieee754 f32 let param3 = context::getu32(1200, &ctx.holdings).unwrap(); // u32 let cmd = context::get(1500, &ctx.holdings).unwrap(); context::set(1500, 0, &mut ctx.holdings).unwrap(); if cmd != 0 { println!("got command code {}", cmd); match cmd { 1 => { println!("saving memory context"); //let _ = savelocked("/tmp/plc1.dat", &ctx).maperr(|| { //eprintln!("unable to save context!"); //}); } _ => println!("command not implemented"), } } drop(ctx); // ============================================== // DO SOME JOB // .......... // WRITE RESULTS let mut ctx = context::CONTEXT.lock().unwrap(); context::set(0, true, &mut ctx.coils).unwrap(); context::setbulk(10, &(vec![10, 20]), &mut ctx.holdings).unwrap(); context::set_f32(20, 935.77, &mut ctx.inputs).unwrap(); } } ```

To let the above program communicate with outer world, Modbus server must be up and running in the separate thread, asynchronously or whatever is preferred.

no_std

rmodbus supports working in no_std mode. Most of the library code is written the way to support both std and no_std.

Switching library to no_std

I found no way to publish 2 libraries from the single crate with cargo. To switch to no_std:

When switched, library loads traits for different types, so if something's wrong, the project will just fail to build.

Types and crates in no_std mode

Single-threaded and async apps

Single-threaded applications can gain up to +60-100% speed boost by removing Modbus context mutex. This can be performed by running "make switch-nostd-single" and replacing mutex with a fake one. For the compatibility, the context still need to be "unlocked", however the fake mutex does this instantly and without any CPU overhead.

Modbus client

Planned.