从21年至今写过不下10个ROS话题通信的工程,今天系统地记录下自定义msg的过程,让后来者少走弯路。
1、自定义msg简介
在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息… std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型。
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:
- int8, int16, int32, int64 (或者无符号类型: uint*)
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
ROS中还有一种特殊类型:Header,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。
2、自定义msg示例
需求:创建自定义消息,该消息包含刚体的6自由度坐标(3D坐标+欧拉角):x, y, z, r, p, y。
操作流程:
- 按照固定格式创建 msg 文件
- 编辑配置文件
- 编译生成可以被 Python 或 C++ 调用的中间文件
2.1、定义msg文件
功能包下新建 msg 目录,添加文件 Pose.msg,因为数据属于浮点型,选择32位的float。
float32 axis_x
float32 axis_y
float32 axis_z
float32 roll
float32 pitch
float32 yaw
2.2、编辑配置文件
package.xml中添加编译依赖时依赖与执行时依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
CMakeLists.txt编辑 msg 相关配置
# 需要加入 message_generation,必须有 std_msgs
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
## 配置 msg 源文件
add_message_files(
FILES
Pose.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
#执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES ros_vo
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
2.3、编译工程
进入工作空间目录并catkin_make编译
Scanning dependencies of target _ros_vo_generate_messages_check_deps_Pose
[ 0%] Built target std_msgs_generate_messages_lisp
[ 0%] Built target std_msgs_generate_messages_py
[ 0%] Built target std_msgs_generate_messages_eus
[ 0%] Built target std_msgs_generate_messages_nodejs
[ 0%] Built target std_msgs_generate_messages_cpp
Scanning dependencies of target run_vo
[ 0%] Built target _ros_vo_generate_messages_check_deps_Pose
Scanning dependencies of target ros_vo_generate_messages_py
Scanning dependencies of target ros_vo_generate_messages_eus
Scanning dependencies of target ros_vo_generate_messages_cpp
Scanning dependencies of target ros_vo_generate_messages_nodejs
Scanning dependencies of target ros_vo_generate_messages_lisp
[ 11%] Generating Lisp code from ros_vo/Pose.msg
[ 33%] Generating C++ code from ros_vo/Pose.msg
[ 33%] Generating Python from MSG ros_vo/Pose
[ 44%] Generating EusLisp manifest code for ros_vo
[ 55%] Generating EusLisp code from ros_vo/Pose.msg
[ 66%] Generating Javascript code from ros_vo/Pose.msg
[ 66%] Built target ros_vo_generate_messages_lisp
[ 66%] Built target ros_vo_generate_messages_nodejs
[ 77%] Building CXX object ros_vo/CMakeFiles/run_vo.dir/src/run_vo.cpp.o
[ 88%] Generating Python msg __init__.py for ros_vo
[ 88%] Built target ros_vo_generate_messages_cpp
[ 88%] Built target ros_vo_generate_messages_py
[ 88%] Built target ros_vo_generate_messages_eus
Scanning dependencies of target ros_vo_generate_messages
[ 88%] Built target ros_vo_generate_messages
[100%] Linking CXX executable /home/dzh/Demo/VO/devel/lib/ros_vo/run_vo
[100%] Built target run_vo
编译后的中间文件查看:
C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h),后续调用相关 msg 时,是从这些中间文件调用的。
3、自定义msg调用
需求:
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。
分析:
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
发布方
接收方
数据(此处为自定义消息)
流程:
编写发布方实现;
编写订阅方实现;
编辑配置文件;
编译并执行。
3.1、发布方实现:
// 1.包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "geometry_msgs/Point.h"
#include "rtsp/Pose.h"
int main(int argc, char *argv[])
{
//设置编码
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
// 参数1和参数2 后期为节点传值会使用
// 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
ros::init(argc, argv, "poseTalker");
//3.实例化 ROS 句柄
ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能
//4.实例化 发布者 对象
//泛型: 发布的消息类型
//参数1: 要发布到的话题名称
//参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
ros::Publisher pub = nh.advertise<rtsp::Pose>("pose", 10);
//5.组织被发布的数据,并编写逻辑发布数据
// x方向像素偏移、y方向像素偏移、z方向真实距离(6m内有效,否则为0)
rtsp::Pose pose;
// 类型为float64
pose.axis_x = 1;
pose.axis_y = 2;
pose.axis_z = 3;
pose.roll = 4;
pose.pitch = 5;
pose.yaw = 6;
//逻辑(一秒1次)
ros::Rate r(10);
//节点不死
while (ros::ok())
{
//发布消息
pub.publish(pose);
//加入调试,打印发送的消息
// ROS_INFO("发送的消息:%f %f %f",point.x, point.y, point.z);
//根据前面制定的发送频率自动休眠 休眠时间 = 1/频率;
r.sleep();
//暂无应用
ros::spinOnce();
}
return 0;
}
3.2、订阅方实现:
// 1.包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "geometry_msgs/Point.h"
#include "rtsp/Pose.h"
// 处理pose消息
void dealPose(const rtsp::Pose::ConstPtr &msg_pose){
ROS_INFO("axis_x: %f, axis_y: %f, axis_z: %f, roll: %f, pitch: %f, yaw: %f", msg_pose.axis_x, msg_pose.axis_y, msg_pose.axis_z, msg_pose.roll, msg_pose.pitch, msg_pose.yaw);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
ros::init(argc,argv,"listener");
//3.实例化 ROS 句柄
ros::NodeHandle nh;
//4.实例化 订阅者 对象,订阅话题为Point
ros::Subscriber sub = nh.subscribe<rtsp::Pose>("pose", 10, dealPose);
//5.处理订阅的消息(回调函数)
//ros::Subscriber sub2 = nh.subscribe<未知结构体类型>("未知话题名称", 10, recvMsg);
// 6.设置循环调用回调函数
ros::spin();//循环读取接收的数据,并调用回调函数处理
return 0;
}
3.3、配置 CMakeLists.txt
add_executable(pose_pub src/pose_pub.cpp)
add_executable(pose_sub src/pose_sub.cpp)
target_link_libraries(pose_pub
${catkin_LIBRARIES}
)
target_link_libraries(pose_sub
${catkin_LIBRARIES}
)
3.4、执行
-
启动 roscore;
-
启动发布节点;
-
启动订阅节点。
PS:可以在终端中使用
rqt_graph
查看节点关系;使用rostopic list
查看话题列表;使用rostopic echo pose
订阅话题内容。记住使用前先source ./devel/setup.bash