ROS2 -03-工作空间与功能包

文章目录

    • [ROS2 工作空间与功能包完全指南](#ROS2 工作空间与功能包完全指南)
      • [一、ROS2 工作空间(Workspace)](#一、ROS2 工作空间(Workspace))
        • [1. 什么是工作空间?](#1. 什么是工作空间?)
        • [2. 工作空间的目录结构](#2. 工作空间的目录结构)
        • [3. 工作空间的类型:Overlay 与 Underlay](#3. 工作空间的类型:Overlay 与 Underlay)
        • [4. 创建工作空间](#4. 创建工作空间)
        • [5. 编译工作空间](#5. 编译工作空间)
      • [二、ROS2 功能包(Package)](#二、ROS2 功能包(Package))
        • [1. 什么是功能包?](#1. 什么是功能包?)
        • [2. 功能包的最小结构](#2. 功能包的最小结构)
        • [3. 创建功能包](#3. 创建功能包)
        • [4. 功能包的依赖管理](#4. 功能包的依赖管理)
        • [5. 编译功能包](#5. 编译功能包)
      • 三、功能测试python代码示例:发布者与订阅者
        • [1. 创建功能包](#1. 创建功能包)
        • [2. 编写发布者节点](#2. 编写发布者节点)
        • [3. 编写订阅者节点](#3. 编写订阅者节点)
        • [4. 配置 setup.py](#4. 配置 setup.py)
        • [5. 编译与运行](#5. 编译与运行)
        • [6. 查看节点和话题](#6. 查看节点和话题)
      • [ROS2 工作空间与功能包(C++ 示例)](#ROS2 工作空间与功能包(C++ 示例))

ROS2 工作空间与功能包完全指南

在 ROS2 中,工作空间(workspace)功能包(package) 是组织和管理代码的核心概念。理解它们对于开发 ROS2 应用程序至关重要。本文将全面介绍这两个概念,并提供一个完整的 Python 发布-订阅示例,每行代码都附带详细注释。


一、ROS2 工作空间(Workspace)

1. 什么是工作空间?

工作空间是一个包含 ROS2 功能包的目录,它提供了一种结构化的方式来组织、构建和运行多个相互关联的功能包。通过工作空间,你可以将自定义的代码与 ROS2 基础环境分离,方便管理和复用。

2. 工作空间的目录结构

一个典型的 ROS2 工作空间包含以下子目录:

复制代码
workspace_name/
├── src          # 存放所有功能包的源代码
├── build        # 存放编译过程中的中间文件(colcon 自动生成)
├── install      # 存放编译后的可执行文件、库、配置文件等(colcon 自动生成)
└── log          # 存放编译日志(colcon 自动生成)
  • src:你手动创建并放置自定义功能包的地方。
  • buildinstalllog 由构建工具 colcon 自动生成,通常不需要手动修改。
3. 工作空间的类型:Overlay 与 Underlay
  • Underlay :基础的 ROS2 安装环境(如系统安装的 /opt/ros/humble)。每个新工作空间都可以叠加在现有环境之上。
  • Overlay :你正在开发的本地工作空间,它依赖于 underlay 提供的依赖,但可以覆盖或扩展其中的功能包。当你 source 一个工作空间的 install/setup.bash 后,该工作空间就成为了当前终端的 overlay。
4. 创建工作空间

使用以下命令创建一个新的工作空间(例如 ros2_ws):

bash 复制代码
mkdir -p ~/ros2_ws/src   # 创建工作空间根目录和 src 文件夹
cd ~/ros2_ws             # 进入工作空间

此时工作空间还是空的,接下来我们需要在 src 文件夹中创建功能包。

5. 编译工作空间

ROS2 的官方构建工具是 colcon。在工作空间根目录下运行:

bash 复制代码
colcon build

常用选项:

  • --packages-select <package_name>:仅编译指定的功能包。
  • --symlink-install:使用符号链接安装,修改 Python 脚本后无需重新编译。
  • --cmake-args -DCMAKE_BUILD_TYPE=Release:传递参数给 CMake。

编译完成后,需要 source 安装文件才能使用该工作空间:

bash 复制代码
source install/setup.bash

(可以将其添加到 ~/.bashrc 中自动加载)


二、ROS2 功能包(Package)

1. 什么是功能包?

功能包是 ROS2 中代码组织的最小单元。它包含节点、库、配置文件、启动文件等,并且必须包含描述包信息和依赖关系的 package.xml 文件以及一个构建文件(CMakeLists.txt 用于 C++,setup.pysetup.cfg 用于 Python)。

2. 功能包的最小结构

以 Python 功能包为例:

复制代码
my_package/
├── package.xml          # 包清单文件(必须)
├── setup.py             # Python 包的安装脚本(必须)
├── setup.cfg            # 配置文件(通常用于声明 entry points)
├── resource/            # 标记文件夹(通常包含一个空文件 my_package)
├── test/                # 测试代码文件夹(可选)
└── my_package/          # 与包同名的 Python 模块,存放源代码
    └── __init__.py
  • package.xml:包含包名、版本、作者、许可证以及依赖项。
  • setup.py:用于安装 Python 包,定义入口点(entry points)以便将脚本作为节点运行。
  • setup.cfg:通常告诉 setuptools 如何安装脚本。
  • resource:标记该文件夹为 ROS2 包(通常包含一个与包名相同的空文件)。
  • my_package:实际的 Python 模块,放置节点代码。

对于 C++ 包,则使用 CMakeLists.txtinclude/src/ 等目录。

3. 创建功能包

使用 ros2 pkg create 命令创建功能包。例如创建一个 Python 包:

bash 复制代码
cd ~/ros2_ws/src
ros2 pkg create my_package --build-type ament_python --dependencies rclpy std_msgs
  • --build-type:指定构建类型,ament_python 用于 Python,ament_cmake 用于 C++。
  • --dependencies:列出该包依赖的其他 ROS2 包(如 rclpystd_msgs)。这些依赖会被自动添加到 package.xml 中。
  • 创建一个C++包
cpp 复制代码
cd ~/ros2_ws/src
ros2 pkg create cpp_pubsub --build-type ament_cmake --dependencies rclcpp std_msgs

cpp_pubsub/

├── CMakeLists.txt # 构建配置文件

├── package.xml # 包清单文件

├── include/ # 存放头文件(可选)

│ └── cpp_pubsub/

└── src/ # 存放源文件

4. 功能包的依赖管理

package.xml 中,依赖通过以下标签声明:

  • <depend>:构建和运行时都需要。
  • <build_depend>:仅构建时需要。
  • <build_export_depend>:导出到依赖此包的其他包所需的依赖。
  • <exec_depend>:仅运行时需要。
  • <test_depend>:仅测试时需要。

例如,package.xml 中的依赖部分可能如下:

xml 复制代码
<depend>rclpy</depend>
<depend>std_msgs</depend>
5. 编译功能包

在工作空间根目录使用 colcon build,可以指定只编译该包:

bash 复制代码
colcon build --packages-select my_package

三、功能测试python代码示例:发布者与订阅者

下面我们创建一个简单的发布者-订阅者示例,使用 Python 编写,并逐行注释。该示例包含两个节点:一个发布字符串消息,一个订阅并打印消息。

1. 创建功能包

首先,确保你已经创建了工作空间(如 ~/ros2_ws),然后在 src 目录下创建包:

bash 复制代码
cd ~/ros2_ws/src
ros2 pkg create py_pubsub --build-type ament_python --dependencies rclpy std_msgs

这条命令会生成一个名为 py_pubsub 的 Python 包,并声明依赖 rclpy(ROS2 Python 客户端库)和 std_msgs(标准消息类型)。

2. 编写发布者节点

py_pubsub/py_pubsub 目录下创建 publisher.py 文件:

bash 复制代码
cd ~/ros2_ws/src/py_pubsub/py_pubsub
touch publisher.py
chmod +x publisher.py   # 赋予执行权限(可选)

编辑 publisher.py,内容如下:

python 复制代码
#!/usr/bin/env python3
# 指定解释器为 Python3,使得可以直接执行该脚本

import rclpy  # 导入 ROS2 Python 客户端库
from rclpy.node import Node  # 导入 Node 类,所有 ROS2 节点都继承自它
from std_msgs.msg import String  # 导入标准字符串消息类型

class MinimalPublisher(Node):
    """
    自定义的发布者节点类,继承自 Node。
    """
    def __init__(self):
        # 调用父类 Node 的构造函数,节点名称为 'minimal_publisher'
        super().__init__('minimal_publisher')
        
        # 创建一个发布者,发布到 'topic' 话题,消息类型为 String,队列长度为 10
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        
        # 创建一个定时器,每隔 0.5 秒调用一次 timer_callback 函数
        # 参数:定时器周期(秒),回调函数
        self.timer = self.create_timer(0.5, self.timer_callback)
        
        # 初始化计数器
        self.i = 0

    def timer_callback(self):
        """
        定时器回调函数:创建消息并发布。
        """
        # 创建 String 类型的消息对象
        msg = String()
        # 设置消息内容,包含计数器的当前值
        msg.data = 'Hello World: %d' % self.i
        
        # 调用发布者的 publish 方法发布消息
        self.publisher_.publish(msg)
        
        # 使用节点的日志系统打印信息(相当于 ROS1 的 ROS_INFO)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        
        # 计数器递增
        self.i += 1

def main(args=None):
    """
    主函数:初始化节点,进入事件循环,最后清理。
    """
    # 初始化 ROS2 客户端库
    rclpy.init(args=args)
    
    # 创建 MinimalPublisher 类的实例
    minimal_publisher = MinimalPublisher()
    
    # 进入事件循环,等待回调函数被触发(此处会阻塞,直到节点被关闭)
    rclpy.spin(minimal_publisher)
    
    # 当 spin 结束后(例如按 Ctrl+C),销毁节点(可选,但推荐)
    minimal_publisher.destroy_node()
    
    # 关闭 ROS2 客户端库
    rclpy.shutdown()

if __name__ == '__main__':
    main()
3. 编写订阅者节点

同样在 py_pubsub/py_pubsub 目录下创建 subscriber.py 文件:

bash 复制代码
touch subscriber.py
chmod +x subscriber.py

编辑 subscriber.py

python 复制代码
#!/usr/bin/env python3

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

class MinimalSubscriber(Node):
    """
    自定义的订阅者节点类,继承自 Node。
    """
    def __init__(self):
        # 节点名称为 'minimal_subscriber'
        super().__init__('minimal_subscriber')
        
        # 创建一个订阅者,订阅 'topic' 话题,消息类型为 String,队列长度为 10
        # 当收到消息时,调用 listener_callback 函数
        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10)
        # 为了防止 "unused variable" 警告,可以将 subscription 保存为成员变量(如上)
        # 但实际上 self.subscription 被使用了,所以没问题

    def listener_callback(self, msg):
        """
        消息回调函数:收到消息时打印内容。
        """
        # 使用节点的日志系统打印收到的消息
        self.get_logger().info('I heard: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)
    minimal_subscriber = MinimalSubscriber()
    rclpy.spin(minimal_subscriber)  # 持续等待消息
    minimal_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
4. 配置 setup.py

为了使 ros2 run 能够找到我们的节点,需要在 setup.pyentry_points 中添加控制台脚本入口。编辑 py_pubsub/setup.py

python 复制代码
from setuptools import setup
import os
from glob import glob

package_name = 'py_pubsub'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        # 安装 package.xml 到 share 目录,以便 ROS2 发现该包
        (os.path.join('share', package_name), ['package.xml']),
        # 如果有启动文件或其他资源,也可以在这里添加
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='your_name',
    maintainer_email='your_email@example.com',
    description='Python publisher and subscriber example',
    license='Apache License 2.0',  # 或其他许可证
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            # 格式: "命令名 = 包名.模块名:主函数名"
            'publisher = py_pubsub.publisher:main',
            'subscriber = py_pubsub.subscriber:main',
        ],
    },
)
5. 编译与运行
  1. 编译工作空间

    bash 复制代码
    cd ~/ros2_ws
    colcon build --packages-select py_pubsub
  2. 加载工作空间环境(如果尚未加载):

    bash 复制代码
    source install/setup.bash
  3. 运行发布者

    打开终端1:

    bash 复制代码
    source ~/ros2_ws/install/setup.bash
    ros2 run py_pubsub publisher

    你将看到每隔 0.5 秒打印一条发布消息。

  4. 运行订阅者

    打开终端2:

    bash 复制代码
    source ~/ros2_ws/install/setup.bash
    ros2 run py_pubsub subscriber

    订阅者会收到发布者发送的消息并打印。

6. 查看节点和话题
  • 使用 ros2 node list 查看运行中的节点。
  • 使用 ros2 topic list 查看可用话题。
  • 使用 ros2 topic echo /topic 直接查看话题上的消息。

ROS2 工作空间与功能包(C++ 示例)

在前面的回答中,我们已经详细介绍了 ROS2 工作空间和功能包的概念。本文将重点聚焦于 C++ 功能包的创建与测试代码,提供一个完整的发布者-订阅者示例,并逐行注释代码。通过这个示例,你将学会如何组织 C++ 节点、配置构建文件,并成功运行。


一、准备工作

确保你已经安装了 ROS2(如 Humble、Iron 或 Rolling),并创建了一个工作空间(例如 ros2_ws):

bash 复制代码
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws

二、创建 C++ 功能包

使用 ros2 pkg create 命令创建一个 C++ 包,并指定依赖项 rclcpp(ROS2 C++ 客户端库)和 std_msgs(标准消息类型):

bash 复制代码
cd ~/ros2_ws/src
ros2 pkg create cpp_pubsub --build-type ament_cmake --dependencies rclcpp std_msgs
  • --build-type ament_cmake:指定构建类型为 CMake(C++ 包的标准)。
  • --dependencies:自动将依赖添加到 package.xmlCMakeLists.txt 中。

生成的目录结构如下:

复制代码
cpp_pubsub/
├── CMakeLists.txt      # 构建配置文件
├── package.xml         # 包清单文件
├── include/            # 存放头文件(可选)
│   └── cpp_pubsub/
└── src/                # 存放源文件

三、编写发布者节点

src 目录下创建 publisher.cpp 文件:

bash 复制代码
cd ~/ros2_ws/src/cpp_pubsub/src
touch publisher.cpp

编辑 publisher.cpp,内容如下(每一行都有详细注释):

cpp 复制代码
// 包含必要的头文件
#include <chrono>          // 时间相关,用于定时器周期
#include <functional>      // 用于 std::bind
#include <memory>          // 用于 std::make_shared
#include <string>          // 字符串处理

#include "rclcpp/rclcpp.hpp"               // ROS2 C++ 客户端库
#include "std_msgs/msg/string.hpp"         // 标准字符串消息类型

using namespace std::chrono_literals;       // 允许使用 500ms 这样的字面量

/* 发布者节点类,继承自 rclcpp::Node */
class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)    // 节点名称,并初始化计数器
  {
    // 创建发布者:话题名称 "topic",消息类型 std_msgs::msg::String,队列大小 10
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    
    // 创建定时器:每隔 500ms 调用一次 timer_callback 函数
    timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

private:
  // 定时器回调函数
  void timer_callback()
  {
    // 创建一个 String 消息对象
    auto message = std_msgs::msg::String();
    message.data = "Hello, world! " + std::to_string(count_++);
    
    // 使用 RCLCPP_INFO 宏打印日志(类似 ROS1 的 ROS_INFO)
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    
    // 发布消息
    publisher_->publish(message);
  }
  
  // 成员变量
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;  // 发布者智能指针
  rclcpp::TimerBase::SharedPtr timer_;                              // 定时器智能指针
  size_t count_;                                                    // 消息计数器
};

/* 主函数 */
int main(int argc, char * argv[])
{
  // 初始化 ROS2
  rclcpp::init(argc, argv);
  
  // 创建节点实例并进入事件循环(spin 会一直阻塞,直到节点关闭)
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  
  // 关闭 ROS2
  rclcpp::shutdown();
  return 0;
}

四、编写订阅者节点

src 目录下创建 subscriber.cpp 文件:

bash 复制代码
touch subscriber.cpp

编辑 subscriber.cpp,内容如下:

cpp 复制代码
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;   // 用于 std::bind 占位符

/* 订阅者节点类 */
class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    // 创建订阅者:话题 "topic",消息类型 std_msgs::msg::String,队列大小 10
    // 当收到消息时,调用 topic_callback 函数
    subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

private:
  // 消息回调函数
  void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
  {
    // 打印接收到的消息
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
  }
  
  // 成员变量:订阅者智能指针
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

五、修改构建文件

C++ 包使用 CMakeLists.txt 来定义如何编译可执行文件。打开 cpp_pubsub/CMakeLists.txt,并进行如下修改:

cmake 复制代码
cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)

# 默认使用 C++17 标准(ROS2 要求)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# 查找依赖包
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)          # 查找 rclcpp
find_package(std_msgs REQUIRED)         # 查找 std_msgs

# 编译发布者节点可执行文件
add_executable(publisher src/publisher.cpp)
# 链接需要的库
ament_target_dependencies(publisher rclcpp std_msgs)

# 编译订阅者节点可执行文件
add_executable(subscriber src/subscriber.cpp)
ament_target_dependencies(subscriber rclcpp std_msgs)

# 安装可执行文件到 lib 目录,使得 ros2 run 可以找到
install(TARGETS
  publisher
  subscriber
  DESTINATION lib/${PROJECT_NAME})

# 如果存在 launch 文件或其他资源,也可以在这里安装

# ament 包相关设置
ament_package()

注意ament_target_dependencies 是 ROS2 提供的便捷函数,它会自动处理头文件路径和依赖库链接。

package.xml 文件在创建包时已自动添加了依赖,检查一下确保包含以下内容:

xml 复制代码
<depend>rclcpp</depend>
<depend>std_msgs</depend>

六、编译与运行
  1. 编译工作空间

    bash 复制代码
    cd ~/ros2_ws
    colcon build --packages-select cpp_pubsub
  2. 加载工作空间环境 (每次新终端都需要执行,或添加到 ~/.bashrc):

    bash 复制代码
    source ~/ros2_ws/install/setup.bash
  3. 运行发布者节点

    打开终端1:

    bash 复制代码
    ros2 run cpp_pubsub publisher

    你将看到每 0.5 秒输出一条发布日志。

  4. 运行订阅者节点

    打开终端2:

    bash 复制代码
    ros2 run cpp_pubsub subscriber

    订阅者会打印接收到的消息。


七、验证与调试
  • 使用 ros2 node list 查看正在运行的节点,应看到 /minimal_publisher/minimal_subscriber
  • 使用 ros2 topic list 查看话题,应看到 /topic
  • 使用 ros2 topic echo /topic 直接查看话题上的消息流。
  • 如果需要重新编译,可以在工作空间根目录再次执行 colcon build,或者只编译修改过的包:colcon build --packages-select cpp_pubsub

相关推荐
兮动人2 小时前
Linux 云服务器部署 OpenClaw 全攻略:从环境搭建到 QQ 机器人集成
linux·服务器·机器人·openclaw
广州赛远4 小时前
SRA166防静电防护服安装保养指南:避免机器人静电损伤的实操详解
人工智能·机器人
物联网软硬件开发-轨物科技4 小时前
【轨物方案】“比特”驱动“瓦特”:详解光伏清洁机器人无刷与有刷智能硬件方案
机器人·智能硬件
视***间4 小时前
视程空间全系列产品解析:以边缘算力为核,铸就全场景智能硬件标杆
人工智能·机器人·边缘计算·智能硬件·视程空间
胡摩西5 小时前
机器人的“室内GPS”:RoomAPS如何为移动设备打造专属室内定位系统
机器人·slam·gps·室内定位·roomaps
达宽科技7 小时前
教程 机器人线束通电检测怎么做?(二)
服务器·功能测试·机器人·汽车
才兄说7 小时前
机器人租售效果好吗?现场演示验证
机器人
WWZZ20257 小时前
具身智能入门Isaac Sim——机器人设置-初级设计轮式机器人3
机器人·大模型·具身智能·isaac
沫儿笙8 小时前
库卡焊接机器人保护气问题解决方案
机器人