结论:横向实验需在速度偏差 < 0.7 m/s、latActive、绝对曲率 < 0.004 1/m、绝对 roll < 0.12 rad 全部成立后才计数,且严格大于 40(理想至少 41 个合格循环)才进入活动并记录基线曲率;就绪计数失败时每轮只减一,所以并非严格连续两秒。活动期只对驾驶员转向/油门输入和车速偏差过大触发 reset(),latActive=False、曲率或 roll 越界本身不会撤销动作。完成保持期前 19 次循环计划无效,第 21 帧会短暂发出一帧有效基线曲率。以下为固定快照 c8fb906 的静态阅读与隔离探针,未运行回放、CAN、硬件或实车。
关键函数与字段
该进程是开发实验进程,不应被当作常规横向规划器。下表按入口列出源码行为与契约边界。
| 入口 | 源码行为 | 契约边界 |
|---|---|---|
| 进程条件 / 配置项 | started && LateralManeuverMode 运行实验进程;参数在 manager 启动和下线切换清理 |
它与纵向的 plannerd 不互斥;UI 令实验开关互斥只是配置层约束 |
_sine_action() / MANEUVERS |
构造 20/30 mph 的阶跃、0.5 Hz 正弦和十段交替 jitter,共八组,每组 repeat=2 |
Action 只断言时间与加速度数组等长,不保证时间节点单调、有限或有界 |
Maneuver.get_accel() |
速度与初速差小于 0.7 m/s、latActive、绝对曲率小于 0.004 1/m、绝对 roll 小于 0.12 rad 时计数;不满足时计数每轮减一 |
严格大于 40;失败仅减一,并非严格连续两秒;活动后不再以 ready 作前置条件 |
Maneuver.reset() |
清动作帧/序号/活动标志,再清就绪计数 | 重复轮次须重新积累就绪计数;_repeated、_finished 不由 reset() 清除 |
main() 中止分支 |
转向按压、油门按压使 aborted_cnt 设为名义一秒并 reset();活动时车速偏差大于 0.7 m/s 也重置 |
未把 latActive=False、曲率/roll 超阈值、订阅输入无效或模型失联列为活动中止条件 |
main() 发布分支 |
每轮发布 alertDebug.valid=True 和 lateralManeuverPlan;计划仅在有动作、active 且 complete_cnt==0 时有效 |
有效位不取 sm.all_checks();sm.update() 默认 100 ms 超时也可返回,动作帧与提示计数按 DT_MDL=0.05 推进 |
Controls.state_control() / SubMaster.update_msgs() |
只看该主题最近一次收到的 valid 来选择实验曲率,随后由 clip_curvature() 限幅 |
发布进程停发后,上次有效计划可能被继续选择;订阅者未在该分支检查 updated、alive 或年龄 |
就绪计数与中止边界
进入活动要同时满足四个严格条件,并累计超过 40 次。get_accel() 在满足时计数、不满足时只减一,因此短暂中断不会清零计数,也就不是严格的「连续两秒合格」。此外,内置实验曲线不是车型认证极限,Action 只断言时间与加速度数组等长,不保证插件提供的时间节点单调、有限或有界。
| 项目 | 阈值 / 计数 |
|---|---|
| 速度与初速差 | 小于 0.7 m/s |
| 绝对曲率 | 小于 0.004 1/m |
| 绝对 roll | 小于 0.12 rad |
| 就绪计数 | 严格大于 40(理想至少 41 个合格循环) |
| 完成提示保持 | int(1/DT_MDL)=20 次循环 |
| 动作时间步 | DT_MDL=0.05 |
中止条件并不覆盖全部「异常」:main() 的中止分支只把转向按压、油门按压,以及活动期间车速偏差大于 0.7 m/s 作为触发。也就是说,latActive=False、曲率或 roll 超出就绪门本身,不会重置动作计划。这是进程状态事实,不代表失活时执行器仍会转向。
完成保持期的边界
继承的 _step() 在最终轮次结束时设置 _finished=True 和 _run_completed=True,但没有清 _active。main() 因 _run_completed 把 complete_cnt 设为 int(1/DT_MDL)=20。保持期前 19 次循环计划无效;最后一次循环先把计数从 1 减至 0,再执行 plan_send.valid = maneuver is not None and maneuver.active and complete_cnt == 0,因此按当前控制流会发送一帧有效的基线曲率(本轮 accel 初始值 0),然后才把完成的 maneuver 清空。
五项原 main() 假发布器探针确认:正常活动时目标为 baseline + accel / vEgo²;最终保持序列中,前 20 帧无效,第 21 帧短暂有效且目标等于基线曲率,下一帧清空动作并提示全部完成。驾驶员转向输入和活动期间车速偏差超限会在计算前调用 reset();仅 latActive=False、曲率或 roll 超出就绪门本身不会触发该复位。这里是隔离消息构造证据,不是已观测到的实际转向脉冲;controlsd 可能在不同调度相位收到该帧,且仍有 latActive、曲率限幅、车型和 panda 门。离线报告生成器按最后一条 valid 计划确定截取边界,这一额外有效帧也可能改变报告边界。
对硬件与插件化的要求
横向实验插件必须定义独立的计划租约、真实采样时戳、计划年龄上限、曲率单位/符号和适用车速;宿主统一处理 valid、存活和失效后的模型回退。armed → active → aborted/completed 状态应显式清理;横向失活、车速/曲率/roll 越界、输入失联和驾驶员干预的中止策略须分别定义。提示保持计数不能决定计划有效性。
动作曲线、基线曲率和加速度到曲率的转换须按目标车型及硬件版本验证;回放重点覆盖第 41 次就绪、就绪中断、活动中失活、重复轮次、最终保持帧和发布者停发。当前归档源码目录未见针对这些状态转移或最终保持帧的专门原仓库自动化测试;本篇十项隔离探针补充了部分控制流证据,未运行真实消息总线、动态回放或实车验证。
常见问题
为什么是第 41 次而不是第 40 次才启动?
因为 get_accel() 用严格大于 40 判定,理想情况下至少 41 个合格循环才记录基线并开始;失败时计数只减一而不清零,所以也并非严格连续两秒。
横向失活会自动中止实验吗?
不会自动中止。main() 的中止分支只把转向按压、油门按压和活动时车速偏差大于 0.7 m/s 列为中止;latActive=False、曲率/roll 超阈值、输入无效、模型失联未被列为活动中止条件。
实验结束后会多发一帧转向吗?
按当前控制流,完成保持期前 19 次循环计划无效,最后一次先把计数从 1 减到 0 再判定 valid,因此会发出一帧有效的基线曲率(本轮 accel 初值 0),随后清空。这是隔离消息构造证据,不是已观测到的实际转向脉冲。
发布者停发后计划会立刻失效吗?
不一定。controlsd 只看该主题最近一次收到的 valid,而 SubMaster 的 alive 独立按收包时间计算;发布进程停发后,上次有效计划可能被继续选择。
延伸阅读
- 纵向机动状态机:就绪计数、动作推进与完成保持 —— 横向实验继承的
_step()机制来源 - 从使能到车轮:执行链上的八道门,以及它们各自不保证什么 —— 实验计划进入执行链后的门
- openpilot 的 100 Hz 控制回路:开源 ADAS 能给车载设备什么 —— 计划有效位与实际执行之间的时序关系
- 车辆模型、动力学与控制的边界:从仿真到实车不能直接复用什么 —— 动作曲线与基线曲率需按车型验证的背景
参考资料
- 项目 02 ——
openpilot-research/LATERAL_MANEUVER_STATE_MACHINE_DEEP_DIVE.md(横向实验的状态机、阈值与完成保持帧) - 项目 02 ——
openpilot-research/LATERAL_TARGET_ACTUATION_DEEP_DIVE.md(横向执行链,目标曲率下游的独立门,作为本文的补充阅读)
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复