ROS 入门:核心概念与编程基础

ROS
4.3k 词

重新系统学习 ROS 过程中的笔记整理,涵盖通信机制、参数系统、时间管理和常用编程模式。

通信机制

话题(Topic)vs 服务(Service)

Topic(话题) Service(服务)
通信模式 发布/订阅 请求/响应
同步性 异步 同步
缓冲区
实时性
结点关系 多对多 一对多(一个Server)
数据类型 msg srv
适用场景 连续高频数据(雷达、里程计) 偶发性逻辑处理(拍照、语音识别)

服务通信适用于对实时性有要求、并有一定逻辑处理的应用场景。话题通信虽实时性不高,但传输效率明显高于服务通信。

服务通信流程

服务通信涉及三个角色:

  • master(管理者):保管 server 和 client 注册信息,匹配并帮助建立连接
  • server(服务端):接收请求并响应
  • client(客户端):发送请求

通信建立在请求响应模式之上,一个节点发送数据后能接收到另一个节点返回的数据。

NodeHandle 与命名空间

NodeHandle 是 ROS 中与节点交互的核心句柄。它不是一个简单的指针,而是系统管理的引用标识——有一个固定的地址指向固定位置,而该位置中的值可以动态变化。

全局 vs 局部命名空间

1
2
ros::NodeHandle n;       // 全局命名空间
ros::NodeHandle nh("~"); // 局部命名空间(使用节点名作为命名空间)

命名规则:

类型 示例 说明
全局名称 /global/name / 开头,全局可访问
相对名称 relative/name 基于默认命名空间
私有名称 ~private/name 基于节点全局名称

实际示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用例1:无默认命名空间
ros::NodeHandle nh;
nh.advertise<Msg>("person_topic", 1); // → /person_topic

// 用例2:有默认命名空间
ros::NodeHandle nh("namespace");
nh.advertise<Msg>("person_topic", 1); // → /namespace/person_topic

// 用例3:topic 本身是全局名
ros::NodeHandle nh("namespace");
nh.advertise<Msg>("/person_topic", 1); // → /person_topic(全局优先)

// 用例4:私有命名空间(节点名 cpp_talker)
ros::NodeHandle nh("~");
nh.advertise<Msg>("person_topic", 1); // → /cpp_talker/person_topic

私有名称不使用当前默认命名空间,而是用节点的全局名称作为命名空间。

参数系统

参数本质是一个中央注册表,支持增删改查。代码和 launch 文件中都可以设置,launch 文件的参数会覆盖代码中的参数。

roscpp 中的两套参数 API

推荐使用 ros::param 方式——它给人的感觉是参数操作是静态的、实例无关的,更符合”参数”的本质。

ros::param 方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <ros/ros.h>

int main(int argc, char **argv) {
ros::init(argc, argv, "param_demo");
ros::NodeHandle nh;
int p1, p2, p3, p4, p5;

// Get
ros::param::get("param1", p1);

// Set
p4 = 4;
ros::param::set("param4", p4);

// Check
if (ros::param::has("param6")) { /* exists */ }

// Delete
ros::param::del("param6");

// Get all param names (ROS Indigo+)
std::vector<std::string> keys;
ros::param::search(keys);
return 0;
}

ros::NodeHandle 方式:

1
2
3
4
5
6
7
8
9
ros::NodeHandle nh;
int p2, p3, p5;

nh.getParam("param2", p2); // Get
nh.param("param3", p3, 33333); // Get with default
nh.setParam("param5", p5); // Set
nh.hasParam("param5"); // Check
nh.deleteParam("param5"); // Delete
nh.getParamNames(keys); // Get all names

launch 文件中加载参数

1
2
3
4
5
6
7
8
9
<launch>
<!-- 全局参数:在 node 标签外 -->
<param name="serial" value="5" />

<node name="name_demo" pkg="name_demo" type="name_demo" output="screen">
<!-- 局部参数:在 node 标签内 -->
<param name="serial" value="10" />
</node>
</launch>

读取全局与局部参数:

1
2
3
4
5
6
7
ros::NodeHandle nh_global;
ros::NodeHandle nh_local("~");

nh_global.getParam("serial", num); // 获取全局 serial
nh_global.getParam("name_demo/serial", num); // 获取局部 serial
nh_local.getParam("serial", num); // 获取局部 serial
nh_local.getParam("/serial", num); // 获取全局 serial

arg 传参

arg 的优势在于不修改 launch 文件就能改变参数值

1
2
<arg name="a" default="1" />
<arg name="b" default="2" />

命令行覆盖:

1
roslaunch my_pkg launch_file.launch a:=10 b:=20

YAML 参数文件

当系统复杂时,用 YAML 文件集中管理参数:

1
<rosparam file="$(find pkg)/config/params.yaml" command="load" ns="namespace" />

等价于终端命令:rosparam load params.yaml

时间管理

ros::Time 与仿真时间

1
ros::Time now = ros::Time::now();  // 系统时间或仿真时间,取决于 use_sim_time
  • use_sim_time=true → 输出仿真时间(如 rosbag 回放的时间)
  • use_sim_time=false → 输出系统时间

设置 use_sim_time:

1
2
<!-- launch 文件 -->
<param name="use_sim_time" value="false" />
1
2
# 终端
rosparam set use_sim_time true

如果使用模拟时钟,ros::Time::now() 在收到第一条 /clock 消息前返回 0。需要真实时间时,使用 ros::WallTime::now()

ros::Duration

1
2
3
4
ros::Time begin = ros::Time::now();
ros::Duration(0.1); // 0.1 秒
ros::Duration(60); // 60 秒
ros::Duration(begin, ros::Time::now()); // 时间差

定时器 ros::Timer

定时器每隔一段时间调用一次回调函数:

1
2
3
4
5
// 创建定时器
ros::Timer timer = nh.createTimer(ros::Duration(0.1), timerCallback);

// 完整签名
ros::Timer createTimer(ros::Duration period, callback, bool oneshot = false);
  • period:回调间隔,如 ros::Duration(0.1) 即每 0.1 秒
  • oneshot:是否只执行一次

定时器可通过 stop()setPeriod()start() 重置:

1
void timerCallback(const ros::TimerEvent& e);  // 必须带参数

ros::TimerEvent 提供时间诊断信息:last_expectedlast_realcurrent_expectedcurrent_real

调试技巧

日志打印

1
2
ROS_INFO_STREAM_ONCE("只打印一次");                           // 确认数据接收但不刷屏
ROS_INFO_STREAM_THROTTLE(0.5, "每0.5秒打印一次"); // 降低高频回调的日志量

查看日志:rqt_console

过滤日志等级:rosrun rqt_logger_level rqt_logger_level

Debug 信息默认不显示,需要先启动 rqt_logger_level 手动开启。

相关链接