# 自动多车联动机制问题分析(MultiVehicleAutoSyncReview) 本文档记录 Tutorial `MultiWheelC` 中**自动运行多车联动**机制的潜在 bug 与工程化缺陷。 **本次仅作分析归档,不包含代码修复。** 相关实现: - [PilotDefinition.cs](../MultiWheel/MultiWheelC/PilotDefinition.cs) — 50ms `MultiVehicleLoop` / `TickMultiVehicle` - [ChassisController.cs](../MultiWheel/MultiWheelC/ChassisController.cs) — 路径跟踪 → `MultiVehicleAuto*` 回调 - [VehicleSyncModels.cs](../MultiWheel/MultiWheelC/VehicleSyncModels.cs) — register/notify 数据模型 - 算法背景见 [MultiVehicleSync.md](./MultiVehicleSync.md) --- ## 自动联动数据流 ```text 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 处理中被整体重新赋值: ```csharp lock (MultiVehicleFleet) MultiVehicleFleet = notification.Fleet ?? new Dictionary(); ``` 同时其它线程(register、Tick、VisualizeFleet)也在 `lock (MultiVehicleFleet)` 上同步。 `lock` 的对象是**引用本身**;字段被赋新字典后,不同线程可能分别锁旧引用与新引用,**互斥语义失效**,存在 torn read / 丢更新风险。 **影响** 高并发下 fleet 快照不一致、Aligned/fleetReady 判定抖动,极端情况下补偿或 notify 基于过期 fleet 状态运行。 **建议修复** - 使用 `private readonly object _multiVehicleFleetLock = new();` 作为唯一锁对象;或 - 永不替换字典引用,改为 `Clear()` + 逐条 `Update`;或 - 使用 `ConcurrentDictionary` + 不可变快照广播。 --- ## B. 自动速度残留滑行 **描述** `MultiVehicleAutoVx / FrontTh / RearTh`(PilotDefinition 第 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` 增加 `LastSeenUtc`;register/notify 收到时刷新; - 主车 Tick 剔除超过 N×SyncInterval 未更新的成员; - `fleetReady` 要求所有成员 `LastSeen` 新鲜。 --- ## D. 曲线路径几何与 idealPos 丢弃 **描述** 主车从 `MultiVehicleAutoFrontTh/RearTh` 广播单一 `(FrontTh, RearTh)`,各从车原样套用(第 417-419、616 行)。 这对**平移、蟹行、绕共有旋转中心**近似成立,但对**一般曲线路径**,各车相对瞬时曲率中心位置不同,应使用不同舵角。 `MultiWheelGeometricController` 已计算 `idealPos / idealAngle` 并传入 `MultiVehicleSendMotion`,但 [ChassisController.cs](../MultiWheel/MultiWheelC/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` 或配置项 `MultiVehicleControlRadius`,删除 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/Th`(ChassisController 第 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=false` 时 `canMove` **恒为 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 回调),不再锁会被整体替换的字段引用。 - **B**:`MultiVehicleSendMotion` 回调写入 `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`,默认开)。 - **E**:新增 `MultiVehicleControlRadius`(0=syncDistance/2),`ControlPointRadius` 与 `SendMotion(localControlRadius)` 统一取该值,删除硬编码 510。 - **F**:notify 改为 POST + JSON body(取代 GET query 串);新增单调递增 `Seq`,从车丢弃乱序旧包(含主车重启回退识别)。 - **G**:新增 `FleetCenterSnapshot` 不可变快照 + `volatile` 引用,`PublishFleetCenter` 整体赋值,控制器线程 `GetFleetCenterSnapshot()` 只读完整快照,消除 torn read。 - **H**:自动模式新增 `MultiVehicleAutoRequireFleetCenter`(默认开) 门控——无有效车队中心(定位丢失)时强制停车,补上纯 SLAM 模式安全网;手动模式不受限。 ## 语义修正(2026-06-29) ### 1. `MultiVehicleSyncUseDetour` 重新定义:仅控制"车队内姿态纠正" **问题**:原实现把"是否读 Detour 全局位姿"与"是否做车队内姿态纠正"绑在同一开关上。`false` 时 `posAvailable` 直接为假、根本不读 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指令角速率/判停原因)便于复现核对。