Rust Modbus协议库tokio-modbus的使用:异步支持RTU和TCP通信的高性能工业设备控制

Rust Modbus协议库tokio-modbus的使用:异步支持RTU和TCP通信的高性能工业设备控制

概述

tokio-modbus是一个基于Tokio的纯Rust Modbus库,支持异步RTU和TCP通信,适用于工业设备控制场景。

特性

  • 纯Rust实现
  • 支持Modbus TCP和RTU通信
  • 同时提供异步(默认)和同步(可选)API
  • 客户端API
  • 服务器实现
    • 开箱即用的实现
    • 可作为定制实现的起点
  • 开源(MIT/Apache-2.0双协议)

安装

在Cargo.toml中添加依赖:

[dependencies]
tokio-modbus = "*"

特性开关

  • "rtu": 异步RTU客户端(默认启用)
  • "tcp": 异步TCP客户端(默认启用)
  • "rtu-sync": 同步RTU客户端
  • "tcp-sync": 同步TCP客户端
  • "rtu-server": (异步)RTU服务器
  • "tcp-server": (异步)TCP服务器
  • "rtu-over-tcp-server": (异步)RTU over TCP服务器

按需启用特性示例

仅需要异步TCP客户端:

[dependencies]
tokio-modbus = { version = "*", default-features = false, features = ["tcp"] }

异步RTU客户端:

[dependencies]
tokio-modbus = { version = "*", default-features = false, features = ["rtu"] }

RTU服务器:

[dependencies]
tokio-modbus = { version = "*", default-features = false, features = ["rtu-server"] }

TCP服务器:

[dependencies]
tokio-modbus = { version = "*", default-features = false, features = ["tcp-server"] }

完整示例代码

异步TCP客户端示例

use tokio_modbus::client::Context;
use tokio_modbus::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到Modbus TCP服务器
    let tcp_client = tcp::connect("127.0.0.1:502").await?;

    // 创建Modbus客户端上下文
    let mut ctx = Context::new(tcp_client);

    // 读取保持寄存器
    let addr = 0x00;  // 寄存器地址
    let cnt = 5;      // 读取数量
    let response = ctx.read_holding_registers(addr, cnt).await?;
    println!("读取到的寄存器值: {:?}", response);

    // 写入单个寄存器
    let addr = 0x01;  // 寄存器地址
    let value = 42;   // 写入值
    ctx.write_single_register(addr, value).await?;
    println!("成功写入寄存器");

    Ok(())
}

异步RTU客户端示例

use tokio_modbus::client::Context;
use tokio_modbus::prelude::*;
use tokio_serial::SerialStream;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 配置串口参数
    let builder = tokio_serial::new("/dev/ttyUSB0", 19200)
        .data_bits(tokio_serial::DataBits::Eight)
        .parity(tokio_serial::Parity::None)
        .stop_b bits(tokio_serial::StopBits::One);

    // 打开串口
    let port = SerialStream::open(&builder)?;

    // 创建RTU客户端
    let rtu_client = rtu::connect(port).await?;

    // 创建Modbus客户端上下文
    let mut ctx = Context::new(rtu_client);

    // 读取输入寄存器
    let addr = 0x00;  // 寄存器地址
    let cnt = 3;      // 读取数量
    let response = ctx.read_input_registers(addr, cnt).await?;
    println!("读取到的输入寄存器值: {:?}", response);

    // 写入单个线圈
    let addr = 0x02;  // 线圈地址
    let value = true; // 写入值
    ctx.write_single_coil(addr, value).await?;
    println!("成功写入线圈");

    Ok(())
}

异步TCP服务器示例

use tokio_modbus::prelude::*;
use tokio_modbus::server::tcp::Server;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建Modbus TCP服务器
    let server = Server::new("127.0.0.1:502".parse().unwrap());

    // 创建服务处理程序
    let handler = std::sync::Arc::new(MyHandler::new());
    
    // 启动服务器
    server.serve(handler).await?;

    Ok(())
}

