ROS2学习笔记,案例实现记录——11.话题topic

前言

在学习了ROS 2官方教程之后,我决定以自导自演的方式,给自己出题来实践所学知识,以加深对ROS 2的各个概念和使用方式的理解。我将把我的学习过程记录在博客中,并将代码放在我的GitHub仓库中https://github.com/YuAndWang/ROS2_Case(当然博客中也会贴上代码),后面有新的案例将会放到对应的章节之后,供大家参考和学习。

欢迎━(`∀´)ノ亻! 大家一起出题,一起探讨!希望我的实践过程对你理解ROS 2有所帮助。如果你有任何问题或建议,欢迎提出!谢谢~~~

11.话题topic

11.1案例1:random_topic

本次的发布者节点非常简单,它每隔一秒发布一个随机数到指定的主题上。

建立功能包

打开一个新的终端, cd 到我的工作空间目录。并且应该在 src 目录中创建包,而不是在工作空间的根目录中创建包。因此,cd 到 my_ws/src,并运行包创建命令:

cd my_ws/src
ros2 pkg create --build-type ament_python random_topic_py

发布者 random_topic_pub.py

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

"""
@作者: 王仰旭
@说明: 发布一个随机数,订阅者订阅
"""

import rclpy
from rclpy.node import Node
from std_msgs.msg import String
import random

class RandomPublisher(Node):

    def __init__(self):
        super().__init__('random_publisher')
        self.pub = self.create_publisher(String, 'topic', 10)
        timer_period = 1
        self.timer = self.create_timer(timer_period, self.timer_callback)

    def create_random(self):
        a = random.randint(0,100)       #产生从0到100的随机数
        return str(a)

    def timer_callback(self):
        msg = String()
        msg.data = self.create_random()
        self.pub.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)
    random_publisher = RandomPublisher()
    rclpy.spin(random_publisher)
    random_publisher.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
代码解释说明:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
import random

class RandomPublisher(Node):

    def __init__(self):
        super().__init__('random_publisher')
        self.pub = self.create_publisher(String, 'topic', 10)  # 创建一个发布者,使用String消息类型,在名为'topic'的主题上发布消息,队列大小为10(消息类型、话题名、队列长度)
        timer_period = 1
        self.timer = self.create_timer(timer_period, self.timer_callback)  # 创建一个定时器,每隔1秒触发一次timer_callback方法

    def create_random(self):
        a = random.randint(0,100)       # 产生从0到100的随机数
        return str(a)

    def timer_callback(self):
        msg = String()  # 创建一个String类型的消息对象
        msg.data = self.create_random()  # 设置消息对象的数据为随机生成的数值
        self.pub.publish(msg)  # 发布消息到主题上
        self.get_logger().info('Publishing: "%s"' % msg.data)  # 记录发布的消息内容到日志

这段代码定义了一个名为RandomPublisher的类,继承自Node类。该类用于创建一个ROS节点,它具有以下功能:

  1. 在构造函数__init__中,节点被初始化为random_publisher,并创建一个发布者(publisher)对象,使用String消息类型,在名为topic的主题上发布消息。发布者队列的大小被设置为10,表示最多可以存储10个未处理的消息。其中参数分别为(消息类型、话题名、队列长度)。
  2. 创建一个定时器,每隔1秒调用一次timer_callback方法。
  3. create_random方法用于生成一个0到100之间的随机数,并将其转换为字符串类型。
  4. timer_callback方法在定时器触发时被调用,它创建一个String消息对象,将随机数作为消息的数据,然后将消息发布到topic主题上。同时,通过get_logger().info方法输出日志,记录发布的消息内容。

以下是主函数的实现:

def main(args=None):
    rclpy.init(args=args)  # 初始化ROS 2
    random_publisher = RandomPublisher()  # 创建RandomPublisher对象
    rclpy.spin(random_publisher)  # 进入主循环,等待节点执行完毕
    random_publisher.destroy_node()  # 销毁节点
    rclpy.shutdown()  # 关闭ROS 2

if __name__ == '__main__':
    main()

在主函数中,首先调用rclpy.init初始化ROS 2。然后创建一个RandomPublisher对象,这将启动一个ROS节点,并执行节点的功能。接下来,使用rclpy.spin函数进入主循环,等待节点执行完毕。最后,调用destroy_node方法销毁节点,再调用rclpy.shutdown关闭ROS 2。

这是一个简单的发布者节点,每秒发布一个随机数到指定的主题上。其他订阅者节点可以订阅相同的主题,接收并处理这些随机数消息。

订阅者 random_topic_sub.py

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

"""
@作者: 王仰旭
@说明: 发布一个随机数,订阅者订阅
"""

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class RandomSubscriber(Node):
    
    def __init__(self):
        super().__init__('random_subscriber')
        self.sub = self.create_subscription(String, 'topic', self.listener_callback, 10)
        
    def listener_callback(self, msg):
        self.get_logger().info('I heard: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)
    minimal_subscriber = RandomSubscriber()
    rclpy.spin(minimal_subscriber)
    minimal_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
代码解释说明:

这段代码是一个使用ROS 2编写的订阅者节点,它会订阅指定主题上的消息,并在接收到消息时打印消息内容。下面是对代码不同部分的概述和解释:

  1. 导入所需的库和模块:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

在这段代码中,我们导入了rclpy模块以及用于创建节点的Node类,还导入了用于订阅消息的String消息类型。

  1. 定义订阅者节点类:
class RandomSubscriber(Node):
    def __init__(self):
        super().__init__('random_subscriber')
        self.sub = self.create_subscription(String, 'topic', self.listener_callback, 10)
        # 创建一个订阅者对象,使用String消息类型,订阅名为'topic'的主题,队列大小为10(消息类型、话题名、订阅者回调函数、队列长度)

RandomSubscriber类中,我们继承了Node类,并在初始化方法中完成以下操作:

  • 调用super().__init__('random_subscriber')来初始化节点,节点名称为random_subscriber
  • 使用create_subscription方法创建一个订阅者对象,订阅名为topic的主题,消息类型为String,队列大小为10。
  • 将订阅者对象赋值给self.sub
  1. 定义回调函数:
def listener_callback(self, msg):
    self.get_logger().info('I heard: "%s"' % msg.data)
    # 当接收到消息时,回调函数listener_callback被触发,打印接收到的消息内容

listener_callback是一个回调函数,当接收到消息时被触发。在本例中,它打印接收到的消息内容。

  1. 主函数:
def main(args=None):
    rclpy.init(args=args)
    minimal_subscriber = RandomSubscriber()
    # 初始化ROS 2并创建一个RandomSubscriber对象
    
    rclpy.spin(minimal_subscriber)
    # 进入主循环,等待节点执行完毕
    
    minimal_subscriber.destroy_node()
    rclpy.shutdown()
    # 销毁节点并关闭ROS 2

if __name__ == '__main__':
    main()

在主函数中,我们进行了以下操作:

  • 调用rclpy.init初始化ROS 2。
  • 创建一个RandomSubscriber对象,这将启动一个ROS节点,成为订阅者。
  • 调用rclpy.spin进入主循环,等待节点执行完毕。
  • 销毁订阅者节点对象,释放资源。
  • 调用rclpy.shutdown关闭ROS 2。

这段代码实现了一个简单的订阅者节点,它会订阅指定主题上的消息,并在接收到消息时打印消息内容。你可以在博客中详细介绍每个部分的作用和意义,帮助读者更好地理解你的代码和ROS 2中订阅者节点的工作原理。

配置package.xml文件

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>random_topic_py</name>
  <version>0.0.0</version>
  <description>random publisher and subscriber using rclpy</description>
  <maintainer email="wangyx6432@gmail.com">wang</maintainer>
  <license>BSD</license>

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

配置setup.py文件

from setuptools import setup

package_name = 'random_topic_py'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='wang',
    maintainer_email='wangyx6432@gmail.com',
    description='random publisher and subscriber using rclpy',
    license='BSD',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'random_topic_pub = random_topic_py.random_topic_pub:main',
            'random_topic_sub = random_topic_py.random_topic_sub:main',
        ],
    },
)

检查setup.cfg文件

[develop]
script_dir=$base/lib/random_topic_py
[install]
install_scripts=$base/lib/random_topic_py

setup.cfg文件用于配置ROS 2的包安装和开发时的相关设置。告诉setuptools把编译后的可执行文件放在lib中,因为运行时ros2 run命令将会在那里寻找它们。

编译

cd my_ws/
colcon build

运行

打开新终端,刷新环境

source ~/my_ws/install/setup.bash
ros2 run random_topic_py random_topic_pub
ros2 run random_topic_py random_topic_sub

效果gif

在这里插入图片描述


11.2案例2:random_topic_interface

这一次想实现一个服务客户端节点,用于向服务端发送服务请求,比较输入的两个整数的大小,并获取最大值和最小值。

实现过程如下:

自定义服务interface srv

需要继续下面服务的学习,需要先定义该服务所使用的消息格式。用request和response来定义请求和响应,以实现我们的服务逻辑。

实现过程如下:

建立功能包

打开一个新的终端, cd 到我的工作空间目录。并且应该在 src 目录中创建包,而不是在工作空间的根目录中创建包。因此,cd 到 my_ws/src,并运行包创建命令:

cd my_ws/src
ros2 pkg create --build-type ament_cmake mine_interface

根据官网的文档,我这里命名"mine_interface"是新包的名称。它是一个CMake包,但这并不限制你可以在哪种类型的包中使用你的消息和服务。可以在一个CMake包中创建自定义接口,然后在C++或Python节点中使用它。

建立对应文件夹

如果是消息msg,则在工作空间内建立msg文件夹,
如果是服务srv,则在工作空间内建立srv文件夹,
后面还会学到动作action,则在工作空间内建立action文件夹,

可以cd进入到功能包后,用命令创建文件夹:

cd my_ws/src/mine_interface
mkdir msg

创建自定义消息文件

这里需要创建消息,我在mine_interface功能包下建立了msg文件夹,在文件夹中创建了一个名为RandomAverage.msg的服务文件,并在其中定义了请求和响应的消息格式。以下是文件内容:

int32 random1      # 随机数1
int32 random2      # 随机数2
float32 ave        # 平均值

上述内容定义了一个自定义的服务消息类型RandomAverage,其中包含三个字段:random1random2ave,分别表示随机数1、随机数2和平均值。

配置package.xml文件

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>mine_interface</name>
  <version>0.0.0</version>
  <description>mine interface</description>
  <maintainer email="wangyx6432@gmail.com">wang</maintainer>
  <license>BSD</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>
  
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

配置CMakeLists.txt文件

记得这里需要在setup.py文件中添加入口点(entry points),以便能够通过命令行运行你的节点。

cmake_minimum_required(VERSION 3.5)
project(mine_interface)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/RandomAverage.msg"
 )
 
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

编译

在工作空间中执行编译命令colcon build,这将会编译你的工作空间中的所有包,包括这个自定义消息包mine_interface

cd my_ws/
colcon build

效果

编译成功后,你会在工作空间下的install/mine_interface/share/mine_interface/srv目录中看到对应的.srv文件,这意味着你的消息包编译成功了!可以开始使用这些自定义的消息类型来定义你的服务,并在节点中实现相应的功能!

在这里插入图片描述

建立功能包

打开一个新的终端, cd 到我的工作空间目录。并且应该在 src 目录中创建包,而不是在工作空间的根目录中创建包。因此,cd 到 my_ws/src,并运行包创建命令:

cd my_ws/src
ros2 pkg create --build-type ament_python random_topic_interface_py

发布者 random_topic_interface_pub.py

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

"""
@作者: 王仰旭
@说明: 自定义消息——发布两个随机数及其平均数
"""

import rclpy
from rclpy.node import Node
from mine_interface.msg import RandomAverage
import random

class RandomPublisher(Node):

    def __init__(self):
        super().__init__('random_interface_pub')
        self.pub = self.create_publisher(RandomAverage, 'topic', 10)
        timer_period = 1
        self.timer = self.create_timer(timer_period, self.timer_callback)

    def timer_callback(self):
        msg = RandomAverage()
        msg.random1 = random.randint(0,100)
        msg.random2 = random.randint(0,100)
        msg.ave = (msg.random1 + msg.random2) / 2
        self.pub.publish(msg)
        self.get_logger().info('Publishing: %d, %d, ave: %f' % (msg.random1, msg.random2, msg.ave))

def main(args=None):
    rclpy.init(args=args)
    node = RandomPublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

订阅者 random_topic_interface_sub.py

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

"""
@作者: 王仰旭
@说明: 自定义消息——订阅发布者发布的两个随机数及其平均数
"""

import rclpy                                                                      # ROS2 Python接口库
from rclpy.node import Node                                                       # ROS2 节点类
from mine_interface.msg import RandomAverage                                   # 自定义的服务接口

class RandomSubscriber(Node):

    def __init__(self):
        super().__init__('random_interface_sub')
        self.sub = self.create_subscription(RandomAverage, "topic", self.listener_callback, 10)

    def listener_callback(self, msg):
        self.get_logger().info('Received: %d, %d, ave: %f' % (msg.random1, msg.random2, msg.ave))

def main(args=None):
    rclpy.init(args=args)
    node = RandomSubscriber()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

配置package.xml文件

记得这里要加入【mine_interface】,声明你的包依赖于自定义的消息包 mine_interface。

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>compare_srvcli_py</name>
  <version>0.0.0</version>
  <description>random with interface using rclpy</description>
  <maintainer email="wangyx6432@gmail.com">wang</maintainer>
  <license>BSD</license>

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <depend>mine_interface</depend>
  
  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

配置setup.py文件

记得这里需要在setup.py文件中添加入口点(entry points),以便能够通过命令行运行你的节点。

from setuptools import setup

package_name = 'random_topic_interface_py'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='wang',
    maintainer_email='wangyx6432@gmail.com',
    description='random with interface using rclpy',
    license='BSD',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'random_topic_interface_pub = random_topic_interface_py.random_topic_interface_pub:main',
            'random_topic_interface_sub = random_topic_interface_py.random_topic_interface_sub:main',
        ],
    },
)

配置setup.cfg文件

setup.cfg文件用于配置ROS 2的包安装和开发时的相关设置。检查一下是否正确配置了安装和开发时的脚本目录。这里会告诉setuptools把编译后的可执行文件放在lib中,因为运行时ros2 run命令将会在那里寻找它们。

[develop]
script_dir=$base/lib/random_topic_interface_py
[install]
install_scripts=$base/lib/random_topic_interface_py

编译

cd my_ws/
colcon build

运行

打开新终端,刷新环境

source ~/my_ws/install/setup.bash
ros2 run random_topic_interface_py random_topic_interface_pub
ros2 run random_topic_interface_py random_topic_interface_sub

效果gif

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼 丸

您的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值