2. ROS2基础
在开始集成任何传感器之前,必须先理解ROS2的“骨架”。ROS2不是一套单纯的库,而是一个分布式通信中间件。它把机器人软件拆解为若干独立单元,通过标准接口协作。本章聚焦四个核心概念——节点、话题、服务、动作,以及工作空间与功能包的创建方法。掌握这些,后续驱动激光雷达或IMU时才能理解数据从哪来、到哪去。
核心概念:节点、话题、服务、动作
节点(Node)是ROS2中执行计算的最小进程单元。一个传感器驱动、一个里程计解算器、一个路径规划器,都可以是独立节点。节点之间不直接调用函数,而是通过“命名图”通信。每个节点有唯一名称,例如 /lidar_driver 或 /imu_filter。
话题(Topic)是异步、单向、连续的数据流通道。发布者(Publisher)向话题写入消息,订阅者(Subscriber)从话题读取消息。话题适合高频传感器数据,如激光雷达点云(/scan)、IMU姿态(/imu/data_raw)。消息类型由接口定义,例如 sensor_msgs/msg/LaserScan。
服务(Service)是同步、双向、一次性的请求-应答模式。客户端发送请求并等待响应。适合低频控制指令,如“开启相机曝光”“重置里程计”。服务用 srv 文件定义请求与响应结构。
动作(Action)用于长时间、可取消、带反馈的任务。例如“导航到目标点”或“机械臂抓取”。动作由目标(Goal)、反馈(Feedback)、结果(Result)三部分组成,客户端可随时取消。动作在ROS2中基于话题和服务实现,但提供了更高层语义。
工作空间与功能包创建
ROS2采用工作空间(Workspace)组织代码。典型结构为 src(源码)、build(编译中间文件)、install(安装产物)、log(日志)。使用 colcon 工具构建。功能包(Package)是ROS2的软件单元,包含节点源码、接口定义、配置文件等。
创建功能包的标准流程如下:
- 创建工作空间目录并初始化
src文件夹。 - 使用
ros2 pkg create生成包骨架。 - 编写节点源码与
CMakeLists.txt/package.xml。 - 使用
colcon build编译。 - source
install/setup.bash使包可见。
# 创建工作空间
mkdir -p ~/ros2_sensor_ws/src
cd ~/ros2_sensor_ws
# 创建C++功能包(含依赖)
ros2 pkg create lidar_driver_pkg \
--build-type ament_cmake \
--dependencies rclcpp sensor_msgs
# 创建Python功能包
ros2 pkg create imu_reader_pkg \
--build-type ament_python \
--dependencies rclpy sensor_msgs
# 编译整个工作空间
colcon build --symlink-install
# 使环境生效
source install/setup.bash
# 验证包可见
ros2 pkg list | grep sensor
--symlink-install 选项。这样修改Python脚本后无需重新编译即可生效,大幅提升调试效率。
案例:搭建激光雷达驱动包
假设我们有一款2D激光雷达,需要编写驱动节点发布 sensor_msgs/msg/LaserScan 消息。以下展示最小实现框架:
// lidar_driver_node.cpp (核心片段)
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/laser_scan.hpp"
class LidarDriver : public rclcpp::Node {
public:
LidarDriver() : Node("lidar_driver") {
publisher_ = this->create_publisher<sensor_msgs::msg::LaserScan>("/scan", 10);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&LidarDriver::publish_scan, this));
}
private:
void publish_scan() {
auto msg = sensor_msgs::msg::LaserScan();
msg.header.stamp = this->now();
msg.angle_min = 0.0;
msg.angle_max = 6.28319; // 360度
msg.angle_increment = 0.0174533; // 1度
msg.range_min = 0.1;
msg.range_max = 10.0;
// msg.ranges 填充实际测量数据...
publisher_->publish(msg);
}
rclcpp::Publisher<sensor_msgs::msg::LaserScan>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<LidarDriver>());
rclcpp::shutdown();
return 0;
}
编译后运行节点,可用 ros2 topic echo /scan 查看数据流,用 ros2 node info /lidar_driver 查看节点详情。
常见误区与复盘
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 所有通信都用话题 | 服务类指令(如校准)难以同步确认 | 根据请求-响应语义选择服务 |
忽略 package.xml 依赖声明 |
在别的机器上编译失败 | 创建包时用 --dependencies 显式声明 |
| 忘记 source install/setup.bash | 运行时报“找不到包” | 每次新终端先source,或加入 ~/.bashrc |
| 话题名与消息类型不匹配 | 订阅者收不到数据 | 用 ros2 topic info /scan 检查类型 |
/scan,会发生数据冲突。建议使用命名空间隔离,例如 /left_lidar/scan 与 /right_lidar/scan。
复盘一个典型调试场景:当你运行 ros2 run lidar_driver_pkg lidar_driver_node 却收到 Package not found 错误时,90%的原因是当前终端未source工作空间。执行 source ~/ros2_sensor_ws/install/setup.bash 即可解决。若仍失败,检查 colcon build 是否成功,以及包名是否拼写正确。
理解节点间的通信模式差异,是设计传感器驱动架构的基础。话题负责数据洪流,服务处理控制指令,动作管理复杂任务。工作空间与功能包则是代码组织的基石。下一阶段,我们将基于这些概念,实际接入第一个传感器并可视化数据流。