3、创建功能包:使用ros2 pkg create创建功能包,理解功能包结构

好,咱们正式开始动手了。

上一章我们聊了工作空间,说白了就是给ROS2项目搭了个「工地」。那功能包是什么?就是工地上的一个个「施工队」——每个队负责一块具体任务,比如控制底盘、处理激光雷达数据、发布里程计信息。

我个人习惯,每开始一个新模块,第一件事就是创建功能包。这步做对了,后面能省不少事。

3.1 创建功能包的命令

ROS2里创建功能包,用的是 ros2 pkg create 命令。我刚开始学的时候,总记不住参数,后来发现其实核心就几个。

最基本的用法:

ros2 pkg create my_package

这样会在当前目录下生成一个叫 my_package 的文件夹。但说实话,这样创建出来的包是「裸」的——没有依赖、没有构建类型,后面编译会报错。

所以,我建议你养成好习惯,创建时就把关键信息带上:

ros2 pkg create my_robot_msgs \
  --build-type ament_cmake \
  --dependencies rclcpp std_msgs

这里解释一下参数:

  • --build-type:构建类型。C++项目用 ament_cmake,Python项目用 ament_python。咱们做自定义消息,一般用CMake。
  • --dependencies:依赖的包。比如 rclcpp(ROS2客户端库)、std_msgs(标准消息类型)。

嗯,这里要注意:如果你创建的是纯消息/服务包(只定义接口,不写节点),依赖可以只写 std_msgsbuiltin_interfaces

小技巧: 我个人习惯把消息包命名为 xxx_msgs,服务包命名为 xxx_srvs。这样一看名字就知道是干啥的。比如 robot_msgsrobot_srvs

3.2 功能包结构长什么样?

创建完成后,咱们进去看看:

cd my_robot_msgs
tree

你会看到这样的结构:

my_robot_msgs/
├── CMakeLists.txt
├── package.xml
├── msg/
├── srv/
└── include/
    └── my_robot_msgs/

我来逐个说说每个文件/文件夹是干嘛的。

文件/文件夹 作用
CMakeLists.txt 构建规则文件。告诉编译器怎么编译、链接哪些库、生成哪些目标。
package.xml 包的「身份证」。包含包名、版本、作者、许可证、依赖关系等信息。
msg/ 存放自定义消息文件(.msg)。一开始是空文件夹。
srv/ 存放自定义服务文件(.srv)。同样一开始是空的。
include/ C++头文件目录。一般放你写的头文件。

你想想看,这个结构其实挺清晰的。消息放 msg/,服务放 srv/,构建规则放 CMakeLists.txt,元信息放 package.xml。各司其职。

3.3 核心文件详解

package.xml

这个文件很重要。ROS2的包管理器(比如 rosdep)就是靠它来识别依赖的。

打开看看:

<?xml version="1.0"?>
<package format="3">
  <name>my_robot_msgs</name>
  <version>0.0.0</version>
  <description>自定义消息与服务包</description>
  <maintainer email="your_email@example.com">your_name</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <build_depend>rclcpp</build_depend>
  <build_depend>std_msgs</build_depend>

  <exec_depend>rclcpp</exec_depend>
  <exec_depend>std_msgs</exec_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

这里有几个关键点:

  • <name>:包名,必须和文件夹名一致。我曾经因为大小写不一致,折腾了半天编译不过。
  • <build_depend>:编译时依赖。比如 rclcpp 是编译节点时需要的。
  • <exec_depend>:运行时依赖。程序跑起来时需要这些包。
  • <export>:声明构建类型。这里写 ament_cmake
注意: 如果你创建的是纯消息包(不写节点代码),可以去掉 rclcpp 依赖,只保留 std_msgsbuiltin_interfaces。否则编译时会多出不必要的依赖。

CMakeLists.txt

这个文件是C++项目的构建脚本。打开看看:

cmake_minimum_required(VERSION 3.8)
project(my_robot_msgs)

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)
find_package(std_msgs REQUIRED)

# 这里后面会添加消息/服务的编译指令

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

嗯,现在这个文件还是「骨架」。后面我们添加自定义消息时,需要在这里加入 rosidl_generate_interfaces() 指令。别急,下一章我会手把手教你改。

3.4 编译功能包

创建完功能包,得编译一下验证有没有问题。

cd ~/ros2_ws
colcon build --packages-select my_robot_msgs

这里 --packages-select 参数只编译指定的包,不用全量编译,省时间。

编译成功后,你会看到:

Starting >>> my_robot_msgs
Finished <<< my_robot_msgs [2.34s]

如果报错,多半是 package.xml 里的依赖没写对,或者 CMakeLists.txt 语法有问题。别慌,仔细看错误信息,ROS2的报错其实挺友好的。

避坑指南: 我曾经在 package.xml 里把 <build_depend> 写成了 <build_depend>(少了个字母),结果编译时提示找不到依赖。检查了半天才发现是拼写错误。所以,写完后最好用 xmllint 验证一下格式。

3.5 功能包结构总结

最后,咱们用一张图来总结功能包的结构:

my_robot_msgs/                  # 功能包根目录
├── CMakeLists.txt              # 构建规则(核心)
├── package.xml                 # 包元信息(核心)
├── msg/                        # 自定义消息文件
│   └── (你的消息文件.msg)
├── srv/                        # 自定义服务文件
│   └── (你的服务文件.srv)
├── include/                    # C++头文件
│   └── my_robot_msgs/
└── src/                        # 源代码(可选)
    └── (你的节点代码.cpp)

说白了,功能包就是ROS2项目的基本单元。每个包做一件事,包与包之间通过消息和服务通信。你想想看,这种模块化的设计,是不是很优雅?

好,这一章就到这里。下一章我们开始真正动手——创建自定义消息类型。到时候我会带你一步步改 CMakeLists.txtpackage.xml,把消息定义出来。

记住:功能包是骨架,消息和服务才是血肉。骨架搭好了,后面就顺了。