RuyiSDK Board Docs

RISC-V ROS2 机器人操作系统编程技术

Source repository

ch02 · ROS 2 核心编程基础

TextbookProgramming languageEnvironmentLessonLab
RISC-VC++17SpacemiT K3 CoM260 Kit / Bianbu 4.0.6 / Humble, with an x86 Ubuntu 22.04 / Humble / Harmonic course containerRead lessonStart lab
x86PythonUbuntu 22.04 / Humble or Ubuntu 24.04 / JazzyRead lessonStart lab

This document is not yet available in English. The Chinese original is shown below.

第2章:ROS 2 核心编程基础

课程:ROS2 Python 编程
章节:第2章
课时:2 课时(90 分钟)
教学方式:讲授 + 演示


2.1 创建 Python 包与节点

知识点 2.1.1:ROS 2 Python 包结构

ROS 2 Python 包使用 ament_python 构建系统,标准目录结构如下:

my_robot_pkg/                     # 包根目录
├── package.xml                   # 包元数据(名称、版本、依赖)
├── setup.py                      # 安装脚本
├── setup.cfg                     # 安装配置
├── resource/
│   └── my_robot_pkg              # ament index 标记文件
├── my_robot_pkg/                 # Python 源码
│   ├── __init__.py
│   └── my_node.py                # 节点代码
├── launch/                       # Launch 文件目录
│   └── demo.launch.py
└── test/                         # 测试代码
    ├── __init__.py
    └── test_my_node.py

图 2-1:ROS 2 Python 包的标准目录结构。

知识点 2.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>my_robot_pkg</name>
  <version>0.1.0</version>
  <description>My first ROS 2 Python package</description>
  <maintainer email="student@example.com">Student Name</maintainer>
  <license>Apache-2.0</license>
 
  <!-- 运行时依赖 -->
  <exec_depend>rclpy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
 
  <!-- 测试依赖 -->
  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
 
  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

知识点 2.1.3:setup.py 安装入口

from setuptools import find_packages, setup
import os
from glob import glob
 
package_name = 'my_robot_pkg'
 
setup(
    name=package_name,
    version='0.1.0',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'),
            glob(os.path.join('launch', '*launch.py'))),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='Student Name',
    maintainer_email='student@example.com',
    description='My first ROS 2 Python package',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_node = my_robot_pkg.my_node:main',
        ],
    },
)

注意:entry_points 是关键配置,它将 Python 函数注册为 ROS 2 可执行文件。 格式为:'可执行文件名 = 包名.模块名:函数名'

知识点 2.1.4:官方要点——包的结构与创建

官方 Creating a package 教程指出,一个 ROS 2 包是可编译、可安装、可分享的最小单元,Python 包的典型结构包括 package.xml(包元数据与依赖声明)与 setup.py/setup.cfg(安装规则)。创建方式为 ros2 pkg create my_pkg --build-type ament_python --dependencies rclpy,其中 --dependencies 会把依赖提前写入 package.xml。

教程特别强调两点实践规范:第一,package.xml 中的 depend 标签必须与实际 import 的依赖一致,否则在纯二进制安装环境中会因缺少依赖而无法运行;第二,Python 包的可执行入口在 setup.py 的 entry_points 中声明,格式为 名称 = 包名.模块名:函数名。这与本章 2.1-2.2 节示范的模板一致,Articulated Robotics 的视频教程还演示了 --symlink-install 带来的「改代码即时生效」效果。


2.2 编写第一个 ROS 2 Python 节点

知识点 2.2.1:最小节点模板

#!/usr/bin/env python3
import rclpy                     # ROS 2 Python 客户端库
from rclpy.node import Node      # 节点基类
 
class MyFirstNode(Node):
    """我的第一个 ROS 2 Python 节点"""
 
    def __init__(self):
        super().__init__('my_first_node')  # 节点名称
        # 设置定时器,每秒调用一次 timer_callback
        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0        # 计数器
        self.get_logger().info('节点已启动!')  # 打印日志
 
    def timer_callback(self):
        """定时器回调函数"""
        self.count += 1
        self.get_logger().info(f'Hello ROS 2! Count: {self.count}')
 
 
def main(args=None):
    rclpy.init(args=args)                    # 初始化 ROS 2
    node = MyFirstNode()                     # 创建节点实例
    try:
        rclpy.spin(node)                     # 进入事件循环(阻塞)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()                  # 销毁节点
        rclpy.shutdown()                     # 关闭 ROS 2
 
if __name__ == '__main__':
    main()

