Add MultiWheelS Simple scheduling plugin for fleet sync
Introduce the MultiWheelS (SimpleComposer host) plugin with the MultiVehicleCar definition so fleet auto-sync is driven via Simple dispatch instead of the hand-rolled AGV.FleetGo path. Simplify MultiWheelC.AGV to BasicInterface, drop the obsolete FleetGo helper and FleetStraightMovementTest, wire MultiWheelS into Tutorial.sln, and update RunAndDeploy docs plus add the MultiVehicleAutoSyncReview notes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,30 +1,9 @@
|
|||||||
using System;
|
|
||||||
using MDCSToolBox.Clumsy.AgvInterfaces;
|
using MDCSToolBox.Clumsy.AgvInterfaces;
|
||||||
using MDCSToolBox.Clumsy.MotionControllers;
|
using MDCSToolBox.Clumsy.MotionControllers;
|
||||||
|
|
||||||
namespace MultiWheelC;
|
namespace MultiWheelC;
|
||||||
|
|
||||||
public class AGV : MultiWheelInterface
|
public class AGV : BasicInterface
|
||||||
{
|
{
|
||||||
public override AbstractGeometricController GetController()
|
public override AbstractGeometricController GetController() => new ChassisController().Get();
|
||||||
{
|
|
||||||
return new ChassisController().Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override MultiWheelMagTracker GetMagController()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Tutorial MultiWheelC does not implement magnetic tracking.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override NaiveMagnetController GetNaiveMagnetController()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Tutorial MultiWheelC does not implement naive magnetic tracking.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FleetGo(float srcX, float srcY, int srcId, float dstX, float dstY, int dstId, int trackId,
|
|
||||||
float speed = -1, bool reverse = false, float carDirectionBias = 0)
|
|
||||||
{
|
|
||||||
BasicGo(srcX, srcY, srcId, dstX, dstY, dstId, trackId, speed, reverse, carDirectionBias,
|
|
||||||
multiVehicleSync: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,24 +218,3 @@ public class WebApiMotionPauseTest : MovementTest
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MovementTest(name = "多舵轮-车队直线1m")]
|
|
||||||
public class FleetStraightMovementTest : MovementTest
|
|
||||||
{
|
|
||||||
public override void Test()
|
|
||||||
{
|
|
||||||
if (PilotDefinition.Conf.MultiVehicleMasterEndpoint != "/")
|
|
||||||
{
|
|
||||||
Hedingben.ToastText("仅主车可发起车队轨迹", "FleetGo");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos = DetourInterface.getCartLocation();
|
|
||||||
var agv = new AGV();
|
|
||||||
agv.FleetGo((float)pos.x, (float)pos.y, 1, (float)pos.x + 1000f, (float)pos.y, 2, 10001, 0.2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void TestStop()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using SimpleComposer.RCS;
|
||||||
|
using SimpleCore.PropType;
|
||||||
|
|
||||||
|
namespace MultiWheelS
|
||||||
|
{
|
||||||
|
[CarType(Name = "多车联动AGV")]
|
||||||
|
public class MultiVehicleCar : GhostCar
|
||||||
|
{
|
||||||
|
public bool MultiVehicleSync = true;
|
||||||
|
|
||||||
|
public static new async Task<MultiVehicleCar> Create()
|
||||||
|
{
|
||||||
|
return new MultiVehicleCar
|
||||||
|
{
|
||||||
|
lstatus = "连接中",
|
||||||
|
address = "127.0.0.1",
|
||||||
|
name = "联动AGV"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<RootNamespace>MultiWheelS</RootNamespace>
|
||||||
|
<AssemblyName>MultiWheelS</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="LessokajiWeaverUtilities">
|
||||||
|
<HintPath>D:\MDCS\Release\deps\LessokajiWeaverUtilities.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="RefSimpleCore">
|
||||||
|
<HintPath>D:\MDCS\Dependencies\Simple\RefSimpleCore.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="SimpleComposer">
|
||||||
|
<HintPath>D:\MDCS\Executables\Simple\SimpleComposer.exe</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="MultiVehicleCar.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<PostBuildEvent>if not exist "$(SolutionDir)build\Simple\plugins" mkdir "$(SolutionDir)build\Simple\plugins"
|
||||||
|
if not exist "$(SolutionDir)build\Simple" mkdir "$(SolutionDir)build\Simple"
|
||||||
|
xcopy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)build\Simple\plugins" /y
|
||||||
|
xcopy "$(TargetDir)$(TargetName).pdb" "$(SolutionDir)build\Simple\plugins" /y
|
||||||
|
copy /Y "D:\MDCS\Executables\Simple\SimpleComposer.exe" "$(SolutionDir)build\Simple\"
|
||||||
|
copy /Y "D:\MDCS\Release\MDCSToolBox.dll" "$(SolutionDir)build\Simple\"
|
||||||
|
copy /Y "D:\MDCS\Release\CommonUsage.dll" "$(SolutionDir)build\Simple\"
|
||||||
|
copy /Y "D:\MDCS\Dependencies\Simple\RefSimpleCore.dll" "$(SolutionDir)build\Simple\"</PostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -3,6 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.5.33424.131
|
VisualStudioVersion = 17.5.33424.131
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DiffWheel", "DiffWheel", "{A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E1}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MultiWheel", "MultiWheel", "{A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E2}"
|
||||||
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffWheelM", "DiffWheel\DiffWheelM\DiffWheelM.csproj", "{7C6F23A7-2B4D-45C2-98E9-6C55FBB0F1B8}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffWheelM", "DiffWheel\DiffWheelM\DiffWheelM.csproj", "{7C6F23A7-2B4D-45C2-98E9-6C55FBB0F1B8}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffWheelC", "DiffWheel\DiffWheelC\DiffWheelC.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffWheelC", "DiffWheel\DiffWheelC\DiffWheelC.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
@@ -11,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiWheelM", "MultiWheel\M
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiWheelC", "MultiWheel\MultiWheelC\MultiWheelC.csproj", "{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiWheelC", "MultiWheel\MultiWheelC\MultiWheelC.csproj", "{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiWheelS", "MultiWheel\MultiWheelS\MultiWheelS.csproj", "{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -33,10 +39,21 @@ Global
|
|||||||
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{7C6F23A7-2B4D-45C2-98E9-6C55FBB0F1B8} = {A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E1}
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E1}
|
||||||
|
{B7B53B6B-98B5-4B6F-9B09-598F7B8166C9} = {A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E2}
|
||||||
|
{C8C64C7C-A9C6-5C7F-0C1A-609F8C9277D0} = {A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E2}
|
||||||
|
{D1D1D1D1-E2E2-F3F3-A4A4-B5B5B5B5B5B3} = {A1A1A1A1-B2B2-C3C3-D4D4-E5E5E5E5E5E2}
|
||||||
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {EF6B92D5-5691-42DE-8B38-C17AE7DD7418}
|
SolutionGuid = {EF6B92D5-5691-42DE-8B38-C17AE7DD7418}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# 自动多车联动机制问题分析(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` 或配置项 `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 自动联动实现与联调日志分析整理 |
|
||||||
+18
-3
@@ -28,13 +28,22 @@ dotnet build Tutorial.sln
|
|||||||
|
|
||||||
- M 插件 → `build\Medulla\plugins\`
|
- M 插件 → `build\Medulla\plugins\`
|
||||||
- C 插件 → `build\Clumsy\` 与 `build\Clumsy_AGV2\`(PostBuild 复制运行时)
|
- C 插件 → `build\Clumsy\` 与 `build\Clumsy_AGV2\`(PostBuild 复制运行时)
|
||||||
|
- S 插件 → `build\Simple\plugins\`(PostBuild 复制 SimpleComposer 宿主到 `build\Simple\`)
|
||||||
|
|
||||||
|
MultiWheelS 为旧式 net48 项目,需用 MSBuild 单独编译:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
msbuild D:\MDCS\Source\Tutorial\MultiWheel\MultiWheelS\MultiWheelS.csproj /p:Configuration=Debug
|
||||||
|
```
|
||||||
|
|
||||||
|
或在 Visual Studio 中打开 `Tutorial.sln` 编译 `MultiWheelS` 项目。
|
||||||
|
|
||||||
## 3. 目录结构
|
## 3. 目录结构
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Tutorial/
|
Tutorial/
|
||||||
DiffWheel/DiffWheelM/ DiffWheel/DiffWheelC/
|
DiffWheel/DiffWheelM/ DiffWheel/DiffWheelC/
|
||||||
MultiWheel/MultiWheelM/ MultiWheel/MultiWheelC/
|
MultiWheel/MultiWheelM/ MultiWheel/MultiWheelC/ MultiWheel/MultiWheelS/
|
||||||
deploy/ # 配置模板(入库)
|
deploy/ # 配置模板(入库)
|
||||||
build/ # 运行目录(gitignore)
|
build/ # 运行目录(gitignore)
|
||||||
```
|
```
|
||||||
@@ -69,7 +78,13 @@ Tutorial/
|
|||||||
|
|
||||||
1. 完成上一节双车启动
|
1. 完成上一节双车启动
|
||||||
2. 至少一台车 `PosAvailable=true`(Detour 定位有效)
|
2. 至少一台车 `PosAvailable=true`(Detour 定位有效)
|
||||||
3. 主车 Clumsy 触发自动联动或调度 `AGV.FleetGo(..., multiVehicleSync:true)`
|
3. 通过 Simple 调度触发自动联动(推荐):
|
||||||
|
- 编译 `MultiWheelS`(见第 2 节),确认 `build\Simple\plugins\MultiWheelS.dll` 存在
|
||||||
|
- 在 `build\Simple\` 运行 `SimpleComposer.exe`(自动加载 `./plugins`)
|
||||||
|
- 添加车型 **「多车联动AGV」**(`MultiVehicleCar`,`MultiVehicleSync=true`)
|
||||||
|
- 主车设置 `cport:8008`、从车 `cport:8009`(可用 `SetCPort` 或 `conf` 字段)
|
||||||
|
- 在该车上规划路径 → 编译为 `agv.BasicGo(..., multiVehicleSync:true)` → 主车 `ChassisController` 开启 `MultiVehicleAutoEnabled`,`PilotDefinition` 50ms 循环广播并驱动车队
|
||||||
|
4. 自动联动机制已知问题见 [MultiVehicleAutoSyncReview.md](./MultiVehicleAutoSyncReview.md)
|
||||||
|
|
||||||
## 7. 差速示例
|
## 7. 差速示例
|
||||||
|
|
||||||
@@ -85,4 +100,4 @@ Tutorial/
|
|||||||
| `chassis.json` | 四轮舵轮几何 |
|
| `chassis.json` | 四轮舵轮几何 |
|
||||||
| `startup.iocmd` | `SetShareObjectTag Multi1/Multi2` |
|
| `startup.iocmd` | `SetShareObjectTag Multi1/Multi2` |
|
||||||
|
|
||||||
详细算法见 [MultiVehicleSync.md](./MultiVehicleSync.md),修复清单见 [BugFixes.md](./BugFixes.md)。
|
详细算法见 [MultiVehicleSync.md](./MultiVehicleSync.md),修复清单见 [BugFixes.md](./BugFixes.md),自动联动机制问题分析见 [MultiVehicleAutoSyncReview.md](./MultiVehicleAutoSyncReview.md)。
|
||||||
|
|||||||
Reference in New Issue
Block a user