Files
Tutorial/docs/MultiVehicleAutoSyncReview.md
T

13 KiB
Raw Blame History

自动多车联动机制问题分析(MultiVehicleAutoSyncReview

本文档记录 Tutorial MultiWheelC自动运行多车联动机制的潜在 bug 与工程化缺陷。
本次仅作分析归档,不包含代码修复。

相关实现:


自动联动数据流

Simple 调度 MultiVehicleCar (MultiVehicleSync=true)
  → 编译 agv.BasicGo(..., multiVehicleSync:true)
  → BasicInterface 设置 _controller.MultiVehicleSync = true
  → MultiWheelGeometricController.Track() 跟踪路径
  → MultiVehicleSendMotion(speed, frontTh, rearTh, idealPos, idealAngle)
  → PilotDefinition.MultiVehicleAutoVx/FrontTh/RearTh + AutoEnabled=true
  → TickMultiVehicle (主车) 广播 notify → 从车跟 FleetVx/FrontTh/RearTh
  → 各车 SetOriginBias(layout) + SendMotion + 检测/SLAM 补偿

问题清单

ID 严重度 问题摘要 定位
A 在会被重新赋值的 MultiVehicleFleet 字段上 lock,互斥失效 PilotDefinition.cs ~185-205, 多处 lock
B 路径结束/回调早退时自动速度不清零,车队滑行 PilotDefinition.cs 44-47, ChassisController.cs 46-57
C 主车 fleet 表无 TTL,从车崩溃后仍认为编队就绪 PilotDefinition.cs ~178-186, 505-508
D 曲线路径广播单一 FrontTh/RearTh,丢弃 idealPos/idealAngle PilotDefinition.cs 417-419,616; ChassisController.cs 46
E ControlPointRadius 与 SendMotion localControlRadius 硬编码冲突 PilotDefinition.cs 519, 616
F notify 经 GET query 串 fire-and-forget,无背压/限频 PilotDefinition.cs 846-854, 696-702
G 车队中心 CenterX/Y/Th 跨线程非原子读写 PilotDefinition.cs 348,434-436; ChassisController.cs 60-66
H 关闭互识别时 SLAM 全程丢失不停车 PilotDefinition.cs 485, 535

A. lock 目标字段会被整体替换

描述
MultiVehicleFleet 在 notify 处理中被整体重新赋值:

lock (MultiVehicleFleet)
    MultiVehicleFleet = notification.Fleet ?? new Dictionary<int, VehicleSyncInfo>();

同时其它线程(register、Tick、VisualizeFleet)也在 lock (MultiVehicleFleet) 上同步。
lock 的对象是引用本身;字段被赋新字典后,不同线程可能分别锁旧引用与新引用,互斥语义失效,存在 torn read / 丢更新风险。

影响
高并发下 fleet 快照不一致、Aligned/fleetReady 判定抖动,极端情况下补偿或 notify 基于过期 fleet 状态运行。

建议修复

  • 使用 private readonly object _multiVehicleFleetLock = new(); 作为唯一锁对象;或
  • 永不替换字典引用,改为 Clear() + 逐条 Update;或
  • 使用 ConcurrentDictionary + 不可变快照广播。

B. 自动速度残留滑行

描述
MultiVehicleAutoVx / FrontTh / RearThPilotDefinition 第 45-47 行)由 ChassisController.MultiVehicleSendMotion 写入。
当路径跟踪结束、DriveTask 完成,或回调因 MultiVehicleFleet.Count != FleetNum 提前 return 时,这些字段不会被清零

MultiVehicleAutoEnabled[AsUpperIO(timeOutReset = true)],在 IO 超时前主车仍认为自动模式开启,继续读取最后一次非零速度并广播给从车。

影响
路径到达终点后车队仍按末速度滑行;与手动模式「松杆即停」行为不一致,存在 overshoot / 碰撞风险。

建议修复

  • DriveTask 完成或 _controller 置 null 时显式清零 MultiVehicleAuto*MultiVehicleAutoEnabled=false
  • 主车广播前检查速度命令新鲜度(时间戳 / 控制器活跃标志);
  • 从车侧对 FleetVx 增加与 notify 相同的 freshness 超时(目前已有 notification 超时,但速度字段本身无独立 TTL)。

C. 主车 fleet 成员无 TTL

描述
从车通过 /multi-vehicle-register 上报,主车仅 MultiVehicleFleet[CarNum] = info 覆盖写入,从不删除过期条目

fleetReady = (fleetCount == MultiVehicleFleetNum)(第 505-508 行)只检查数量,不检查从车是否仍在线。

