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:
@@ -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.dll(scene.mag)
|
||||
├── StandardScene.QrLidar → 输出 StandardScene.QrLidar.dll(scene.qrlidar)
|
||||
├── StandardScene.Devices → 输出 StandardScene.Devices.dll(scene.device)
|
||||
└── StandardScene.Protocol.VDA5050 → 输出 StandardScene.Protocol.VDA5050.dll(scene.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` | 否 | VDA5050(MQTT)协议栈与标准车型 |
|
||||
|
||||
---
|
||||
|
||||
## 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 / SegmentPlan(SimpleCore)
|
||||
↓
|
||||
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 NuGet(Core 最重)
|
||||
|
||||
`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-class(WebApi、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
|
||||
→ TransportMission(ChainedDeliveryMission.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 专题手册 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:scene.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 启动即可生效** 的全局初始化入口之一。
|
||||
@@ -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
@@ -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 是否已启动
|
||||
|
||||
### 任务不执行
|
||||
|
||||
优先检查:
|
||||
|
||||
- 车辆是否在线
|
||||
- 路径是否可达
|
||||
- 是否被互锁、流控或门控拦截
|
||||
- 是否已有标签将车辆标记为忙碌或充电中
|
||||
@@ -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`,体验区域流控
|
||||
@@ -0,0 +1,412 @@
|
||||
# AbstractLoopMission 抽象环线任务基类
|
||||
|
||||
## 概述
|
||||
|
||||
`AbstractLoopMission` 是环线任务的抽象基类,提供了完整的循环任务调度框架。它支持多种启动类型、任务类别、流量控制和优先级调度,是所有具体环线任务实现的基础。
|
||||
|
||||
## 核心特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
|
||||
|------|------|
|
||||
| 多启动类型 | AutoLoop(自动循环)、Api、Plc、ButtonBox、Charge |
|
||||
| 多任务类别 | Loop(普通循环)、BranchPoint(分流点)、JoinPoint(汇合点) |
|
||||
| 流量控制 | 限制目标站点的最大车辆数 |
|
||||
| 优先级调度 | 高优先级任务优先处理 |
|
||||
| 配置热更新 | tasklist.json 文件变更自动刷新 |
|
||||
| 路径缓存 | 避免重复计算路径,提升性能 |
|
||||
| 条件触发 | 子类可重写事件回调,自定义触发条件 |
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 快速开始
|
||||
|
||||
### 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();
|
||||
}
|
||||
}
|
||||
|
||||
#### 示例2:PLC 触发 - 检查 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. 执行车辆重置
|
||||
@@ -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` 后,第二台车进入同区域会被阻止
|
||||
@@ -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 洞察 G:UI 形态需从 WinForms 迁到 CycleGUI/Web(net8.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 API(deprecated,暂留,后期删)
|
||||
├─ 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) | 拆分 | 联动留 Core;Qr 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) | 迁UI(CycleGUI) |
|
||||
| `VDA5050SiteField`(被 ArmCar 引用) | Core 公共字段 或 VDA dll | 待复核(ArmCar 留 Core 则下沉 Core) |
|
||||
|
||||
### 4.3 任务族(`Chained/` `InterLock/` `Scheduler/`)
|
||||
|
||||
| 现状 | 目标 | 动作 |
|
||||
|---|---|---|
|
||||
| `Chained/*`、`InterLock/*`、`Scheduler/*` 逻辑 | Core | 移动(导航无关) |
|
||||
| `DeliveryViewer/LoopViewer/TrafficInterlockViewer`(窗体) | Core(Ui) | 迁UI(CycleGUI/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` | 191–537 | `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 dll;SimpleLite 侧待新增(无直接对应) |
|
||||
| `map/getLidarMap`(激光) | 1021 | 暂随 Laser dll;SimpleLite 侧待新增(无直接对应) |
|
||||
|
||||
> 注:二维码地图下发、激光 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(重点)**:所有窗体迁 CycleGUI(SimpleLite 立即模式 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`(去 WinForms,UI 迁 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 错误;搬运/环线/充电/交管不回归。
|
||||
- 选择性加载 + 热插拔:激活集合内能力可用,未激活不加载、可干净卸载。
|
||||
- 反射 API(SimpleLite `/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-windows,dotnet 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.0;UI 去 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**=两 coder(4 参 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)。*
|
||||
Reference in New Issue
Block a user