async-modbus 0.7.2

A lightweight asynchronous Modbus protocol implementation for embedded environments.
Documentation
use std::{array, future::Ready, sync::Mutex};

use async_modbus::client::{read_holdings, read_inputs, write_holding, write_holdings};
use embedded_io_adapters::tokio_1::FromTokio;
use tokio_modbus::{ExceptionCode, Request, server::Service};
use tokio_serial::SerialStream;

struct MyService {
    holdings: Mutex<[u16; 16]>,
    inputs: Mutex<[u16; 16]>,
}

impl MyService {
    fn new() -> Self {
        Self {
            holdings: Mutex::new(array::from_fn(|i| i as u16)),
            inputs: Mutex::new(array::from_fn(|i| 0xa000 + i as u16)),
        }
    }
}

impl Service for MyService {
    type Request = tokio_modbus::Request<'static>;
    type Response = tokio_modbus::Response;
    type Exception = ExceptionCode;
    type Future = Ready<Result<Self::Response, Self::Exception>>;

    fn call(&self, req: Self::Request) -> Self::Future {
        let ret = match req {
            Request::ReadHoldingRegisters(addr, qty) => {
                let holdings = self.holdings.lock().unwrap();

                if (addr as usize) + (qty as usize) > holdings.len() {
                    Err(ExceptionCode::IllegalDataAddress)
                } else {
                    let data = holdings[addr as usize..(addr as usize + qty as usize)].to_vec();
                    Ok(tokio_modbus::Response::ReadHoldingRegisters(data))
                }
            }
            Request::WriteMultipleRegisters(addr, values) => {
                let mut holdings = self.holdings.lock().unwrap();

                if (addr as usize) + values.len() > holdings.len() {
                    Err(ExceptionCode::IllegalDataAddress)
                } else {
                    for (i, v) in values.iter().enumerate() {
                        holdings[addr as usize + i] = *v;
                    }
                    Ok(tokio_modbus::Response::WriteMultipleRegisters(
                        addr,
                        values.len() as u16,
                    ))
                }
            }
            Request::WriteSingleRegister(addr, value) => {
                let mut holdings = self.holdings.lock().unwrap();

                if (addr as usize) < holdings.len() {
                    holdings[addr as usize] = value;
                    Ok(tokio_modbus::Response::WriteSingleRegister(addr, value))
                } else {
                    Err(ExceptionCode::IllegalDataAddress)
                }
            }
            Request::ReadInputRegisters(addr, qty) => {
                let inputs = self.inputs.lock().unwrap();

                if (addr as usize) + (qty as usize) > inputs.len() {
                    Err(ExceptionCode::IllegalDataAddress)
                } else {
                    let data = inputs[addr as usize..(addr as usize + qty as usize)].to_vec();
                    Ok(tokio_modbus::Response::ReadInputRegisters(data))
                }
            }
            _ => unimplemented!(),
        };

        std::future::ready(ret)
    }
}

#[tokio::test]
async fn test_server() -> Result<(), Box<dyn std::error::Error>> {
    let (client, server) = SerialStream::pair()?;

    tokio::spawn(tokio_modbus::server::rtu::Server::new(server).serve_forever(MyService::new()));

    let mut s = FromTokio::new(client);

    assert_eq!(read_holdings(&mut s, 1, 4).await?, [4, 5, 6, 7]);
    write_holding(&mut s, 1, 4, 104).await?;
    write_holdings(&mut s, 1, 6, [59u16.into()]).await?;
    assert_eq!(read_holdings(&mut s, 1, 2).await?, [2, 3, 104, 5, 59, 7, 8]);

    assert_eq!(read_inputs(&mut s, 1, 0).await?, [40_960, 40_961]);

    Ok(())
}