ROS1学习笔记:话题消息的定义与使用(ubuntu20.04)

参考B站古月居ROS入门21讲:话题消息的定义与使用
基于VMware Ubuntu 20.04 Noetic版本的环境

一、话题模型

在前面的章节中不管我们给小海龟发布的速度指令Twist消息还是去订阅小海龟的位置Pose消息,Twist消息和Pose消息都是ROS内部定义好的,可以直接使用。
在ROS的元功能包common_msgs中提供了许多不同消息类型的功能包如std_msgs标准数据类型、geometry_msgs几何学数据类型、sensor_msgs传感器数据类型等。这些功能包中提供了大量常用的消息类型可以满足一般场景下的常用消息。
如果我们在自己的开发中,没有ROS直接定义好的消息该怎么办?
我们可以自己定义消息的类型,本节我们来完成话题消息的定义和使用,同时完成Publisher和Subscriber的使用。

这节我们完成一个消息传输,传输一个“Person”的个人信息,包括人的名字、年龄、性别… …
我们自定义一个消息类型“Person”,并完成发布订阅整个过程,Publisher进行发布个人信息,Subscriber来接收个人信息。Topic定义名为“person_info”
在这里插入图片描述

二、自定义话题消息

2.1 定义msg文件

我们通过自定义msg文件来自定义话题消息
我们定义的msg文件名为:Person.msg

先在learning_topic文件夹下创建msg文件夹
在这里插入图片描述
然后进入msg文件夹,打开终端,输入touch Person.msg创建文件(当然手动创建也可以)
在这里插入图片描述
注意Person的P要大写

然后打开Person.msg文件,把下面的宏定义复制到该文件下:

string name
uint8 sex
uint8 age

uint8 unknown = 0
uint8 male = 1
uint8 female = 2

在这里插入图片描述

这里使用的基础数据类型string、uint8都是语言无关的,编译阶段会变成各种语言对应的数据类型。
定义话题消息有自己的一套规则,并不是严格地按照C++或者Python的类型定义,这是一个动态过程。每当我们使用C++或者Python语言去调用的时候它会动态地调用相应语言的规则,因此我们还要添加功能包依赖和添加编译规则。

2.2 在package.xml文件中添加功能包依赖

打开package.xml文件,添加动态生成程序的功能包依赖,将以下代码复制到如图相应位置:

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

在这里插入图片描述
build_depend为编译依赖,这里依赖的是一个会动态产生message的功能包;
exer_depend为执行依赖,这里依赖的是一个动态runtime运行的功能包。

2.3 在CMakeLists.txt文件中添加编译选项

  • 因为在package.xml添加了功能包编译依赖,在CMakeList.txt里的find_package中也要加上对应的部分;
  • 需要将定义的Person.msg作为消息接口,针对它做编译;
    需要指明编译这个消息接口需要哪些ROS已有的包;
  • 有了这两个配置才可将定义的msg编译成不同的程序文件;
  • 因为在package.xml添加了功能包执行依赖,在CMakeList.txt里的catkin_package中也要加上对应的部分;

打开CMakeLists.txt文件,跟我一样将以下信息添加到如图的地方:

find_package( ...... message_generation)

add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)

catkin_package( ...... message_runtime)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 编译生成C++头文件和Python库

回到工作空间根目录,执行编译:

cd ~/catkin_ws
catkin_make

在这里插入图片描述
编译完成后,我们可以在/home/ywl/catkin_ws/devel/lib/python3/dist-packages/learning_topic/msg/home/ywl/catkin_ws/devel/include/learning_topic两个目录下分别找到对应的Python和C++文件:
在这里插入图片描述
在这里插入图片描述
有了这两个文件,我们就可以通过编写程序来执行自定义话题的代码了。

三、创建代码

3.1 以C++为例

3.1.1 创建C++代码

我们创建一个Publisher代码和Subscriber代码,拷贝到learning_topicsrc目录下:
在这里插入图片描述
person_publisher.cpp的完整代码:

/***********************************************************************
Copyright 2020 GuYueHome (www.guyuehome.com).
***********************************************************************/

/**
 * 该例程将发布/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;
}

person_subscriber.cpp的完整代码:

/***********************************************************************
Copyright 2020 GuYueHome (www.guyuehome.com).
***********************************************************************/

/**
 * 该例程将订阅/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;
}

3.1.2 配置代码编译规则

打开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)

在这里插入图片描述
与之前的配置规则不同,这里新增了一个添加依赖项,因为本次代码涉及到动态生成,我们需要将可执行文件与动态生成的程序产生依赖关系。

3.1.3 编译运行

编译:

cd ~/catkin_ws
catkin_make

在这里插入图片描述
编译成功后,我们可以使用如下命令查看自定义的Person的消息类型:

rosmsg show Person

在这里插入图片描述

运行:

roscore
rosrun learning_topic person_subscriber
rosrun learning_topic person_publisher

在这里插入图片描述
可以看到运行成功了,订阅者接受到了发布者的Person的信息。

3.1.4 查看计算图

rqt_graph

在这里插入图片描述
可以看到有三个节点,发布者订阅一个person_info话题给订阅者接收。

下面我们关掉ROS master试试(关掉roscore),同时终止rqt_graph终端:
在这里插入图片描述
在这里插入图片描述

可以看到打不开了。因为ROS master是帮助发布者和订阅者之间通信的,当两者成功建立起通信后,ROS master也就没有什么作用了。
ROS master就好比是一个婚介所,帮助男女建立认识,当这两名男女认识之后也就没有婚介所什么事了,同时该男女还一直保持着联系,如下图所示,当我们关闭了roscore之后,发布者和订阅者扔保持通信连接。
在这里插入图片描述

3.2 以Python为例

3.2.1 创建Python代码

我们创建一个Publisher代码和Subscriber代码,拷贝到learning_topicscripts目录下:
在这里插入图片描述
person_publisher.py的完整代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

########################################################################
####          Copyright 2020 GuYueHome (www.guyuehome.com).          ###
########################################################################

# 该例程将发布/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

person_subscriber.py的完整代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

########################################################################
####          Copyright 2020 GuYueHome (www.guyuehome.com).          ###
########################################################################

# 该例程将订阅/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()

别忘了打开代码执行权限!!!

3.2.2 配置代码编译规则

打开CMakeLists.txt文件,添加以下代码编译规则,注意文件名字:
在这里插入图片描述

3.2.3 编译运行

编译:

cd ~/catkin_ws
catkin_make

在这里插入图片描述

运行:

roscore
rosrun learning_topic person_subscriber.py
rosrun learning_topic person_publisher.py

运行成功:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_56197703/article/details/126959430