影响
从车进程崩溃或网络断开后,主车仍认为编队完整并继续 SendMotion + notify,存活车辆按过期 layout/DetectOk 运行,编队几何错误或未检测到邻车仍运动。

建议修复

  • VehicleSyncInfo 增加 LastSeenUtcregister/notify 收到时刷新;
  • 主车 Tick 剔除超过 N×SyncInterval 未更新的成员;
  • fleetReady 要求所有成员 LastSeen 新鲜。

D. 曲线路径几何与 idealPos 丢弃

描述
主车从 MultiVehicleAutoFrontTh/RearTh 广播单一 (FrontTh, RearTh),各从车原样套用(第 417-419、616 行)。
这对平移、蟹行、绕共有旋转中心近似成立,但对一般曲线路径,各车相对瞬时曲率中心位置不同,应使用不同舵角。

MultiWheelGeometricController 已计算 idealPos / idealAngle 并传入 MultiVehicleSendMotion,但 ChassisController.cs 回调签名中完全忽略这两个参数,只转发 speed + frontTh + rearTh。

影响
自动模式下走弧线/贝塞尔时,从车仅靠检测/SLAM 补偿「事后纠偏」,存在滞后与稳态横向误差(与原地旋转 PI 纠偏同类问题,但自动模式无专用 PI)。

建议修复

  • notify 增加 FleetIdealX/Y/Th 或 per-car layout 目标位姿;
  • 从车按 layout 目标做前馈 + 补偿,而非共用 frontTh/rearTh
  • 或限制自动联动仅支持直线段(文档/配置门控)。

E. 控制半径硬编码冲突

描述
chassis.SetOriginBias 后设置 ControlPointRadius = syncDistance / 2f(第 519 行),但 SendMotion 传入 localControlRadius: 510(第 616 行)硬编码,覆盖几何含义。

影响
编队间距配置变更时,实际控制点半径仍固定 510mm,补偿/转向几何与 TestCarSyncDistance 不一致,调参困难。

建议修复
统一使用 syncDistance / 2f,删除 magic number 510。


F. notify 传输层缺陷

描述
主车每 50ms 对每台从车 FireAndForgetNotify:将完整 VehicleSyncNotification(含整队 Fleet 字典)JSON 序列化后嵌入 GET query 字符串(第 846-854 行),共享单个 HttpClient,无 await、无重试、无背压。

影响

  • 车队规模增大时 URL 超长(浏览器/服务器 query 上限);
  • 从车处理慢时请求堆积;
  • 乱序到达时从车可能短暂应用旧 notification
  • GET 语义不适合大 payload,且不利于缓存/安全审计。

建议修复

  • 改为 POST + JSON body
  • 仅广播命令增量 + 各车 register 拉取全量;
  • 限频 / 合并同 tick 多次 notify
  • 记录 notify 序列号供从车丢弃旧包。

G. 车队中心跨线程 torn read

描述
MultiVehicleLoop(后台线程)写入 CenterX/Y/Th(第 434-436 行)。
路径控制器线程通过 MultiVehicleGetFleetPos 读取 PilotDefinition.Self.CenterX/Y/ThChassisController 第 60-66 行)。
三个 float 字段无锁、无 volatile,可能读到「x 已更新、y/th 仍为旧值」的组合。

影响
单帧路径跟踪使用错误车队中心,产生短暂错误 ideal 点 → 自动速度抖动。

建议修复

  • struct FleetCenter { float X,Y,Th; long Tick; } 整体赋值/读取(或 lock 快照);
  • 控制器只读上一完整快照。

H. 关闭互识别时 SLAM 丢失不停车

描述
canMove = !MultiVehicleUseDetect || (ownDetectOk && othersDetectOk)(第 485 行)。
MultiVehicleUseDetect=falsecanMove 恒为 true,即使 Detour 丢失、TryInferFleetCenter 失败,仍执行 SendMotion(仅 pos 补偿分支跳过)。

影响
纯 SLAM 自动模式下定位全丢后,车队仍按最后 broadcast 速度运动,无 detect 安全网。

建议修复

  • 自动模式额外要求 posAvailable && TryInferFleetCenter 或 notify PosAvailable
  • 与手动模式区分:手动可仅靠 detect,自动必须有有效 fleet 中心。

优先级建议(若后续修复)

  1. P0:A(锁)、B(停车)、C(TTL)— 直接关系安全停车与状态一致
  2. P1:G(中心快照)、H(SLAM 门控)、F(notify 传输)— 稳定性与可扩展
  3. P2:D(曲线几何)、E(半径统一)— 精度与可维护性

修订记录

