Rust ROS2通信中间件库r2r的使用,实现高效机器人操作系统2(ROS2)的节点通信与消息处理

Rust ROS2通信中间件库r2r的使用,实现高效机器人操作系统2(ROS2)的节点通信与消息处理

R2R是一个易于使用、运行时无关的异步Rust绑定库,用于ROS2。它不需要接入ROS2构建基础设施,只需使用cargo build即可完成构建。通过调用C自省库生成方便的Rust类型,绕过了ROS2的.msg/.idl管道,依赖于已生成的C代码。

使用方法

  1. 确保已安装libclang(例如Ubuntu上的libclang-dev)
  2. 在Cargo.toml中添加依赖:r2r = "0.9.5"
  3. 在构建/运行前需要source您的ROS2安装环境
  4. 绑定会在您source工作空间后自动重建
  5. 如果修改了现有消息类型,运行cargo clean -p r2r_msg_gen强制在下一次构建时重新编译Rust消息类型

示例使用:

. /opt/ros/humble/setup.sh
cargo build
cargo run --example subscriber
# 在另一个终端
ros2 topic pub /topic std_msgs/msg/String "data: 'Hello, world'"

构建时间说明

由于默认行为是构建所有已source的消息类型,对于较大的工作空间,构建时间可能会迅速增加。为避免构建所有内容,可以使用环境变量IDL_PACKAGE_FILTER声明仅需要的消息包。

当前功能

  • 支持最新的ROS2版本(Foxy、Galactic、Humble、Iron、Jazzy)
  • 构建Rust类型
  • 发布/订阅
  • 服务
  • 动作
  • 参数处理
  • 模拟时间(构建时确保已sourcerosgraph_msgs以启用)
  • 可在Linux、OSX和Windows上运行

完整示例代码

下面是一个完整的Rust ROS2节点通信示例,包含发布者和订阅者:

use r2r;
use std::sync::{Arc, Mutex};
use tokio::task;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化ROS2上下文
    let ctx = r2r::Context::create()?;
    let mut node = r2r::Node::create(ctx, "rust_node", "")?;
    
    // 创建发布者
    let publisher = node.create_publisher::<r2r::std_msgs::msg::String>("/topic")?;
    
    // 创建订阅者
    let subscriber = node.subscribe::<r2r::std_msgs::msg::String>("/topic")?;
    
    // 使用Arc和Mutex共享数据
    let counter = Arc::new(Mutex::new(0));
    
    // 启动发布任务
    let pub_counter = counter.clone();
    task::spawn(async move {
        let mut msg = r2r::std_msgs::msg::String::default();
        loop {
            {
                let mut count = pub_counter.lock().unwrap();
                *count += 1;
                msg.data = format!("Hello from Rust! Count: {}", *count);
            }
            publisher.publish(&msg).unwrap();
            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        }
    });
    
    // 处理订阅消息
    while let Ok(msg) = subscriber.recv().await {
        println!("Received: {}", msg.data);
    }
    
    Ok(())
}

服务示例

use r2r;
use tokio::task;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ctx = r2r::Context::create()?;
    let mut node = r2r::Node::create(ctx, "rust_service_node", "")?;
    
    // 创建服务客户端
    let client = node.create_client::<r2r::example_interfaces::srv::AddTwoInts>("/add_two_ints")?;
    
    // 创建服务服务器
    let server = node.create_service::<r2r::example_interfaces::srv::AddTwoInts, _>(
        "/add_two_ints",
        move |req| {
            println!("Received request: {} + {}", req.a, req.b);
            let sum = req.a + req.b;
            Ok(r2r::example_interfaces::srv::AddTwoIntsResponse { sum })
        },
    )?;
    
    // 发送服务请求
    task::spawn(async move {
        let mut req = r2r::example_interfaces::srv::AddTwoIntsRequest::default();
        req.a = 2;
        req.b = 3;
        
        match client.request(&req).await {
            Ok(response) => println!("Got response: {}", response.sum),
            Err(e) => println!("Error: {:?}", e),
        }
    });
    
    // 保持节点运行
    loop {
        node.spin_once(std::time::Duration::from_millis(100)).await;
    }
}

