ROS2 + EM Planner 开发记录(二):EgoVehicle 模块
ROS2 + EM Planner 开发记录(二):EgoVehicle 模块
1. 模块流程
EgoVehicle 是自车在代码里的代理,负责三件事:存位姿 → 执行运动学 → 广播 TF。
文件依赖关系
common.hpp ← 所有模块共享的类型定义
│
ego_vehicle.hpp ← 声明接口(不继承 Node)
│
ego_vehicle.cpp ← 运动学积分 + TF 广播
│
main.cpp ← 定时器驱动,周期性调 updateState
数据流
main 定时器 50Hz
│
└→ ego.updateState(dt)
├─ 从 vehicle_info_ 读取 v、omega、当前位姿
├─ 自行车模型积分:x, y, yaw 更新
├─ yaw → 四元数转换
├─ 更新状态机(STANDBY ↔ CRUISING)
└─ broadcaster_.sendTransform(...) → /tf
和 Visualizer 一样不继承 Node
Visualizer 拿 node 指针干活,EgoVehicle 也一样。一个进程只有一个 ROS 节点,所有模块挂上面。后面 FrenetConverter、DpPlanner 也是这个模式。
main.cpp 当前做的事
// 画参考线(静态,发一次)
vis.publishReferenceLine(reference_points);
// 设置自车初态 + 指令
ego.setPose(0.0, 0.0, 0.0);
ego.setCommand(0.7, 0.0); // 沿着x轴直走
// 50Hz 定时器驱动
auto timer = node->create_wall_timer(20ms, [&]() {
ego.updateState(0.02);
});
2. Pose 和 VehicleInfo 的解耦
为什么要拆两个结构体
刚开始想把所有字段塞一起:
// 不拆的写法(不好)
struct VehicleState {
double x, y, yaw; // 位姿
double v, omega; // 速度
StateEnum state; // 状态
};
问题:位姿信息(x, y, yaw)不止 EgoVehicle 要用——规划器要知道自车在哪,控制器要知道跟轨迹的偏差,后面感知模块也要用。每次都传整个 VehicleState 进去?太重了。
解耦设计
// 纯位姿 — 轻量,到处都能用
struct Pose {
double x = 0.0;
double y = 0.0;
double yaw = 0.0;
};
// 车辆完整状态 — 只有 EgoVehicle 内部关心
struct VehicleInfo {
Pose pose;
double v = 0.0;
double omega = 0.0;
VehicleState current_state = VehicleState::INIT;
};
这样后面规划器只需要 Pose,不用关心速度字段。控制模块给 EgoVehicle 发指令只需要调 setCommand(v, omega),不用关心状态机内部怎么转。
状态机设计
enum class VehicleState : uint8_t {
INIT = 0,
STANDBY = 1,
CRUISING = 2,
EMERGENCY = 3
};
用 uint8_t 做底层类型,省内存,跨模块传参也安全。当前 auto 转换逻辑:
|v| < 0.01 → STANDBY (静止)
|v| >= 0.01 → CRUISING (行驶中)
后面 EMERGENCY 由控制器触发,比如偏离参考线太远就停车。
3. 踩坑:四元数默认值与 RViz2 显示异常
问题
RViz2 中添加 TF 显示后,base_link 坐标系的朝向一直在跳动,或者干脆不显示。
排查过程
TF 发布逻辑中,先构造了 geometry_msgs::msg::TransformStamped:
geometry_msgs::msg::TransformStamped transform;
transform.header.frame_id = "map";
transform.child_frame_id = "base_link";
transform.transform.translation.x = vehicle_info_.pose.x;
transform.transform.translation.y = vehicle_info_.pose.y;
由于编译和运行都没有报错,错误地以为 transform.transform.rotation 的四个分量有默认值(即单位四元数 w=1)。
根因
查了 ROS2 源码发现,geometry_msgs/msg/Quaternion 的消息定义中,所有四个分量的默认值都是 0.0:
| 分量 | 以为的默认值 | 实际的默认值 |
|---|---|---|
| x | 0 | 0 |
| y | 0 | 0 |
| z | 0 | 0 |
| w | 1 | 0 |
四个分量全为 0 不是有效四元数(模长不为 1),RViz2 解析这个无效旋转时行为未定义,导致显示异常。
修复
在发布之前,显式用 tf2::Quaternion 计算旋转并逐分量填入:
tf2::Quaternion qtn;
qtn.setRPY(0.0, 0.0, vehicle_info_.pose.yaw);
transform.transform.rotation.x = qtn.getX();
transform.transform.rotation.y = qtn.getY();
transform.transform.rotation.z = qtn.getZ();
transform.transform.rotation.w = qtn.getW();
之后 RViz2 正常显示 base_link 坐标系。
ROS2 消息的默认值不一定是"合理值"。Quaternion 默认不是单位四元数,Point 默认不是 NaN。涉及数学上有约束的类型,一定要显式赋值,不要依赖默认值。
下一步:参考线生成 + 纯跟踪控制器
当前自车以恒定 v 和 omega 运动,完全忽视参考线。下一步两个模块:
- 参考线生成器(ReferenceLine):用几个路点 + 三次样条插值,把
std::sin换掉,画出真正的车道中心线 - 纯跟踪控制器(PurePursuit):读取参考线 + 当前车位姿 → 计算前视距离和转向角 → 调
ego.setCommand(v, omega),让车真正沿参考线跑
做完这两个模块后,自车不再是直线或画圈,而是能跟随任意参考线。跑通后实现 Frenet 坐标系转换,最终集成 EM Planner 的 DP 路径规划。
本次提交
git commit -m "feat: EgoVehicle模块,自行车模型+TF广播+状态机+Pose/VehicleInfo解耦"
GitHub 链接:github.com/gj-465930/ros2-pnc-planner
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)