日期 说明
2026-06-28 初版:基于 Tutorial MultiWheelC 自动联动实现与联调日志分析整理
2026-06-28 A~H 全部修复落地(PilotDefinition.cs / ChassisController.cs / VehicleSyncModels.cs / PilotConfig.cs),见下「修复实现」
2026-06-29 修正 MultiVehicleSyncUseDetour 语义:仅控制"车队内姿态纠正",整车队姿态计算始终用 Detour;并修复 FleetRotateInPlace 欠转(航向闭环判停),见下「语义修正」

修复实现(A~H

  • A:新增 public readonly object FleetLock,所有对 MultiVehicleFleet 的读写统一 lock(FleetLock)(含 ChassisController 回调),不再锁会被整体替换的字段引用。
  • BMultiVehicleSendMotion 回调写入 MultiVehicleAutoCmdTime;主车自动分支按 MultiVehicleAutoCmdTimeoutMs(0=auto) 判定命令新鲜度,超时清零速度/idealPos 并关闭 AutoEnabled,避免末速度滑行。
  • C:新增本地 _multiVehicleFleetSeen 存活时刻表,register/notify 收到即刷新;主车 Tick PruneStaleFleetMembers()MultiVehicleMemberTtlMs(0=auto) 剔除掉线成员,fleetReady(数量==总数) 因此蕴含全员新鲜。
  • D:回调不再丢弃 idealPos/idealAngle,写入 MultiVehicleAutoIdeal* 并经 notify(HasIdeal/IdealX/Y/Th) 广播;自动模式下以理想车队中心作为各车 layout 前馈目标(MultiVehicleAutoUseIdealCenter,默认开)。
  • EControlPointRadiusSendMotion(localControlRadius) 统一取 syncDistance / 2f,删除硬编码 510。
  • Fnotify 改为 POST + JSON body(取代 GET query 串);新增单调递增 Seq,从车丢弃乱序旧包(含主车重启回退识别)。
  • G:新增 FleetCenterSnapshot 不可变快照 + volatile 引用,PublishFleetCenter 整体赋值,控制器线程 GetFleetCenterSnapshot() 只读完整快照,消除 torn read。
  • H:自动模式新增 MultiVehicleAutoRequireFleetCenter(默认开) 门控——无有效车队中心(定位丢失)时强制停车,补上纯 SLAM 模式安全网;手动模式不受限。

语义修正(2026-06-29

1. MultiVehicleSyncUseDetour 重新定义:仅控制"车队内姿态纠正"

问题:原实现把"是否读 Detour 全局位姿"与"是否做车队内姿态纠正"绑在同一开关上。falseposAvailable 直接为假、根本不读 SLAM,导致 TryInferFleetCenter 失败、整车队姿态无法计算——这与该开关应有的含义不符。

修正PilotDefinition.TickMultiVehicle):将单一 posAvailable 拆为三个语义清晰的量:

变量 含义 取值
useDetourCorrection 是否做车队内姿态纠正PosBias* 逐车 SLAM 补偿) = MultiVehicleSyncUseDetour
slamRead 本车本轮是否读取 Detour 全局位姿 useDetourCorrection || (isMaster && autoMode)
fleetPosValid 整车队全局姿态是否已知(主车=自身读到,从车=主车广播) 见代码
  • 整车队姿态计算(反推/广播车队中心、SLAM 间距、自动安全门 H、自动入口门)一律改用 slamRead/fleetPosValid始终依赖 Detour,不再受开关限制;自动入口门与 H 门去掉 && MultiVehicleSyncUseDetour 条件。
  • 车队内姿态纠正PosBias* 补偿块)是唯一受 useDetourCorrection 控制的开关点。
  • false 时:自动模式主车仍 getCartLocation() 计算整车队姿态(无定位则阻塞停车),但不再逐车 SLAM 纠偏。

2. FleetRotateInPlace 欠转修复(航向闭环判停)

问题:原地旋转 MovementDefinition 的判停沿用 MultiVehicleSyncUseDetour,关闭时退化为"按估算时长开环停止",实际转速 < 指令时(PI 纠偏吃速率 + 起步斜坡)会没转到目标就停(实测 180° 欠转)。

修正MovementTests.FleetRotateInPlace):新增 FleetRotateUseDetourHeading(默认 true)MultiVehicleSyncUseDetour 解耦——转到指定角度属于"整车队姿态计算",故默认读主车 SLAM 航向闭环累计实际转角,到 |TargetDeltaDeg| 才停。新增 FleetRotateDbg 落盘日志(实际航向/累计转角/实际vs指令角速率/判停原因)便于复现核对。