1 .理论模型
话题通信实现模型是比较复杂的,该模型如下图所示,该模型中涉及到三个角色:
- ROS Master (管理者)
- Talker (发布者)
- Listener (订阅者)
ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。
2 话题通信基本操作A(C++)
1. 创建工作空间
接下来,先创建一个新的ROS工作空间:
mkdir -p ~/catkin_ws/src
再进入工作空间
cd ~/catkin_ws/
编译工作空间
catkin_make
刷新环境变量
source devel/setup.bash
2. 创建发布者节点
先进入src目录
cd ~/catkin_ws/src
创建依赖
catkin_create_pkg my_talker std_msgs rospy roscpp
在~/catkin_ws/src/mytalker/src
目录下创建一个新的C++文件,比如talker.cpp
:
gedit talker.cpp
// my_talker/src/talker.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");//防止出现乱码现象
// 初始化ROS节点
ros::init(argc, argv, "talker");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,话题名为"chatter",消息类型为std_msgs/String
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// 设置发布频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// 创建一个消息实例
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
// 发布消息
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
// 循环等待
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
发布者(talker)代码解释
包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
ros/ros.h
:包含ROS的核心功能,如节点初始化、日志记录等。std_msgs/String.h
:包含标准消息类型std_msgs/String
的定义,这是一个简单的字符串消息。<sstream>
:包含字符串流的功能,用于构建字符串。
main函数
int main(int argc, char *argv)
{
// 初始化ROS节点,节点名为"talker"
ros::init(argc, argv, "talker");
// 创建节点句柄,用于与ROS系统交互
ros::NodeHandle n;
// 创建一个Publisher,话题名为"chatter",消息类型为std_msgs/String,消息处理队列大小为1000
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// 设置发布频率,这里设置为10Hz
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok()) // 只要ROS系统还在运行
{
// 创建一个消息实例
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count; // 使用字符串流构建消息内容
msg.data = ss.str(); // 将字符串流的内容赋值给消息的数据字段
// 发布消息
ROS_INFO("%s", msg.data.c_str()); // 在日志中打印消息内容(可选)
chatter_pub.publish(msg); // 发布消息到话题"chatter"
// 循环等待,保持发布频率
ros::spinOnce(); // 处理任何挂起的回调函数
loop_rate.sleep(); // 休眠1/10s
++count; // 计数器递增
}
return 0;
}
ros::init(argc, argv, "talker")
:初始化ROS节点,节点名为"talker"。ros::NodeHandle n
:创建节点句柄,用于与ROS系统交互。n.advertise<std_msgs::String>("chatter", 1000)
:创建一个发布者,话题名为"chatter",消息类型为std_msgs/String
,队列大小为1000。队列大小决定了在订阅者来不及处理消息时,系统可以缓存多少条未处理的消息。ros::Rate loop_rate(10)
:设置发布频率为10Hz。ros::ok()
:检查ROS系统是否还在运行。std_msgs::String msg
:创建一个消息实例。std::stringstream ss
和ss << ...
:使用字符串流构建消息内容。msg.data = ss.str()
:将字符串流的内容转换为字符串并赋值给消息的数据字段。ROS_INFO("%s", msg.data.c_str())
:在日志中打印消息内容(这一步是可选的,仅用于调试)。chatter_pub.publish(msg)
:发布消息到话题"chatter"。ros::spinOnce()
:处理任何挂起的回调函数。在这个例子中,可能没有挂起的回调函数,但这是一个好习惯,可以确保系统的响应性。loop_rate.sleep()
:休眠直到下一个发布周期。
3. 创建订阅者节点
同样地,在my_talker
包的src
目录下创建一个新的C++文件,比如listener.cpp
:
gedit listener.cpp
// my_talker/src/listener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
// 回调函数,当收到消息时被调用
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
setlocale(LC_ALL,"");//防止出现乱码现象
// 初始化ROS节点
ros::init(argc, argv, "listener");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,话题名为"chatter",消息类型为std_msgs/String,回调函数为chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
接收者(listener)代码解释
回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
chatterCallback
:这是一个回调函数,当订阅者收到话题"chatter"上的消息时,ROS系统会自动调用这个函数。const std_msgs::String::ConstPtr& msg
:这是传递给回调函数的消息参数,它是一个指向std_msgs/String
消息类型的常量指针的智能指针。ROS_INFO("I heard: [%s]", msg->data.c_str())
:在日志中打印接收到的消息内容。
main函数
int main(int argc, char **argv)
{
// 初始化ROS节点,节点名为"listener"
ros::init(argc, argv, "listener");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,话题名为"chatter",消息类型为std_msgs/String,回调函数为chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
ros::init(argc, argv, "listener")
:初始化ROS节点,节点名为"listener"。ros::NodeHandle n
:创建节点句柄。n.subscribe("chatter", 1000, chatterCallback)
:创建一个订阅者,话题名为"chatter",消息类型为std_msgs/String
,队列大小为1000,回调函数为chatterCallback
。当收到消息时,ROS系统会自动调用chatterCallback
函数。ros::spin()
:进入一个循环,等待并处理回调函数。在这个例子中,它会等待直到收到话题"chatter"上的消息,并调用chatterCallback
函数。由于chatterCallback
函数中没有显式的退出条件,因此ros::spin()
会无限循环下去,直到ROS节点被手动停止。
5. 编译代码
回到~/catkin_ws
目录,编译你的包:
cd ~/catkin_ws
catkin_make
4. 运行节点
首先,确保ROS核心服务正在运行:
roscore
然后,打开两个新的终端窗口,分别运行发布者和订阅者节点:
source ~/catkin_ws/devel/setup.bash
rosrun my_talker talker
source ~/catkin_ws/devel/setup.bash
rosrun my_talker listener
你应该能在终端2中看到订阅者节点不断接收到发布者节点发送的消息