目录
说明:
1. 话题模型
2. 实现过程(C++)
自定义话题消息
Person.msg文件内容
Person.msg文件内容说明
编译配置
在package.xml文件中添加功能包依赖
在CMakeLists.txt中添加编译选项
编译生成语言相关文件
创建发布者代码(C++)
创建订阅者代码(C++)
配置代码编译规则
配置内容说明
编译并运行
编译
运行
扩展
3. 实现过程(Python)
创建发布者代码(Python)
创建订阅者代码(Python)
运行
说明:
1. 本系列学习笔记基于B站:古月居《ROS入门21讲》课程,且使用的Ubuntu与ROS系统版本与课程完全一致;
虚拟机版本 | Linux系统版本 | ROS系统版本 |
---|---|---|
VMware WorkStation Pro 16 | Ubuntu18.04 | Melodic |
2. 课程中的所有示例代码均已跑通,且对Pyhon版本的代码也都做了运行验证,并附带验证过程(错误均已修正);
3. 本节是整个笔记的第10节,对应视频课程的第12节,请自行对应学习;
4. 整个系列笔记基本已经完结,但部分章节仍需润色修改 ,后面会陆续发布,请大家持续关注, 创作不易,感谢支持!
1. 话题模型
2. 实现过程(C++)
自定义话题消息
cd ~/catkin_ws/src/learning_topic mkdir msg cd msg touch Person.msg
Person.msg文件内容
string name // 名字
uint8 sex // 性别:分为3种,男、女、未知,下面以012宏定义做表示判断
uint8 age // 年龄
uint8 unknown = 0
uint8 male = 1
uint8 female = 2
Person.msg文件内容说明
Person.msg文件里定义的内容跟语言无关的,这既不是c++,也不是python,这里面的string uint8表示在不同的程序里面扩展成对应该种程序的表示方法,可以类比单片机里无符号整型变量类型unsigned int来理解,缩写就是uint。
cd ~/catkin_ws/src/learning_topic mkdir msg
注意:新建的这个文件夹名字不能随便命名,只能叫msg,否则编译会报错,CMakeList文件中有说明,要放在名叫msg文件夹中。
cd msg touch Person.msg
(注意:这里的文件名首字母P一定要大写!否则后面会报错)
打开Person.msg文件,把定义的内容复制到文件里并保存,
(注意:输入的内容中,注释不要,空格不能用tab键缩进,否则后面也会报错)
编译配置
路径:~/catkin_ws/src/learning_topic
在package.xml文件中添加功能包依赖
<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
build_depend:编译依赖
exec_depend:运行依赖
在CMakeLists.txt中添加编译选项
find_package(......message_generation)
add_message_files(FILES Person.msg) generate_messages(DEPENDENCIES std_msgs)
add_message_files: 把我们定义的Person.msg文件,做为我们定义的接口;
generate_messages:编译Person.msg文件的时候需要用到一些依赖于ROS已有的库或包,
我们这里用到的依赖是std_msgs,我们前面看到的string,uint8都是在std_msgs里面做定义的
catkin_package(......message_runtime)
编译生成语言相关文件
cd ~/catkin_ws catkin_make
编译成功之后可以在~/catkin_ws/devel/include/learning_topic下看到Person.msg文件编译生成的C++的头文件Person.h
创建发布者代码(C++)
cd ~/catkin_ws/src/learning_topic/src touch person_publisher.cpp
-
初始化ROS节点;
-
向ROS Master注册节点信息,包括发布消息的话题名,话题中的消息类型;
-
创建消息数据;
-
按照一定频率循环发布消息,
/**
* 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "person_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);
// 设置循环的频率
ros::Rate loop_rate(1);
int count = 0;
while (ros::ok())
{
// 初始化learning_topic::Person类型的消息
learning_topic::Person person_msg;
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = learning_topic::Person::male;
// 发布消息
person_info_pub.publish(person_msg);
ROS_INFO("Publish Person Info: name:%s age:%d sex:%d",
person_msg.name.c_str(), person_msg.age, person_msg.sex);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
创建订阅者代码(C++)
cd ~/catkin_ws/src/learning_topic/src touch person_subscriber.cpp
初始化ROS节点;
订阅需要的话题;
循环等待话题消息,接收到消息后进入回调函数;
在回调函数中完成消息处理。
/**
* 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "person_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
配置代码编译规则
将下面6行内容复制到CMakeLists.txt文件中,
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)
add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
配置内容说明
add_executable:
将person_publisher.cpp和person_subscriber.cpp代码文件编译成person_publisher与person_subscriber可执行文件;
target_link_libraries:
把编译生成的可执行文件person_publisher与person_subscriber跟ROS相关的一些库做连接的,比如调用的C++的接口;
add_dependencies:
有一些代码是动态生成的,而可执行文件也需要与动态生成的程序产生依赖关系,所以这个就是用来动态的和刚才生成的头文件Person.h产生依赖关系的;
复制到CMakeLists.txt文件中的位置如下:
编译并运行
编译
cd ~/catkin_ws catkin_make
运行
roscore rosrun learning_topic person_subscriber rosrun learning_topic person_publisher
第一步:打开ROS Master;
第二步:运行订阅者person_subscriber,让其先等待着发布者发布消息数据,
第三步:运行发布者person_publisher,让其发布消息数据,
扩展
如果把启动ROS_Master的roscore关掉,发布者与订阅者依然在发布和接收消息数据,此时ROS_Master的结束对两者并无影响,
因为ROS_Master是帮助节点建立连接的,一旦连接创立,ROS_Master在这里的作用就不再存在了,但关掉ROS_Master后,
如果有第三个节点要加入进来的话,是无法再建立连接的。
3. 实现过程(Python)
创建发布者代码(Python)
cd ~/catkin_ws/src/learning_topic/scripts touch person_publisher.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Person
def velocity_publisher():
# ROS节点初始化
rospy.init_node('person_publisher', anonymous=True)
# 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
person_info_pub = rospy.Publisher('/person_info', Person, queue_size=10)
#设置循环的频率
rate = rospy.Rate(10)
while not rospy.is_shutdown():
# 初始化learning_topic::Person类型的消息
person_msg = Person()
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = Person.male;
# 发布消息
person_info_pub.publish(person_msg)
rospy.loginfo("Publsh person message[%s, %d, %d]",
person_msg.name, person_msg.age, person_msg.sex)
# 按照循环频率延时
rate.sleep()
if __name__ == '__main__':
try:
velocity_publisher()
except rospy.ROSInterruptException:
pass
创建订阅者代码(Python)
cd ~/catkin_ws/src/learning_topic/scripts touch person_subscriber.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Person
def personInfoCallback(msg):
rospy.loginfo("Subcribe Person Info: name:%s age:%d sex:%d",
msg.name, msg.age, msg.sex)
def person_subscriber():
# ROS节点初始化
rospy.init_node('person_subscriber', anonymous=True)
# 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
rospy.Subscriber("/person_info", Person, personInfoCallback)
# 循环等待回调函数
rospy.spin()
if __name__ == '__main__':
person_subscriber()
注意:给person_publisher.py 和 person_subscriber.py文件赋予作为程序文件执行的权限,
点击person_publisher.py文件,右键,属性,权限,勾选(允许作为程序文件执行),
person_subscriber.py文件操作同上,python文件不需要编译,直接运行即可。
运行
roscore rosrun learning_topic person_subscriber.py rosrun learning_topic person_publisher.py
第一步:打开ROS Master;
第二步:运行订阅者person_subscriber.py,让其先等待着发布者发布消息数据,
第三步:运行发布者person_publisher.py,让其发布消息数据,