将 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 项)暂未处理,留待单独任务。
20 KiB
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 依赖关系(星型拓扑)
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 启动与加载流程
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()(死锁回调等)
场景选择优先级(高 → 低):
- CLI 参数
--scenes active-scenes.jsonsimple.json中的scenes字段- 加载全部已发现的卫星
注意:若
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
部署步骤:
- 先构建宿主:
dotnet build Simple\SimpleLite\SimpleLite.csproj - 构建本方案:
dotnet build StandardScene.sln - 将
build\plugins\内容复制到SimpleLite.exe工作目录的plugins\ - 配置
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。
注册三要素:
- 类型特性:
[CarType(Name="显示名", editor=typeof(XxxCar))] - 轨迹编码器:
[ProgramTrackCoderSettings(priority=N, program=typeof(XxxCoder))] - 场景清单:
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:
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.NaiveMagGoMagneticSceneProfile.cs+StandardScene.Magnetic.scene.json
StandardScene.QrLidar(12 文件)
CarTypes/— Forklift, Kiva, ArmCar, DualLiftingCar, MultiVehicleCar, MultiWheelForkLifter, MultiWheelLifterCarCad/SyncQrMap.cs— 二维码地图同步 CAD 工具QrLidarSceneProfile.cs+StandardScene.QrLidar.scene.json
StandardScene.Devices(8 文件)
Door/ModbusDoorController.csCharge/FLChargeStation.cs,PCBChargeStation.cs,MuXingChargeStation.csButtonBox/LeegButtonBox.cs,AzowieButtonBox.csStandardScene.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
- 在
StandardScene.Core合适目录新建类,继承Mission(或现有抽象基类) - 添加
[MissionType(Name="...", editor=typeof(...))] - 实现
Create()、Execute()、Stop()三件套 - 参考:
Scheduler/HeartBeatMission.cs(最小)、Scheduler/RegionalTrafficControlMission.cs(事件订阅)
6.2 新增车型(导航卫星)
- 确定目标卫星(Magnetic / QrLidar / VDA5050)
- 新建
CarTypes/XxxCar.cs,继承GhostCar,标注[CarType]+[ProgramTrackCoderSettings] - 如需新 Coder,在同插件
Coders/实现ITrackCoder - 更新
XxxSceneProfile.CarTypes与*.scene.json的provides.carTypes - 字段袋扩展放在 Core
CarTypes/(internal,卫星通过InternalsVisibleTo访问)
6.3 新增设备驱动
- 在
StandardScene.Devices实现 Core 抽象(如BasicDoorController) - 添加类型特性(如
[DoorType("MyDoor")]) - 更新
StandardScene.Devices.scene.json的provides列表 - 确保
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 演进方向(摘要)
- 抽离
StandardScene.Abstractions(纯契约 + 模型) - 充电子系统独立为卫星
StandardScene.Charge - WebApi 按资源拆模块并迁出 Core
- UI 全部迁至 CycleGUI 或独立 UI 程序集
- 目标 TFM 从
net8.0-windows过渡到net8.0(基础设施层)
10. AI / 新工程师接手清单
10.1 第一天:建立全局认知
- 阅读
README.md→ 本文档 →DEVELOPMENT_GUIDE.md - 打开
DocumentHub.html浏览模块导航 - 本地构建:先 SimpleLite,再 StandardScene,部署到
plugins\ - 启动宿主,确认
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):
{
"id": "scene.mag",
"displayName": "磁导航平台",
"navKind": "magnetic",
"assembly": "StandardScene.Magnetic.dll",
"coreVersion": ">=1.0.0",
"requiresCore": "StandardScene.dll",
"provides": {
"carTypes": ["MagCar"],
"missionTypes": []
}
}
设备插件(scene.device):
{
"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:插件引导钩子
基座在宿主加载早期通过反射调用:
// StandardScene.Core/Commons.cs
public class CustomOperationsBeforeLoading
{
public static void Set()
{
TrafficControl.OnDeadLock = (loopingCar) => { /* 死锁告警 */ };
}
}
这是少数几个 无 Mission 启动即可生效 的全局初始化入口之一。