文章结构
- 任务要求
- 话题模型
- 实现步骤
- 创建工作空间并初始化
- 创建功能包并添加依赖
- 创建发布者代码(C++)
- 创建订阅方代码(C++)
- 配置CMakeLists.txt
- 执行
- 启动roscore
- 编译
- 启动发布和订阅节点
- launch封装
- 执行
任务要求
使用 ROS 话题(Topic)机制实现消息发布与订阅:
- 创建一个发布者,每隔 100ms 依次发送斐波拉契数列的数字到话题/fibonacci 中;
- 创建一个订阅者,订阅该话题,输出订阅结果。如,订阅者依次输出: 1 1 2 3 5 8…
- 将发布者和订阅者分别封装成launch文件,并能成功实现上述功能
话题模型
实现步骤
创建工作空间并初始化
$ mkdir -p ROS_Topic_Demo/src
$ cd ROS_Topic_Demo
$ catkin_make
上述命令,首先会创建一个工作空间以及一个 src 子目录,然后再进入工作空间调用 catkin_make命令编译。
创建功能包并添加依赖
在工作空间的src文件夹的目录下打开终端并创建功能包
$ catkin_create_pkg ROS_Topic_Demo roscpp rospy std_msgs
创建发布者代码(C++)
如何实现一个发布者:
- 初始化ROS节点
- 向 ROS Master注册节点信息,包括发布的话题名和话题中的消息类型
- 创建消息数据
- 按照一定频率循环发布消息
在ROS_Topic_Demo下的src文件夹中创建一个cpp文件:
$ touch topic_demo_pub_c.app
/*创建一个发布者,每隔 100ms 依次发送斐波拉契数列的数字到话题/fibonacci 中*/
//1.头文件
#include "ros/ros.h" //万能头
// #include "iostream"
#include "std_msgs/String.h" //普通文本类型的消息
int main(int argc, char *argv[])
{
//设置编码(其实这行在这个任务里头没啥用,只不过拿来凑行数而已,应要说的话就是能在打印的时候看的更加清楚而已)。
setlocale(LC_ALL,"");
//2.初始化ROS节点
//ros::init()函数需要查看 argc 和 argv,以便执行命令行提供的任何 ROS 参数和名称重映射。
//参数1和参数2用于传参,参数3为节点名称,需要保持名称唯一
ros::init(argc,argv,"Publisher");
//3.实例化ROS节点句柄
//节点句柄用来管理ROS相关的api资源。调用api时,经常需要使用节点句柄进行调用。
ros::NodeHandle n;
//4.实例化发布者对象
//advertise()函数用于告诉ROS需要发布的主题名称。这将调用ROS Master节点,该节点将会记录谁在发布,谁在订阅。
//调用 advertise() 后,Master节点会通知任何试图订阅该主题名称的节点,并进行配对。
//advertise() 返回一个发布者对象,它允许您使用该对象通过调用 publish() 在该主题上发布消息。
//一旦返回的 Publisher 对象的所有副本都被销毁后,该主题将自动销毁。
//第一个参数为话题名称,第二个参数为发布消息队列缓冲区的大小。
ros::Publisher fibonacci_pub = n.advertise<std_msgs::String>("/fibonacci",100);
//5.组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
int num = 1;
int temp = 0;
//设置循环频率
ros::Rate time(10);
ros::Rate time1(1);
time1.sleep(); //确保发布的代码比订阅的代码晚运行,保证订阅者可以完整的订阅到发布者的信息,防止漏掉一开始的信息。
while(ros::ok())
{
//发布消息
std::stringstream ss;
ss<<num;
msg.data = ss.str();
fibonacci_pub.publish(msg);
//打印发送的消息
ROS_INFO("发送数据:%s",msg.data.c_str());
int former = num;
num+=temp;
temp=former;
//设置休眠时间
time.sleep();
}
return 0;
}
创建订阅方代码(C++)
在ROS_Topic_Demo下的src文件夹中创建一个cpp文件:
$ touch topic_demo_sub_c.app
//1.头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
//5.利用回调函数读取数据
void callBack(const std_msgs::String::ConstPtr &msg)
{
//通过msg获取并操作订阅到的数据
ROS_INFO("订阅到的数:%s",msg->data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ROS节点
ros::init(argc,argv,"Subscriber");
//3.实例化ROS节点句柄
ros::NodeHandle n;
//4.实例化发布者对象
ros::Subscriber fibonacci_sub = n.subscribe<std_msgs::String>("/fibonacci",100,callBack);
//6.设置循环调用回调函数
ros::spin(); //循环读取接收的数据,并调用回调函数处理
return 0;
}
配置CMakeLists.txt
add_executable(topic_demo_pub_c src/topic_demo_pub_c.cpp)
add_executable(topic_demo_sub_c src/topic_demo_sub_c.cpp)
target_link_libraries(topic_demo_pub_c
${catkin_LIBRARIES}
)
target_link_libraries(topic_demo_sub_c
${catkin_LIBRARIES}
)
位置如图所示:
执行
启动roscore
$ roscore
编译
$ catkin_make
启动发布和订阅节点
$ source ./devel/setup.bash
$ rosrun rosrun topic_demo topic_demo_sub_c
再开一个终端
$ source ./devel/setup.bash
$ rosrun rosrun topic_demo topic_demo_pub_c
效果如下:
launch封装
在功能包添加 launch 文件夹,并添加 launch 文件
<launch>
<node pkg="topic_demo" type="topic_demo_pub_c" name="Subscriber" output="screen"/>
<node pkg="topic_demo" type="topic_demo_sub_c" name="Publisher" output="screen"/>
</launch>
- node: 包含的某个节点
- pkg: 功能包
- type: 被运行的节点文件
- name: 为节点命名
- output: 设置日志的输出目标
执行
$ roslaunch topic_demo topic_demo_launch.launch