struct MyHandler;

impl MyHandler {
    fn new() -> Self {
        Self
    }
}

impl tokio_modbus::server::Service for MyHandler {
    type Request = Request<'static>;
    type Response = Response;
    type Error = std::io::Error;
    type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>>;

    fn call(&self, req: Self::Request) -> Self::Future {
        Box::pin(async move {
            match req {
                Request::ReadInputRegisters(addr, cnt) => {
                    // 模拟读取输入寄存器
                    let values = vec![0; cnt as usize];
                    Ok(Response::ReadInputRegisters(values))
                }
                Request::WriteSingleRegister(addr, value) => {
                    // 模拟写入寄存器
                    Ok(Response::WriteSingleRegister)
                }
                _ => Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidInput,
                    "Unsupported request",
                )),
            }
        })
    }
}

测试

可以使用以下命令测试所有功能:

cargo test --workspace
cargo test --workspace --all-features

协议规范

  • Modbus应用协议规范v1.1b3
  • Modbus串行通信规范与实现指南v1.02
  • Modbus TCP/IP实现指南v1.0b

许可证

MIT/Apache-2.0双许可


1 回复

Rust Modbus协议库tokio-modbus的使用指南

概述

tokio-modbus是一个基于Rust异步运行时Tokio的Modbus协议实现库,支持RTU和TCP通信模式,专为高性能工业设备控制设计。它提供了异步API,适合在现代Rust应用中集成Modbus设备通信功能。

主要特性

  • 支持Modbus RTU和TCP协议
  • 基于Tokio的异步实现
  • 客户端和服务端实现
  • 线程安全的设计
  • 支持常见Modbus功能码

安装

在Cargo.toml中添加依赖:

[dependencies]
tokio-modbus = "0.10"
tokio = { version = "1.0", features = ["full"] }

基本使用方法

Modbus TCP客户端示例

use tokio_modbus::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到Modbus TCP服务器
    let socket_addr = "192.168.1.100:502".parse().unwrap();
    let mut ctx = tcp::connect(socket_addr).await?;

    // 读取保持寄存器
    let registers = ctx.read_holding_registers(0x00, 5).await?;
    println!("读取的寄存器值: {:?}", registers);

    // 写入单个寄存器
    ctx.write_single_register(0x01, 1234).await?;
    
    Ok(())
}

Modbus RTU客户端示例

use tokio_modbus::prelude::*;
use tokio_serial::SerialStream;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 配置串口
    let builder = tokio_serial::new("/dev/ttyUSB0", 19200)
        .data_bits(tokio_serial::DataBits::Eight)
        .parity(tokio_serial::Parity::None)
        .stop_b bits(tokio_serial::StopBits::One);
    
    // 创建RTU上下文
    let port = SerialStream::open(&builder).unwrap();
    let mut ctx = rtu::connect(port).await?;

    // 读取线圈状态
    let coils = ctx.read_coils(0x00, 5).await?;
    println!("线圈状态: {:?}", coils);

    Ok(())
}

Modbus服务器示例

use tokio_modbus::prelude::*;
use tokio_modbus::server::tcp::Server;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let server = Server::new("0.0.0.0:5502");
    
    let new_service = |_| {
        // 实现服务逻辑
        async move {
            Ok(())
        }
    };
    
    server.serve(new_service).await?;
    
    Ok(())
}

高级功能

批量操作

// 批量读取多个寄存器
let registers = ctx.read_holding_registers(0x00, 10).await?;

// 批量写入多个寄存器
ctx.write_multiple_registers(0x10, &[100, 200, 300]).await?;

错误处理

match ctx.read_coils(0x00, 5).await {
    Ok(coils) => println!("读取成功: {:?}", coils),
    Err(e) => eprintln!("Modbus错误: {}", e),
}

性能优化建议

  1. 复用上下文对象而不是频繁创建
  2. 对于高频操作,考虑使用批量读写方法
  3. 在RTU模式下调整适当的超时设置
  4. 在高并发场景下使用连接池