程序 2-1:ROS 2 Python 最小节点模板。main 函数遵循"初始化→创建→spin→销毁→关闭"的标准流程。

知识点 2.2.2:节点名称与命名空间

# 方式1:直接在构造函数中设置
newnode = rclpy.create_node('node_name', namespace='my_ns')
 
# 方式2:通过命令行参数重映射
# ros2 run my_pkg my_node --ros-args -r __ns:=/new_ns -r __node:=new_name
 
# 方式3:在 Node 构造函数中指定
class MyNode(Node):
    def __init__(self):
        super().__init__('node_name', namespace='my_ns')

命名规则:节点名称只能包含字母、数字和下划线,不能包含空格。 完整节点标识 = {namespace}/{node_name},如 /my_ns/node_name。

知识点 2.2.3:日志系统

ROS 2 提供分级日志系统,优先级从低到高:

日志级别 Python API 用途
DEBUG get_logger().debug() 调试信息(默认不输出)
INFO get_logger().info() 一般信息
WARN get_logger().warn() 警告信息
ERROR get_logger().error() 错误信息
FATAL get_logger().fatal() 致命错误
# 设置日志级别
self.get_logger().set_level(rclpy.logging.LoggingSeverity.DEBUG)
 
# 节流日志(避免高频输出中刷屏)
self.get_logger().info('此消息每秒最多输出一次',
    throttle_duration_sec=1)
 
# 一次性日志(整个节点生命周期内只输出一次)
self.get_logger().info('此消息只输出一次', once=True)

知识点 2.2.4:官方要点——编写并运行节点、重映射与命名空间

官方以 talker/listener 为例演示最小节点:节点类继承自 Node,在 __init__ 中 create_publisher,再用 create_timer 驱动周期发布;运行方式为 ros2 run demo_nodes_py talker。教程同步展示 ros2 node list 与 ros2 node info <node> 的用法——后者能显示节点的订阅者/发布者列表、服务端与客户端以及相关的 QoS 配置,是日后排查节点间「连不上」问题的第一工具。

CLI Tools 教程的多篇页面(Understanding ROS 2 nodes 等)详细解释了三件事:一是节点名(Node Name)与可执行文件名是两回事,同一可执行文件可通过重映射生成多个节点实例;二是 --ros-args -r __ns:=/sensor 与 __node:=new_name 分别用于命名空间与节点名重映射,重映射后话题名会受命名空间影响;三是不同命名空间下的节点相互隔离,同名节点在同一命名空间下无法共存。

这些机制在 DDS 层面体现为:每个节点在发现阶段广播自己的参与者信息,RMW 层将其映射为实体;ros2 node list 之所以能跨命名空间列出全部节点,正是因为发现机制是全局的。理解这一层,对后续章节的多机器人仿真与分布式部署大有裨益。


2.3 开发工具链

知识点 2.3.1:命令行工具

# 构建与运行
colcon build --packages-select my_robot_pkg --symlink-install
source install/setup.bash
ros2 run my_robot_pkg my_node
 
# 查看节点
ros2 node list                          # 列出所有节点
ros2 node info /my_first_node           # 查看节点详情
 
# 查看话题
ros2 topic list                         # 列出所有话题
ros2 topic echo /topic_name             # 监听话题消息
ros2 topic info /topic_name             # 查看话题信息
ros2 topic hz /topic_name               # 查看话题频率
 
# 查看包信息
ros2 pkg list                           # 列出所有包
ros2 pkg xml my_robot_pkg               # 查看包 XML

知识点 2.3.2:rqt_graph 可视化

# 启动 rqt_graph 可视化节点通信图
rqt_graph
# 或通过 ros2 命令启动
ros2 run rqt_graph rqt_graph

图 2-2:rqt_graph 界面示意图,安装命令为 sudo apt install ros-humble-rqt-graph。图中椭圆表示节点,矩形表示话题,连线表示发布/订阅关系,勾选「Nodes/Topics (all)」可以查看完整的通信图。

知识点 2.3.3:RViz2 可视化

# 启动 RViz2
rviz2
# 或指定配置文件
rviz2 -d /path/to/config.rviz

图 2-3:RViz2 界面布局,安装命令为 sudo apt install ros-humble-rviz2。其中左侧面板为 Displays(显示项配置),可添加 RobotModel、TF、LaserScan 等显示项;右侧面板为 Views(视图控制),支持 Orbit、TopDownOrtho 等多种视角。

知识点 2.3.4:官方要点——生命周期节点实现(进阶)

