一、话题通讯机制简介
ros的核心就是分布式通讯机制,ros的所有软件和工具都是基于这种分布式通讯机制之上的,话题通讯是ros的三大核心通讯机制之一,也是最为复杂,最为频繁的通讯模式,其模型如下图所示:
首先关注图中的三个角色:
1)、Talker:话题发布者,通过1234端口使用RPC协议向Ros Master注册发布者信息,包括话题名,RPC地址。该角色中包含了两个内容,一个是RPC通讯地址,用于注册到Ros Master当中,二是TCP通讯地址,用于后续跟Listener进行通讯传递消息数据。
2)、Listener:话题订阅者,上电后通过RPC向Ros Master注册要订阅的话题,订阅成功(即有Talker注册了Listener要订阅的话题)后方可接受Talker的消息。
3)、Ros Master:这可是Ros的核心组件了,管理了所有Talker和Listern,它们通讯前都要把自己的地址、发布或订阅的话题注册到Ros Master当中,当发布的话题和订阅的话题能够匹配到时,才能让二者进行连接。
可以说Ros Master作为Talker和Listener这两个角色通讯前的中介,当二者“对上眼”之后,Ros Master进程退出也不会影响二者的通讯,毕竟Listener都知道了Talker的TCP通讯地址了,但是,其他节点的Listener也无法订阅该话题。
这是一种多对多的异步通讯机制,就好像一个微信群里面,任何一个任务发布了一条消息,里面的人都能够看到,这种机制适合于高频的数据发布和接收:雷达探测、里程计等。
二、自定义消息
1、创建工作空间
#创建工作空间ros_wc
mkdir -p ~/catkin_ros/src
cd ~/catkin_ros/src
#初始化工作空间
catkin_init_workspace
cd ..
#编译
catkin_make
#添加环境变量
source devel/setup.bash
2、创建功能包
cd ~/catkin_ros/src
#创建功能包TopicTest,后续跟该功能包的依赖包
catkin_create_pkg learning_topic roscpp rospy std_msgs
cd ..
#编译功能包
catkin_make
3、功能包添加自定义消息文件
cd ~/catkin_ros/
#创建话题消息文件夹,必须名为msg
mkdir msg; cd msg
#编译话题文件
vim Person.msg
在Person.msg文件中加入
string name
uint8 sex
uint8 age
uint8 unkown = 0
uint8 male = 1
uint8 femal = 2
Person.msg定义了自定义消息结构体,包含了三个未初始化变量name、sex、age,用于后续传递消息,以及三个初始化变量,作为sex变量的值,此处类似于c中的宏定义。uint8为8bit整型,string为字符串类型。
4、配置编译
打开功能包的package.xml,添加如下编译和运行的相关依赖项
<exec_depend> message_runtime</exec_depend>
<build_depend>message_generation</build_depend>
打开功能包的CMakeList.txt,在find_package()中添加消息生生依赖的功能包message_generation,这样在编译时才能找到所需的文件。
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
同样的catkin依赖项也要设置
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES learning_topic
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
配置消息生生器
add_message_files(
FILES
Person.msg
)
调用消息生成
generate_messages(
DEPENDENCIES
std_msgs
)
5、编译并查看消息
cd ~/catkin_ws/
catkin_make
rosmsg show Person
[learning_topic/Person]:
uint8 unkonw=0
uint8 male=1
uint8 female=2
string name
uint8 sex
uint8 age
三、基于自定义消息的c++话题编程
1、创建publisher,在功能包learning_topic的src下创建talker.cpp,内容如下
#include <stdio.h>
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "learning_topic/Person.h" //自定义消息类型的头文件
using namespace ros;
int main(int argc, char *argv[])
{
//定义Person对象
learning_topic::Person p;
//初始化ros节点
init(argc, argv, "talker");
//创建节点句柄
ros::NodeHandle nh;
//创建一个消息发布者,发布名为chatter的topic,消息类型为learning_topic::Person
ros::Publisher pub = nh.advertise<learning_topic::Person>("chatter", 1000);
//设置循环频率为10HZ
ros::Rate loopRate(10);
int dwCnt = 0;
//进入循环,存在异常时ros::ok()返回false,异常包括:1、SIGINT信号;2、被另一个同名节点踢掉线;3、调用ros::shutdown;4、ros::NodeHandles句柄销毁
while (ros::ok())
{
//填充自定义消息Person
char acBuf[100];
sprintf(acBuf, "name %d", dwCnt++);
p.name = acBuf;
p.age = dwCnt;
p.sex = p.male;
// 打印,类似于printf
ROS_INFO("%s", p.name.c_str());
//发布消息
pub.publish(p);
//循环等待回调函数,虽然目前是发布者,没有订阅消息,也就是说spinOnce不是必须的,但是为了保证功能完整性,一般建议加上
ros::spinOnce();
//休眠,休眠时长就是前面设置的10HZ
loopRate.sleep();
}
return 0;
}
2、创建Subscribe,订阅publish的消息,在功能包leaning_topic的src下创建listerner.cpp:
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "learning_topic/Person.h"
void chatterCallBack(const learning_topic::Person::ConstPtr& msg)
{
//将接收的消息打印出来
ROS_INFO("i heard: [name:%s, age:%d, sex:%d]", msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char *argv[])
{
//定义Person对象
learning_topic::Person p;
//初始化ros节点
ros::init(argc, argv, "listener");
//创建节点句柄
ros::NodeHandle nh;
//创建Subscribe,订阅名为chatter的话题,注册回调函数chatterCallBack
ros::Subscriber sub = nh.subscribe("chatter", 100, chatterCallBack);
//循环等待消息回调
ros::spin();
return 0;
}
3、修改编译脚本并编译:打开功能包中的CMakeLists.txt文件,在末尾添加如下编译项
#设置头文件的相对路径。全局默认路径是功能包所在目录,比如功能包的头文件一般放置到功能包根目录下 的include文件夹,所以此处需添加该文件夹,这是一般的操作规范,即使本程序在include下并没有什么相关内容。此外,ros内置的${catkin_INCLUDE_DIRS}包含了ros catkin编译器默认包含安装路径、Linux系统路径下的头文件路径。
include_directories(include ${catkin_INCLUDE_DIRS})
#设置生成的可执行文件,以及对应的源码文件,如果有多个源文件用空格分隔。此处设置了生成talker
add_executable(talker src/talker.cpp)
#设置可执行文件依赖的动态库,此处talker使用默认连接库${catkin_LIBRARIES}
target_link_libraries(talker ${catkin_LIBRARIES})
#设置依赖项。当我们需要定义语言无关的消息类型时,消息类型会在编译过程中产生语言的代码,如果可执行文件依赖于这些动态代码,需要添加此项。
add_dependencies(talker ${PROJECT_NAME}_generate_message_cpp)
#设置生成listerner
add_executable(listerner src/listerner.cpp)
target_link_libraries(listerner ${catkin_LIBRARIES})
add_dependencies(listerner ${PROJECT_NAME}_generate_message_cpp)
在~/catkin_ws执行catkin_make,编译无误会在~/catki5、n_ws/devel/lib/leaning_topic/下生成talker和listerner。
5、运行publisher和Subscribe:
1)、启动roscore,执行:
roscore
2)、启动publisher(publisher和Subscribe节点的启动顺序没有要求):
lsm@lsm-Aspire-E5-472G:~/catkin_ws$ rosrun learning_topic talker
[ INFO] [1558112091.960899493]: name 0
[ INFO] [1558112092.060915997]: name 1
[ INFO] [1558112092.160923816]: name 2
[ INFO] [1558112092.260913136]: name 3
[ INFO] [1558112092.360919062]: name 4
[ INFO] [1558112092.460912128]: name 5
3)、启动Subscribe:
lsm@lsm-Aspire-E5-472G:~/catkin_ws$ rosrun learning_topic listerner
[ INFO] [1558112244.636315626]: i heard: [name:name 34, age:35, sex:1]
[ INFO] [1558112244.736236803]: i heard: [name:name 35, age:36, sex:1]
[ INFO] [1558112244.836169060]: i heard: [name:name 36, age:37, sex:1]
[ INFO] [1558112244.936199924]: i heard: [name:name 37, age:38, sex:1]
[ INFO] [1558112245.036216563]: i heard: [name:name 38, age:39, sex:1]
[ INFO] [1558112245.136180517]: i heard: [name:name 39, age:40, sex:1]