注意事项

  • RTU模式需要正确配置串口参数(波特率、数据位、停止位等)
  • TCP模式需要确保网络连接稳定
  • 工业环境中考虑添加重试机制
  • 注意Modbus协议中的地址偏移问题(有些设备从0开始,有些从1开始)

完整示例代码

完整的Modbus TCP客户端示例

use tokio_modbus::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接到Modbus TCP服务器
    let socket_addr = "192.168.1.100:502".parse().unwrap();
    println!("正在连接到Modbus TCP服务器: {}", socket_addr);
    
    let mut ctx = tcp::connect(socket_addr).await?;
    println!("连接成功");

    // 读取保持寄存器
    println!("读取保持寄存器0x00-0x04");
    let registers = ctx.read_holding_registers(0x00, 5).await?;
    println!("读取的寄存器值: {:?}", registers);

    // 写入单个寄存器
    println!("写入寄存器0x01, 值1234");
    ctx.write_single_register(0x01, 1234).await?;
    println!("写入成功");

    // 批量写入多个寄存器
    println!("批量写入寄存器0x10-0x12");
    let data = vec![100, 200, 300];
    ctx.write_multiple_registers(0x10, &data).await?;
    println!("批量写入完成");

    Ok(())
}

完整的Modbus RTU客户端示例

use tokio_modbus::prelude::*;
use tokio_serial::SerialStream;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 配置串口
    let port_name = "/dev/ttyUSB0";
    let baud_rate = 19200;
    
    println!("配置串口{},波特率{}", port_name, baud_rate);
    let builder = tokio_serial::new(port_name, baud_rate)
        .data_bits(tokio_serial::DataBits::Eight)
        .parity(tokio_serial::Parity::None)
        .stop_bits(tokio_serial::StopBits::One);
    
    // 创建RTU上下文
    println!("正在打开串口...");
    let port = SerialStream::open(&builder).unwrap();
    let mut ctx = rtu::connect(port).await?;
    println!("RTU连接成功");

    // 读取线圈状态
    println!("读取线圈状态0x00-0x04");
    let coils = ctx.read_coils(0x00, 5).await?;
    println!("线圈状态: {:?}", coils);

    // 写入单个线圈
    println!("写入线圈0x01,状态true");
    ctx.write_single_coil(0x01, true).await?;
    println!("写入成功");

    Ok(())
}

完整的Modbus TCP服务器示例

use tokio_modbus::prelude::*;
use tokio_modbus::server::tcp::Server;
use tokio_modbus::server::Service;

struct ModbusService;

impl Service for ModbusService {
    type Request = Request;
    type Response = Response;
    type Error = std::io::Error;
    type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>>;

    fn call(&self, req: Self::Request) -> Self::Future {
        Box::pin(async move {
            match req {
                Request::ReadInputRegisters(addr, cnt) => {
                    println!("读取输入寄存器: addr={}, cnt={}", addr, cnt);
                    let mut values = vec![0; cnt as usize];
                    for i in 0..cnt {
                        values[i as usize] = i;
                    }
                    Ok(Response::ReadInputRegisters(values))
                }
                Request::WriteSingleRegister(addr, value) => {
                    println!("写入单个寄存器: addr={}, value={}", addr, value);
                    Ok(Response::WriteSingleRegister(addr, value))
                }
                _ => {
                    eprintln!("不支持的请求: {:?}", req);
                    Err(std::io::Error::new(std::io::ErrorKind::Other, "不支持的操作"))
                }
            }
        })
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let server = Server::new("0.0.0.0:5502");
    
    println!("启动Modbus TCP服务器在0.0.0.0:5502");
    
    server.serve(|| Ok(ModbusService)).await?;
    
    Ok(())
}

tokio-modbus为Rust开发者提供了强大而灵活的Modbus通信能力,特别适合需要高性能和异步处理的工业自动化应用场景。

回到顶部