ROS 入门 —— 话题消息的定义与使用

ROS 入门 —— 话题消息的定义与使用

话题模型

在 ROS 已经订阅好的消息中,美誉能够满足我们需求的,这时我们就需要自己来定义消息的类型了,下面我们就要介绍消息的定义,以及 publisher 和 subscriber 的使用

如何定义一个话题消息?这里我们以 Person 为例,我们需要先定义一些数据,然后再定义一些确定的数据值就相当于程序中的宏定义,我们可以在程序中直接调用值:

这里给出的类型只是一个预先类型,仅定义了数据的长度、格式等,并不是最终的数据类型,最终的数据类型会根据编程语言的不同而扩展成不同的类型,在编译的时候会进行动态扩展,所以我们还需要加入一系列的编译选项才能完整正确的编译

具体流程如下:

定义数据接口

首先我们在我们的 learning_topic 包中创建一个 msg 文件夹,存储跟消息相关的定义,便于管理,进入 msg 目录下,创建我们的 msg 文件,Person.msg 文件内容如下:

string name
uint8  age
uint8  sex

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

添加依赖

首先我们需要一个动态生成程序的功能包依赖,我们直接添加到 learning_topic 路径下的 package.xml 文件中:

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

这里的 message_generation 是动态生成程序的依赖,message_runtime 是运行程序时动态连接的依赖,添加位置如下:

在 xml 中添加了功能包依赖的同时,我们也要将依赖提添加到 CMakeLists.txt 文件中:

然后我们还需要在 CMakeLists.txt 文件中添加一个能够把 msg 编译成对应的不同程序文件的配置项:

add_message_files(
  FILES
  Person.msg
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

add_message_files 会把我们定义的这个 msg 文件作为我们定义的接口,我们在编译的时候就会发现这个接口并针对它进行编译

generate_messages 会在编译 msg 文件的时候补全一些依赖的 ROS 中已有的包或库

添加位置如下:

最后我们还需要在 catkin_message 中创建一个 msg 运行的依赖:

这里我们直接取消注释就行,到这里,我们就完成了编译 msg 文件的所有基本配置,然后我们直接编译就行

调用 msg 中定义的变量

我们在编译成功后,会在 catkin_ws/devel/include/learning_topic 路径下生成 Person.msg 对应的头文件 Person.h

要想调用我们发布的 msg 的变量,我们可以直接调用生成的 Person.h 这个头文件,这里我们同时发布一个 publisher 和一个 subscriber:

点击查看完整代码:

person_publisher.cpp
//
// Created by ppqpp on 2023/2/27.
//

/**
 * 该例程将发布/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
//
// Created by ppqpp on 2023/2/27.
//

/**
 * 该例程将订阅/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;
}
person_publisher.py
#!/usr/bin/env python3
# -*- 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
person_subscriber.py
#!/usr/bin/env python3
# -*- 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()

我们这里在 subscriber 的程序中使用了回调函数,如果我们设置为全局接收,那么我们的程序将会循环运行,将占用大量的系统资源,而我们是了回调函数,程序仅在对方发出对应程序,我方成功接收到指定程序的基础下执行下一步程序,所以这里选择使用回调函数来接收信息

然后我们需要在 CMakeList.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_dependencies ,它的主要功能就是将我们所添加的 publisher 或 subscribe 与 在编译中生成的其他文件进行连接

这里我们直接编译发布就行

运行测试

这里我们直接运行我们前面发布的节点:

这里我们先运行订阅者(subscriber)程序,可以看到,由于我们没有运行发布者(publisher)程序,我们没有接收到任何内容

然后我们运行发布者(publisher)程序,这里可以看到我们已经进行了信息的发布

然后我们再看前面运行的订阅者(subscriber)程序,根据时间戳,我们可以判断我们在发布的同一时间接收到了信息

这里我们还可以进行一个测试,当我们将 ros-master 关闭后,我们的发布者和订阅者的程序是否还在正常运行?

我们可以直观的看到,当我们关闭了 ros-master 之后,程序依然在正常运行,其实 ros-master 在这里充当的就是一个中介的作用,当我们两个程序已经进行了连接,就已经不需要再有这个中介来对程序进行管理了

猜你喜欢

转载自blog.csdn.net/m0_59161987/article/details/129415289
今日推荐