refactor: 插件 UI 从 WinForms 迁移到 CycleGUI,并修复代码质量问题

将 StandardScene 各插件的配置/监控窗体从 WinForms 迁移到 CycleGUI(删除 .Designer.cs/.resx,重写为 PanelBuilder 立即模式 UI,新增 CycleUiHelper 统一对话框)。

同时修复代码审核中的问题:
- 后台文件写入加锁 + try/catch(ButtonBoxManager / DoorManager,对齐 LoopViewer.SaveTasks 模式)
- CoderFieldsMetadata.cs 启用 #nullable enable,消除 CS8632 警告
- DummyCar 移除已废弃的 rightClickAction()/SetPosition()
- CarRemoteHelper.OpenVehicleWebPage 的 Process.Start 加 try/catch
- 重命名名不副实的 Mstsc()(现为打开网页)
- 统一弃元命名为 _
- TrafficInterlockViewer 改用稳定 Id(GUID)做选择/编辑,替代行索引
- csproj 改用 $(CGUILibDir) 解析 CycleGUI,绝对路径收敛到 Directory.Build.props

构建:dotnet build StandardScene.sln → 0 错误,30 警告(均为历史遗留)。
注:static 单例状态重构(审核第 8 项)暂未处理,留待单独任务。
This commit is contained in:
zhaowei.huang
2026-06-26 15:00:53 +08:00
parent c8e540d272
commit a0dc1e6cd0
91 changed files with 3946 additions and 15419 deletions
+525
View File
@@ -0,0 +1,525 @@
# StandardScene 代码架构文档
> **目标读者**:接手本仓库的工程师、AI Agent、架构审查人员
> **最后更新**2026-06-25
> **配套文档**`DEVELOPMENT_GUIDE.md`(开发实操)、`QUICK_REFERENCE.md`(速查)、`StandardScene架构重构方案.md`(演进方向)
---
## 1. 项目定位
StandardScene 是一套 **AGV/AMR 场内调度与联动控制** 的场景插件库,不是独立可执行程序。
| 维度 | 说明 |
| --- | --- |
| 工程类型 | 类库插件(1 基座 + 4 卫星 = 5 个 DLL |
| 宿主 | `SimpleLite.exe`CycleGUI 桌面应用) |
| 内核 | `SimpleCore.dll`(Mission、交通、路径编译、导航契约) |
| 目标框架 | `net8.0-windows`x64 |
| 语言 | C# |
| UI 演进 | WinForms → CycleGUI`DeliveryViewer` 等已迁移) |
典型业务能力:搬运任务、环线任务、区域交通管制、充电编排、门禁/按钮盒联动、HTTP / MQTT / Modbus 对外接口。
---
## 2. 解决方案结构
```
StandardScene.sln
├── StandardScene.Core → 输出 StandardScene.dll(基座,始终加载)
├── StandardScene.Magnetic → 输出 StandardScene.Magnetic.dllscene.mag
├── StandardScene.QrLidar → 输出 StandardScene.QrLidar.dllscene.qrlidar
├── StandardScene.Devices → 输出 StandardScene.Devices.dllscene.device
└── StandardScene.Protocol.VDA5050 → 输出 StandardScene.Protocol.VDA5050.dllscene.vda5050
```
### 2.1 依赖关系(星型拓扑)
```mermaid
graph TD
Host["SimpleLite.exe"]
SC["SimpleCore.dll"]
SL["SimpleLite.dll"]
Core["StandardScene.dll<br/>基座"]
Mag["StandardScene.Magnetic.dll"]
Qr["StandardScene.QrLidar.dll"]
Dev["StandardScene.Devices.dll"]
VDA["StandardScene.Protocol.VDA5050.dll"]
Host --> Core
Host --> Mag
Host --> Qr
Host --> Dev
Host --> VDA
Core --> SC
Core --> SL
Mag --> Core
Qr --> Core
Dev --> Core
VDA --> Core
```
**规则**
- 卫星 **只能** 引用基座,禁止循环引用
- 所有插件 **运行时** 依赖 `SimpleCore` + `SimpleLite`
- 卫星通过 `InternalsVisibleTo` 访问 Core 的 `internal` 字段袋类型,避免大规模 `public` 暴露
### 2.2 插件清单对照表
| DLL | scene id | 始终加载 | 职责 |
| --- | --- | --- | --- |
| `StandardScene.dll` | — | 是 | 任务调度、交通互锁、充电编排、门禁抽象、HTTP API、数据模型 |
| `StandardScene.Magnetic.dll` | `scene.mag` | 否 | 磁导航车型 + 磁循迹 Coder |
| `StandardScene.QrLidar.dll` | `scene.qrlidar` | 否 | 激光 SLAM + 二维码导航,多种车型 |
| `StandardScene.Devices.dll` | `scene.device` | 否 | Modbus 门控、充电桩、按钮盒具体驱动 |
| `StandardScene.Protocol.VDA5050.dll` | `scene.vda5050` | 否 | VDA5050MQTT)协议栈与标准车型 |
---
## 3. 运行时架构
### 3.1 启动与加载流程
```mermaid
sequenceDiagram
participant Host as SimpleLite.exe
participant PM as PluginManager
participant Core as StandardScene.dll
participant Sat as 卫星 DLL
Host->>PM: 扫描 plugins\ 目录
PM->>Core: 加载 requiresCore 指向的基座(不可回收)
PM->>PM: 读取 active-scenes.json / CLI --scenes
PM->>Sat: 按场景 id 加载选中的卫星 DLL
Sat->>Core: 引用已加载的基座类型
PM->>PM: UiTypeDiscovery 反射 [MissionType]/[CarType]/[DoorType] 等
alt 导航插件
PM->>Sat: 实例化 NavigationProfileBase → OnActivate
end
Host->>Core: CustomOperationsBeforeLoading.Set()(死锁回调等)
```
**场景选择优先级**(高 → 低):
1. CLI 参数 `--scenes`
2. `active-scenes.json`
3. `simple.json` 中的 `scenes` 字段
4. 加载全部已发现的卫星
> **注意**:若 `active-scenes.json` 仅包含 `scene.mag`,则 `scene.device` / `scene.vda5050` 不会加载,门控/充电桩/VDA5050 车型将不可用。生产部署需显式包含所需场景 id。
### 3.2 构建与部署
`Directory.Build.targets` 在编译后自动将产物复制到 `build\plugins\`
- 各插件 DLL + PDB
- 各卫星的 `*.scene.json`
- Devices 插件附带的 `leegKeys-sdk.dll`
部署步骤:
1. 先构建宿主:`dotnet build Simple\SimpleLite\SimpleLite.csproj`
2. 构建本方案:`dotnet build StandardScene.sln`
3.`build\plugins\` 内容复制到 `SimpleLite.exe` 工作目录的 `plugins\`
4. 配置 `active-scenes.json` 后启动 `SimpleLite.exe`
### 3.3 业务运行时链路
```
外部系统 (HTTP/MQTT/Modbus)
WebApi / 设备驱动 / 车型通信
Mission(后台进程:搬运/环线/充电/门控/心跳…)
SimpleLib / TrafficControl / SegmentPlanSimpleCore
GhostCar / Car 实例(车辆状态机 + 下位机通信)
地图站点 / 轨道 / fields & tags 配置
```
---
## 4. 核心概念
### 4.1 Mission(场景进程)
Mission 是本系统的 **可启动后台业务单元**,继承自 `SimpleCore.Mission`,由宿主 UI 或 HTTP 反射 API 启停。
**生命周期模式**(以 `HeartBeatMission` 为范本):
| 阶段 | 典型实现 |
| --- | --- |
| 注册 | `[MissionType(Name="...", editor=typeof(...))]` |
| 工厂 | `public static Mission Create()` |
| 启动 | `[MethodMember(Name="启动进程")] public override void Execute()` |
| 后台 | `Thread` / `Task.Run` / `async` + `CancellationTokenSource` |
| 状态 | 更新 `status.status` 或自定义 `MissionStatus` 子类 |
| 停止 | `[MethodMember(Name="关闭进程")] public void Stop()` |
**Mission 继承树**
```
Mission (SimpleCore)
├── HeartBeatMission, RegionalTrafficControlMission, SecuritySignalMission
├── NodeIsEnableMission
├── DoorMission, ButtonMission
├── AbstractInterlockMission
│ ├── TrafficInterlockMission
│ └── AbstractChargeLogicMission → StandardChargeMission
├── ChainedDeliveryMission → TransportMission(搬运调度)
└── AbstractLoopMission → LoopMission(环线任务)
```
### 4.2 CarType(车型)
车型代表一种 AGV 下位机协议与行为模型,继承自 `SimpleLite.RCS.CarTypes.GhostCar``Car`
**注册三要素**
1. **类型特性**`[CarType(Name="显示名", editor=typeof(XxxCar))]`
2. **轨迹编码器**`[ProgramTrackCoderSettings(priority=N, program=typeof(XxxCoder))]`
3. **场景清单**`scene.json``provides.carTypes` +(导航插件)`NavigationProfileBase.CarTypes`
**Coder(轨迹编码器)** 实现 `SimpleCore.Compiler.ITrackCoder`,在路径规划时将站点/轨道上的 `fields` 编译为 Topaz 脚本片段(如 `agv.MagGo``agv.BasicGo`)。
### 4.3 设备驱动(门 / 充电桩 / 按钮盒)
设备采用 **抽象在 Core、实现在 Devices** 的模式:
| 设备 | Core 抽象 | Devices 实现 | 发现机制 |
| --- | --- | --- | --- |
| 门控 | `BasicDoorController` | `ModbusDoorController` | `[DoorType("...")]` + `UiTypeDiscovery` |
| 充电桩 | `AbstractChargeStation` | `FL/PCB/MuXingChargeStation` | 类型全名 + `UiTypeDiscovery` |
| 按钮盒 | `BasicButtonBox` | `Leeg/AzowieButtonBox` | 类名 + `UiTypeDiscovery` |
### 4.4 Fields & Tags(轻量配置)
除 JSON 配置文件外,系统大量使用地图上的 **fields**(键值对)和 **tags** 驱动行为,例如:
- 站点 `Region1=1` → 区域流控
- 轨道 `Magnet=1` → 磁导航段
- 车辆 `address` → 下位机 IP
通用读写入口:`Commons.cs` 中的 `GetXxxField` / `AddOrUpdateXxxField` 系列方法。
### 4.5 NavigationProfile(导航场景画像)
**Magnetic****QrLidar** 两个导航卫星实现 `SimpleCore.Navigation.NavigationProfileBase`
```csharp
public sealed class MagneticSceneProfile : NavigationProfileBase
{
public override NavKind Kind => NavKind.Magnetic;
public override string SceneId => "scene.mag";
public override IReadOnlyList<Type> CarTypes => new[] { typeof(MagCar) };
public override void OnActivate(ISceneContext context) { ... }
}
```
`scene.json``NavigationProfile` 中的 `CarTypes` **必须保持一致**
---
## 5. 模块地图(StandardScene.Core
基座约 110+ 源文件,按目录职责划分如下:
| 目录 | 职责 | 关键类型 |
| --- | --- | --- |
| `Scheduler/` | 辅助后台 Mission(心跳、区域流控、安全信号) | `HeartBeatMission`, `RegionalTrafficControlMission` |
| `Chained/` | 搬运与环线任务主流程 | `ChainedDeliveryMission`, `TransportMission`, `AbstractLoopMission`, `LoopMission` |
| `Chained/Loop/` | 环线规则策略接口 | `IEnterRule`, `IExitRule`, `IJoinRule`, `IBranchRule`, `ITaskStrategy` |
| `Charge/` | 充电策略、站点管理、UDP 通信、配置 UI | `StandardChargeMission`, `AbstractChargeLogicMission` |
| `ChargeStationType/` | 充电桩抽象基类 | `AbstractChargeStation` |
| `InterLock/` | 区域互锁与交通管制 | `AbstractInterlockMission`, `TrafficInterlockMission` |
| `ExtendDevice/Door/` | 门控抽象、管理器、Mission | `BasicDoorController`, `DoorMission`, `DoorManager` |
| `ExtendDevice/ButtonBox/` | 按钮盒抽象与管理 | `BasicButtonBox`, `ButtonMission`, `ButtonBoxManager` |
| `CarTypes/` | 共享字段袋、模拟车 | `BasicFields`, `DummyCar`, `KivaFields` |
| `Coders/` | 导航无关的通用轨迹编码器 | `CommonTrackCoders`, `LidarAreaSwitchCoder` |
| `Model/` | 任务、地图、配置数据模型 | `TaskModel`, `LoopTask`, `Map`, `ChargingSetting` |
| `TCP/` | 异步 TCP 客户端 | `AsyncTcpClient` |
| `Utils/` | JSON、Modbus、WebAPI、CycleGUI 辅助 | `WebAPIHelper`, `CycleUiHelper`, `CarRemoteHelper` |
| `CommonTools/` | 原子文件写入、雪花 ID | `AtomicFileUpdateHelper`, `SnowflakeIdGenerator` |
| 根目录 | 全局工具与 HTTP 入口 | `Commons.cs`, `WebApi.cs`, `Heuristic.cs`, `LadderLogic.cs` |
### 5.1 卫星插件内容
**StandardScene.Magnetic**5 文件)
- `CarTypes/MagCar.cs` — 磁导航 UDP 车型
- `Coders/MagneticTrackCoder.cs``agv.MagGo` / `agv.NaiveMagGo`
- `MagneticSceneProfile.cs` + `StandardScene.Magnetic.scene.json`
**StandardScene.QrLidar**12 文件)
- `CarTypes/` — Forklift, Kiva, ArmCar, DualLiftingCar, MultiVehicleCar, MultiWheelForkLifter, MultiWheelLifterCar
- `Cad/SyncQrMap.cs` — 二维码地图同步 CAD 工具
- `QrLidarSceneProfile.cs` + `StandardScene.QrLidar.scene.json`
**StandardScene.Devices**8 文件)
- `Door/ModbusDoorController.cs`
- `Charge/FLChargeStation.cs`, `PCBChargeStation.cs`, `MuXingChargeStation.cs`
- `ButtonBox/LeegButtonBox.cs`, `AzowieButtonBox.cs`
- `StandardScene.Devices.scene.json`
**StandardScene.Protocol.VDA5050**13 文件)
- `VDACar/VDA5050Car.cs` — MQTT VDA5050 标准车
- `VDACar/MasterMQTTCommunication.cs` — MQTT 通信层
- `StandardScene.Protocol.VDA5050.scene.json`
### 5.2 已注册车型一览
| 类名 | 所属插件 | 显示名 |
| --- | --- | --- |
| `DummyCar` | Core | 模拟车-包络 |
| `MagCar` | Magnetic | 磁导航车 |
| `Forklift` | QrLidar | 叉车 |
| `MultiWheelForkLifter` | QrLidar | 多舵轮叉车 |
| `DualLiftingCar` | QrLidar | 锂电双举升 |
| `MultiVehicleCar` | QrLidar | 多车联动 AGV |
| `ArmCar` | QrLidar | ArmCar |
| `Kiva` | QrLidar | Kiva |
| `MultiWheelLifterCar` | QrLidar | 多舵轮顶升车 |
| `VDA5050Car` | Protocol.VDA5050 | VDA5050 标准车 |
### 5.3 已注册 Mission 一览
| 类名 | 显示名 | 目录 |
| --- | --- | --- |
| `HeartBeatMission` | 调度心跳进程 | Scheduler |
| `RegionalTrafficControlMission` | 区域流量监控 | Scheduler |
| `SecuritySignalMission` | 安全信号交互 | Scheduler |
| `NodeIsEnableMission` | 锁点上传迷毂 | Scheduler |
| `TransportMission` | 搬运任务进程 | Chained |
| `LoopMission` | 环线进程 | Chained |
| `StandardChargeMission` | 充电进程 | Charge |
| `TrafficInterlockMission` | 交通管制 | InterLock |
| `DoorMission` | 门控进程 | ExtendDevice/Door |
| `ButtonMission` | 按钮进程 | ExtendDevice/ButtonBox |
---
## 6. 扩展点指南
接手后最常见的三类扩展:
### 6.1 新增 Mission
1.`StandardScene.Core` 合适目录新建类,继承 `Mission`(或现有抽象基类)
2. 添加 `[MissionType(Name="...", editor=typeof(...))]`
3. 实现 `Create()``Execute()``Stop()` 三件套
4. **参考**`Scheduler/HeartBeatMission.cs`(最小)、`Scheduler/RegionalTrafficControlMission.cs`(事件订阅)
### 6.2 新增车型(导航卫星)
1. 确定目标卫星(Magnetic / QrLidar / VDA5050
2. 新建 `CarTypes/XxxCar.cs`,继承 `GhostCar`,标注 `[CarType]` + `[ProgramTrackCoderSettings]`
3. 如需新 Coder,在同插件 `Coders/` 实现 `ITrackCoder`
4. 更新 `XxxSceneProfile.CarTypes``*.scene.json``provides.carTypes`
5. 字段袋扩展放在 Core `CarTypes/``internal`,卫星通过 `InternalsVisibleTo` 访问)
### 6.3 新增设备驱动
1.`StandardScene.Devices` 实现 Core 抽象(如 `BasicDoorController`
2. 添加类型特性(如 `[DoorType("MyDoor")]`
3. 更新 `StandardScene.Devices.scene.json``provides` 列表
4. 确保 `active-scenes.json` 包含 `scene.device`
### 6.4 新增 HTTP 接口
入口在 `StandardScene.Core/WebApi.cs``ApiController`Nancy 框架)。常见路由前缀:
- `/car/*` — 车辆与任务
- `/map/*` — 地图
- `/task/*` — 任务查询
- `/mission_reflection/*` — Mission 反射调用
> 长期计划是将 WebApi 从 Core 拆出并迁移到 SimpleLite EmbedIO,当前仍以 `WebApi.cs` 为唯一活跃 HTTP 入口。
---
## 7. 配置文件
| 文件 | 位置 | 用途 |
| --- | --- | --- |
| `active-scenes.json` | 宿主 `plugins\` | 选择加载哪些卫星场景 |
| `simple.json` | 宿主工作目录 | 宿主基础配置(含 scenes 备选) |
| `Config/traffic.json` | 运行时 | 交通互锁 / 区域配置 |
| `Config/ChargeStations.json` | 运行时 | 充电桩定义 |
| `Config/ChargeStrategyConfig.json` | 运行时 | 充电策略 |
| `Config/AlarmConfigs.json` | 运行时 | 报警配置 |
| `DoorConfig.json` | 运行时 | 门禁配置 |
| `tasklist.json` | 运行时 | 环线任务配置 |
---
## 8. 外部依赖
### 8.1 程序集引用(HintPath,需本机构建)
| DLL | 用途 |
| --- | --- |
| `SimpleLite.dll` | 宿主框架:RCS、CAD、UI、`UiTypeDiscovery`、特性标注 |
| `SimpleCore.dll` | 内核:Mission、Car、交通、路径编译、导航契约 |
| `CommonUsage.dll` | 数学、VDA5050 消息类型 |
| `Topaz.dll` | Coder 脚本模板引擎 |
| `CycleGUI.dll` | 3D / 立即模式 UI`Private=false`,由宿主加载) |
| `MDCSToolBox.dll` | 运动学 / 数学(Core、QrLidar |
| `LessokajiWeaverUtilities.dll` | i18n、诊断工具 |
| `leegKeys-sdk.dll` | Leeg 按钮盒 SDK |
### 8.2 NuGetCore 最重)
`Nancy`, `MQTTnet`, `EasyModbusTCP`, `IoTClient`, `Jint`, `DocumentFormat.OpenXml`, `Newtonsoft.Json`
Protocol.VDA5050 额外使用 `MQTTnet`Magnetic / QrLidar 仅 `Newtonsoft.Json`
---
## 9. 关键设计决策与现状问题
### 9.1 当前设计的合理之处
- **星型插件拓扑**:基座承载全部 Mission 与设备抽象,卫星按场景热插拔
- **`scene.json` 清单**:声明式描述插件能力,宿主无需硬编码
- **`NavigationProfileBase`**:导航平台与车型注册的标准契约
- **`Chained/Loop/` 规则接口**:环线策略可插拔(`IEnterRule` 等)
- **`InternalsVisibleTo` 拆分策略**:在不破坏封装的前提下外移车型代码
### 9.2 已知架构债务(详见 `StandardScene架构重构方案.md`
| 编号 | 问题 | 影响 |
| --- | --- | --- |
| S1 | Core 背负 MQTT/Modbus/Nancy/OpenXml 等协议依赖 | 基座臃肿,卫星无法独立瘦身 |
| S2 | WinForms 与 CycleGUI 并存 | 阻塞纯 `net8.0` 跨平台 |
| S3 | 多个 600~2700 行 God-classWebApi、AbstractLoopMission 等) | 改动风险高、难测试 |
| S8 | Coder 注册表限定内核程序集反射 | 导航 Coder 热插拔受限 |
| S9 | csproj 绝对路径 HintPath | CI / 换机构建需手动适配 |
### 9.3 演进方向(摘要)
1. 抽离 `StandardScene.Abstractions`(纯契约 + 模型)
2. 充电子系统独立为卫星 `StandardScene.Charge`
3. WebApi 按资源拆模块并迁出 Core
4. UI 全部迁至 CycleGUI 或独立 UI 程序集
5. 目标 TFM 从 `net8.0-windows` 过渡到 `net8.0`(基础设施层)
---
## 10. AI / 新工程师接手清单
### 10.1 第一天:建立全局认知
1. 阅读 `README.md` → 本文档 → `DEVELOPMENT_GUIDE.md`
2. 打开 `DocumentHub.html` 浏览模块导航
3. 本地构建:先 SimpleLite,再 StandardScene,部署到 `plugins\`
4. 启动宿主,确认 `active-scenes.json` 包含所需场景
### 10.2 第二天:跟踪一条完整链路
**搬运任务链路**(推荐):
```
WebApi /car/createTask
→ TransportMissionChainedDeliveryMission.LoopAsync
→ Commons.NearestTask / 选车逻辑
→ GhostCar 下发路径(SegmentPlan + Coder 脚本)
→ TrafficControl 互锁
→ 下位机 HTTP/MQTT/UDP 通信
```
**充电链路**
```
StandardChargeMission
→ AbstractChargeLogicMission(电量策略)
→ AbstractInterlockMission(站点互锁)
→ AbstractChargeStation 实例(Devices 插件驱动)
```
### 10.3 按任务类型定位文件
| 我要做… | 先看 |
| --- | --- |
| 最小 Mission 样板 | `Scheduler/HeartBeatMission.cs` |
| 搬运调度 | `Chained/ChainedDeliveryMission.cs`, `Chained/TransportMission.cs` |
| 环线任务 | `Chained/AbstractLoopMission.cs`, `Chained/LoopMission.cs` |
| 区域流控 | `Scheduler/RegionalTrafficControlMission.cs` |
| 充电 | `Charge/StandardChargeMission.cs`, `Charge/AbstractChargeLogicMission.cs` |
| 门控 | `ExtendDevice/Door/DoorMission.cs`, `Devices/Door/ModbusDoorController.cs` |
| HTTP API | `WebApi.cs` |
| 新车型 | 对应卫星 `CarTypes/` + `Coders/` + `*SceneProfile.cs` |
| 字段/选车/路径辅助 | `Commons.cs` |
| 地图与任务模型 | `Model/` |
### 10.4 修改前的安全检查
- [ ] 确认目标变更属于 Core 还是卫星(避免在 Core 放平台专有逻辑)
- [ ] 若改车型,同步 `scene.json` + `NavigationProfile.CarTypes`
- [ ] 若改设备驱动,确认 `scene.device``active-scenes.json`
- [ ] 若改 Coder,注意 `priority` 与字段袋哨兵值(`-1` 语义)
- [ ] 编译后检查 `build\plugins\` 产物是否完整
---
## 11. 文档索引
| 文档 | 用途 |
| --- | --- |
| **本文档 `ARCHITECTURE.md`** | 整体架构、模块地图、扩展点、接手清单 |
| `DEVELOPMENT_GUIDE.md` | 开发环境、案例教程、调试建议 |
| `QUICK_REFERENCE.md` | 高频入口与配置速查 |
| `StandardScene架构重构方案.md` | 架构演进与分层目标 |
| `StandardScene拆分计划.md` | 程序集拆分进度 |
| `StandardScene代码审查报告.md` | 质量缺陷与修复记录 |
| `StandardScene.Core/Docs/` | 充电、门控、Coder 专题手册 |
---
## 附录 Ascene.json 完整示例
**导航插件(scene.mag**
```json
{
"id": "scene.mag",
"displayName": "磁导航平台",
"navKind": "magnetic",
"assembly": "StandardScene.Magnetic.dll",
"coreVersion": ">=1.0.0",
"requiresCore": "StandardScene.dll",
"provides": {
"carTypes": ["MagCar"],
"missionTypes": []
}
}
```
**设备插件(scene.device**
```json
{
"id": "scene.device",
"displayName": "设备驱动(门 / 充电桩 / 按钮盒)",
"assembly": "StandardScene.Devices.dll",
"coreVersion": ">=1.0.0",
"requiresCore": "StandardScene.dll",
"provides": {
"doorControllers": ["ModbusDoorController"],
"chargeStations": ["FLChargeStation", "PCBChargeStation", "MuXingChargeStation"],
"buttonBoxes": ["LeegButtonBox", "AzowieButtonBox"]
}
}
```
## 附录 B:插件引导钩子
基座在宿主加载早期通过反射调用:
```csharp
// StandardScene.Core/Commons.cs
public class CustomOperationsBeforeLoading
{
public static void Set()
{
TrafficControl.OnDeadLock = (loopingCar) => { /* 死锁告警 */ };
}
}
```
这是少数几个 **无 Mission 启动即可生效** 的全局初始化入口之一。
+797
View File
@@ -0,0 +1,797 @@
# StandardScene 项目答辩综合题
**难度等级**:★★★★☆(中高难度)
**预期答题时间**60-90 分钟
**评分标准**:架构理解 30% + 代码实现 40% + 问题分析 20% + 创新性 10%
---
## 背景场景
你是一个物流仓库的技术负责人,现有以下业务需求:
### 现状描述
仓库中有多台 VDA5050 标准车,目前系统存在以下问题:
1. **效率问题**:有些任务被标记为"堵塞",车辆无法被正确分配
2. **公平性问题**:优先级低的任务可能永远无法执行
3. **可靠性问题**:当 MQTT 连接断开时,系统无法自动恢复
4. **成本问题**:车辆频繁往返避让点,增加运营成本
### 业务指标
| 指标 | 目标 | 当前 |
|-----|------|-----|
| 任务通过率 | 100% | 92% |
| 平均等待时间 | <30s | 45s |
| 车辆利用率 | 85% | 72% |
| 系统可用性 | 99% | 94% |
---
## 综合题目(三选一,必答主题题)
### 【主题题】:智能避障与任务重规划系统设计
**题目描述**
当前系统在以下场景中表现不佳:
**场景 1**:取货点被另一台车占用
```
时间线:
T0: 任务A(从工位1取货→工位2放货)分配给车C1
T1: 车C1 前往工位1 取货
T2: 同时,车C2 的任务指向工位1(C2也要取货)
T3: C2 已抵达工位1,正在取货
T4: C1 抵达工位1,发现被阻挡,任务堵塞
```
**场景 2**:放货点被占用且无避让点
```
T0: 任务B(工位5→工位8)分配给车C3,已取货
T1: 工位8 被 C4 占用,C3 被迫等待
T2: C4 的放货逻辑出现故障,一直占用工位8
T3: C3 无法完成任务,系统卡死
```
**场景 3**:避让点本身被占用
```
T0: 避让点9 本来是给C1 用的
T1: 但 C5 的任务终点恰好是工位9
T2: C1 无法到达避让点,任务更新失败
```
### 要求
你需要设计一个**智能避障与任务重规划系统**,包括以下内容:
#### A. 系统架构设计(15 分)
**请完成以下工作**
1. **绘制系统架构图**,包含以下组件及其交互关系:
- 避障决策引擎(Obstacle Avoidance Engine
- 任务重规划模块(Task Rescheduling Module
- 冲突检测器(Conflict Detector
- 路权管理器(Right-of-Way Manager
2. **定义数据结构**,用于:
- 表示车辆状态转移(State Transitions
- 存储冲突信息(Conflict Information
- 记录避障历史(Avoidance History
3. **说明各模块的职责**,特别是:
- 如何检测冲突?
- 如何选择避障策略?
- 如何决定任务重规划?
#### B. 代码实现(40 分)
**请实现以下代码**(在现有代码框架基础上):
**B.1 增强的冲突检测器** 15分)
```csharp
/// <summary>
/// 增强的冲突检测类
/// 需要检测以下场景:
/// 1. 多个车同时到达同一工位(资源冲突)
/// 2. 路径交叉(交通冲突)
/// 3. 避让点不足(空间冲突)
/// 4. 任务优先级冲突
/// </summary>
public class EnhancedConflictDetector
{
// 待实现:检测资源冲突的方法
public bool DetectResourceConflict(AbstractDelivery d, AbstractCar car, out AbstractCar[] blockingCars)
{
// TODO: 实现资源冲突检测
// 返回是否存在冲突,以及冲突的车辆列表
blockingCars = null;
return false;
}
// 待实现:检测避让点不足的方法
public bool IsGiveWayAvailable(AbstractCar car, int targetSiteId, out int availableGiveWaySiteId)
{
// TODO: 实现检查是否有可用避让点
// 返回是否有可用的避让点,以及避让点的ID
availableGiveWaySiteId = -1;
return false;
}
// 待实现:获取冲突等级
public ConflictLevel GetConflictLevel(ConflictInfo conflict)
{
// TODO: 根据冲突信息判断严重程度
// 返回冲突等级:Low(可以继续等待)、Medium(需要避让)、Critical(需要重规划)
return ConflictLevel.Low;
}
// 数据结构定义
public class ConflictInfo
{
public int DeliveryId { get; set; }
public int AssignedCarId { get; set; }
public int[] BlockingCarIds { get; set; }
public int ConflictSiteId { get; set; } // 冲突发生的工位
public DateTime DetectTime { get; set; }
public string Description { get; set; }
}
public enum ConflictLevel
{
None = 0,
Low = 1,
Medium = 2,
Critical = 3,
Deadlock = 4
}
}
```
**B.2 任务重规划模块** 15分)
```csharp
/// <summary>
/// 任务重规划模块
/// 实现多种避障和恢复策略
/// </summary>
public class TaskReschedulingModule
{
private AbstractChainedDeliveryMission _mission;
public TaskReschedulingModule(AbstractChainedDeliveryMission mission)
{
_mission = mission;
}
/// <summary>
/// 根据冲突情况选择合适的避障策略
///
/// 策略优先级:
/// 1. 推挤任务(被阻挡车执行其他待处理任务)
/// 2. 避让点绕行(被阻挡车前往避让点)
/// 3. 任务切换(当前任务改派给其他车,被阻挡车接其他任务)
/// 4. 任务延迟(等待冲突解除)
/// 5. 强制中止(仅在死锁时使用)
/// </summary>
public async Task<bool> ResolveConflict(
EnhancedConflictDetector.ConflictInfo conflict,
EnhancedConflictDetector.ConflictLevel level)
{
// TODO: 实现冲突解决逻辑
// 返回是否成功解决
switch (level)
{
case EnhancedConflictDetector.ConflictLevel.Low:
// 低级冲突:等待
return await WaitForConflictResolution(conflict);
case EnhancedConflictDetector.ConflictLevel.Medium:
// 中等冲突:尝试推挤或避让
if (await TryPushAwayTask(conflict))
return true;
return await GoToGiveWay(conflict);
case EnhancedConflictDetector.ConflictLevel.Critical:
// 严重冲突:任务重规划
if (await TrySwitchCar(conflict))
return true;
if (await TryRescheduleDelivery(conflict))
return true;
return await GoToGiveWay(conflict);
case EnhancedConflictDetector.ConflictLevel.Deadlock:
// 死锁:强制中止某个任务
return ForceBreakDeadlock(conflict);
default:
return false;
}
}
/// <summary>
/// 尝试推挤任务:让被阻挡车执行其他待处理任务
///
/// 条件:
/// - 被阻挡车附近有其他待处理任务
/// - 这个任务的优先级不能太低
/// - 不能形成任务链死锁
///
/// 返回:是否成功推挤
/// </summary>
private async Task<bool> TryPushAwayTask(EnhancedConflictDetector.ConflictInfo conflict)
{
// TODO: 实现推挤逻辑
return false;
}
/// <summary>
/// 让被阻挡车前往避让点
///
/// 条件:
/// - 避让点可达
/// - 避让点未被占用
///
/// 返回:是否成功
/// </summary>
private async Task<bool> GoToGiveWay(EnhancedConflictDetector.ConflictInfo conflict)
{
// TODO: 实现避让点逻辑
return false;
}
/// <summary>
/// 尝试切换车辆:将冲突的任务改派给其他车
///
/// 条件:
/// - 任务未取货
/// - 有其他可用车辆
///
/// 返回:是否成功
/// </summary>
private async Task<bool> TrySwitchCar(EnhancedConflictDetector.ConflictInfo conflict)
{
// TODO: 实现车辆切换逻辑
return false;
}
/// <summary>
/// 尝试重新规划任务
///
/// 思路:
/// - 将任务分解为多个子任务
/// - 调整执行顺序
/// - 更新优先级
///
/// 返回:是否成功
/// </summary>
private async Task<bool> TryRescheduleDelivery(EnhancedConflictDetector.ConflictInfo conflict)
{
// TODO: 实现任务重规划逻辑
return false;
}
/// <summary>
/// 等待冲突自动解除
///
/// 条件:
/// - 被阻挡车等待时间 < 阈值
/// - 阻挡车正在移动
///
/// 返回:是否成功
/// </summary>
private async Task<bool> WaitForConflictResolution(EnhancedConflictDetector.ConflictInfo conflict)
{
// TODO: 实现等待逻辑
return false;
}
/// <summary>
/// 强制中止某个任务以破坏死锁
///
/// 策略:
/// - 找到优先级最低的任务
/// - 取消该任务
/// - 释放其持有的资源
///
/// 返回:是否成功
/// </summary>
private bool ForceBreakDeadlock(EnhancedConflictDetector.ConflictInfo conflict)
{
// TODO: 实现死锁破坏逻辑
return false;
}
}
```
**B.3 连接恢复机制** 10分)
```csharp
/// <summary>
/// MQTT 连接恢复机制
/// 在当前 MasterMQTTCommunication 的基础上增强
/// </summary>
public class ResilientMQTTCommunication : MasterMQTTCommunication
{
private DateTime _lastSuccessfulConnection = DateTime.Now;
private int _connectionFailureCount = 0;
private const int MAX_RETRY_COUNT = 5;
private const int RETRY_INTERVAL_SECONDS = 10;
/// <summary>
/// 启用自动重连机制
/// 监控连接状态,当断线时自动重连
/// </summary>
public async Task EnableAutoReconnect()
{
// TODO: 实现自动重连逻辑
// 1. 监听连接状态变化
// 2. 当断线时,指数退避重试
// 3. 重连成功后,恢复订阅
// 4. 同步未发送的消息
}
/// <summary>
/// 持久化消息队列
/// 连接断开时,将未发送的消息保存到本地
/// 恢复连接后,自动重发
/// </summary>
public void EnableMessagePersistence(string persistDir)
{
// TODO: 实现消息持久化
// 1. 创建消息队列文件
// 2. 连接断开时,保存消息
// 3. 恢复连接时,读取并重发
// 4. 成功发送后,删除文件
}
/// <summary>
/// 健康检查
/// 定期向 MQTT broker 发送心跳,检查连接是否正常
/// </summary>
public async Task StartHealthCheck(int intervalSeconds = 30)
{
// TODO: 实现健康检查
// 1. 创建定时器,每 intervalSeconds 发送一次心跳
// 2. 如果未收到响应,标记连接异常
// 3. 触发重连机制
}
}
```
#### C. 问题分析 20 分)
**C.1 场景分析** 10 分)
请分析以下三个真实场景,并说明系统应该如何处理:
**场景 A:优先级倒挂**
```
时间 事件 任务状态
T0 任务A(优先级=5)加入队列 Waiting
T1 任务B(优先级=10)加入队列 Waiting
T2 任务A 被分配给车C1 Fetching
T3 任务B 到达避让点等待 Waiting
T4 车C1 堵塞在取货点(4分钟) Blocked
T5 任务B 等待超过 300 秒,必须执行 必须安排
问题:此时应该做什么?
A) 继续等待C1完成?
B) 让C1中止当前任务,让B先执行?
C) 给B分配其他车?
D) 其他方案?
请说明你的理由,并考虑以下因素:
- 公平性(任务不能无限期等待)
- 效率(减少车辆空闲时间)
- 一致性(系统状态不能出现不一致)
```
**场景 B:级联避让**
```
[工位1]
[车C1]←──(被C2阻挡)
/ \
[避1] [工位2]
[车C2]←──(被C3阻挡)
/ \
[避2] [工位3]
问题:C1、C2、C3 都被阻挡,谁应该先让开?
如何避免以下问题:
- 所有车都跑到避让点,导致避让点满?
- 车辆在避让点之间来回奔波(浪费能源)?
- 形成避让死锁(无法找到有效的避让点链)?
```
**场景 C:突发故障恢复**
```
时间线:
T0 主控向AGV发送订单(包含10个节点)
T1-T4 前3个节点执行成功
T5 MQTT 连接断开
T6 主控检测到断线,进行重连
T7 重连成功,但AGV已执行第5个节点
T8 主控应该重新同步状态
问题:
1. 主控应该重发整个订单还是部分订单?
2. 如何处理已执行但未被主控确认的节点?
3. 如果重发导致节点重复执行怎么办?
4. 如何确保数据一致性?
```
**请对每个场景做以下分析**
1. 识别系统中涉及的关键决策点
2. 列举可能的处理方案(至少3个)
3. 分析每个方案的优缺点
4. 给出最终推荐方案及理由
---
**C.2 性能与可靠性分析** 10 分)
1. **性能分析**
- 当系统中有 100 个待处理任务时,调度循环的时间复杂度是多少?
- 如何优化避障搜索以减少平均响应时间?
- 推荐的检查间隔是多少?
2. **可靠性分析**
- 系统最多能容忍多少个并发冲突而不死锁?
- 如何检测死锁?
- 死锁恢复的代价是什么?
3. **可扩展性分析**
- 当车辆数增加到 100+ 时,系统如何扩展?
- MQTT broker 是否成为瓶颈?
- 建议如何分布式部署?
#### D. 创新性改进 10 分)
**请提出至少 2 项创新改进方案**
1. **改进方案 A**
- 描述问题
- 提出解决思路
- 说明实现步骤
- 预期效果
2. **改进方案 B**
- 描述问题
- 提出解决思路
- 说明实现步骤
- 预期效果
**参考方向**
- 机器学习优化任务分配
- 图论优化路权分配
- 预测性避障(提前预防冲突)
- 动态路线更新
- 能耗优化
- 多目标优化(吞吐量 vs 能耗 vs 公平性)
---
### 【选择题 1】:MQTT 状态同步机制优化
**题目描述**
当前 VDA5050Car 的状态同步方式是:
1. VDA5050Car 通过 `keepAlive()` 定期调用 `ProcessCacheAndSendOrderMessage()`
2. 车端通过 MQTT 发送 `stateMessage`
3. `UpdateState()` 接收并更新本地缓存
**问题**:这种方式存在以下缺陷:
- 状态更新延迟可能达到 100ms+keepAlive 间隔)
- 快速变化的状态可能被覆盖
- 消息顺序性无法保证
**请设计一个改进的状态同步机制**,包括:
1. **架构设计**10 分):
- 使用事件驱动模式代替轮询
- 引入状态版本号防止过期状态覆盖
- 实现状态变更日志
2. **实现代码**20 分):
```csharp
/// <summary>
/// 事件驱动的状态管理器
/// </summary>
public class EventDrivenStateManager
{
private Queue<StateChangeEvent> _stateChangeHistory = new();
private uint _stateVersion = 0;
public event EventHandler<StateChangeEventArgs> OnStateChanged;
public struct StateChangeEvent
{
public uint Version { get; set; }
public DateTime Timestamp { get; set; }
public string StateType { get; set; }
public object OldValue { get; set; }
public object NewValue { get; set; }
}
// TODO: 实现状态版本控制
// TODO: 实现状态变更通知
// TODO: 实现状态历史查询
}
```
3. **问题分析**10 分):
- 新机制如何处理乱序消息?
- 如何避免状态爆炸?
- 实现成本是多少?
---
### 【选择题 2】:车辆故障诊断与自愈系统
**题目描述**
你需要为 VDA5050Car 设计一个**自动故障诊断与自愈系统**。
**常见故障场景**
- MQTT 连接断开
- 脚本执行超时
- 车辆不响应
- 路径规划失败
- 死锁状态
**请设计一个诊断系统**,包括:
1. **故障检测** 10 分):
- 定义故障指标(KPI
- 实现故障识别算法
- 设计告警规则
2. **自愈机制** 20 分):
```csharp
/// <summary>
/// 车辆自愈系统
/// </summary>
public class VehicleSelfHealingSystem
{
public enum FaultType
{
MQTTDisconnected,
ScriptTimeout,
VehicleUnresponsive,
PathPlanningFailed,
DeadLock
}
public class FaultDiagnosis
{
public FaultType Type { get; set; }
public DateTime DetectTime { get; set; }
public string Description { get; set; }
public int Severity { get; set; } // 0-100
}
// TODO: 实现故障诊断
public FaultDiagnosis Diagnose(Car car)
{
// 分析车辆状态,识别故障
return null;
}
// TODO: 实现自愈策略
public async Task<bool> SelfHeal(FaultDiagnosis fault)
{
// 根据故障类型,选择合适的恢复策略
return false;
}
// TODO: 实现故障恢复验证
public bool VerifyHealing(FaultDiagnosis fault)
{
// 验证自愈是否成功
return false;
}
}
```
3. **案例分析** 10 分):
- MQTT 断线如何诊断和恢复?
- 脚本超时如何处理?
- 如何区分临时故障和永久故障?
---
### 【选择题 3】:多目标优化调度算法
**题目描述**
当前系统的调度目标单一(最近距离优先)。实际上,运营方关心多个目标:
| 目标 | 说明 | 权重 |
|-----|------|------|
| 吞吐量 | 完成任务数/时间 | 40% |
| 公平性 | 任务等待时间方差 | 30% |
| 能耗 | 车辆行驶距离 | 20% |
| 可靠性 | 避免冲突和死锁 | 10% |
**请设计一个多目标优化调度算法**
1. **算法设计** 15 分):
- 定义目标函数
- 说明优化方法(贪心/遗传/蚁群等)
- 证明算法收敛性
2. **实现代码** 15 分):
```csharp
/// <summary>
/// 多目标优化调度器
/// </summary>
public class MultiObjectiveScheduler
{
public struct ScheduleObjectives
{
public double Throughput { get; set; } // 吞吐量
public double Fairness { get; set; } // 公平性(1-方差)
public double EnergyEfficiency { get; set; } // 能耗效率
public double Reliability { get; set; } // 可靠性
}
// TODO: 实现多目标评分函数
public double CalculateScore(ScheduleObjectives obj, Dictionary<string, double> weights)
{
// 根据权重计算综合评分
return 0;
}
// TODO: 实现帕累托前沿搜索
public List<AbstractDelivery> FindParetoOptimalSchedule(
List<AbstractDelivery> candidates)
{
// 找到帕累托前沿上的最优调度方案
return null;
}
}
```
3. **权衡分析** 10 分):
- 为什么不能同时最大化所有目标?
- 如何在这些目标之间找到平衡?
- 运营决策者应该如何选择权重?
---
## 评分标准
### A. 系统架构(15 分)
| 评分 | 标准 |
|-----|-----|
| 15 | 架构清晰完整,组件划分合理,交互明确,支持扩展 |
| 12 | 架构基本清晰,大部分组件正确,少数地方欠考虑 |
| 9 | 架构思路正确,但细节不完善,缺少某些重要组件 |
| 6 | 架构基本可行,但设计粗糙,重要细节缺失 |
| 0 | 没有架构或完全错误 |
### B. 代码实现(40 分)
| 评分 | 标准 |
|-----|-----|
| 40 | 代码完整可运行,逻辑清晰,异常处理完善,性能良好 |
| 32 | 代码基本完整,核心逻辑正确,少数边界情况未处理 |
| 24 | 代码框架正确,核心逻辑基本实现,缺少优化 |
| 16 | 代码结构合理,但实现不完整,存在明显缺陷 |
| 8 | 代码框架可见,但实现很不完善 |
| 0 | 没有代码或完全无法运行 |
### C. 问题分析(20 分)
| 评分 | 标准 |
|-----|-----|
| 20 | 分析全面深入,考虑周全,方案可行性强,论证充分 |
| 16 | 分析基本全面,考虑大部分因素,方案合理 |
| 12 | 分析有深度,但不够全面,某些方案欠妥 |
| 8 | 分析浮表,缺少深入思考,方案可行性一般 |
| 4 | 分析肤浅,考虑不足,方案有问题 |
| 0 | 没有分析或完全错误 |
### D. 创新性(10 分)
| 评分 | 标准 |
|-----|-----|
| 10 | 提出的改进方案新颖,技术难度高,应用价值大 |
| 8 | 方案创新,有一定难度,实用性较好 |
| 6 | 方案合理但不够创新,实用性一般 |
| 4 | 方案基本可行,但缺乏创新 |
| 2 | 方案平凡,基本没有创新 |
| 0 | 没有方案或完全无创意 |
---
## 答题建议
### 时间分配
```
总时间:90 分钟
1. 审题与理解 (5 分钟)
2. A. 架构设计 (15 分钟)
3. B. 代码实现 (40 分钟)
- B.1 冲突检测 (12 分钟)
- B.2 任务重规划 (15 分钟)
- B.3 连接恢复 (13 分钟)
4. C. 问题分析 (20 分钟)
5. D. 创新性改进 (10 分钟)
```
### 答题策略
1. **优先完成主题题**:主题题分值最高(85 分)
2. **先做框架,后做细节**:先完成整体设计,再补充实现
3. **代码示例重于完整实现**:关键方法的伪代码比完整但有 bug 的代码更好
4. **多用图表和表格**:架构图、状态机、时间线等有助于表达
5. **充分论证方案**:说明"为什么"往往比"如何做"更重要
### 常见错误
? **错误做法**
- 只写代码不做设计
- 过于关注细节,忽视整体架构
- 没有考虑系统中的并发和同步问题
- 忽视边界情况和故障处理
- 完全照搬现有代码,没有创新
? **正确做法**
- 先思考后编码
- 重视系统设计和权衡
- 充分考虑并发、故障、扩展性
- 列举各种情况并给出处理方案
- 在理解基础上进行创新改进
---
## 参考资源
### 理论基础
- 《分布式系统》—— Kleppmann
- 《设计数据密集型应用》 —— Kleppmann
- 多目标优化理论
- 状态机设计模式
- 事件驱动架构
### 相关技术
- MQTT 协议详解
- C# 异步编程
- 线程安全与同步
- 图论算法(最短路径、避障)
- 调度算法(EDF、LLF 等)
### 项目代码
- `VDA5050Car.cs` —— 车型实现参考
- `AbstractChainedDeliveryMission.cs` —— 任务调度参考
- `Commons.cs` —— 工具方法参考
- `MasterMQTTCommunication.cs` —— 通信参考
---
**祝你答题顺利!** ??
如有疑问,请参考 `DEVELOPMENT_GUIDE.md` 开发指导文档。
---
**题目版本**1.0
**难度等级**:★★★★☆
**预期评审时间**90 分钟
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
# StandardScene 开发指南
## 1. 项目定位
`StandardScene` 是一个由 `SimpleLite.exe`(CycleGUI 应用)宿主加载的场景插件库,已拆分为「基座 + 4 个卫星」共 5 个插件 DLL(基座输出 `StandardScene.dll`),不是独立 EXE。仓库主要面向 AGV/AMR 场内调度与联动控制,覆盖:
- 搬运任务与环线任务
- 区域流控与交通互锁
- 充电策略与充电桩管理
- 门禁联动与安全信号
- HTTP / MQTT / Modbus 等外围接口
## 2. 技术与运行方式
| 项目项 | 说明 |
| --- | --- |
| 语言 | `C#` |
| 框架 | `net8.0-windows` |
| 工程类型 | `Library`(基座 + 4 卫星,共 5 个插件 DLL) |
| 宿主 | `SimpleLite.exe`CycleGUI 应用) |
| 界面技术 | 由 WinForms 迁移到 CycleGUI(宿主同栈);`DeliveryViewer` 已迁移 |
| 关键入口 | `MissionType``CarType``WebApi.cs` |
### 本机依赖
`.csproj``HintPath` 指向以下依赖(宿主产物需先构建 Simple 解决方案):
- `D:\MDCS\Dependencies\Commons\CommonUsage.dll``MDCSToolBox.dll``CycleGUI.dll`
- `..\Simple\SimpleLite\bin\Debug\SimpleLite.dll``LessokajiWeaverUtilities.dll`
- `..\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll`
### 构建与运行
> 必须先构建宿主依赖,否则会出现 `SimpleCore` 版本不匹配等编译错误。
1. 先构建宿主:`dotnet build Simple\SimpleLite\SimpleLite.csproj`(会一并构建 `SimpleCore` 项目)
2. 打开 `StandardScene.sln`,编译 `Debug|x64``Release|x64`
3. 编译后 `Directory.Build.targets` 会把 5 个插件 DLL+ PDB + `*.scene.json`)复制到 `build\plugins`
4.`build\plugins` 部署到 `SimpleLite.exe` 工作目录下的 `plugins\`,运行 `SimpleLite.exe`
## 3. 目录结构
| 路径 | 作用 |
| --- | --- |
| `CarTypes\` | 各车型与协议适配 |
| `Chained\` | 搬运任务、链式调度、环线任务 |
| `Charge\` | 充电策略、充电桩管理 |
| `ChargeStationType\` | 具体充电桩类型 |
| `InterLock\` | 区域互锁、交通控制 |
| `Scheduler\` | 心跳、安全、区域流控等后台 Mission |
| `ExtendDevice\Door\` | 门禁设备接入与联动 |
| `Model\` | 任务、配置、地图等数据模型 |
| `TCP\` / `Utils\` | TCP、JSON、Web API、Modbus 工具 |
| `Commons.cs` | 通用字段、标签、选车、路径辅助 |
| `WebApi.cs` | 对外 HTTP 接口 |
## 4. 运行时架构
运行链路通常是:
1. 宿主启动并扫描 `build\plugins`
2. 加载 `StandardScene.dll`
3. 通过特性反射识别 `MissionType``CarType`
4. 启动具体 Mission
5. Mission 调用 `SimpleLib``TrafficControl``SegmentPlan` 等核心能力
6. 车辆状态、交通状态、充电状态在运行时持续联动
## 5. 核心模块理解
### `Scheduler`
这是最适合入门的目录。
- `HeartBeatMission.cs`:最小线程式 Mission
- `RegionalTrafficControlMission.cs`:事件订阅型 Mission
- `SecuritySignalMission.cs`:安全信号类任务
### `Chained`
主业务调度的核心区域。
- `ChainedDeliveryMission.cs`:搬运任务总控(旧版 `AbstractChainedDeliveryMission.cs` 已删除)
- `TransportMission.cs`:常规运输 Mission
- `AbstractLoopMission.cs`:环线任务骨架
- `LoopMission.cs`:环线业务实例
### `Charge`
负责能量与充电协同。
- `AbstractChargeLogicMission.cs`
- `StandardChargeMission.cs`
- `ChargeStationDataService.cs`
- `ChargeStationManagementExample.cs`
### `CarTypes`
负责车型与协议适配。
- `VDA5050Car.cs`
- `Forklift.cs`
- `Kiva.cs`
- `MultiVehicleCar.cs`
- `MasterMQTTCommunication.cs`
### `WebApi.cs`
对外暴露 HTTP 能力,常见路由包括:
- `/car/createTask`
- `/car/getAllCars`
- `/car/goSite`
- `/map/getMap`
- `/task/getTask`
- `/mission_reflection/get_mission_list`
## 6. 配置文件
| 文件 | 作用 |
| --- | --- |
| `Config\traffic.json` | 交通互锁 / 区域配置 |
| `Config\ChargeStations.json` | 充电桩定义 |
| `Config\ChargeStrategyConfig.json` | 充电策略 |
| `Config\AlarmConfigs.json` | 报警配置 |
| `DoorConfig.json` | 门禁配置 |
| `tasklist.json` | 环线任务配置 |
| `simple.json` | 宿主基础配置 |
除了 JSON 文件,本仓库还大量使用 `fields``tags` 作为轻量配置入口,尤其是站点与车辆行为控制。
## 7. 快速开始案例
### 案例 A:新增一个最小 Mission
最推荐的新手入门案例,直接参考 `Scheduler\HeartBeatMission.cs`
```csharp
using System.Threading;
using Newtonsoft.Json;
using SimpleLite.RCS;
using SimpleCore;
namespace StandardScene.Scheduler
{
[MissionType(Name = "Hello Mission", editor = typeof(HelloMission))]
[I18N.DocumentTranslation(Name = "Hello Mission", locale = "en")]
public class HelloMission : Mission
{
[JsonIgnore] private bool _started;
[JsonIgnore] private Thread _thread;
public static Mission Create()
{
return new HelloMission();
}
public override void Execute()
{
if (_started) return;
_started = true;
status.status = "已启动";
_thread = new Thread(() =>
{
int count = 0;
while (_started)
{
Thread.Sleep(1000);
count++;
status.status = $"tick:{count}";
}
});
_thread.Start();
}
public void Stop()
{
_started = false;
status.status = "已停止";
}
}
}
```
#### 关键提醒
当前工程已是 SDK 风格 `.csproj``net8.0-windows`),目录下的 `.cs` 文件会被自动包含,无需再手工添加 `<Compile Include>`。新增任务/车型/驱动后,记得补上对应特性(`[MissionType]``[CarType]``[DoorType]` 等)与静态 `Create()`,否则宿主反射不到。
#### 验证方式
1. 编译解决方案并把插件部署到宿主 `plugins\`
2. 运行 `SimpleLite.exe`
3. 启动 `Hello Mission`
4. 观察 `status.status` 是否变成 `tick:1``tick:2`
### 案例 B:配置区域流控
该案例对应 `Scheduler\RegionalTrafficControlMission.cs`
给区域内站点添加字段:
```text
Region1 = 1
```
含义是:
- 站点属于 `Region1`
- `Region1` 最多允许 1 台车进入
#### 实验步骤
1. 给同一区域内多个站点加上 `Region1=1`
2. 启动“区域流量监控” Mission
3. 让两台车先后进入该区域
4. 观察第二台车是否被阻止
5. 查看 Mission 状态中的区域统计与拦截次数
## 8. 开发工作流建议
1. 先判断功能属于 `Scheduler``Chained``Charge``CarTypes` 还是 `WebApi.cs`
2. 找最接近的现有类作为模板
3. 明确配置入口是 JSON、`fields` 还是 `tags`
4. 补齐日志、状态与停止逻辑
5. 确认文件已加入工程
6. 编译后在宿主里验证是否能被识别
## 9. 调试建议
优先观察这些点:
- `status.status`
- `Diagnosis.Post` / `Diagnosis.Log`
- `car.status.pendingLocks`
- `car.status.holdingLocks`
- 站点 / 车辆的 `fields``tags`
- `WebApi.cs` 中的实际路由
推荐调试方式:
-`SimpleLite.exe` 作为外部程序启动调试
- 或先运行宿主,再附加进程
## 10. 常见坑
### 新增 Mission 看不到
优先检查:
- 是否加了 `MissionType`
- 是否有静态 `Create()`
- 是否复制到了 `build\plugins` 并部署到宿主 `plugins\`
- 卫星插件是否已在 `active-scenes.json` 中启用对应场景
### 区域流控不生效
优先检查:
- 字段名是否以 `Region` 开头
- 字段值是否能解析为整数
- Mission 是否已启动
### 任务不执行
优先检查:
- 车辆是否在线
- 路径是否可达
- 是否被互锁、流控或门控拦截
- 是否已有标签将车辆标记为忙碌或充电中
+60
View File
@@ -0,0 +1,60 @@
# StandardScene 文档导航
## 1. 文档入口
| 文档 | 适合谁 | 用途 |
| --- | --- | --- |
| `DocumentHub.html` | 所有人 | 单文件浏览入口,双击直接打开 |
| `README.md` | 第一次接触仓库的人 | 快速了解项目定位与运行方式 |
| `ARCHITECTURE.md` | 工程师 / AI Agent 接手 | **整体代码架构**、模块地图、扩展点、接手清单 |
| `DEVELOPMENT_GUIDE.md` | 要开始开发的人 | 系统化理解架构、模块、配置、案例 |
| `QUICK_REFERENCE.md` | 正在写代码的人 | 快速查入口、路径、配置、排错点 |
## 2. 推荐阅读路线
### 路线 A:第一次接触仓库
1. 打开 `DocumentHub.html`
2. 阅读 `README.md`
3. 阅读 `ARCHITECTURE.md`(建立架构全局观)
4. 阅读 `DEVELOPMENT_GUIDE.md`
5. 打开 `Scheduler\HeartBeatMission.cs`
6. 动手做 HelloMission 案例
### 路线 B:已经会跑工程,准备开发
1. 阅读 `QUICK_REFERENCE.md`
2. 根据目标定位目录
3. 找最接近的参考类
4. 写代码
5. 回到 `DEVELOPMENT_GUIDE.md` 查配置与调试建议
### 路线 C:准备改真实业务逻辑
1. 先确认变更属于哪个模块
2. 如果是调度,看 `Chained\`
3. 如果是联动控制,看 `Scheduler\``InterLock\`
4. 如果是设备或协议,看 `CarTypes\``Charge\``ExtendDevice\Door\`
5. 如果是外部系统接入,看 `WebApi.cs`
## 3. 按目标查文件
| 目标 | 直接打开这些文件 |
| --- | --- |
| 了解插件怎么被宿主识别 | `Scheduler\HeartBeatMission.cs` |
| 做区域流量限制 | `Scheduler\RegionalTrafficControlMission.cs` |
| 做搬运调度 | `Chained\AbstractChainedDeliveryMission.cs``Chained\TransportMission.cs` |
| 做充电策略 | `Charge\AbstractChargeLogicMission.cs``Charge\StandardChargeMission.cs` |
| 查 API | `WebApi.cs` |
| 查通用工具 | `Commons.cs` |
## 4. 本地打开 HTML 文档
直接双击 `DocumentHub.html` 即可,无需任何部署。页面已内嵌样式与脚本,并显式声明 `UTF-8` 编码。
## 5. 建议的第一个练习
1.`DEVELOPMENT_GUIDE.md` 的案例 A 新建 `HelloMission`
2. 编译后部署到宿主 `plugins\`,运行 `SimpleLite.exe`
3. 启动 Mission,确认状态每秒递增
4. 再按案例 B 给站点添加 `Region1=1`,体验区域流控
+412
View File
@@ -0,0 +1,412 @@
# AbstractLoopMission 抽象环线任务基类
## 概述
`AbstractLoopMission` 是环线任务的抽象基类,提供了完整的循环任务调度框架。它支持多种启动类型、任务类别、流量控制和优先级调度,是所有具体环线任务实现的基础。
## 核心特性
| 特性 | 说明 |
|------|------|
| 多启动类型 | AutoLoop(自动循环)、Api、Plc、ButtonBox、Charge |
| 多任务类别 | Loop(普通循环)、BranchPoint(分流点)、JoinPoint(汇合点) |
| 流量控制 | 限制目标站点的最大车辆数 |
| 优先级调度 | 高优先级任务优先处理 |
| 配置热更新 | tasklist.json 文件变更自动刷新 |
| 路径缓存 | 避免重复计算路径,提升性能 |
| 条件触发 | 子类可重写事件回调,自定义触发条件 |
## 类图
![alt text](image.png)
## 快速开始
### 1. 配置任务列表
在 任务进程方法显示界面中配置任务或者在`tasklist.json` 中配置任务
[ { "Id": 1, "Name": "主线循环", "CurrentStationId": 100, "TargetStationId": 200, "StartType": "AutoLoop",
"Kind": "Loop", "Priority": 10, "TrafficControl": 2, "IsViaPoint": true },
{ "Id": 2, "Name": "分流点A", "CurrentStationId": 150, "TargetStationId": 201, "StartType": "AutoLoop", "Kind": "BranchPoint", "Priority": 8, "TrafficControl": 1 },
{ "Id": 3, "Name": "汇合点B", "CurrentStationId": 180, "TargetStationId": 300, "StartType": "Plc", "Kind": "JoinPoint", "Priority": 5, "TrafficControl": 1 } ]
### 2. 创建子类
public class MyLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 业务逻辑判断 if (ShouldProcessTask(task, car)) { // 方式1:使用子类指定的目标站点 return ExternalTriggerResult.UseTarget(200);
// 方式2:使用配置文件中的目标站点
// return ExternalTriggerResult.UseConfigTarget();
}
// 业务失败,不分配任务
return ExternalTriggerResult.Fail();
}
protected override ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car)
{
// PLC 信号触发逻辑
if (CheckPlcSignal(currentSiteId))
{
return ExternalTriggerResult.UseConfigTarget();
}
return ExternalTriggerResult.Fail();
}
}
## 任务类别说明
### Loop(普通循环)
车辆从当前站点移动到目标站点的简单任务。
站点A ──────────────► 站点B
### BranchPoint(分流点)
一个站点可以分流到多个目标站点,按优先级和流量控制选择目标。
┌──► 目标站点1 (优先级高)
分流点 ────┼──► 目标站点2 └──► 目标站点3 (优先级低)
### JoinPoint(汇合点)
多个站点汇合到同一个目标站点,按优先级决定放行顺序。
来源站点1 ──┐ 来源站点2 ──┼──► 汇合点 来源站点3 ──┘
## 流量控制
通过 `TrafficControl` 属性限制目标站点的最大车辆数:
// 检查流量控制 if (!CheckTrafficControl(targetSiteId, task.TrafficControl)) { // 目标站点流量已满,等待 continue; }
- `TrafficControl = 0`:不限制
- `TrafficControl = 1`:目标站点最多 1 辆车
- `TrafficControl = N`:目标站点最多 N 辆车
统计范围包括:
1. 已在目标站点的车辆(`holdingLocks` 包含该站点)
2. 正在前往目标站点的车辆(`pendingLocks` 最后一个为该站点)
### 3. 启动任务
var mission = new MyLoopMission();
// 启动所有线程(策略同步 + 业务逻辑) mission.StartAll();
// 或者分别启动 // mission.StartLoop(); // 启动策略同步线程 // mission.StartLogicLoop(); // 启动业务逻辑线程
// 停止任务 // mission.StopAll();
// 释放资源 // mission.Dispose();
## 启动类型说明
| 类型 | 枚举值 | 说明 | 子类接口 |
|------|--------|------|----------|
| AutoLoop | `TaskStartType.AutoLoop` | 自动循环,车辆到站自动触发 | 无需重写 |
| Api | `TaskStartType.Api` | API 外部调用触发 | `OnApiTrigger()` |
| Plc | `TaskStartType.Plc` | PLC 信号触发 | `OnPlcTrigger()` |
| ButtonBox | `TaskStartType.ButtonBox` | 按钮盒触发 | `OnButtonTrigger()` |
| Charge | `TaskStartType.Charge` | 充电条件触发 | `OnChargeTrigger()` |
## 子类可重写事件回调
### 概述
`AbstractLoopMission` 提供了四个可重写的事件回调方法,子类可以通过重写这些方法实现自定义的触发条件逻辑。当车辆到达配置的当前站点时,系统会根据任务的 `StartType` 调用对应的回调方法。
### 回调方法签名
| 方法 | 触发条件 | 默认行为 |
|------|----------|----------|
| `OnApiTrigger(int currentSiteId, LoopTask task, Car car)` | StartType = Api | 返回 `Fail()` |
| `OnPlcTrigger(int currentSiteId, LoopTask task, Car car)` | StartType = Plc | 返回 `Fail()` |
| `OnButtonTrigger(int currentSiteId, LoopTask task, Car car)` | StartType = ButtonBox | 返回 `Fail()` |
| `OnChargeTrigger(int currentSiteId, LoopTask task, Car car)` | StartType = Charge | 返回 `Fail()` |
### 回调参数说明
| 参数 | 类型 | 说明 |
|------|------|------|
| `currentSiteId` | `int` | 车辆当前所在站点ID |
| `task` | `LoopTask` | 匹配到的任务配置 |
| `car` | `Car` | 到达站点的车辆对象 |
### 返回值说明
回调方法必须返回 `ExternalTriggerResult` 对象:
| 返回方式 | 说明 | 使用场景 |
|----------|------|----------|
| `ExternalTriggerResult.UseTarget(siteId)` | 成功,使用子类指定的目标站点 | 需要动态计算目标站点时 |
| `ExternalTriggerResult.UseConfigTarget()` | 成功,使用配置文件中的目标站点 | 条件满足,使用预设目标时 |
| `ExternalTriggerResult.Fail()` | 失败,不分配任务 | 条件不满足,等待下次检查时 |
### 使用示例
#### 示例1:API 触发 - 检查外部系统状态
public class ApiLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 检查外部 API 是否允许发车 var apiResult = ExternalApiService.CheckCanDispatch(currentSiteId, car.id);
if (apiResult.Success)
{
// API 返回指定目标
if (apiResult.TargetSiteId.HasValue)
{
return ExternalTriggerResult.UseTarget(apiResult.TargetSiteId.Value);
}
// 使用配置目标
return ExternalTriggerResult.UseConfigTarget();
}
// 条件不满足,等待
return ExternalTriggerResult.Fail();
}
}
#### 示例2PLC 触发 - 检查 PLC 信号状态
public class PlcLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car) { // 读取 PLC 信号 bool plcSignal = PlcManager.ReadBool($"Station{currentSiteId}.AllowDispatch");
if (plcSignal)
{
// 根据 PLC 数据决定目标
int plcTarget = PlcManager.ReadInt($"Station{currentSiteId}.TargetStation");
if (plcTarget > 0)
{
return ExternalTriggerResult.UseTarget(plcTarget);
}
return ExternalTriggerResult.UseConfigTarget();
}
return ExternalTriggerResult.Fail();
}
}
#### 示例3:按钮盒触发 - 检查按钮状态
public class ButtonBoxLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnButtonTrigger(int currentSiteId, LoopTask task, Car car) { // 检查按钮盒是否按下 var buttonBox = ButtonBoxManager.GetByStation(currentSiteId);
if (buttonBox != null && buttonBox.IsPressed)
{
// 重置按钮状态
buttonBox.Reset();
// 根据按钮类型选择目标
switch (buttonBox.PressedButton)
{
case ButtonType.Green:
return ExternalTriggerResult.UseTarget(task.TargetStationId);
case ButtonType.Yellow:
return ExternalTriggerResult.UseTarget(GetAlternativeTarget(currentSiteId));
default:
return ExternalTriggerResult.UseConfigTarget();
}
}
return ExternalTriggerResult.Fail();
}
}
#### 示例4:充电触发 - 检查电量条件
public class ChargeLoopMission : AbstractLoopMission { private const int LOW_BATTERY_THRESHOLD = 20;
protected override ExternalTriggerResult OnChargeTrigger(int currentSiteId, LoopTask task, Car car)
{
// 获取车辆电量
int batteryLevel = car.GetBatteryLevel();
// 检查是否需要充电
if (batteryLevel <= LOW_BATTERY_THRESHOLD)
{
// 查找最近的空闲充电站
int chargeStation = FindNearestAvailableChargeStation(currentSiteId);
if (chargeStation > 0)
{
Diagnosis.Post($"车辆 {car.name} 电量 {batteryLevel}%,前往充电站 {chargeStation}", "Charge", true);
return ExternalTriggerResult.UseTarget(chargeStation);
}
}
// 电量充足或无可用充电站,不触发充电任务
return ExternalTriggerResult.Fail();
}
private int FindNearestAvailableChargeStation(int currentSiteId)
{
// 实现查找最近充电站逻辑
return ChargeStationManager.FindNearest(currentSiteId);
}
}
#### 示例5:组合条件触发
public class ComplexLoopMission : AbstractLoopMission { protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) { // 组合多个条件判断
// 条件1:检查车辆是否携带货物
bool hasLoad = car.tags.ContainsKey("hasLoad") && car.tags["hasLoad"] == "true";
// 条件2:检查目标站点是否可用
var targetSite = SimpleLib.GetSite(task.TargetStationId);
bool targetAvailable = targetSite != null && !targetSite.IsDisabled();
// 条件3:检查时间窗口
bool inTimeWindow = DateTime.Now.Hour >= 8 && DateTime.Now.Hour <= 20;
// 条件4:检查优先级车辆
bool isPriorityCar = car.tags.ContainsKey("priority");
// 组合判断
if (hasLoad && targetAvailable && (inTimeWindow || isPriorityCar))
{
return ExternalTriggerResult.UseConfigTarget();
}
// 记录不满足条件的原因
if (!hasLoad) Diagnosis.Post($"车辆 {car.name} 未携带货物", "ApiTrigger", true);
if (!targetAvailable) Diagnosis.Post($"目标站点 {task.TargetStationId} 不可用", "ApiTrigger", true);
if (!inTimeWindow && !isPriorityCar) Diagnosis.Post($"当前不在工作时间窗口", "ApiTrigger", true);
return ExternalTriggerResult.Fail();
}
}
### 回调执行流程
车辆到达站点 │ ▼ 匹配任务配置 (CurrentStationId) │
▼ 检查车辆可用性 (Commons.SelectCar) │
▼ 根据 StartType 调用对应回调 │ ├
─ AutoLoop ──► 自动处理,无需回调
├─ Api ────────► OnApiTrigger()
├─ Plc ────────► OnPlcTrigger()
├─ ButtonBox ──► OnButtonTrigger() └─ Charge ─────► OnChargeTrigger()
│ ▼ 检查返回结果 │ ├─ Fail() ──────────► 跳过,等待下次检查
│ └─ Success ─────────► 确定目标站点 │
├─ CustomTargetSiteId 有值 ──► 使用子类指定目标 └─ CustomTargetSiteId 为空 ──► 使用配置文件目标
│ ▼ 流量控制检查 │ ├─ 通过 ──► AssignCarToTarget() └─ 不通过 ──► 等待
### 汇合点回调执行流程
按目标站点分组
遍历每个汇合点组
组内任务按优先级降序排列
遍历排序后的任务
├─► 流量控制检查 ──► 不通过 ──► 跳过
├─► 查找车辆 ──► 未找到 ──► 跳过
├─► 检查车辆可用 ──► 不可用 ──► 跳过
├─► 调用子类接口 ──► 返回失败 ──► 跳过
├─► 确定最终目标(子类指定 > 配置)
├─► 子类指定不同目标时再次检查流量
└─► 分配目标站点
### 注意事项
## 外部触发结果
`ExternalTriggerResult` 用于子类返回触发处理结果:
| 方法 | 说明 |
|------|------|
| `UseTarget(siteId)` | 使用子类指定的目标站点 |
| `UseConfigTarget()` | 使用配置文件中的目标站点 |
| `Fail()` | 业务失败,不分配任务 |
protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car) {
// 场景1:动态计算目标 int dynamicTarget = CalculateTarget(car); return ExternalTriggerResult.UseTarget(dynamicTarget);
// 场景2:使用配置目标
return ExternalTriggerResult.UseConfigTarget();
// 场景3:条件不满足
return ExternalTriggerResult.Fail();
}
1. **默认返回 Fail**:所有回调方法默认返回 `Fail()`,子类必须重写才能启用对应的触发功能。
2. **线程安全**:回调方法在业务逻辑线程中执行,访问共享资源时需注意线程安全。
3. **执行频率**:业务逻辑线程每 500ms 执行一次,回调方法应避免长时间阻塞。
4. **异常处理**:回调方法中的异常会被捕获并记录,不会影响其他任务的处理。
5. **流量控制**:即使回调返回成功,仍会进行流量控制检查,可能因流量已满而等待。
## 路径查找与任务匹配
### 路径查找
基于轨道连接的广度优先搜索,支持轨道方向:
// 获取两站点之间的路径 var path = GetSitesBetween(100, 200); // 返回: [100, 150, 180, 200]
// 检查路径上是否有车辆 bool hasCar = HasCarOnPath(100, 200, excludeCar);
// 获取路径长度 int length = GetPathLength(100, 200);
### 任务策略匹配
根据车辆当前位置匹配最优任务:
// 查找车辆的最优任务 var match = FindBestTaskForCar(car);
if (match != null) { Console.WriteLine($"任务ID: {match.TaskId}");
Console.WriteLine($"目标站点: {match.TargetSiteId}");
Console.WriteLine($"下一站点: {match.NextSiteId}");
Console.WriteLine($"剩余距离: {match.DistanceToTarget}");
Console.WriteLine($"进度: {match.ProgressPercent:F1}%"); }
匹配优先级:
1. 起点匹配优先
2. 任务优先级高的优先
3. 距离目标近的优先
> **注意**:如果站点是路径的终点,则不匹配该任务。
### TaskListChanged 事件
任务列表变更时触发:
mission.TaskListChanged += (sender, e) =>
{ switch (e.ChangeType) {
case TaskChangeType.Added: Console.WriteLine($"添加任务: 索引={e.Index}"); break;
case TaskChangeType.Updated: Console.WriteLine($"更新任务: 索引={e.Index}"); break;
case TaskChangeType.Removed: Console.WriteLine($"移除任务: 索引={e.Index}"); break;
case TaskChangeType.Replaced: Console.WriteLine("任务列表已替换"); break; } };
## 线程模型
| 线程 | 名称 | 间隔 | 职责 |
|------|------|------|------|
| 策略同步线程 | `AbstractLoopMission_Strategy` | 1000ms | 同步配置文件,更新任务列表 |
| 业务逻辑线程 | `AbstractLoopMission_Logic` | 500ms | 处理任务调度,分配车辆目标 |
// 分别控制线程 mission.StartLoop(); // 启动策略同步 mission.StartLogicLoop(); // 启动业务逻辑
mission.StopLoop(); // 停止策略同步 mission.StopLogicLoop(); // 停止业务逻辑
## 常量配置
| 常量 | 值 | 说明 |
|------|------|------|
| `STRATEGY_SYNC_INTERVAL_MS` | 1000 | 策略同步间隔(毫秒) |
| `LOGIC_LOOP_INTERVAL_MS` | 500 | 业务逻辑间隔(毫秒) |
| `FILE_CHANGE_DEBOUNCE_MS` | 50 | 文件变更防抖延迟(毫秒) |
| `ERROR_RECOVERY_DELAY_MS` | 3000 | 异常恢复等待时间(毫秒) |
| `SCRIPT_ERROR_TRIGGER_DELAY_MS` | 3000 | 脚本异常检测延迟(毫秒) |
## 辅助方法
### 车辆查找
// 查找到达指定站点的车辆 Car car = FindCarArrivedAtSite(siteId);
// 获取在站或前往站点的车辆 var cars = GetCarsAtOrHeadingToSite(siteId);
// 统计车辆数量 int count = CountCarsAtOrHeadingToSite(siteId);
// 检查车辆是否空闲 bool idle = IsCarIdle(car);
### 车辆分配
// 分配目标站点 AssignCarToTarget(car, targetSiteId);
// 导航到目标站点 await GoSite(car, targetSite, action: "/", reverse: false);
## 异常处理
### 脚本异常恢复
当车辆脚本状态为 `Error``Bad` 时,自动执行恢复流程:
1. 标记站点不可用
2. 禁止车辆调度
3. 重置车辆状态
4. 清理车辆标签
5. 执行车辆重置
+148
View File
@@ -0,0 +1,148 @@
# StandardScene 快速参考
## 1. 一眼看懂这个仓库
| 项目项 | 说明 |
| --- | --- |
| 工程类型 | 插件库(基座 `StandardScene.dll` + 4 卫星,共 5 个 DLL |
| 运行方式 | 由 `SimpleLite.exe`CycleGUI 宿主)从 `plugins\` 加载 |
| 目标框架 | `net8.0-windows` |
| 核心入口 | `MissionType``CarType``WebApi.cs` |
| 新手起步文件 | `Scheduler\HeartBeatMission.cs` |
| 进阶起步文件 | `Scheduler\RegionalTrafficControlMission.cs` |
## 2. 关键目录速记
| 路径 | 你通常在这里做什么 |
| --- | --- |
| `CarTypes\` | 新车型、协议接入、状态同步 |
| `Chained\` | 搬运任务、环线任务、调度逻辑 |
| `Charge\` | 充电策略、充电桩管理 |
| `InterLock\` | 互锁与交通控制 |
| `Scheduler\` | 心跳、安全、区域流控等后台 Mission |
| `ExtendDevice\Door\` | 门控联动 |
| `Model\` | 配置与数据模型 |
| `WebApi.cs` | HTTP API |
| `Commons.cs` | 标签、字段、选车、路径辅助 |
## 3. 构建运行速记
1. 先构建宿主依赖:`dotnet build Simple\SimpleLite\SimpleLite.csproj`
2. 打开 `StandardScene.sln`,确保本机依赖路径存在
3. 编译解决方案(`Debug|x64` / `Release|x64`
4.`build\plugins` 部署到宿主 `plugins\`,运行 `SimpleLite.exe`
5. 确认插件已从 `plugins\` 加载
### 注意
当前工程已是 SDK 风格 `.csproj``net8.0-windows`),目录下 `.cs` 文件会被自动包含,无需手工添加 `<Compile Include>`。卫星插件需在 `active-scenes.json` 中启用对应场景才会被宿主加载。
## 4. 新增功能时先看谁
| 目标 | 优先参考 |
| --- | --- |
| 新增最小 Mission | `Scheduler\HeartBeatMission.cs` |
| 做区域限流 | `Scheduler\RegionalTrafficControlMission.cs` |
| 做运输调度 | `Chained\TransportMission.cs` |
| 做环线 | `Chained\AbstractLoopMission.cs` |
| 做充电 | `Charge\StandardChargeMission.cs` |
| 做充电桩管理 | `Charge\ChargeStationManagementExample.cs` |
| 做 Web 接口 | `WebApi.cs` |
## 5. 高频代码片段
### 获取车辆与站点
```csharp
var car = SimpleLib.GetCar(carId);
var allCars = SimpleLib.GetAllCars();
var site = SimpleLib.GetSite(siteId);
var allSites = SimpleLib.GetAllSites();
var currentSiteId = car.GetLastSite();
```
### 更新标签与字段
```csharp
Commons.AddOrUpdateTag(car.tags, "occupied", "yes");
Commons.AddOrUpdateCarField(car, "group", "A");
Commons.AddOrUpdateSiteField(site, "giveWay", "true");
```
### 规划并执行路径
```csharp
var plan = new SegmentPlan { usingCar = car };
plan.fields["action"] = "move";
plan.fields["allow_destination_on_route"] = "true";
plan.FindRoute(SimpleLib.GetSite(srcId), SimpleLib.GetSite(dstId));
await plan.Compile("move").Queue();
```
### 输出日志
```csharp
Diagnosis.Post("任务已启动", "demo", true);
Diagnosis.Log("详细调试信息", "demo", true);
car.AppendDebug("车辆状态变化");
```
## 6. 常见配置文件
| 文件 | 用途 |
| --- | --- |
| `Config\traffic.json` | 交通 / 互锁配置 |
| `Config\ChargeStations.json` | 充电桩数据 |
| `Config\ChargeStrategyConfig.json` | 充电策略 |
| `Config\AlarmConfigs.json` | 充电报警 |
| `DoorConfig.json` | 门控配置 |
| `tasklist.json` | 环线任务列表 |
| `simple.json` | 宿主基础配置 |
## 7. 常见 API 路由
| 路由 | 作用 |
| --- | --- |
| `/car/createTask` | 创建任务 |
| `/car/getAllCars` | 获取车辆列表 |
| `/car/goSite` | 让车辆前往站点 |
| `/map/getMap` | 获取地图 |
| `/task/getTask` | 查询任务 |
| `/mission_reflection/get_mission_list` | 获取 Mission 列表 |
| `/mission_reflection/execute/{id}/{method}` | 反射执行 Mission 方法 |
## 8. 调试优先级
出现问题时,建议按这个顺序排查:
1. 插件是否被宿主加载
2. Mission / CarType 是否被识别
3. 配置文件和 `fields` 是否正确
4. 车辆是否在线、是否被标签占用
5. 路径规划是否成功
6. 是否被交通控制或区域流控拦截
7. 外部接口是否真的打到了 `WebApi.cs`
## 9. 常见故障速查
| 现象 | 优先检查 |
| --- | --- |
| 新增 Mission 看不到 | 特性、`Create()``.csproj` 引用、插件复制 |
| 任务一直不执行 | 车辆在线状态、路径、标签占用、前置条件 |
| 区域限流无效 | 站点字段是否以 `Region` 开头,值是否为整数 |
| API 调不通 | 路由路径、宿主端口、Nancy 是否已启动 |
| 车辆不动 | 路径失败、程序未下发、协议未连通 |
## 10. 推荐上手案例
### HelloMission
- 目标:理解最小插件生命周期
- 参考:`Scheduler\HeartBeatMission.cs`
- 验证:启动后 `status.status` 每秒递增
### 区域流控
- 目标:理解字段驱动 + 事件订阅
- 参考:`Scheduler\RegionalTrafficControlMission.cs`
- 验证:给站点添加 `Region1=1` 后,第二台车进入同区域会被阻止
+613
View File
@@ -0,0 +1,613 @@
# StandardScene 插件化拆分计划(导航场景插件化 · Phase C 落地)
> 版本:草案 **v2**(按评审反馈更新)
> 编写依据:对当前工程 `E:\Work\Core\Simple-FR\StandardSence`.NET Framework 4.8 单体插件)的**逐文件精读**。
> 上游设计:承接并细化《配置向导与导航场景插件化设计.md》第 5 节「StandardScene 拆分」与第 11.3 节「Phase C」。
> 本文目标:把上游"框架级"拆分意图,落地为**基于真实代码事实、可直接执行**的文件级/类级拆分计划。
> 范围声明:本文原为**计划文档**。**自「会话1」(2026-06-09) 起已开始落地实施**,实际进度与代码现状见 **§11 实施进度记忆**(含已落地改动、当前可编译状态、待确认阻塞项)。
### ⚠️ 架构决策变更(2026-06-12,用户拍板):「磁 / 二维码 / 激光」三场景 → **两场景平台**
原计划的 `Magnetic` / `QrCode` / `Laser` 三个导航 dll 调整为 **两个场景平台插件**(已落地,全解决方案 0 错误):
| 插件 dll | scene id | 内容 | 说明 |
|---|---|---|---|
| `StandardScene.Magnetic.dll` | `scene.mag` | Kiva、MultiWheelLifterCar + `MagneticTrackCoder` | 磁导航平台(保留车型上的 Qr 地标段能力) |
| `StandardScene.QrLidar.dll` | `scene.qrlidar` | Forklift、MultiWheelForkLifter、DualLiftingCar、MultiVehicleCar、ArmCar + `SyncQrMap` | 激光+二维码融合平台:激光坐标导航由内核 `GhostCar.BasicGo` 兜底,`QrGo` 按轨道 tag 逐段触发,**融合或单独使用均可** |
| `StandardScene.Devices.dll` | `scene.device` | 不变 | 清单 id 由 `devices` 规范化 |
| `StandardScene.Protocol.VDA5050.dll` | `scene.vda5050` | 不变 | 清单 id 由 `protocol.vda5050` 规范化 |
关键机制结论与配套改动:
1. **车型按平台归位**(推翻 v2 "车型留基座"):依据 = 项目存档按**短类名**(`JSONLoader``type.Name.ToLower()`)匹配车型,类迁 dll 不破坏旧存档;coder 特性 `GetCustomAttributes(inherit:true)` 沿继承链收集,内核 `GhostCar` 的 BasicGo 天然被所有车型继承。
2. **共享字段袋下沉基座**`KivaFields.cs` / `MultiWheelLifterFields.cs`ArmCar、MultiVehicleCar 跨平台继承它们);`IScriptErrorRecoverable` 接口解耦 `AbstractLoopMission` 对 Kiva 的反向依赖。
3. **清单命名修正**:内核只识别 `<dll>.scene.json`,原裸 `scene.json` 永远不会被扫描到——已全部改名并规范 id。
4. **内核配套**Simple 仓库):① `Startup.LoadPlugins` 两阶段加载——清单 `requiresCore` 声明的基座(`StandardScene.dll`)自动并入 alwaysLoad 且 **non-collectible 先行加载**(否则衍生插件的 collectible ALC 解析不到基座,运行期必炸);② `SceneManifest.navKinds` 多导航声明 + `INavigationProfile.Kinds`qrlidar 同时覆盖 Laser+QrCode`FindByKind` 按集合匹配);③ `ShouldLoad` 规则改为「**有清单(含设备/协议)即参与激活判定**,无清单兼容全加载」,scene.device 因此可被选择性加载。
5. **平台配套**Migu2.0):`DeploymentProfile.NavKindToSceneId` 改映射 magnetic→scene.mag、qrcode/laser→scene.qrlidar;向导写 active-scenes.json 时固定并入 scene.device。
### v2 评审反馈要点(本次更新依据)
1. **宿主切换**StandardScene 改为依赖 **`SimpleLite`net8.0****老 `SimpleComposer.exe`(net4.8)废弃**。内核引用层(命名空间 `SimpleComposer.RCS``SimpleLite.RCS` 等)正由**另一个 AI 会话**同步改造,本文按新宿主表述。
2. **目标框架统一 `net8.0`**(与 SimpleLite 一致;**非 net8.0-windows**)。SimpleLite UI 采用 **CycleGUI**、WebApi 采用 **EmbedIO**,故 StandardScene **需去除 WinForms 依赖**,窗体迁移到 CycleGUI 或平台 Web。
3. **设备驱动必须独立 dll**,且**支持热卸载/加载**(配合 SimpleLite `/plugins` reload/unload + collectible ALC)。
4. **`FactoryTest` 与松灵残留:直接删除**,不纳入 Core,也不纳入 Customer。
5. **`WebApi.cs` 是面向老平台的 Nancy 接口**:本次**整理后暂留 Core**,**后期废弃**,能力迁移到 SimpleLite 接口(见 `E:\Work\Core\Simple-FR\Simple\SimpleLite\Docs\MIGU-API.md`)。此版本先保留。
---
## 0. TL;DR(结论先行)
1. **导航方式与车型是正交的两个维度**。导航(磁 / 二维码 / 激光)由**轨道/站点字段 + TrackCoder**决定,不是车型固有属性;同一车型(如 `Kiva`)的类上**同时**挂磁、二维码、激光避障三类 coder。上游"按车型整包切到某导航 dll"**切不干净**,修正为「**车型留基座 + 导航能力抽离为可插拔 coder**」。
2. **激光"避障"(LidarArea) ≠ 激光"导航定位"(LidarMap/SLAM)**:避障通用留 Core,仅 SLAM 地图进 Laser。
3. **`WebApi.cs`(123KB) 是老平台 Nancy 接口**,导航耦合极弱(仅二维码 `QrMap`、激光 `getLidarMap`)。**整理后暂留 Core,标记 deprecated,后期迁 SimpleLite EmbedIO 接口**(§4.7 给出端点映射)。
4. **存在大量重复代码**可借本次"抽离标准功能"沉淀:磁导航循迹器写了**两份**`newReset/newTrafficReset` 在 3 个车型复制、`SetDisplayInfo/GetCarStatus/Mstsc/EmergencyStop` 雷同。
5. **三个正交维度**:①导航(磁/二维码/激光) ②车型(顶升/叉车/Kiva/机械臂…) ③设备驱动(充电桩/门/按钮盒)。设备驱动**独立成可热插拔 dll**。
6. **VDA5050 是自成一体的 MQTT 协议栈**,独立 dll。
7. **两大技术主线**:① 把 TrackCoder 从"编译期特性硬绑定车型"改为"导航插件运行期注册",需内核 `SimpleCore` 配合;② **net4.8 → net8.0 迁移**:换宿主(SimpleComposer→SimpleLite+ **去 WinForms(窗体迁 CycleGUI/Web**
---
## 1. 现状盘点(基于代码事实)
### 1.1 工程概况(来自 `StandardScene.csproj`
| 项 | 现状 | 目标(本次方向) |
|---|---|---|
| 输出类型 | `Library`(插件) | 多个 `Library` 插件 dll |
| 目标框架 | `.NET Framework 4.8` | **`net8.0`**(与 SimpleLite 一致,去 WinForms |
| 宿主 | `SimpleComposer.exe`(net4.8) | **`SimpleLite`(net8.0)**,老 Composer 废弃 |
| 契约引用 | `RefSimpleCore.dll` + `SimpleComposer.exe` | **`SimpleCore`(netstandard2.0/net8) + `SimpleLite` 程序集** |
| UI | `System.Windows.Forms`(大量窗体) | **CycleGUI / 平台 Web**(移除 WinForms |
| 对外接口 | `WebApi.cs`Nancy 1.4.5 | 暂留 Core;后期迁 **SimpleLite EmbedIO**MIGU-API |
| 其它程序集 | `CommonUsage``MDCSToolBox``leegKeys-sdk``LessokajiWeaverUtilities` | 随归属 dll 保留/下沉 |
| NuGet | `MQTTnet``EasyModbusTCP``IoTClient``Jint``Nancy(1.4.5)``Newtonsoft.Json``DocumentFormat.OpenXml` | 随归属拆分(Nancy 验证 net8 兼容或对齐 SimpleLite 的 Nancy 2.0 |
| 产物落地 | `PostBuildEvent` 拷 dll → `build\plugins\` | 各 dll + `scene.json` → SimpleLite `plugins\` |
### 1.2 模块与文件分组(约 110 个有效 `.cs`)
| 目录 | 内容 | 现状性质 |
|---|---|---|
| `CarTypes/` | `Forklift``Kiva``ArmCar``DualLiftingCar``MultiWheelForkLifter``MultiVehicleCar``MultiWheelLifterCar``DummyCar``UselessCar``BasicFields``VehicleMonitor`(窗体) | 车型 + 内联导航 coder |
| `CarTypes/VDACar/` | `VDA5050Car``VDA5050Commons/Helper/Interface/Segment/WebApi``MasterMQTTCommunication``TextViewer`(窗体) | VDA5050 通信协议栈 |
| `Chained/`(含 `Loop/` | 搬运/环线任务 + `DeliveryViewer/LoopViewer`(窗体) | 任务调度 |
| `Charge/` | 充电任务 + `ChargeStation*`/`ChargeStrategy*`/`Alarm*`/`Communication*` + 多个 `*Form`(窗体) | 充电逻辑 + UI/服务 |
| `ChargeStationType/` | `AbstractChargeStation``FL`/`MuXing`/`PCB` | 充电桩厂商驱动 |
| `InterLock/` | `AbstractInterlockMission``TrafficInterlockMission``TrafficInterlockViewer`(窗体) | 互锁/交管 |
| `Scheduler/` | `HeartBeat`/`NodeIsEnable`/`RegionalTrafficControl`/`SecuritySignal` Mission | 调度信号 |
| `ExtendDevice/Door/` | `BasicDoorController``ModbusDoorController``DoorMission``DoorTypeAttribute``DoorManager/DoorMonitor`(窗体) | 门控驱动 |
| `ExtendDevice/ButtonBox/` | `BasicButtonBox``LeegButtonBox`/`AzowieButtonBox``ButtonMission``ButtonBoxManager`(窗体) | 呼叫器驱动 |
| `Model/` | `Map`(含 `LidarMap`)、`SimpleMap``TaskModel``LoopTask`、各 `*Setting``VehicleStatus``MissionState``MapStructure` | 数据模型 |
| `Utils/` `TCP/` `CommonTools/` | Json/Modbus/WebAPIHelper、AsyncTcpClient、Snowflake/AtomicFile | 基础设施 |
| 根 | `Commons.cs``Heuristic.cs``LadderLogic.cs``StandardCADTool.cs``WebApi.cs``FactoryTest.cs` | 公共 + 契约 + 老 API + 产测 |
### 1.3 车型清单与继承
| 车型类 | `[CarType]` | 基类 | 关键特征 |
|---|---|---|---|
| `Kiva` | "Kiva" | `GhostCar` | 内联磁 coder `AllCarMagTrackCoder` + Qr + 避障 + 取放货 + `KivaCarTrackCoder`(转弯) |
| `Forklift` | "叉车" | `GhostCar` | 避障 + 取放货(无显式磁/二维码) |
| `MultiWheelLifterCar`(CarTypes) | "多舵轮顶升车" | `GhostCar` | 内联磁 coder `MagTrackCoder` + Qr + 钻车/夹抱 |
| `MultiWheelForkLifter` | "多舵轮叉车" | `GhostCar` | 简化取放货 |
| `MultiVehicleCar` | "多车联动AGV" | `GhostCar` | 多车 sync + Qr + 钻车 |
| `DualLiftingCar` | "锂电双举升" | `GhostCar` | 无 coder(仅 UI/测试) |
| `ArmCar` | "ArmCar" | `GhostCar` | 机械臂动作;**依赖 `VDA5050SiteField`**、继承 `Kiva*Fields` |
| `DummyCar` | "模拟车-包络" | `Car` | **命名空间 `AMRScene1`**`Jint` 仿真 |
| `UselessCar` | (已注释,未注册) | `GhostCar` | 测试残留 |
### 1.4 Mission 继承体系(均派生内核 `Mission`,导航无关)
```
Mission
├─ AbstractChainedDeliveryMission / ChainedDeliveryMission(→TransportDelivery) Chained/
├─ AbstractLoopMission(→LoopMission) Chained/
├─ TrafficInterlockMission / AbstractInterlockMission InterLock/
│ └─ AbstractChargeLogiceMission(→StandardChargeMission) Charge/
├─ HeartBeat/NodeIsEnable/RegionalTrafficControl/SecuritySignal Mission Scheduler/
├─ DoorMission / ButtonMission ExtendDevice/
└─ FactoryTest(产测,本次删除) 根
```
### 1.5 设备驱动体系(第三正交维度,本次独立 dll + 热插拔)
- 充电桩:`AbstractChargeStation``FLChargeStation` / `MuXingChargeStation`(牧星) / `PCBChargeStation``ChargeStationType` 枚举;`StandardChargeMission``car.fields["MuXing"]` 区分 FRLD/MuXing。
- 门:`BasicDoorController``ModbusDoorController``DoorTypeAttribute`(注册契约)。
- 按钮盒:`BasicButtonBox``LeegButtonBox`(依赖 `leegKeys-sdk`/`leegiot`) / `AzowieButtonBox`
### 1.6 VDA5050 子系统(独立 MQTT 协议栈)
- `VDA5050Car : Car` + `MasterMQTTCommunication` + `VDA5050Interface/Helper/Commons/Segment/WebApi` + `TextViewer`(窗体);依赖 `CommonUsage.Protocols.VDA5050.*` + `MQTTnet`
- VDA5050 车端自管导航定位,与磁/二维码/激光维度无关。
- 耦合点:`ArmCar` 引用 `VDA5050SiteField`(拆分时解依赖)。
### 1.7 删除项(拆分前清理,**不进 Core/Customer**
| 文件/类 | 原因 | 处置 |
|---|---|---|
| `FactoryTest.cs` | 产测专用,非标准能力 | **删除**(评审已确认) |
| 根 `SongLingDeliveryViewer.Designer.cs` | 松灵客户残留,仅 `.Designer.cs`、无主文件、未被 `csproj` 收录 | **删除** |
| 根 `MultiWheelLifterCar.cs` | 与 `CarTypes/MultiWheelLifterCar.cs` 重名旧文件,**未被 `csproj` 收录**(死文件) | **删除** |
| `CarTypes/UselessCar.cs` | `[CarType]` 已注释、仅反射示例 | 删除(或移测试样例) |
---
## 2. 关键洞察(精读结论 → 对上游设计的修正)★
### 2.1 洞察 A:导航是"轨道/站点字段 + Coder",与车型正交
- `CarTypes/BasicFields.cs:23` `TagValue //磁导航,二维码值,或者rfid 值`(站点字段)。
- 磁:`Kiva.cs:37``CarTypes/MultiWheelLifterCar.cs:34` `track.fields["Magnet"]`/`NaiveMagnet`(轨道字段)。
- 二维码:车型 coder `useVerb="dst.tag>0 && src.tag>0"``agv.QrGo(...)`
- 激光避障:`useVerb="track.LidarArea != -2"``agv.SwitchLidarArea(...)`
- **结论**:同一车型在不同轨道字段下走不同 coder 分支;按车型切 dll 会复制车型或丢能力。
### 2.2 洞察 B:导航逻辑以"特性"内联在车型,且多导航混杂
- `[TemplateTrackCoderSettings]`/`[ProgramTrackCoderSettings(program=typeof(...))]` 编译期硬绑定到车型类型。
- `Kiva` 同时挂磁(prio19)+二维码(prio30)+避障+取放货。
- **结论**:导航 dll 要可插拔地为车型提供 coder,必须把 coder 外置并由内核支持**运行期注册**(§7.1)。
### 2.3 洞察 C:激光"避障" ≠ 激光"导航定位"
- 避障:`LidarArea`/`SwitchLidarArea`/`ChangeAvoidanceDistance`/`FrontLidarDetect`——几乎所有车型都有,通用 → Core。
- 定位:`Model/Map.cs:183 LidarMap`(拉 `127.0.0.1:4321` SLAM 栅格) + `WebApi.cs:1021 /map/getLidarMap` → Laser。
### 2.4 洞察 D:可"抽离为标准功能"的重复代码
| 重复项 | 位置 | 抽离目标 |
|---|---|---|
| 磁循迹器(选路+`MagGo/NaiveMagGo` | `Kiva.cs:32 AllCarMagTrackCoder``CarTypes/MultiWheelLifterCar.cs:29 MagTrackCoder` | 合并为唯一 `MagneticTrackCoder`(Magnetic dll) |
| `newReset/newTrafficReset` | `Kiva.cs:715``Forklift.cs:279``CarTypes/MultiWheelLifterCar.cs:347` | 上提 Core 车型基类 |
| `SetDisplayInfo` | 各车型雷同 | Core 基类默认实现 |
| `Mstsc/EmergencyStop/ResetClumsy`(HTTP:8008) | 多车型重复 | Core 基类 |
| 避障/IO/纠偏 coder 模板 | 各车型重复粘贴 | Core 通用 coder 模板集 |
| `GetCarStatus` | `Commons``MultiWheelForkLifter` 各一份 | 统一 `Commons` |
### 2.5 洞察 E`WebApi.cs` 是老平台 Nancy 接口,整体面临废弃
- `ApiController : NancyModule`(`WebApi.cs:35`)40+ 端点;导航相关仅二维码 `QrMap`(`:38/:40/:2050`)+`QrSite`(`:2807`) 与激光 `getLidarMap`(`:1021`)。
- 其能力在 SimpleLite 已由 **EmbedIO**`/projection/*`(投影快照 + reflection + map-edit + scenes,见 MIGU-API.md)覆盖。
- **处置(本版)**:整理后**暂留 Core**(标 `[Obsolete]`/注释 deprecated);**后期整体废弃**,迁移索引见 §4.7。
### 2.6 洞察 F:设备驱动是第三个正交维度(本次独立 dll + 热插拔)
- 充电桩/门/按钮盒按厂商/型号扩展,与导航、车型都正交。
- 通过既有特性(`ChargeStationType`/`DoorTypeAttribute`/新增 ButtonBoxType)注册,编译为独立 `Devices.*` dll,支持 SimpleLite `/plugins` 热加载/卸载。
### 2.7 洞察 GUI 形态需从 WinForms 迁到 CycleGUI/Webnet8.0 约束)
- SimpleLite = `net8.0`(纯)、UI 用 **CycleGUI**、WebApi 用 **EmbedIO****不依赖 `System.Windows.Forms`**。
- StandardScene 现有窗体:`DeliveryViewer/LoopViewer/TrafficInterlockViewer/VehicleMonitor/各 Charge*Form/Door*/ButtonBox*/TextViewer` + `Commons``MessageBox`
- **统一 net8.0 ⇒ 必须移除 WinForms**:窗体迁 **CycleGUI**SimpleLite 立即模式 UI)或平台 **Web**`MessageBox` 类提示改 CycleGUI 弹窗 / 平台通知。这是本次**重要工作量与风险**。
---
## 3. 目标架构与目录规划
### 3.1 分层依赖图
```mermaid
flowchart TD
SC["SimpleCore (netstandard2.0/net8)<br/>AbstractCar/CarType/Mission/ITrackCoder/Heuristics<br/>+ 导航契约 NavKind/INavigationProfile/ISceneContext<br/>+ (新增)TrackCoder 运行期注册表"]
SL["SimpleLite (net8.0 宿主)<br/>CycleGUI UI · EmbedIO WebApi · /plugins 热插拔 · /scenes 选择性加载"]
CORE["StandardScene.Core (net8.0)<br/>导航无关基座:车型本体+任务/充电/互锁/调度+模型/工具+Commons+通用避障coder<br/>(暂留)WebApi.Core(Nancy, deprecated)"]
MAG["StandardScene.Magnetic"]
QR["StandardScene.QrCode"]
LAS["StandardScene.Laser"]
VDA["StandardScene.Protocol.VDA5050"]
DCH["StandardScene.Devices.Charge<br/>(热插拔)"]
DDR["StandardScene.Devices.Door<br/>(热插拔)"]
DBT["StandardScene.Devices.ButtonBox<br/>(热插拔)"]
SC --> CORE
SC --> SL
CORE --> MAG & QR & LAS & VDA & DCH & DDR & DBT
SL -. 加载/卸载 .-> CORE & MAG & QR & LAS & VDA & DCH & DDR & DBT
```
### 3.2 解决方案目录布局(单工程 → 多工程,统一 net8.0)
```
StandardScene/ 解决方案根
├─ StandardScene.Core/ net8.0,标准基座(alwaysLoad,不单独作为导航场景)
│ ├─ Navigation/ INavigationProfile/NavKind/ISceneContext/Registry
│ ├─ Cars/ 车型基类 StandardCarBase + 各车型本体(无导航 coder、无 WinForms
│ │ └─ Sim/ DummyCar(仿真,AMRScene1 命名空间收敛)
│ ├─ Coders/ 通用 coder(避障/IO/纠偏/取放货模板)
│ ├─ Chained/ Charge/ ChargeStationType(抽象) / InterLock/ Scheduler/
│ ├─ ExtendDevice/ 设备框架(基类 + 注册特性)
│ ├─ Model/ Utils/ TCP/ CommonTools/
│ ├─ Ui/ CycleGUI 面板(替代原 WinForms 窗体)
│ ├─ Commons.cs Heuristic.cs LadderLogic.cs StandardCADTool.cs
│ └─ WebApi.Core.cs 老 Nancy APIdeprecated,暂留,后期删)
├─ StandardScene.Magnetic/ → plugins/ + scene.json
├─ StandardScene.QrCode/ → plugins/ + scene.json
├─ StandardScene.Laser/ → plugins/ + scene.json
├─ StandardScene.Protocol.VDA5050/ → plugins/ + scene.json
├─ StandardScene.Devices.Charge/ → plugins/(热插拔)
├─ StandardScene.Devices.Door/ → plugins/(热插拔)
├─ StandardScene.Devices.ButtonBox/ → plugins/(热插拔)
└─ StandardScene.sln
```
### 3.3 各 dll 职责
| dll | 职责 | 可激活/热插拔 |
|---|---|---|
| `StandardScene.Core` | 导航无关一切:车型本体、任务/充电/互锁/调度、模型/工具、`Commons`/契约、通用避障 coder、(暂留)老 WebApi | 基座(`alwaysLoad` |
| `StandardScene.Magnetic` | 统一 `MagneticTrackCoder` + 磁条字段语义 | 是 |
| `StandardScene.QrCode` | `QrTrackCoder` + `SyncQrMap` + 二维码地图下发 | 是 |
| `StandardScene.Laser` | `LidarMap`/`getLidarMap`SLAM | 是 |
| `StandardScene.Protocol.VDA5050` | VDA5050/MQTT 协议栈 + `VDA5050Car` | 是 |
| `StandardScene.Devices.Charge` | 充电桩 FL/MuXing/PCB 驱动 | **是(热插拔)** |
| `StandardScene.Devices.Door` | 门 Modbus 驱动 | **是(热插拔)** |
| `StandardScene.Devices.ButtonBox` | 按钮盒 Leeg/Azowie 驱动 | **是(热插拔)** |
---
## 4. 功能归属矩阵(文件级 / 类级)★
> 动作:**移动**/**拆分**/**抽离**/**保留**/**删除**/**迁UI**(WinForms→CycleGUI/Web)/**待复核**。
### 4.1 车型(`CarTypes/`
| 现状 | 目标 | 动作 | 说明 |
|---|---|---|---|
| `BasicFields.cs` | Core | 移动 | 车型公共字段 |
| `Kiva.cs::Kiva` | Core(Cars) | 拆分 | 本体留 Core;移除内联磁/Qr coder 特性 |
| `Kiva.cs::AllCarMagTrackCoder` | Magnetic | 抽离 | 并入唯一 `MagneticTrackCoder` |
| `Kiva.cs::KivaCarTrackCoder` | Core(Coders) | 移动 | 转弯,导航无关 |
| `Forklift.cs` | Core(Cars) | 移动 | 通用避障/取放货 |
| `CarTypes/MultiWheelLifterCar.cs::MultiWheelLifterCar` | Core(Cars) | 拆分 | 本体留 Core;移除内联磁/Qr |
| `CarTypes/MultiWheelLifterCar.cs::MagTrackCoder` | Magnetic | 抽离 | 并入 `MagneticTrackCoder`(去重) |
| `MultiWheelForkLifter.cs` | Core(Cars) | 移动 | `GetCarStatus` 并入 `Commons` |
| `MultiVehicleCar.cs` | Core(Cars) | 拆分 | 联动留 CoreQr coder 外移 QrCode |
| `DualLiftingCar.cs` | Core(Cars) | 移动 | 无 coder |
| `ArmCar.cs` | Core(Cars) | 拆分/解耦 | 解除 `VDA5050SiteField` 依赖 |
| `DummyCar.cs` | Core(Cars/Sim) | 移动 | `AMRScene1` 命名空间收敛 |
| `UselessCar.cs` | — | 删除 | 未注册 |
| `VehicleMonitor.cs(.Designer)` | Core(Ui) | **迁UI** | WinForms → CycleGUI |
### 4.2 VDA5050`CarTypes/VDACar/`
| 现状 | 目标 | 动作 |
|---|---|---|
| `VDA5050*` + `MasterMQTTCommunication` | `Protocol.VDA5050` | 移动(整子目录) |
| `TextViewer.cs(.Designer)` | `Protocol.VDA5050`(Ui) | 迁UICycleGUI |
| `VDA5050SiteField`(被 ArmCar 引用) | Core 公共字段 或 VDA dll | 待复核(ArmCar 留 Core 则下沉 Core |
### 4.3 任务族(`Chained/` `InterLock/` `Scheduler/`
| 现状 | 目标 | 动作 |
|---|---|---|
| `Chained/*``InterLock/*``Scheduler/*` 逻辑 | Core | 移动(导航无关) |
| `DeliveryViewer/LoopViewer/TrafficInterlockViewer`(窗体) | Core(Ui) | 迁UICycleGUI/Web |
### 4.4 充电(`Charge/` `ChargeStationType/`
| 现状 | 目标 | 动作 | 说明 |
|---|---|---|---|
| `AbstractChargeLogicMission``StandardChargeMission` | Core | 移动 | 充电逻辑 |
| `ChargeStation*`/`ChargeStrategy*`/`Alarm*`/`Communication*`(服务) | Core | 移动 | 充电管理/服务 |
| 各 `Charge/*Form`(窗体) | Core(Ui) | 迁UI | CycleGUI/Web |
| `ChargeStationType/AbstractChargeStation.cs` | Core | 移动 | 抽象 + 注册契约 |
| `ChargeStationType/{FL,MuXing,PCB}ChargeStation.cs` | **`Devices.Charge`** | 拆分 | 厂商驱动,独立热插拔 dll |
### 4.5 扩展设备(`ExtendDevice/`
| 现状 | 目标 | 动作 | 说明 |
|---|---|---|---|
| `Door/BasicDoorController``DoorMission``DoorModel``DoorTypeAttribute` | Core(框架) | 移动 | 门控框架 + 注册契约 |
| `Door/ModbusDoorController` | **`Devices.Door`** | 拆分 | Modbus 驱动,独立热插拔 |
| `Door/DoorManager/DoorMonitor`(窗体) | Core(Ui)/Devices.Door(Ui) | 迁UI | CycleGUI |
| `ButtonBox/BasicButtonBox``ButtonMission``ButtonBoxModel` | Core(框架) | 移动 | 按钮盒框架 |
| `ButtonBox/LeegButtonBox`(`leegKeys-sdk`)、`AzowieButtonBox` | **`Devices.ButtonBox`** | 拆分 | 厂商驱动,`leegKeys-sdk` 随驱动走 |
| `ButtonBox/ButtonBoxManager`(窗体) | 对应 dll(Ui) | 迁UI | CycleGUI |
### 4.6 公共 / 契约 / 模型 / 基础设施
| 现状 | 目标 | 动作 | 说明 |
|---|---|---|---|
| `Commons.cs::Commons` | Core | 移动 | 解除对 `DummyCar` 硬引用(`GetVehicleStatus` |
| `Commons.cs::CustomOperationsBeforeLoading` | 各可激活 dll 各一份 | 复制/分发 | `MessageBox` 死锁提醒改 CycleGUI 弹窗 |
| `Commons.cs::NoReflectionApi`/`ReflectionApiWithParameter` | SimpleCore 或 Core | 待复核 | 多 dll 共用建议下沉 SimpleCore |
| `Heuristic.cs` | Core | 移动 | 启发式(宿主反射注册) |
| `LadderLogic.cs` | Core | 移动 | 防抖/梯形逻辑 |
| `StandardCADTool.cs`(多数 `CADTool`) | Core | 移动 | 地图编辑工具 |
| `StandardCADTool.cs::SyncQrMap` | QrCode | 拆分 | 二维码地图下发 |
| `Model/Map.cs::Map/ArcHelper` | Core | 移动 | 地图序列化 |
| `Model/Map.cs::LidarMap` | Laser | 拆分 | SLAM 栅格图 |
| `Model/*`(其余) | Core | 移动 | 数据模型 |
| `Utils/*``TCP/*``CommonTools/*` | Core | 移动 | 基础设施 |
| `FactoryTest.cs` | — | **删除** | 评审确认 |
| 根 `MultiWheelLifterCar.cs`、根 `SongLingDeliveryViewer.Designer.cs` | — | **删除** | 死/残留 |
### 4.7 老 WebApi`WebApi.cs`)整理与迁移索引
**本版处置**:通用端点整理为 `WebApi.Core.cs` 暂留 Core 并标 deprecated;二维码/激光端点随导航 dll;**后期整体删除**,前端改用 SimpleLite 接口。下表为废弃迁移索引(老 Nancy → SimpleLite EmbedIO,依据 MIGU-API.md):
| 老端点(`WebApi.cs` | 行号 | SimpleLite 替代(`/api/sl/projection/...` |
|---|---|---|
| `car_reflection/mission_reflection get_type/methods/fields/execute` | 191537 | `reflection/methods/*``/fields/*``/execute/*``/bundle/*` |
| `car/getAllCars``api/agv/list` | 1047、2095 | `projection/cars` |
| `task/getTask` | 1099 | `projection/missions` |
| `map/getMap` | 995 | `projection/sites`+`/tracks`、或 `map-edit` |
| `car/goSite` | 1070 | `reflection/car/{id}/goto-site` |
| `car/reset/repair/blown/ForceStop/ReStart/...` | 767+ | `reflection/execute/{kind}/{id}/{method}``[MethodMember]` |
| `car/createTask/carTask/cancelTask` | 542/597/900 | `reflection/execute` + `projection/deliveries/*` |
| `set{Charging,Envelope,TrafficControl,PlanRules}Setting` | 1680+ | `reflection/fields/{kind}/{id}/{field}`(写字段) |
| `api/CreateSite` | 2055 | `map-edit/objects/site` |
| `api/QrMap`(二维码) | 2050 | 暂随 QrCode dllSimpleLite 侧待新增(无直接对应) |
| `map/getLidarMap`(激光) | 1021 | 暂随 Laser dllSimpleLite 侧待新增(无直接对应) |
> 注:二维码地图下发、激光 SLAM 取图在 SimpleLite 现有 MIGU-API 中**无直接对应**,迁移时需在 SimpleLite/场景插件侧补接口,故这两块随导航 dll 先行保留。
---
## 5. "抽离标准功能"清单(去重与沉淀)
1. **统一磁导航循迹器**`AllCarMagTrackCoder` + `MagTrackCoder` → 唯一 `StandardScene.Magnetic.MagneticTrackCoder`(参数化 fields)。
2. **车型基类 `StandardCarBase`(Core)**:上提 `newReset/newTrafficReset``SetDisplayInfo``Mstsc``EmergencyStop/Release``ResetClumsy``GetCarStatus` 默认实现。
3. **通用 Coder 模板集(Core/Coders)**:沉淀避障/IO/纠偏/避障尺寸等重复 `[TemplateTrackCoderSettings]`
4. **导航能力抽象 `INavigationProfile`**:每个导航 dll 实现一个 Profile(声明 `NavKind` + 提供的 coder/字段语义),Core/宿主反射收集。
5. **`Commons` 去重**:合并 `GetCarStatus``GetVehicleStatus` 去除对 `DummyCar` 的硬编码(改标记接口)。
6. **设备注册标准化**:充电桩/门/按钮盒统一特性注册,驱动 dll 即插即用 + 热插拔。
7. **UI 标准化**:原 WinForms 窗体统一迁 CycleGUI 面板(沉淀少量通用面板基类)。
---
## 6. 命名空间与依赖治理
- **宿主/命名空间迁移**`SimpleComposer.RCS`/`SimpleComposer.UI``SimpleLite.RCS`/对应(以内核改造为准,另一会话进行中);引用 `SimpleComposer.exe``SimpleLite` 程序集 + `SimpleCore` 契约。
- **命名空间收敛**`DummyCar``AMRScene1``StandardScene.Core.Cars.Sim`
- **去 WinForms**:移除 `System.Windows.Forms` 引用,窗体迁 CycleGUI/Web`MessageBox` 改 CycleGUI 弹窗。
- **反向依赖治理**`Commons.GetVehicleStatus``DummyCar``ArmCar``VDA5050SiteField`;二维码相关集中 QrCode dll,Core 仅留中立扩展点。
- **三方依赖随归属**`MQTTnet`→VDA5050`leegKeys-sdk`→ButtonBox 驱动;`EasyModbusTCP/IoTClient`→Door/Charge 驱动或 Core 工具;`Nancy`→(暂留的 WebApi.Core,验证 net8 兼容或对齐 SimpleLite 的 Nancy 2.0);`Jint`→含 DummyCar 仿真的 Core。
---
## 7. 关键技术难点与对策
### 7.1 难点①:TrackCoder 由"编译期特性"改为"运行期注册"
- 现状:coder 通过特性硬绑定车型类型。
- 目标:导航 dll 不改 Core 车型源码即可为车型补 coder。
- 对策:① **内核扩展(推荐)**——`SimpleCore` 增 coder 注册表,导航 dll 在 `INavigationProfile.OnActivate` 注册/卸载时移除(契合 `/plugins` 热插拔);② 车型留占位、导航 dll 实现委派;③ **兜底**——Core 内置全量 coder,导航 dll 只承载 API/地图/字段/清单。
- **行动项**:与正在改内核引用的会话同步,确认是否纳入注册表 API。
### 7.2 难点②:net4.8 → net8.0 迁移(换宿主 + 去 WinForms
- 宿主:引用 `SimpleComposer.exe`(net4.8) → `SimpleLite`/`SimpleCore`(net8)`SimpleComposer` 独有类型在内核侧补齐/下沉(与另一会话协作)。
- **去 WinForms(重点)**:所有窗体迁 CycleGUISimpleLite 立即模式 UI)或平台 Web;`MessageBox`→CycleGUI 弹窗/平台通知。工作量集中在 `Charge/*Form``Delivery/Loop/TrafficInterlock Viewer``VehicleMonitor``Door/ButtonBox` 管理窗体、`VDACar/TextViewer`
- 三方库 net8 验证:`Nancy(1.4.5)`(暂留 WebApi,验证或升 2.0)、`MQTTnet/EasyModbusTCP/IoTClient/Jint/Newtonsoft/DocumentFormat.OpenXml`
- Fody/`LessokajiWeaver`:与内核一致(注意上游 §13.2 环境约束)。
### 7.3 难点③:插件钩子与多 dll 初始化
- `CustomOperationsBeforeLoading.Set()` 宿主按 dll 反射调用:拆分后每个可激活 dll 各保留一份;用 `INavigationProfile.OnActivate` 做导航 dll 自初始化,避免重复注册全局回调。
### 7.4 难点④:设备/导航 dll 热插拔(collectible 卸载)
- 配合 SimpleLite `/plugins/reload``/plugins/{name}/unload``/scenes/apply`dll 须可被 collectible ALC 干净卸载(卸载前无存活 `Car/Mission` 实例),`OnDeactivate` 清理注册的 coder/设备/回调/MQTT 连接/TCP 客户端。
### 7.5 难点⑤:老 WebApi 暂留与最终下线
- `WebApi.Core`(Nancy) 暂留 Core 仅为过渡;新功能一律走 SimpleLite EmbedIO。按 §4.7 索引逐步迁移,迁完即删;二维码/激光取图需先在 SimpleLite/场景插件侧补接口。
---
## 8. 分阶段实施计划(务实路线)
> 原则:每阶段都能编译、能加载、可回退。
### C0 · 准备与对齐(低风险,先做)
- 删除:`FactoryTest.cs`、根 `SongLingDeliveryViewer.Designer.cs`、根 `MultiWheelLifterCar.cs``UselessCar`
- 去重沉淀(§5):统一 `MagTrackCoder``GetCarStatus`;提取 `StandardCarBase`
- 命名空间收敛 `AMRScene1`→Core。
- 与内核会话对齐:`SimpleLite`/`SimpleCore` 引用与命名空间、coder 注册表(§7.1)、导航契约(上游 Phase A 已就绪)。
- 验收:编译通过、行为不变。
### C1 · Core 基座 net8.0 化(换宿主 + 去 WinForms 闭环)
- 新建 `StandardScene.Core`(net8.0),迁入导航无关全部内容;引用切到 `SimpleLite`/`SimpleCore`
- **去 WinForms**:窗体迁 CycleGUI`MessageBox`→CycleGUI。
- 整理老 `WebApi``WebApi.Core`(deprecated, 暂留)。
- coder 先用兜底(§7.1 方案③)保证不回归。
- 验收:`StandardScene.Core.dll` 被 SimpleLite 加载、跑通搬运/充电/交管。
### C2 · 抽离导航能力 dll
- `Magnetic`(唯一 `MagneticTrackCoder`/ `QrCode``QrTrackCoder`+`SyncQrMap`+`QrMap`/ `Laser``LidarMap`+`getLidarMap`),各带 `scene.json`
- 按 §7.1 选定方案把 coder 接到车型。
- 验收:仅激活某导航时对应能力可用,其余不加载。
### C3 · 设备驱动 dll 化(必做,热插拔)
- `Devices.Charge`(FL/MuXing/PCB)、`Devices.Door`(Modbus)、`Devices.ButtonBox`(Leeg/Azowie)`leegKeys-sdk` 随驱动。
- 验证 `/plugins` reload/unload 热插拔;`OnDeactivate` 清理连接。
- 验收:设备驱动可独立热加载/卸载。
### C4 · VDA5050 协议 dll
- `Protocol.VDA5050`(整 `VDACar/` 迁出,含 MQTT);解 `ArmCar↔VDA5050SiteField`
- 验收:激活后 VDA5050 车可用、可卸载。
### C5 · 联调、WebApi 迁移与文档
- 三导航 + 设备 + 协议组合联调;对接 `/scenes/apply` + `active-scenes.json`
- 按 §4.7 把可迁的 WebApi 端点切到 SimpleLite,逐步删 `WebApi.Core`
- 更新本文件 §11 进度 + `scene.json` 实际清单 + `README/DEVELOPMENT_GUIDE`
---
## 9. 插件清单契约(`scene.json`,对接上游 §5.5 与 SimpleLite `/scenes`
```json
{
"id": "scene.magnetic",
"displayName": "磁导航场景",
"navKind": "Magnetic",
"assembly": "StandardScene.Magnetic.dll",
"requiresCore": "StandardScene.Core.dll",
"provides": { "trackCoders": ["MagneticTrackCoder"], "carTypeBindings": ["*"] }
}
```
- 平台「配置向导」→ `WizardController` 写配置 → 调 SimpleLite `POST /api/sl/projection/scenes/apply`(写 `active-scenes.json` + 增量 reload)。
- 数据契约:`data/config-deployment.json`(平台)/ `plugins/active-scenes.json`(内核)/ `plugins/<dll>.scene.json`(插件)。
---
## 10. 风险、待确认与验收口径
### 已确认(v2
- 宿主 = SimpleLite;老 SimpleComposer 废弃。
- 目标框架 = `net8.0`(去 WinFormsUI 迁 CycleGUI/Web)。
- 设备驱动 = 独立 dll + 热插拔。
- `FactoryTest`、松灵残留 = 删除。
- `WebApi.cs` = 整理后暂留 Core、后期废弃迁 SimpleLite。
### 待确认
1. **coder 注册表**:内核是否纳入"运行期把 TrackCoder 注册到车型"的 API(否则 C2 用兜底方案③)。
2. **UI 迁移范围与节奏**:窗体一次性迁 CycleGUI,还是按 dll 分批;是否部分仅保留平台 Web 入口。
3. **VDA5050** 独立 dll 确认(推荐独立)。
4. **二维码地图下发 / 激光 SLAM 取图**SimpleLite 侧补接口的归属与时间点。
5. **车型↔导航映射**:是否存在车型仅支持单一导航的硬约束。
### 验收口径(每阶段通用)
- 编译 0 错误;搬运/环线/充电/交管不回归。
- 选择性加载 + 热插拔:激活集合内能力可用,未激活不加载、可干净卸载。
- 反射 APISimpleLite `/reflection`)、车型/面板随已激活 dll 自然收敛。
---
## 11. 实施进度记忆(跨会话持续更新)
> 状态:⬜ 未开始 / 🟦 进行中 / ✅ 完成 / ⏸ 暂缓。
> **最近更新:会话1(续3,2026-06-09)——设备三 dll 合并为单一 `StandardScene.Devices` + `ChangeAvoidanceParam` 去重(方案 B),全解 `dotnet build` 0 错误。**
### 11.0 当前代码现状(重要:已领先早期文档)
会话1 接手时发现**代码已被先前会话推进**,与 v2 文档/会话14交接摘要描述的「net4.8 单体、未动代码」**不一致**。以 `StandardScene.csproj` 实际为准:
| 项 | 早期文档记述 | **当前实际** |
|---|---|---|
| 目标框架 | net4.8 | **`net8.0-windows`**(仍 `UseWindowsForms=true`,过渡态,**尚未**去 WinForms 到纯 net8.0 |
| 宿主/契约引用 | SimpleComposer.exe + RefSimpleCore | **已切 `SimpleLite.dll` + `SimpleCore.dll`(netstandard2.0)** |
| Nancy | 1.4.5 | **已对齐 `2.0.0`** |
| 死代码 | 在仓库 | csproj 已 `Compile Remove`,会话1 已物理删除 |
| 命名空间 | SimpleComposer.RCS | 车型 using 已是 `SimpleLite.RCS`(内核改造会话已推进) |
**可编译基线**`dotnet build`net8.0-windowsdotnet SDK 10.0.300= **0 错误 / 47 警告**(均无害:重复 using、隐藏成员 CS0108、未用变量、`Thread.Abort` SYSLIB0006、CS4014 未 await 等)。
### 11.1 步骤状态
| 步骤 | 内容 | 状态 | 说明 |
|---|---|---|---|
| P0 | 精读现状 + 产出拆分计划 v1 | ✅ | 逐文件精读 |
| P0.1 | 按评审反馈更新 v2 | ✅ | |
| **路线决策** | 评审定 **路线乙**:先「纯搬运不改逻辑」的结构拆分,去重在新结构里从容做 | ✅ | 会话1 用户拍板 |
| **C0-a** | 物理删除死代码 + 清理 csproj | ✅ | 见 §11.3 |
| **C0-b** | 抽离 **4 个通用(导航无关) coder** 并在 4 车型切换 | ✅ | 见 §11.3;编译 0 错误 |
| C0-c | 合并两份磁循迹器为唯一 `MagneticTrackCoder` | ✅ | 用户定:采用含 `track.Speed``NaiveMagGo`;MWL 旧类已删、引用改向;编译 0 错误 |
| C0-d | `ChangeAvoidanceParam` 去重 | ✅ | 方案 B:4 参 flag 版 `AvoidanceParamCoder`(MWL+MultiVehicle) + 2 参 L,W 版 `AvoidanceParamLWCoder`(Kiva+Forklift);见 §11.4-2 |
| C0-e | 提取 `StandardCarBase`newReset/Mstsc/GetCarStatus 等上提)| ⬜ | 未启动 |
| C0-f | `AMRScene1` 命名空间收敛 | ⬜ | 建议并入 C1 建 Core 工程时一起做 |
| 路线乙·1 | 工程迁入 `StandardScene.Core/` + 重写 sln(纯搬运) | ✅ | 编译 0 错;AssemblyName 仍=`StandardScene`,对外 dll 名不变 |
| C1 | 去 WinForms + WebApi.Core 整理 | ⏸ | 用户暂缓(窗体后续迁 migu 平台);过渡期各工程暂 `net8.0-windows` |
| C2 | Magnetic/QrCode/Laser 三导航 dll | ⏸ | 受阻于内核 coder 注册表(§11.4-4) + 用户将自行重构 coder |
| **C3** | 设备驱动 dll 化(Charge/Door/ButtonBox,热插拔) | ✅ | 见 §11.3-E3;**合为单一 `StandardScene.Devices`**(用户定,不分多个),编译 0 错、scene.json 产出 |
| **C4** | VDA5050 协议 dll | ✅ | 见 §11.3-E2`VDACar/` 整迁出、`VDA5050SiteField` 下沉 Core |
| C5 | 联调 + WebApi 迁移 SimpleLite + 文档收尾 | ⬜ | 含卫星 dll→宿主 `plugins/` 部署 + 真机联调(见 §11.4-5) |
### 11.2 关键决策记录
- **路线乙(会话1 确认)**:结构拆分优先、纯搬运不改逻辑;去重沉淀在新结构内做。理由:本环境无法跑真实 AGV 回归,纯搬运行为风险低、编译即可保障。
- 导航与车型正交;导航 coder 外置为可插拔(运行期注册,内核配合;**当前用兜底=Core/单体内置 coder**,用户要求"先抽离合适的通用 coder")。
- 激光避障(LidarArea)留 Core,仅 SLAM 地图(LidarMap/getLidarMap)进 Laser。
- 设备驱动单列 `Devices.*` 独立 dll + 热插拔(评审确认)。
- 宿主 SimpleComposer→SimpleLite;框架统一 net8.0UI 去 WinForms 迁 CycleGUI/Web(评审确认)。
- `FactoryTest`、松灵残留删除(评审确认,会话1 已执行)。
-`WebApi.cs`(Nancy) 暂留 Core、标 deprecated,后期迁 SimpleLite EmbedIO,迁移索引见 §4.7(评审确认)。
### 11.3 会话1 已落地改动明细(2026-06-09
**A. C0 死代码物理删除**(编译范围不变,0 错误)
-`FactoryTest.cs``[MissionType]` 展会演示进程,反射注册,无其它引用)
- 删 根 `MultiWheelLifterCar.cs`(与 `CarTypes/MultiWheelLifterCar.cs` 重名死文件)
- 删 根 `SongLingDeliveryViewer.Designer.cs`(松灵残留,无主文件)
-`CarTypes/UselessCar.cs``[CarType]` 已注释、未注册)
- `csproj`:移除上述 3 条 `Compile Remove`**保留** `Chained\AbstractChainedDeliveryMission.cs` 的 Remove(孤立未编译旧版本,去留待后续核对)
**B. 抽离通用(导航无关) coder** —— 新增 `Coders/CommonTrackCoders.cs`(命名空间 `StandardScene.Coders`
- 基类 `CommonTemplateTrackCoder : ITrackCoder`:内部复用内核 `ProgramCoderHelper.PrepareTrackEngine` + Topaz 求值,`useVerb`/`templateString` 与原模板逐字一致;统一用 `BasicXxxFields`(这些 coder 仅引用 Basic 字段,等价)。priority 由车型 `[ProgramTrackCoderSettings]` 指定。
- 4 个通用 coder
- `LidarAreaSwitchCoder`(避障,`track.LidarArea != -2``SwitchLidarArea`
- `AvoidanceDistanceCoder`(无 useVerb → `ChangeAvoidanceDistance(StopDistance,SlowDistance)`
- `IoAreaSwitchCoder``track.IOArea != -1``SwitchIoArea`
- `TrackingErrThreshCoder``BiasAlarmThresh>0||DthAlarmThresh>0``ChangeTrackingErrThresh`
- 4 车型已把对应内联 `[TemplateTrackCoderSettings]` 删除,改为 `[ProgramTrackCoderSettings]` 引用(保留各自原 priority):
| 车型 | LidarArea | AvoidanceDistance | IoArea | TrackingErrThresh |
|---|---|---|---|---|
| `Kiva` | 17 | 20 | 20 | 20 |
| `MultiWheelLifterCar` | 27 | 20 | 20 | 20 |
| `MultiVehicleCar` | 27 | 20 | 20 | 20 |
| `Forklift` | 20 | —(原无) | 20 | 20 |
- 各车型加 `using StandardScene.Coders;`
- **验证**:每步 `dotnet build` 均 0 错误(Kiva 单切先验范式,再批量推 MWL/MultiVehicle/Forklift)。
**C. 仍保留为内联 Template / 程序 coder(本次未动)**
- 磁程序 coder`Kiva.AllCarMagTrackCoder``MultiWheelLifterCar.MagTrackCoder`(待 C0-c 合并)
- `ChangeAvoidanceParam`(三变体,待 C0-d
- `Kiva.KivaCarTrackCoder`(转弯)、`CalibrateWheelEncoder`(仅 Kiva)
- 二维码 `QrGo`(导航专用,归 QrCode dll);各车型取放货/钻车/夹抱/TireFollowing 等(车型专用)
**D. 会话1(续)去重落地**(编译 0 错误 / 0 lint
- **磁循迹器合并(C0-c**`Kiva.AllCarMagTrackCoder` 重命名为唯一 `MagneticTrackCoder`(含 `${track.Speed}``NaiveMagGo`,用户确认);`MultiWheelLifterCar.MagTrackCoder` 整类删除,其 `[ProgramTrackCoderSettings(priority=19)]` 改向 `MagneticTrackCoder`。(暂置 `Kiva.cs``StandardScene.CarTypes` 命名空间,C2 抽 Magnetic dll 时物理迁出。)
- **避障 4 参对合并(C0-d 部分)**:新增 `Coders.AvoidanceParamCoder``CommonTemplateTrackCoder` 子类,覆写站点字段袋为含 `ChangeAvoidanceParam` 标志位的 `AvoidanceParamSiteFields`),useVerb/模板与原 `MWL`/`MultiVehicle` 逐字一致;两车型删内联 4 参模板、改 `[ProgramTrackCoderSettings(priority=20)]` 引用。基类新增可覆写 `SiteFieldsType` 钩子(默认 `BasicSiteFields`)。
**E. 会话1(续2)结构性拆分落地**(路线乙·结构拆分;全解决方案 `dotnet build` 0 错误 / 45 无害警告)
*E1. 工程迁入 `StandardScene.Core/`(纯搬运)*
- 原单体整体迁入子目录 `StandardScene.Core/``StandardScene.csproj``StandardScene.Core.csproj`**AssemblyName 仍=`StandardScene`**,对外 dll 名不变);重写 `StandardScene.sln`
- 新增 `Properties/InternalsVisibleTo.cs`:对 4 卫星程序集开放 internal(`Protocol.VDA5050`/`Devices.Charge`/`Devices.Door`/`Devices.ButtonBox`),使卫星以"纯搬运"访问 Core internal,免去大量 internal→public 侵入式改动。
*E2. `StandardScene.Protocol.VDA5050`C4*
- `CarTypes/VDACar/`10 文件)整迁至新工程;`scene.json`(provides VDA5050Car)csproj 引 Core(ProjectReference)+SimpleLite/SimpleCore/CommonUsage/Topaz/Weaver + MQTTnet/Nancy/Jint/Newtonsoft。
- 解耦:`VDA5050SiteField``VDACar/VDA5050Car.cs` **下沉**至 Core `CarTypes/ArmCar.cs``ArmCar` 留 Core 且引用它,不能反依赖 VDA dll)。
*E3. `StandardScene.Devices`C3,用户选 A**门/充电桩/按钮盒合并为单一 dll**,用户定不分多个;内部按 `Door/``Charge/``ButtonBox/` 子目录组织,Core 的 IVT 仅 `StandardScene.Devices` 一项)*
- 搬出 6 具体驱动(**命名空间不变**,仍 `StandardScene.ExtendDevice.*` / `StandardScene.ChargeStationType`,以满足发现谓词 `t.Namespace==基类.Namespace`):
- Door`ModbusDoorController`(基类 `BasicDoorController`+`[DoorType]` 留 Core
- Charge`FL/PCB/MuXingChargeStation`(基类 `AbstractChargeStation`+`ChargeUdpService`/`StandardChargeMission` 留 Core
- ButtonBox`Leeg/AzowieButtonBox`(基类 `BasicButtonBox`+`ButtonMission`/`ButtonBoxManager` 留 Core;该 dll 额外引 `Ref/leegKeys-sdk.dll`
- **行为保持解耦(2 处下转型→基类虚方法)**:
- `ChargeUdpService``if(cs is PCBChargeStation s) s.IndexReceive=msg[1]``cs.OnUdpMessage(msg)``AbstractChargeStation` 空实现、`PCB` override)。
- `ButtonMission``if(bb is AzowieButtonBox a) a.ClearButtonRegister(i)``bb.ClearButtonRegister(i)``BasicButtonBox` 空实现、`Azowie` 改 override)。
- **类型发现改跨程序集(5 处,关键)**:原 Core 限定扫描(`Type.GetType(简单名)` / `typeof(基类).Assembly.GetTypes()` / `Assembly.GetExecutingAssembly().GetTypes()`)只在 Core 内找类型,驱动外移后**运行期将找不到**。统一改用内核公开的 `SimpleLite.Utils.UiTypeDiscovery.AllTypes()``AppDomain.GetAssemblies()` 全域 + 内置 `ReflectionTypeLoadException` 安全枚举),谓词不变 → 今天在 Core 内仍找到原类型(**行为等价**)、外移后亦能发现。涉及 `StandardChargeMission` / `DoorMission` / `DoorManager` / `ButtonMission` / `ButtonBoxManager`
- 关键依据:SimpleLite 自身类型发现即 `UiTypeDiscovery`(AppDomain 全域) + `PluginManager`(从 `./plugins/*.dll` 以 collectible ALC 热加载)→**卫星 dll 路线运行期成立**(同时回证 VDA5050Car 可被发现)。
### 11.4 待确认 / 阻塞项(影响 C0 收尾与 C2)
1. ~~**磁 `NaiveMagGo` 参数差异**~~ **【会话1 已决】**:用户定采用含 `${track.Speed}` 的版本(Kiva 版)。已合并为唯一 `MagneticTrackCoder``MultiWheelLifterCar.MagTrackCoder` 删除并改向引用。编译 0 错误。(普通 `MagGo` 两份原本一致;合并后磁逻辑全平台统一。)
2. ~~**`ChangeAvoidanceParam` 统一方式**~~ **【会话1续3 已决:方案 B】**:用户选 B。落地=4 参 flag 版 `AvoidanceParamCoder`(MWL+MultiVehicle) + 新增 2 参 L,W 版 `AvoidanceParamLWCoder`(Kiva+Forklift,触发统一取 `dst.CarLength!=-1 && dst.CarWidth!=-1`);两车型内联 `[TemplateTrackCoderSettings]``[ProgramTrackCoderSettings(priority=20)]` 引用。Forklift 行为不变;Kiva 原无条件→统一后未配置长宽(-1)时不再下发 `(-1,-1)`(预期内安全收敛)。编译 0 错。原分析留档如下:
- 现状:`Kiva`=无条件/2 参;`Forklift`=`L≠-1&&W≠-1`/2 参;`MWL`/`MultiVehicle`=`ChangeAvoidanceParam==true`/4 参(含中心点)。
- 已落地(零行为变更):把**逐字相同**的 `MWL`+`MultiVehicle` 4 参对合并为 `Coders.AvoidanceParamCoder`
- 关键事实:`CarLength/CarWidth/CarCenterX/CarCenterY` 均已在 `BasicSiteFields` 定义(中心点默认 0),故 4 参模板对各车型都可解析编译;差异只剩「触发条件 + 参数个数」。
- 选项(已在会话中给用户):**A**=真·单 coder 分支(flag→4 参 / 否则 L,W 有效→2 参;副作用:Kiva 未配置站点不再发 `(-1,-1)` + MWL 设了 L,W 但 flag=false 会新发 2 参);**B**=两 coder4 参 flag 版+2 参 L,W 版;仅 Kiva `(-1,-1)` 行为差,无 MWL 污染);**C**=只留已合并的同构对,Kiva/Forklift 维持内联(零变更)。**建议 B**(避障涉安全,最小行为差)。
3. ~~**去 WinForms 迁移策略**~~ **【会话1 暂定】**:用户"暂不/去掉",本阶段不动窗体,留待 C1 专门处理。
4. ~~**内核 coder 运行期注册表**~~ **【会话1 已核查】**:当前内核**无插件化 coder 注册表**。`SegmentPlan.SetCoders()` 仅硬接 `TemplateCoderSet`+`ProgramCoderSet` 两套,均按**车型类特性**(`[*TrackCoderSettings]`)反射、在**同程序集**内 `Activator.CreateInstance`(无参构造)实例化——无外部 dll 扫描/动态注册 API。
- 推论①:本次抽出的 `StandardScene.Coders.*` 程序 coder **能被正确加载**(车型特性 `typeof()` 引用 + 无参构造,命名空间无关),故 C0-b/c/d 成立。
- 推论②:**C2「导航 coder 拆独立可热插拔 dll」受阻**——需内核侧(另一会话)补「运行期 coder 注册 / 插件程序集扫描」插桩;在此之前**继续兜底=coder 留在 Core 同程序集**、由车型特性引用。
5. ~~**设备驱动外移后运行期发现**~~ **【会话1续2 已解】**StandardScene 自有的设备类型发现原为 **Core 限定扫描**(与内核 `UiTypeDiscovery` 全域发现不一致),直接外移会断。已把 5 处统一改 `UiTypeDiscovery.AllTypes()`(行为等价、跨程序集)。**遗留验证项(运行期,归 C5)**:卫星 dll 须被 SimpleLite 实际加载入 AppDomain`PluginManager``./plugins/` 加载)发现方生效——需将 `StandardScene.Devices.*.dll`+`scene.json` 部署到宿主 `plugins/` 并做一次真机/集成联调。与 coder(§11.4-4) 不同:设备发现码在**本仓库**、可自主修改,故 C3 不受内核阻塞。
### 11.5 下一会话推荐起手式
1. 读 §11.0(代码现状)+ §11.3(已落地,含 **E. 结构拆分**+ §11.4(待确认/已解)。
2. **结构拆分现状**`StandardScene.Core` + 卫星 `Protocol.VDA5050``Devices.{Door,Charge,ButtonBox}` 已建并全解 0 错;导航 dll(C2) 待用户重构 coder + 内核注册表后再动。
3. 待用户就 §11.4-2 拍板:`ChangeAvoidanceParam` 收尾(建议 B)。coder 整体重构由用户主导。
4. C5 验证项:把 `StandardScene.Devices.*.dll`+`scene.json` 部署到宿主 `plugins/`,真机/集成验证设备与 VDA 车型的**运行期发现**(§11.4-5)。
5. 校验手段:每步 `dotnet build StandardScene.sln`net8.0-windows)须保持 0 错误;真机回归留待有环境时。
### 11.6 变更日志
| 时间 | 步骤 | 改动 | 备注 |
|---|---|---|---|
| 初始 | P0 | 新增 `StandardScene拆分计划.md`(v1) | 现状精读 + 归属矩阵 + 抽离清单 + 技术难点 + 分阶段 |
| v2 | P0.1 | 全文更新 | 宿主 SimpleLite、框架 net8.0、去 WinForms、设备独立 dll、删除项、WebApi 暂留+迁移索引 |
| 会话1 | C0-a | 物理删除 4 个死代码文件 + 清理 csproj | 编译 0 错误 |
| 会话1 | C0-b | 新增 `Coders/CommonTrackCoders.cs`4 通用 coder);Kiva/MWL/MultiVehicle/Forklift 切换为 ProgramTrackCoder 引用 | 编译 0 错误;NaiveMag/AvoidanceParam 留待确认 |
| 会话1续 | C0-c | 磁循迹器去重:`AllCarMagTrackCoder`→唯一 `MagneticTrackCoder`(含 `track.Speed`);删 `MagTrackCoder` 改向引用 | 用户确认 track.Speed 版;编译 0 错误 |
| 会话1续 | C0-d | 避障同构对去重:`MWL`+`MultiVehicle` 4 参模板→`AvoidanceParamCoder`;基类加 `SiteFieldsType` 钩子 | 零行为变更;Kiva/Forklift 并法待定(§11.4-2) |
| 会话1续 | 核查 | 内核 coder 注册机制核查:无插件注册表,仅车型特性+同程序集反射 | 见 §11.4-4;C2 需内核插桩 |
| 会话1续2 | 路线乙·1 | 工程迁入 `StandardScene.Core/``.csproj``StandardScene.Core.csproj`、重写 `.sln`、加 `Properties/InternalsVisibleTo.cs`(4 卫星) | 纯搬运;编译 0 错 |
| 会话1续2 | C4 | 拆 `Protocol.VDA5050``CarTypes/VDACar/`(10 文件)整迁出;`VDA5050SiteField` 下沉 `ArmCar.cs` 解耦;scene.json | 编译 0 错 |
| 会话1续2 | C3 | 拆 `Devices.{Door,Charge,ButtonBox}`6 驱动外移;2 处 `is 具体类`→基类虚方法;5 处类型发现→`UiTypeDiscovery.AllTypes()` 跨程序集 | 行为等价;全解编译 0 错、3 dll+scene.json 产出 |
| 会话1续3 | C3 | 三 `Devices.*` **合并为单一 `StandardScene.Devices`**(内部 Door/Charge/ButtonBox 子目录;IVT 3→1;删旧 3 工程、重写 sln) | 用户定"不分多个";编译 0 错、单 dll+scene.json 产出 |
| 会话1续3 | C0-d | `ChangeAvoidanceParam` 去重收尾(方案 B):新增 2 参 `AvoidanceParamLWCoder`Kiva/Forklift 内联模板→`[ProgramTrackCoderSettings(priority=20)]` 引用 | 编译 0 错;Forklift 不变、Kiva `(-1,-1)` 安全收敛 |
---
*附:本计划引用的类/文件/行号均来自当前工程 `E:\Work\Core\Simple-FR\StandardSence` 实际代码走查;SimpleLite 接口依据 `E:\Work\Core\Simple-FR\Simple\SimpleLite\Docs\MIGU-API.md` 与 `SimpleLite.csproj`net8.0 / CycleGUI / EmbedIO)。*