动作(Action)服务完整示例

下面是一个实现ROS2动作服务的完整示例:

use r2r;
use tokio::task;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ctx = r2r::Context::create()?;
    let mut node = r2r::Node::create(ctx, "rust_action_node", "")?;

    // 创建动作服务器
    let action_server = node.create_action_server::<
        r2r::example_interfaces::action::Fibonacci,
    >(
        "/fibonacci",
        |goal_handle| async move {
            println!("Received goal request with order: {}", goal_handle.goal.order);
            
            let mut feedback = r2r::example_interfaces::action::FibonacciFeedback::default();
            let mut result = r2r::example_interfaces::action::FibonacciResult::default();
            let mut sequence = vec![0, 1];
            
            for i in 1..goal_handle.goal.order as usize {
                let next = sequence[i-1] + sequence[i];
                sequence.push(next);
                
                // 发送反馈
                feedback.sequence = sequence.clone();
                goal_handle.publish_feedback(&feedback).unwrap();
                
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
            }
            
            result.sequence = sequence;
            goal_handle.succeed(result).unwrap();
            
            Ok(())
        },
    )?;

    // 创建动作客户端
    let action_client = node.create_action_client::<
        r2r::example_interfaces::action::Fibonacci,
    >("/fibonacci")?;

    // 发送动作请求
    task::spawn(async move {
        let mut goal = r2r::example_interfaces::action::FibonacciGoal::default();
        goal.order = 5;
        
        match action_client.send_goal(&goal).await {
            Ok((goal_handle, result_future)) => {
                // 处理反馈
                let feedback_stream = goal_handle.get_feedback_stream();
                tokio::spawn(async move {
                    while let Ok(feedback) = feedback_stream.recv().await {
                        println!("Received feedback: {:?}", feedback.sequence);
                    }
                });
                
                // 等待结果
                match result_future.await {
                    Ok(result) => println!("Final result: {:?}", result.sequence),
                    Err(e) => println!("Error: {:?}", e),
                }
            }
            Err(e) => println!("Failed to send goal: {:?}", e),
        }
    });

    // 保持节点运行
    loop {
        node.spin_once(std::time::Duration::from_millis(100)).await;
    }
}

参数处理完整示例

use r2r;
use tokio::task;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ctx = r2r::Context::create()?;
    let mut node = r2r::Node::create(ctx, "rust_param_node", "")?;

    // 声明参数
    node.declare_parameter("my_int_param", 42)?;
    node.declare_parameter("my_string_param", "hello")?;
    node.declare_parameter("my_double_param", 3.14)?;

    // 设置参数
    node.set_parameter("my_int_param", 100)?;

    // 获取参数
    match node.get_parameter::<i32>("my_int_param") {
        Ok(value) => println!("Got int parameter: {}", value),
        Err(e) => println!("Failed to get parameter: {:?}", e),
    }

    // 列出所有参数
    for param in node.list_parameters()? {
        println!("Parameter: {}", param);
    }

    // 保持节点运行
    loop {
        node.spin_once(std::time::Duration::from_millis(100)).await;
    }
}

许可证

除非在源文件中另有说明,所有代码均采用MIT许可证。包含的来自rclrust包的部分代码采用Apache 2.0许可证。


1 回复

Rust ROS2通信中间件库r2r使用指南

简介

r2r是一个纯Rust实现的ROS2客户端库,允许开发者用Rust语言创建ROS2节点,实现与ROS2系统的通信。它提供了发布/订阅模式、服务调用、参数管理等核心ROS2功能,同时利用Rust的安全性和高性能特性。

主要特性

  • 纯Rust实现,无C++依赖
  • 支持ROS2 DDS中间件通信
  • 线程安全的设计
  • 支持常见ROS2消息类型
  • 异步/同步API

安装

在Cargo.toml中添加依赖:

[dependencies]
r2r = "0.7"

完整示例代码

1. 完整的发布-订阅示例

