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),
}
性能优化建议
- 复用上下文对象而不是频繁创建
- 对于高频操作,考虑使用批量读写方法
- 在RTU模式下调整适当的超时设置
- 在高并发场景下使用连接池
注意事项
- 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通信能力,特别适合需要高性能和异步处理的工业自动化应用场景。