Files
Tutorial/docs/MultiVehicleAutoSyncReview.md
T

234 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自动多车联动机制问题分析(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<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 / 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`,删除 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**`ControlPointRadius``SendMotion(localControlRadius)` 统一取 `syncDistance / 2f`,删除硬编码 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指令角速率/判停原因)便于复现核对。