如何基于SimpleCore核心库进行调度系统开发
概述 / Overview
MDCS 的车队调度被分为两层:SimpleCore(核心算法库,包含交管、寻路、包络、可达性、DPS 等内核算法)与 SimpleComposer(基于 SimpleCore 的 UI 壳层,包含 CAD 工具、可视化、插件宿主)。本页面对开发者:你想自己写一个调度应用(或在 SimpleComposer 之外用 SimpleCore 内核)该如何开始。
MDCS fleet management is split into two layers: SimpleCore (algorithm library — traffic control, pathfinding, envelopes, reachability, DPS) and SimpleComposer (a UI shell built on SimpleCore — CAD tool, visualisation, plugin host). This page targets developers writing their own scheduling app on top of SimpleCore, or extending SimpleComposer with a custom car / mission type.
何时基于 SimpleCore 开发 / When to use SimpleCore directly
| 场景 / Scenario | 建议 / Recommendation |
|---|---|
| 集成进非 SimpleComposer 的现成系统 | 直接用 SimpleCore,把 UI 留给原系统 |
| 给 SimpleComposer 加车型 / 业务 | 写插件在 SimpleComposer 上挂载,不必自己写调度 |
| 算法研究(新交管 / 新寻路) | SimpleCore,作为算法宿主 |
| 单工位单车 | 都不需要,写一个小循环够了 |
核心抽象 / Core abstractions
Car 体系 / Car hierarchy
SimpleCore 把"车"按照 是否使用 MDCS 车载系统 分为两支: SimpleCore splits "cars" into two branches by whether they use the MDCS on-vehicle stack:
| 类 / Class | 文件 / File | 用途 / Use |
|---|---|---|
| `AbstractCar` | `D:\src\Simple\SimpleCore\PropType\AbstractCar.cs:346` | 调度可见的车的最小契约 / minimal scheduling contract |
| `Car` | `D:\src\Simple\SimpleComposer\RCS\Car.cs:33` | SimpleComposer 中的"车"基类 / Composer-side base |
| `ClumsyCar : Car` | `D:\src\Simple\SimpleComposer\RCS\ClumsyCar.cs:34` | 跑 MDCS 车载(Medulla + Clumsy)的车 / runs full MDCS on-vehicle stack |
| 自评估 `Car`(自定义子类)/ Self-eval `Car` (custom subclass) | 各 vendor 适配代码 / vendor adapters | PLC / 磁导航 / 二维码 类无车载 PC 的车 / cars without on-board MDCS (PLC, magstrip, QR) |
| `AGVInterface` | `D:\src\Simple\SimpleCore\BasicProps\AGVInterface.cs` | 任务脚本调用的 API / API surface called by mission scripts |
AGVInterface 与 Queue 机制
`AGVInterface` 暴露给任务脚本(TopazScript)的核心方法是 `Queue(params Func<Task>[] actions)` —— 超标量 异步流水线:
The core method on `AGVInterface` exposed to mission scripts is `Queue(params Func<Task>[] actions)` — a "super-scalar" async pipeline:
- 每个 Func 是一个 行 / row,按提交顺序进入流水线。
- 同一行内的代码顺序执行。
- 上一行未完成前,下一行不会开始 —— 但下一行可在上一行最后一个 await 后立即 schedule,达成宏观流水。
- `WaitAsync(int pipe)` 让调用方等到指定行完成再继续。
- Each Func is a row in the pipeline.
- Code within a row runs sequentially.
- The next row only starts after the previous row's last await, but its prelude can overlap, giving macro-level pipelining.
- `WaitAsync(int pipe)` lets the caller wait for a specific row.
TryLock / Leave — 交管 / Traffic control
`TryLock(int siteID)` / `Leave(int siteID)` 是 SimpleCore 交管在 每个 mission 脚本里的钩子。任务脚本在进入一个交管区前必须 `TryLock`;离开时 `Leave`。SimpleCore 的内核保证:
`TryLock(siteID)` / `Leave(siteID)` are SimpleCore's traffic-control hooks per mission script. Scripts must `TryLock` before entering a controlled segment and `Leave` on exit. SimpleCore's kernel guarantees:
- 无死锁(DPS 算法,见 DPS调度算法详解)
Deadlock freedom (via DPS — see the DPS page). - 公平(按到达顺序授予锁)
Fairness — locks granted in arrival order. - 兼容性(同一锁可被声明 可兼容车型 一起持有)
Compatibility — a lock can be held simultaneously by compatible car types.
写一个 ClumsyCar 子类 / Subclassing ClumsyCar
ClumsyCar 用于车上跑完整 MDCS(Clumsy 自动驾驶 + Medulla 硬件抽象)的情况。最小子类骨架:
ClumsyCar is the right base when the vehicle runs the full MDCS stack on-board. Minimal subclass:
using SimpleComposer.RCS;
namespace Vendor.Carts
{
[CarType(Name = "MyCart", Title = "我的车型 / MyCart")]
public class MyCart : ClumsyCar
{
public MyCart() : base()
{
conf = "configs/MyCart.config";
api_retries = 3;
}
public override async Task actualSendScript(string script)
{
// 把 TopazScript 通过 HTTP 发到车载 Clumsy 适配器 / POST to on-board Clumsy adapter
var url = $"http://{address}:30080/run";
var resp = await HttpClient.PostAsync(url, new StringContent(script));
resp.EnsureSuccessStatusCode();
}
public override async Task keepAlive()
{
// 心跳 + 状态轮询 / heartbeat + status poll
var status = await HttpClient.GetStringAsync($"http://{address}:30080/status");
UpdateStatusFromJson(status);
}
}
}
参考样例(80 行可读完):`D:\src\cookbook\adaption-reference\法睿兰达\CartAdapters\FY2208008\Scenes\AMRScene\ProjNameCar.cs`。 Reference (80 lines, easy to read end-to-end): `D:\src\cookbook\adaption-reference\法睿兰达\CartAdapters\FY2208008\Scenes\AMRScene\ProjNameCar.cs`.
写一个自评估 Car 子类 / Subclassing Car for self-eval vehicles
自评估车(磁导航 / 色带 / PLC 控制 / 二维码 等)的车载没有 MDCS,调度只能发"高层意图",由 SimpleCore 侧 的 AGV 接口翻译成厂商命令,再通过 RFID / Modbus / HTTP 下发。
Self-evaluating vehicles (magnetic-strip, tape, PLC-controlled, QR-only) don't run MDCS on-board. The scheduler dispatches "high-level intents", and a fleet-side `AGV : AGVInterface` translates them into vendor commands (RFID frames, Modbus, HTTP) and reads back `(x, y, th, siteID)` via `keepAlive()`.
经典样例:`D:\src\cookbook\adaption-reference\苏州凌鸟\Scenes\AMRScene1\Cars\MNavCar.cs`(293 行,含双向导航 + RFID 命令队列 + 启发式约束容器 `MNavCarHeuristics`)。
Canonical example: `D:\src\cookbook\adaption-reference\苏州凌鸟\Scenes\AMRScene1\Cars\MNavCar.cs` (293 lines — two-way navigation, RFID command queue, `MNavCarHeuristics` for no-go turns / forced reversals).
骨架 / Skeleton:
[CarType(Name = "MyMagCart", Title = "磁导航车 / Magnetic-strip cart")]
public class MyMagCart : Car
{
public string address;
public int rfidPort = 502;
public class AGV : AGVInterface
{
private readonly MyMagCart car;
public AGV(MyMagCart c) { car = c; }
public override bool TryLock(int siteID, int route_id) { /* SimpleCore 内置 / inherit */ return base.TryLock(siteID, route_id); }
public override bool Leave(int siteID) { return base.Leave(siteID); }
public void Go(double sx, double sy, int sid, double dx, double dy, int did, int routeId)
{
Queue(
async () =>
{
// 在锁拿到后才发指令 / send intent only after lock granted
if (!TryLock(did, routeId)) return;
car.SendRFIDGoCommand(did);
},
async () =>
{
// 等到车真的到了 / wait until reported arrival
while (car.lastReportedSiteId != did) await Task.Delay(100);
Leave(sid);
});
}
}
public override async Task actualSendScript(string script) { /* 自评估车通常不发 TopazScript */ }
public override async Task keepAlive()
{
// Modbus / TCP 读位置并写入 (x, y, th, siteId) / poll position
var (x, y, th, siteId) = await PollPLCAsync(address);
UpdatePose(x, y, th); lastReportedSiteId = siteId;
}
}
任务下发 / Mission dispatch flow
调度内核 → SegmentPlan.Compile() → TopazScript(一段脚本)
↓
CarProgram.script (放入 Car.status.programs.forecasted)
↓
Car.actualSendScript(script)
↓ (ClumsyCar) ↓ (self-eval)
HTTP POST 到车载 直接在调度侧本地跑 SelfEvaluating(agv, script)
↓ ↓
车载 SelfEvaluating(agv, script) AGV.Go / AGV.Queue 等映射到 RFID/Modbus 命令
↓
Clumsy Movement 执行 → 物理运动
Reachability 与包络 / Reachability & envelope
SimpleCore 提供 可达性状态机(见 可达性状态编程)与 包络声明(见 包络如何定义)。你的 Car 子类一般只需要:
- 声明默认包络(无载货状态的车体包络)。
- 在带载 / 升叉 / 顶升等状态时声明 备用包络,由 `HaveExtraEnvelope` 状态触发。
- 在不可达的路径段上声明 可达性规则(例如:单向通道、最低剩余电量、强制掉头)。
A Car subclass typically: (1) declares the empty envelope, (2) declares alternate envelopes triggered by carry / fork-up / jack-up states, (3) declares reachability rules on path segments (one-way, min battery, mandatory reverse-out).
用作算法宿主:在 SimpleComposer 之外用 SimpleCore
SimpleCore 是普通 .NET 库(`D:\src\Simple\SimpleCore\`),可以独立引用。最小集成:
using SimpleCore;
var world = new World(); // 加载站点 + 路径图 / load sites + path graph
world.LoadFromJson(File.ReadAllText("map.json"));
var car1 = new MyAbstractCarImpl(); // 实现 AbstractCar 接口
var car2 = new MyAbstractCarImpl();
world.AddCar(car1); world.AddCar(car2);
var planner = new DPSPlanner(world);
var mission = planner.Plan(
from: world.GetSite("A"),
to: world.GetSite("Z"));
foreach (var step in mission.Steps)
{
Console.WriteLine($"car={step.Car} site={step.Site} action={step.Action}");
}
算法 / Algorithms
SimpleCore 内置: SimpleCore ships with:
- DPS 调度(动态规划交管)—— `DPS调度算法详解`
- 寻路启发器 —— `寻路启发器功能`
- 流场规划 —— `流场规划`
- 网络流业务规划 —— `基于网络流的业务规划器`
调试 / Debugging
- 用 CycleGUI 把交管锁、车辆状态、路径片段实时画出来。
- SimpleComposer 的 Trace 面板 显示每条任务的 Queue 行执行轨迹。
- 死锁怀疑时打开 DPS 决策可视化(DPS 算法会输出每次决策的剪枝树)。
- Use CycleGUI to render locks, car state, path segments live.
- SimpleComposer's Trace panel shows the Queue-row execution timeline per mission.
- When deadlock is suspected, enable DPS decision visualisation.