【ROS1】07-话题通信中使用自定义msg
目录
一、为什么需要自定义msg
二、使用自定义msg
2.1 定义msg文件
2.2 编辑配置文件
2.3 编译
三、自定义Msg案例
3.1 C++实现
3.1.1 配置
3.1.2 发布方
3.1.3 订阅方
3.2 Python实现
3.2.1 配置
3.2.2 发布方
3.2.3 订阅方
一、为什么需要自定义msg
ROS提供的标准消息类型很方便,但它们是通用的。在实际项目中,你经常需要传递一组特定的、有业务逻辑关联的数据。
举个例子:
假设想创建一个节点来检测摄像头画面中的人,并把检测结果发布出去。通常需要传递以下信息:
-
名字(string)
-
年龄(int)
-
在摄像头坐标系下的位置(geometry_msgs/Point)
-
检测的可信度(float)
当然可以把这些信息拼接成一个长字符串,然后用std_msgs/String来发布,但这样做有几个巨大的缺点:
-
解析困难:订阅方需要费力地分割和解析这个字符串,容易出错。
-
效率低下:字符串处理比处理结构化数据慢得多。
-
类型不安全:所有数据都是字符串,失去了原有的数据类型(如整数、浮点数)。
-
可读性差:代码会变得非常混乱。
使用自定义消息就可以避免这些问题。 我们可以创建一个消息,内部可以包含name, age, position, confidence这四个字段。这样,数据既结构化、又高效、还易于使用。
二、使用自定义msg
2.1 定义msg文件
在功能包中添加一个文件夹“msg”,用于存放自定义消息
新建一个msg文件,这里命名为“Person.msg”,然后在“Person.msg”中添加如下信息。这里定义了一个msg文件用于描述一个人的信息,该msg包含人的姓名、身高和年龄。
string name
int32 age
float32 height
2.2 编辑配置文件
打开功能包中的“package.xml”,添加如下部分
打开功能包中的“CMakeLists.txt”,添加如下内容:
配置msg源文件
生成消息时依赖std_msgs
执行时依赖
2.3 编译
Ctrl+Shift+B编译一下,没报错表示编译成功
编译后可以看到在“devel”目录下产生了如下中间文件
三、自定义Msg案例
3.1 C++实现
3.1.1 配置
打开“c_cpp_properties.json”,添加如下内容,来配置头文件路径
3.1.2 发布方
在功能包的src目录中新建一个cpp文件,这里命名为“demo03_pub_person.cpp”
添加如下代码
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"/*
发布方实现:1. 包含头文件2. 初始化ROS节点3. 创建节点句柄4. 创建发布者对象5. 编写发布逻辑并发布数据
*/int main(int argc, char *argv[])
{setlocale(LC_ALL,"");//2. 初始化ROS节点ros::init(argc,argv,"pub_person");//3. 创建节点句柄ros::NodeHandle nh;//4. 创建发布者对象ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("Topic3",10);//5. 编写发布逻辑并发布数据//5.1 创建被发布的数据plumbing_pub_sub::Person person;person.name = "张三";person.age = 18;person.height = 170;//5.2 设置发布频率为1秒1次ros::Rate rate(1);//5.3 循环发布数据while (ros::ok()){person.height += 0.01; //修改被发布的数据pub.publish(person); //发布数据rate.sleep();ros::spinOnce();}return 0;
}
打开功能包下的“CMakeLists.txt”,添加如下内容,其中第149行的作用是保证msg先被编译,再编译cpp文件
add_executable(demo03_pub_person src/demo03_pub_person.cpp)add_dependencies(demo03_pub_person ${PROJECT_NAME}_generate_messages_cpp)target_link_libraries(demo03_pub_person${catkin_LIBRARIES}
)
编译一下,然后开启3个终端窗口,分别用于启动roscore、启动发布方、打印发布信息
roscore //启动ros核心cd demo02_ws/ //进入工作空间
source ./devel/setup.bash //刷新环境变量
rosrun plumbing_pub_sub demo03_pub_person //启动ros节点cd demo02_ws/
source ./devel/setup.bash
rostopic echo Topic3 //打印“Topic3”话题
执行后可以看到打印
如果想打印发布的信息,我们可以添加如下代码
再次编译后执行可以看到发布的信息打印:
3.1.3 订阅方
创建一个.cpp文件,这里命名为“demo04_sub_person.cpp”
文件中添加如下代码:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"/*
订阅方实现:1. 包含头文件2. 初始化ROS节点3. 创建节点句柄4. 创建订阅者对象5. 处理订阅到的数据6. 调用spin()
*///5. 处理订阅到的数据
void call_doPerson(const plumbing_pub_sub::Person::ConstPtr& person){ROS_INFO("订阅的信息:%s,%d,%.2f", person->name.c_str(), person->age, person->height);
}int main(int argc, char *argv[])
{setlocale(LC_ALL, "");//2. 初始化ROS节点ros::init(argc, argv, "sub_person");//3. 创建节点句柄ros::NodeHandle nh;//4. 创建订阅者对象ros::Subscriber sub = nh.subscribe("Topic3",10,call_doPerson);//6. 调用spin()ros::spin();return 0;
}
打开功能包中的“CMakeLists.txt”添加如下内容
编译后执行
rosrun plumbing_pub_sub demo03_pub_person //运行发布方rosrun plumbing_pub_sub demo04_sub_person //运行订阅方
执行效果如下,可以看到订阅方正确打印了订阅到的信息
3.2 Python实现
3.2.1 配置
打开“settings.json”,添加如下内容:
{"editor.tabSize": 4,"cmake.sourceDirectory": "/home/chaochao/demo02_ws/src/helloworld","files.associations": {"sstream": "cpp"},"python.autoComplete.extraPaths": ["/opt/ros/noetic/lib/python3/dist-packages","/home/chaochao/demo02_ws/devel/lib/python3/dist-packages"]
}
-
"/opt/ros/noetic/lib/python3/dist-packages":
将这个路径添加进去后,当在代码中写下 import rospy 或者 from std_msgs.msg import String 时,VS Code 就知道去哪里找到这些模块。这样,你就可以获得 rospy. 的方法提示(如 rospy.init_node),并且 VS Code 不会报错说“找不到模块”。
-
"/home/chaochao/demo02_ws/devel/lib/python3/dist-packages":
将这个路径添加进去,VS Code 才能识别自定义的模块。例如,如果你创建了一个名为 my_package 的功能包,里面有一个消息 MyStatus.msg,那么在代码中你就可以写 from my_package.msg import MyStatus,VS Code 会正确识别它,并为你提供字段提示,而不会报错。
3.2.2 发布方
在功能包的“scripts”目录中新建一个文件,这里命名为“py_pub_person.py”
在“py_pub_person.py”添加如下代码
#! /usr/bin/env python
# -*- coding: utf-8 -*-import rospy
from plumbing_pub_sub.msg import Person"""
发布方实现:1. 导包2. 初始化ROS节点3. 创建发布者对象4. 编写发布逻辑并发布数据
"""if __name__ == "__main__":#2. 初始化ROS节点rospy.init_node("pub_person")#3. 创建发布者对象pub = rospy.Publisher("Topic4", Person, queue_size=10)#4. 编写发布逻辑并发布数据#4.1 创建Person数据p = Person()p.name = "张三"p.age = 18p.height = 170#4.2 创建Rate对象rate = rospy.Rate(1)#4.3 循环发布数据while not rospy.is_shutdown():pub.publish(p)rospy.loginfo("发布的信息:%s,%d,%.2f", p.name, p.age, p.height)rate.sleep()
cd到“scripts”目录下,输入如下指令来给新创建的Python添加可执行权限
chmod +x *.py
打开“CMakeLists.txt”,添加如下内容
编译一下,然后在终端中运行发布方并通过命令“rostopic echo”查看发布信息
rosrun plumbing_pub_sub py_pub_person.pyrostopic echo Topic4
3.2.3 订阅方
在功能包的“scripts”目录下新建一个Python文件,这里命名为“py_sub_person.py”
在“py_sub_person.py” 中添加如下代码:
#! /usr/bin/env python
# -*- coding: utf-8 -*-import rospy
from plumbing_pub_sub.msg import Person"""
订阅方实现:1. 导包2. 初始化ROS节点3. 创建订阅者对象4. 处理订阅到的数据5. spin()
"""# 4. 处理订阅到的数据
def call_doPerson(p):rospy.loginfo("订阅的数据:%s, %d, %.2f", p.name, p.age, p.height)if __name__ == "__main__":# 2. 初始化ROS节点rospy.init_node("sub_person")# 3. 创建订阅者对象sub = rospy.Subscriber("Topic4", Person, call_doPerson)# 5. spin()rospy.spin()
再次给新创建的Python文件添加可执行权限
打开“CMakeLists.txt”,添加如下一行
编译后执行
rosrun plumbing_pub_sub py_pub_person.pyrosrun plumbing_pub_sub py_sub_person.py