use r2r::{Context, Node, QosProfile};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化上下文和节点
    let ctx = Context::create()?;
    let mut node = Node::create(ctx, "rust_pubsub_node", "")?;
    
    // 创建发布者 - 发布String消息到/rust_chatter话题
    let publisher = node.create_publisher::<r2r::std_msgs::msg::String>(
        "/rust_chatter", 
        QosProfile::default()
    )?;
    
    // 创建订阅者 - 订阅/rust_chatter话题
    let mut subscriber = node.subscribe::<r2r::std_msgs::msg::String>(
        "/rust_chatter", 
        QosProfile::default()
    )?;
    
    // 异步处理接收到的消息
    tokio::spawn(async move {
        while let Ok(msg) = subscriber.recv().await {
            println!("[Subscriber] 收到消息: {}", msg.data);
        }
    });
    
    // 发布10条消息
    for i in 0..10 {
        let msg = r2r::std_msgs::msg::String {
            data: format!("这是来自Rust的第{}条消息", i+1),
        };
        publisher.publish(&msg)?;
        println!("[Publisher] 已发送: {}", msg.data);
        tokio::time::sleep(Duration::from_millis(500)).await;
    }
    
    Ok(())
}

2. 完整的服务通信示例

use r2r::{Context, Node, QosProfile};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ctx = Context::create()?;
    let mut node = Node::create(ctx, "rust_service_node", "")?;
    
    // 创建加法服务服务器
    let mut add_service = node.create_service::<r2r::example_interfaces::srv::AddTwoInts, _>(
        "/add_two_ints_rust",
        Box::new(|request, _header| {
            println!("[Server] 收到请求: {} + {}", request.a, request.b);
            let sum = request.a + request.b;
            Ok(r2r::example_interfaces::srv::AddTwoIntsResponse { sum })
        }),
    )?;
    
    // 创建服务客户端
    let client = node.create_client::<r2r::example_interfaces::srv::AddTwoInts>(
        "/add_two_ints_rust"
    )?;
    
    // 等待服务可用
    println!("[Client] 等待服务准备就绪...");
    while !client.service_ready()? {
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    }
    
    // 发送3个加法请求
    for i in 1..=3 {
        let req = r2r::example_interfaces::srv::AddTwoIntsRequest { 
            a: i * 10, 
            b: i * 5 
        };
        println!("[Client] 发送请求: {} + {}", req.a, req.b);
        
        match client.request(&req)?.await {
            Ok(response) => println!("[Client] 收到响应: 和 = {}", response.sum),
            Err(e) => println!("[Client] 请求失败: {}", e),
        }
        
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }
    
    Ok(())
}

3. 带参数管理的完整节点示例

use r2r::{Context, Node, ParameterValue};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ctx = Context::create()?;
    let mut node = Node::create(ctx, "rust_param_node", "")?;
    
    // 声明节点参数
    node.declare_parameter("robot_name", "Rusty")?;
    node.declare_parameter("speed_limit", 2.5)?;
    node.declare_parameter("enable_logging", true)?;
    
    // 获取并打印参数值
    if let Some(name) = node.get_parameter("robot_name")? {
        println!("机器人名称: {}", name.value);
    }
    
    if let Some(speed) = node.get_parameter("speed_limit")? {
        println!("速度限制: {} m/s", speed.value);
    }
    
    if let Some(logging) = node.get_parameter("enable_logging")? {
        println!("日志记录已{}", if logging.value { "启用" } else { "禁用" });
    }
    
    // 修改参数值
    node.set_parameter("speed_limit", 3.0)?;
    println!("已更新速度限制参数");
    
    // 列出所有参数
    println!("当前所有参数:");
    for param in node.list_parameters()? {
        println!("- {}: {:?}", param.name, param.value);
    }
    
    Ok(())
}

性能优化建议

  1. 使用QosProfile调整服务质量策略
  2. 对于高频消息,考虑使用零拷贝技术
  3. 合理使用异步API避免阻塞
  4. 批量处理消息减少系统调用

注意事项

  • 确保ROS2环境已正确设置(source ROS2环境)
  • 目前r2r支持ROS2 Foxy和Humble版本
  • 某些高级ROS2功能可能尚未实现

r2r为Rust开发者提供了与ROS2生态系统交互的高效方式,结合了Rust的性能优势与ROS2的机器人开发能力。

回到顶部