目录
自定义msg简介
在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty.... 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性。
当传输一些复杂的数据,比如: 激光雷达的信息... std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型msg
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:
-
int8, int16, int32, int64 (或者无符号类型: uint*)
-
float32, float64
-
string
-
time, duration
-
other msg files
-
variable-length array[] and fixed-length array[C]
当用到别人的msg文件时可能会用到特殊类型Header
ROS中还有一种特殊类型:Header
,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头
。
自定义msg本质上就是一个文本文件
自定义msg实现
需求:创建自定义消息,该消息包含人的信息:姓名、身高、年龄等。
流程:
- 按照固定格式创建 msg 文件
- 编辑配置文件
- 编译生成可以被 Python 或 C++ 调用的中间文件
在vscode中新建文件夹和文件
添加依赖
这样修改后
编译的时候会导入
message_generation
message_runtime
然后修改CMakeLists
这样build depend 编译的时候要依赖message_generation这个功能包
find_package的用处是当编译plumbing_pub_sub功能包的时候必须依赖这里面的包
备注
ctrl + /
组合键可以在vscode里直接去掉注释
添加message文件
代表编译时依赖 std_msgs包
因为组成msg的复杂消息的都是标准消息所以需要std_msg
上面的find_pachage 又依赖于catkin_package里面的包,有层层依赖关系
然后进行编译
编译后会产生中间文件
以下是python产生的中间文件,调用的话就是用的这个文件
话题通信自定义msg调用(C++)
需求:
编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。
分析:
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
- 发布方
- 接收方
- 数据(此处为自定义消息)
vscode配置
为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性:
获取路径然后复制
复制进去然后改成**
这样做的好处是
不至于误抛异常
实现发布方
然后新建文件
复制代码
/*
需求: 循环发布人的信息
*/
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//1.初始化 ROS 节点
ros::init(argc,argv,"talker_person");
//2.创建 ROS 句柄
ros::NodeHandle nh;
//3.创建发布者对象
ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("chatter_person",1000);
//4.组织被发布的消息,编写发布逻辑并发布消息
plumbing_pub_sub::Person p;
p.name = "sunwukong";
p.age = 2000;
p.height = 1.45;
ros::Rate r(1);
while (ros::ok())
{
pub.publish(p);
p.age += 1;
ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);
r.sleep();
ros::spinOnce();
}
return 0;
}
到这里然后改成**
然后跟之前一样修改CMakeLists
这是添加可执行权限
这是初始化
添加依赖
这个的作用是防止先编译cpp文件还没编译msg文件此时就会报错
有了这个就不会出现这种原因导致的报错,即逻辑问题上的报错
然后编译功能包
Invoking"make cmake_check_build_system"failed
这个报错大概率是文件名或者节点名对应不上导致的
我的报错是因为之前这里person写错了
没有仔细看报错原因
改完后编译成功
然后开终端新建
实现订阅方
新建文件
此处报错是因为还没修改include文件
无需着急
复制以下代码(对应自己文件名等做对应修改)
/*
需求: 订阅人的信息
*/
#include "ros/ros.h"
#include "demo02_talker_listener/Person.h"
void doPerson(const demo02_talker_listener::Person::ConstPtr& person_p){
ROS_INFO("订阅的人信息:%s, %d, %.2f", person_p->name.c_str(), person_p->age, person_p->height);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//1.初始化 ROS 节点
ros::init(argc,argv,"listener_person");
//2.创建 ROS 句柄
ros::NodeHandle nh;
//3.创建订阅对象
ros::Subscriber sub = nh.subscribe<demo02_talker_listener::Person>("chatter_person",10,doPerson);
//4.回调函数中处理 person
//5.ros::spin();
ros::spin();
return 0;
}
对应更改配置文件
打开终端
打开计算图
Python实现
流程:
- 编写发布方实现;
- 编写订阅方实现;
- 为python文件添加可执行权限;
- 编辑配置文件;
- 编译并执行。
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/xxx/yyy工作空间/devel/lib/python3/dist-packages"
]
}
完成vscode配置
这是为了自动补齐功能,不影响运行
编写发布方
然后复制以下代码
#! /usr/bin/env python
"""
发布方:
循环发送消息
"""
import rospy
from demo02_talker_listener.msg import Person
if __name__ == "__main__":
#1.初始化 ROS 节点
rospy.init_node("talker_person_p")
#2.创建发布者对象
pub = rospy.Publisher("chatter_person",Person,queue_size=10)
#3.组织消息
p = Person()
p.name = "葫芦瓦"
p.age = 18
p.height = 0.75
#4.编写消息发布逻辑
rate = rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish(p) #发布消息
rate.sleep() #休眠
rospy.loginfo("姓名:%s, 年龄:%d, 身高:%.2f",p.name, p.age, p.height)
写完后添加执行权限
接下来修改CMakeLists配置
启动终端
实现订阅方
新建文件
复制代码
#! /usr/bin/env python
"""
订阅方:
订阅消息
"""
import rospy
from demo02_talker_listener.msg import Person
def doPerson(p):
rospy.loginfo("接收到的人的信息:%s, %d, %.2f",p.name, p.age, p.height)
if __name__ == "__main__":
#1.初始化节点
rospy.init_node("listener_person_p")
#2.创建订阅者对象
sub = rospy.Subscriber("chatter_person",Person,doPerson,queue_size=10)
rospy.spin() #4.循环
ps:
如果需要输出完整消息把上述代码修改:
def doPerson(p):
rospy.loginfo("接收到的人的信息:%s, %d, %.2f",p)
即可
添加执行权限
修改CMakeLists配置
然后打开终端运行
完成
这里记住python也要source
这要才能在bash里编译否则会报错
打开计算图
实现定时结束订阅
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
output_file = "topic_output.txt"
def callback(msg):
with open(output_file, "a") as f:
f.write(str(msg) + "\n")
rospy.loginfo("Saved message to %s: %s", output_file, msg.data)
def timer_callback(event):
rospy.loginfo("5 seconds passed. Shutting down.")
rospy.signal_shutdown("5 seconds passed")
def topic_subscriber():
rospy.init_node("topic_subscriber", anonymous=True)
rospy.Subscriber("/example_topic", String, callback)
rospy.Timer(rospy.Duration(5), timer_callback, oneshot=True)
rospy.spin()
if __name__ == "__main__":
try:
topic_subscriber()
except rospy.ROSInterruptException:
pass
我们添加了一个名为timer_callback
的新函数,该函数在5秒后被调用,并关闭节点。rospy.Timer
类创建一个计时器,当计时器达到指定的持续时间(在这种情况下为5秒)时,它将调用timer_callback
函数。oneshot=True
表示计时器将仅触发一次,然后停止。
这将使节点在订阅并保存消息5秒后关闭。