官方 Concepts 与 ros2/examples 提供了生命周期节点的最小实现:继承 rclpy.node.Node 的子类 rclpy.lifecycle.LifecycleNode,重写 on_configure、on_activate、on_deactivate 等回调,即可响应外部状态迁移请求。CLI 侧使用 ros2 lifecycle get /node 查询状态、ros2 lifecycle set /node configure 触发迁移。

The Construct 的课程将生命周期节点与「系统准备就绪」需求关联讲解:在启动阶段只有全部组件进入 Active 状态后,导航等任务级应用才允许发送控制指令。这与第1章 1.4 节演示的生命周期状态机及截图输出逐状态对应,建议读者对照官方 lifecycle/talker.py 示例重写一遍状态回调。


2.4 本章小结

本章的核心知识点包括六个方面:ROS 2 Python 包标准结构包含 package.xml(元数据)、setup.py(安装入口)和源码目录;entry_points 配置将 Python 函数映射为 ROS 2 可执行命令;节点遵循"init → create_node → spin → destroy → shutdown"的生命周期;日志系统提供 DEBUG/INFO/WARN/ERROR/FATAL 五个级别,支持节流和一次性输出;ros2 node/cli/topic/pkg 命令行工具是调试 ROS 2 系统的核心手段;rqt_graph 可视化节点通信拓扑,RViz2 可视化机器人传感器和执行器数据。


2.5 练习题

练习 2.1:创建一个名为 hello_pkg 的 ROS 2 Python 包,包含一个名为 hello_node 的节点,每秒输出一句问候语。3.1

练习 2.2:修改练习 2.1 的节点,添加日志级别参数 --ros-args --log-level DEBUG,观察不同日志级别的输出差异。

练习 2.3:运行两个 hello_node 节点,使用 --ros-args -r __node:=hello2 重命名第二个节点,用 ros2 node list 验证。

(从3.2里改)ros2 run hello_pkg hello_node ros2 run hello_pkg hello_node --ros-args -r __node:=hello2 ros2 node list

alt text

练习 2.4:使用 ros2 node info 查看 talker 节点的发布者和订阅者信息,画出其通信拓扑图。3.3

练习 2.5:启动 demo_nodes_py talker 和 listener,使用 rqt_graph 可视化节点通信关系。3.3

练习 2.6:安装并启动 RViz2,加载默认配置,认识 RViz2 的显示面板和视角控制。3.3


仿真结合实例(当前仓库):在 Gazebo 仿真中运行自己的 Python 节点

目标与知识点对应

第2章讲解如何创建 Python 包、用 entry_points 注册可执行节点,并通过 ros2 node list/info 与 RViz2 观察节点。这里我们在运行时复用 robot_sim_demo 启动的 Gazebo 仿真,然后自行创建并运行一个 Python 节点,订阅仿真发布的 /scan,验证"用 ros2 run 启动自己的节点、被 DDS 自动发现"这一套开发流程。

运行命令

source /opt/ros/jazzy/setup.bash
source install/setup.bash
 
# 终端 1:启动 Gazebo 仿真(无界面更省资源)
ros2 launch robot_sim_demo gazebo2.launch.py gui:=false rviz:=false drive:=false

另开终端运行 demo_nodes_py 自带的 Python 节点来体会节点发现与重命名(对应练习 2.3):

source install/setup.bash
ros2 run demo_nodes_py listener
ros2 node list          # 会看到 listener 以及其他仿真节点
ros2 node info /listener

再自行创建包:src/my_first_pkg/(见 2.1-2.2 节模板),在 entry_points 中注册 my_node,重新 build 后运行:

colcon build --packages-select my_first_pkg
source install/setup.bash
ros2 run my_first_pkg my_node

需要观察到的现象

运行上述命令后应重点观察三类现象:其一,ros2 node list 中,你自己的节点和仿真节点(gazebo2_robot_state_publisher 等)在同一 DDS 域内被自动发现,无需任何中心节点;其二,ros2 node info /listener 显示其订阅关系,若订阅 /scan,用 ros2 topic list | grep scan 能看到仿真的话题;其三,用 --ros-args -r __node:=my_node2 运行副本,验证命名空间/节点重映射(对应 2.2.2 节)。

源码与相关位置

src/robot_sim_demo/launch/gazebo2.launch.py 是仿真启动入口;节点包模板与 setup.py 见 2.1-2.2 节示例,真实 /scan 话题由 src/robot_sim_demo/config/gazebo2_bridge.yaml 桥接发布。

说明:若以真实传感器话题验证订阅逻辑,可参考 lab_manuals/images/runtime/ch09_gazebo_headless.png 中 /scan 的桥接证据。

ch02 Python 节点运行输出


学习材料: