ROS 话题订阅模型之自定义消息类型
1.自定义消息类型好处
ROS提供了许多标准的消息类型,如 std_msgs/String
、sensor_msgs/Image
等,涵盖了很多常见的数据类型和传感器数据。但是,在实际的开发中,我们经常会遇到需要传输的数据类型是标准消息类型中没有的,或者我们希望定义自己的数据结构以适应特定的应用场景。
因此,ROS允许用户定义自己的消息类型,这样用户可以更灵活地描述和传输数据。一些常见的情况包括:
- 特定的传感器数据:例如,你可能有一种特殊类型的传感器,它输出的数据类型在标准消息中没有涵盖。
- 自定义控制命令:你可能需要向机器人或设备发送一些自定义的控制命令,以便执行特定的动作或行为。
- 自定义数据结构:你可能需要在ROS系统中传输一些复杂的数据结构,例如地图、路径规划数据等。
自定义消息使得ROS更加灵活,能够满足各种各样的应用需求。通过定义自己的消息类型,用户可以更好地组织和传输数据,从而更容易地开发复杂的ROS应用。
2. 使用自定义消息类型
以发送模拟相机数据自定义类型数据为例:
2.1 定义msg 文件
在功能包下新建msg目录,添加Image.msg 文件,用来模拟相机数据
添加以下内容:
uint16 frame_id # 图像帧的ID
uint16 width # 图像的宽度
uint16 height # 图像的高度
int64 timestamp # 图像的时间戳,通常以纳秒为单位
2.2 编辑配置文件
package.xml中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
CMakeLists.txt编辑 msg 相关配置
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)
## 配置 msg 源文件
add_message_files(
FILES
Image.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
#执行时依赖
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
2.3 编译
使用编译命令之后,在 工程目录下的 devel/include/包名 会生成对应的C++头文件
后续编写代码要引用这些头文件
3.编写简单用例 C++ 传输模拟相机数据
如果使用VsCode开发,为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性:
{
"configurations": [
{
"browse": {
"databaseFilename": "${default}",
"limitSymbolsToIncludedHeaders": false
},
"includePath": [
"/opt/ros/melodic/include/**",
"/usr/include/**",
"/opt/ros/melodic/include",
"/home/marxist/userdata/catkin_ws/devel/include/*"
],
"name": "ROS",
"intelliSenseMode": "gcc-x64",
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu17",
"cppStandard": "c++17"
},
{
"name": "ros_inc",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "gnu++14",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
3.1 发布模拟相机数据
#include "ros/ros.h"
#include "image_node/Image.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//1.初始化 ROS 节点
ros::init(argc,argv,"image_pub_node");
//2.创建 ROS 句柄
ros::NodeHandle nh;
//3.创建发布者对象
ros::Publisher pub = nh.advertise<image_node::Image>("image_info",1000);
//4.组织被发布的消息,编写发布逻辑并发布消息
image_node::Image image_data;
int start_id =0;
image_data.height=1080;
image_data.width=1920;
ros::Rate r(1);
while (ros::ok())
{
// 获取当前时间
ros::Time current_time = ros::Time::now();
// 将当前时间转换为int64纳秒时间戳
image_data.timestamp = current_time.toNSec();
pub.publish(image_data);
// 打印消息字段
ROS_INFO("Image frame ID: %d, Width: %d, Height: %d, Timestamp: %ld",image_data.frame_id,image_data.width,image_data.height,image_data.timestamp);
ros::spinOnce();
}
return 0;
}
3.2 接受模拟相机数据
#include "ros/ros.h"
#include "image_node/Image.h"
void doGetData(const image_node::Image::ConstPtr& image_data){
ROS_INFO("Image frame ID: %d, Width: %d, Height: %d, Timestamp: %ld",image_data->frame_id,image_data->width,image_data->height,image_data->timestamp);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//1.初始化 ROS 节点
ros::init(argc,argv,"get_image");
//2.创建 ROS 句柄
ros::NodeHandle nh;
//3.创建订阅对象
ros::Subscriber sub = nh.subscribe<image_node::Image>("image_info",10,doGetData);
//4.回调函数中处理 person
//5.ros::spin();
ros::spin();
return 0;
}
3.3 配置CmakeLists.txt
add_executable(
image_pub src/Image_pub.cpp)
add_dependencies(image_pub ${PROJECT_NAME}_generate_messages_cpp)
target_link_libraries(image_pub
${catkin_LIBRARIES}
)
add_executable(
image_sub src/Image_sub.cpp)
add_dependencies(image_sub ${PROJECT_NAME}_generate_messages_cpp)
target_link_libraries(image_sub
${catkin_LIBRARIES}
)