一文搞懂 ROS2 工程结构:工作空间、功能包、msg/srv/action 自定义接口

张开发
2026/6/8 19:13:14 15 分钟阅读

分享文章

一文搞懂 ROS2 工程结构:工作空间、功能包、msg/srv/action 自定义接口
目录前言一、本章核心知识点1.1 ROS2 工作空间是什么1.2 ROS2 功能包是什么1.3 msg、srv、action 分别用来做什么二、ROS2 工作空间结构2.1 src、build、install、log 四个目录2.2 src 源码空间1工作空间中的 src2功能包内部的 src2.3 build 编译空间1build 的作用2什么时候需要清理 build2.4 install 安装空间1install 是运行结果所在目录2src、build、install 的关系2.5 log 日志空间2.6 为什么修改代码后需要重新编译1C 代码不是解释执行2正确编译运行流程三、ROS2 功能包概念3.1 什么是功能包功能包是 ROS2 中组织代码的基本单位。3.2 功能包和普通文件夹有什么区别1普通文件夹没有 ROS2 工程属性2C 功能包基本结构3.3 C 功能包和 Python 功能包的区别1两种常见构建类型2创建 C 功能包3创建 Python 功能包3.4 功能包内部常见目录结构及作用1完整功能包结构2普通 C 节点功能包3自定义接口功能包四、ROS2 自定义接口 msg、srv、action4.1 为什么需要自定义接口1ROS2 已经提供了很多标准消息2标准消息不够用时就需要自定义接口4.2 msg 消息文件是什么1msg 用于 Topic 话题通信2msg 文件放在哪里3LimoStatus.msg 示例4std_msgs/Header header 是什么意思5发布消息时如何赋值6msg 文件注意事项4.3 srv 服务文件是什么1srv 用于 Service 服务通信2srv 文件放在哪里3LimoSrv.srv 示例4Service 适合什么场景4.4 action 动作文件是什么1action 适合长时间任务2action 文件放在哪里3LimoAction.action 示例4Action 的特点4.5 msg、srv、action 对比总结五、本章总结5.1 ROS2 工作空间通常包含四个核心目录5.2 功能包是 ROS2 组织代码的基本单位。5.3 ROS2 中常见的自定义接口有三种前言本篇是《松灵 ROS2》第 4 讲的整理笔记主题是ROS2 代码实践。这一讲不再只停留在节点、话题、服务这些概念层面而是开始真正进入 ROS2 工程结构工作空间怎么组织、功能包怎么创建、自定义消息怎么写、以及CMakeLists.txt和package.xml到底应该写什么。本章主要讲 ROS2 工程基础概念包括工作空间、功能包、msg、srv、action、CMakeLists.txt、package.xml以及编译运行流程。具体的 Topic、Service、Action 控制 小车代码实现会放到下一讲单独展开。一、本章核心知识点1.1 ROS2 工作空间是什么工作空间可以理解成 ROS2 项目的总文件夹。它用来统一存放源代码、编译中间文件、安装后的可执行文件以及日志文件。一个典型 ROS2 工作空间一般长这样agilex_open_class_ws/ ├── src ├── build ├── install └── log1.2 ROS2 功能包是什么功能包是 ROS2 中组织代码的基本单位。简单理解功能包就是用来存放某一类功能源代码的文件夹。比如一个功能包可以专门放小车运动控制代码一个功能包可以专门放自定义消息一个功能包可以专门放 launch 启动文件。ROS2 不是直接管理零散的源码文件而是通过功能包来管理代码和依赖。1.3 msg、srv、action 分别用来做什么ROS2 中常见的自定义通信接口有三类msg、srv、action。接口类型文件后缀用途适合场景消息.msg定义Topic发布和订阅的数据结构状态上报、传感器数据、速度指令服务.srv定义Service请求和响应的数据结构一次性查询、参数设置、触发某个动作动作.action定义Action的目标、结果和反馈导航、长时间运动任务、需要过程反馈的任务二、ROS2 工作空间结构2.1 src、build、install、log 四个目录ROS2 工作空间中最常见的四个目录如下目录作用学习时的理解src源码空间存放我们自己写的代码、脚本、launch 文件、功能包源码build编译空间编译过程中产生的中间文件一般不需要手动修改install安装空间编译安装后的可执行文件、脚本、接口文件会放在这里log日志空间记录编译和运行过程中的日志出错时可以查看工作空间目录里能看到build、install、log、src四个文件夹。这也是 ROS2 使用colcon build后最典型的结构。注意工作空间下的 src 不是直接随便放代码的地方而是用来存放 ROS2 功能包的地方。例如agilex_open_class_ws/ └── src ├── limo_learning ├── limo_msgs └── other_package这里的limo_learning、limo_msgs才是真正的 ROS2 功能包。2.2 src 源码空间src是源码空间主要用来存放我们自己创建或下载的 ROS2 功能包。1工作空间中的 src工作空间下的src不是随便放代码的地方而是用来存放 ROS2 功能包的地方。例如agilex_open_class_ws/ └── src ├── limo_learning ├── limo_msgs └── other_package这里的limo_learning limo_msgs other_package才是真正的 ROS2 功能包。2功能包内部的 src在功能包内部也可能还有一个src文件夹。例如limo_learning/ ├── src ├── include ├── launch ├── config ├── CMakeLists.txt └── package.xml这里要特别区分两个src工作空间的 src用来存放功能包功能包内部的 src用来存放 C 源代码例如agilex_open_class_ws/src/limo_learning/src/limo_topic_cmd.cpp这里前面的src是工作空间源码目录后面的src是功能包内部的 C 源码目录。完整结构如下agilex_open_class_ws/ └── src └── limo_learning ├── src │ └── limo_topic_cmd.cpp ├── CMakeLists.txt └── package.xml2.3 build 编译空间1build 的作用build是编译空间。当我们在工作空间根目录执行colcon buildROS2 会根据每个功能包中的CMakeLists.txt和package.xml进行编译。编译过程中产生的中间文件会放到build目录中例如build/limo_learning build/limo_msgs一般情况下build目录不需要我们手动修改。2什么时候需要清理 build如果遇到编译缓存异常、依赖更新后不生效等问题可以清理后重新编译rm -rf build install log colcon build这相当于重新生成整个工作空间的编译结果。2.4 install 安装空间1install 是运行结果所在目录install是 ROS2 中非常重要的目录。很多初学者以为ros2 run运行的是src里面的源码其实不是。对于 C 功能包来说我们运行的通常是编译后安装到 install 空间中的可执行文件。例如运行ros2 run limo_learning limo_topic_cmd系统会在已经source的install空间中查找limo_topic_cmd这个可执行文件。2src、build、install 的关系可以这样理解 ROS2 的编译运行流程src 里面写代码 ↓ colcon build 编译 ↓ build 里面生成中间文件 ↓ install 里面生成运行结果 ↓ ros2 run 从 install 空间查找程序这也是为什么修改 C 源代码之后一定要重新编译colcon build source install/setup.bash如果只修改了src里的源码但没有重新编译那么install里面还是旧的可执行文件运行结果自然不会变化。2.5 log 日志空间log是日志空间。每次执行colcon build编译过程中的日志都会保存在log目录中。如果某个功能包编译失败可以去log目录查看更详细的错误信息。不过对于初学阶段来说大多数时候直接看终端中的报错信息就已经够用了。但是当项目变大后log目录会很有用尤其是排查复杂编译错误时。2.6 为什么修改代码后需要重新编译1C 代码不是解释执行这是初学 ROS2 时非常重要的问题。因为ROS2 的 C 代码不是解释执行的而是需要先编译成可执行文件。修改 C 文件后src/limo_learning/src/limo_topic_cmd.cpp只是源码发生了变化。但是运行时用到的可执行文件在install/limo_learning/lib/limo_learning/limo_topic_cmd如果没有重新执行colcon build那么install里面的可执行文件不会更新。2正确编译运行流程正确流程应该是cd ~/agilex_open_class_wscolcon buildsource install/setup.bashros2 run limo_learning limo_topic_cmd如果只修改了某一个功能包也可以单独编译colcon build --packages-select limo_learning source install/setup.bash三、ROS2 功能包概念3.1 什么是功能包功能包是 ROS2 中组织代码的基本单位。一个 ROS2 工程不是直接管理零散的源码文件而是通过一个个功能包来管理代码、接口、配置和依赖。一个功能包可以包含C 节点代码Python 节点代码自定义 msg 消息自定义 srv 服务自定义 action 动作launch 启动文件config 配置文件URDF / xacro 机器人模型文件RViz 配置文件例如limo_learning可以用来存放小车的控制节点代码。limo_msgs可以专门用来存放小车相关的自定义消息、服务和动作接口。所以功能包不只是一个普通文件夹它是 ROS2 用来组织工程的基本单位。3.2 功能包和普通文件夹有什么区别1普通文件夹没有 ROS2 工程属性普通文件夹只是一个目录本身没有 ROS2 工程属性。而 ROS2 功能包通常至少包含两个关键文件CMakeLists.txt package.xml其中文件作用CMakeLists.txt描述如何编译、生成可执行文件、安装文件package.xml描述功能包名称、版本、依赖、构建类型等信息也就是说一个文件夹想被 ROS2 当作功能包识别不能只是随便建一个目录而是需要符合 ROS2 功能包结构。2C 功能包基本结构例如一个 C 功能包可能长这样limo_learning/ ├── src │ └── limo_topic_cmd.cpp ├── CMakeLists.txt └── package.xml其中src用来存放 C 源代码。CMakeLists.txt用来描述 C 编译规则。package.xml用来描述功能包信息和依赖关系。3.3 C 功能包和 Python 功能包的区别1两种常见构建类型ROS2 中常见的功能包构建类型有两种ament_cmakeament_python它们分别对应不同语言的开发方式。构建类型适合语言特点ament_cmakeC需要CMakeLists.txt编译生成可执行文件ament_pythonPython主要通过setup.py安装 Python 节点入口2创建 C 功能包如果要创建 C 功能包可以执行cd ~/agilex_open_class_ws/src ros2 pkg create --build-type ament_cmake limo_learning如果是写 C 节点一般选择ament_cmake3创建 Python 功能包如果要创建 Python 功能包可以执行cd ~/agilex_open_class_ws/src ros2 pkg create --build-type ament_python limo_learning_python如果是写 Python 节点一般选择ament_python在 ROS2 实际开发中底层控制、驱动、实时性要求较高的模块常用 C。快速验证、工具脚本、简单节点也经常使用 Python。3.4 功能包内部常见目录结构及作用1完整功能包结构一个比较完整的 ROS2 功能包内部结构可能如下limo_learning/ ├── src ├── include ├── launch ├── config ├── msg ├── srv ├── action ├── CMakeLists.txt └── package.xml各目录作用如下目录或文件作用src存放 C 源代码include存放 C 头文件launch存放 launch 启动文件config存放参数配置文件msg存放自定义消息文件srv存放自定义服务文件action存放自定义动作文件CMakeLists.txtC 编译和安装规则package.xml功能包信息和依赖声明2普通 C 节点功能包不过不是每个功能包都必须包含所有目录。例如普通 C 节点功能包可能只需要limo_learning/ ├── src ├── CMakeLists.txt └── package.xml3自定义接口功能包自定义接口功能包可能是limo_msgs/ ├── msg ├── srv ├── action ├── CMakeLists.txt └── package.xml四、ROS2 自定义接口 msg、srv、action4.1 为什么需要自定义接口1ROS2 已经提供了很多标准消息ROS2 已经提供了很多常用消息类型例如geometry_msgs/msg/Twist sensor_msgs/msg/LaserScan nav_msgs/msg/Odometry std_msgs/msg/String这些消息类型可以满足很多基础需求。例如Twist 用来表示速度指令 LaserScan 用来表示激光雷达数据 Odometry 用来表示里程计数据 String 用来表示字符串消息2标准消息不够用时就需要自定义接口但是实际机器人开发中经常会遇到系统自带消息不够用的情况。比如我们想描述小车状态车辆状态控制模式电池电压错误码运动模式这时候就可以自定义一个消息LimoStatus.msg如果想定义一个服务让客户端发送速度请求服务端返回是否执行成功可以定义LimoSrv.srv如果想定义一个长时间执行的任务例如发送目标、执行过程反馈、最后返回结果可以定义LimoAction.action这就是自定义接口的意义。4.2 msg 消息文件是什么1msg 用于 Topic 话题通信msg表示 message也就是消息。它主要用于 Topic 话题通信。Topic 通信可以理解为发布者不断发布消息订阅者接收消息结构类似Publisher ---- topic ---- Subscriber2msg 文件放在哪里自定义 msg 文件一般放在功能包的msg文件夹中。例如limo_msgs/ ├── msg │ └── LimoStatus.msg3LimoStatus.msg 示例LimoStatus.msg示例std_msgs/Header header uint8 vehicle_state uint8 control_mode float64 battery_voltage uint16 error_code uint8 motion_mode4std_msgs/Header header 是什么意思其中std_msgs/Header header这个消息里包含了一个标准消息头 header它不是普通变量而是 ROS2 里std_msgs包提供的一个标准结构。header 时间戳 坐标系名称 时间戳 stamp 坐标系 frame_id其余每一行格式一般是字段类型 字段名例如float64 battery_voltage表示定义一个float64类型的字段字段名叫battery_voltage可以用来表示电池电压。5发布消息时如何赋值比如发布时可以这样填msg.header.stamp this-now(); msg.header.frame_id base_link; msg.vehicle_state 1; msg.control_mode 2; msg.battery_voltage 24.5; msg.error_code 0; msg.motion_mode 1;表示这条消息产生的时间。比如机器人系统中经常需要知道这条状态消息是什么时候发布的这条传感器数据是什么时候采集的这条里程计数据是什么时候计算出来的所以stamp用来记录时间。6msg 文件注意事项需要注意msg文件里面不需要写 C 的分号。错误写法float64 battery_voltage;正确写法float64 battery_voltage如果msg 文件中使用了其他功能包里的类型例如std_msgs/Header header那么在package.xml和CMakeLists.txt中也要声明对应依赖。4.3 srv 服务文件是什么1srv 用于 Service 服务通信srv表示 service也就是服务。Service 是请求响应模型。它的特点是客户端发送请求服务端处理请求服务端返回响应结构类似Client ---- request ---- Server Client --- response --- Server2srv 文件放在哪里自定义 srv 文件一般放在功能包的srv文件夹中。例如limo_msgs/ ├── srv │ └── LimoSrv.srv3LimoSrv.srv 示例LimoSrv.srv示例float32 x float32 y float32 z --- bool success其中---是分隔符。---上面是请求部分float32 x float32 y float32 z---下面是响应部分bool success可以理解为客户端发送x、y、z三个数据服务端处理后返回success表示本次请求是否执行成功。4Service 适合什么场景Service 适合一次性请求场景例如查询一次状态 设置一次参数 触发一次动作 请求一次计算结果4.4 action 动作文件是什么1action 适合长时间任务action表示动作。Action 可以理解为增强版 Service。Service 更适合短时间完成的“一问一答”任务而 Action 更适合执行时间较长、需要过程反馈的任务。例如导航到某个目标点 机械臂移动到某个位置 机器人执行一段巡检任务 小车执行一段运动任务这些任务往往不是瞬间完成的。客户端发送目标后服务端需要执行一段时间并且在执行过程中持续反馈状态最后再返回执行结果。2action 文件放在哪里自定义 action 文件一般放在功能包的action文件夹中。例如limo_msgs/ ├── action │ └── LimoAction.action3LimoAction.action 示例LimoAction.action示例float32 x float32 y float32 z --- bool success --- uint32 statusAction 文件由三部分组成中间用两个---分隔目标 Goal --- 结果 Result --- 反馈 Feedback对应上面的例子第一部分表示客户端发送的目标数据。float32 x float32 y float32 z第二部分表示任务最终执行结果。bool success第三部分表示任务执行过程中的状态反馈。uint32 status4Action 的特点所以 Action 的特点是发送目标 执行过程中持续反馈 执行结束返回最终结果4.5 msg、srv、action 对比总结三种接口的区别可以总结为类型通信方式文件后缀适合场景msg发布 / 订阅.msg状态上报、传感器数据、速度指令srv请求 / 响应.srv参数设置、状态查询、触发一次动作action目标 / 反馈 / 结果.action导航、巡检、机械臂运动、长时间任务更简单的记忆方式msg一直发srv问一下回一下action发目标中途反馈最后返回结果例如/cmd_vel 速度控制适合 Topic 查询机器人是否准备好适合 Service 导航到目标点适合 Action理解这三种通信方式之后后面写 ROS2 代码时就能根据任务特点选择合适的通信模型。五、本章总结本章主要讲解了 ROS2 工程基础概念。5.1 ROS2 工作空间通常包含四个核心目录src 源码空间用来存放功能包 build 编译空间用来存放编译中间文件 install 安装空间用来存放编译安装后的结果 log 日志空间用来存放编译和运行日志其中最关键的是理解src 里面写代码 install 里面放运行结果 ros2 run 通常从 install 空间查找程序所以修改 C 源码、接口文件、CMakeLists.txt 或 package.xml 后通常都需要重新执行colcon build source install/setup.bash5.2 功能包是 ROS2 组织代码的基本单位。普通文件夹只是目录而 ROS2 功能包通常需要包含CMakeLists.txt package.xmlC 功能包一般使用ament_cmakePython 功能包一般使用ament_python5.3 ROS2 中常见的自定义接口有三种msg 用于 Topic 话题通信 srv 用于 Service 服务通信 action 用于 Action 动作通信三者可以简单记忆为msg一直发 srv问一下回一下 action发目标中途反馈最后返回结果

更多文章