如何基于SimpleCore核心库进行调度系统开发

来自MDCS wiki2
跳到导航 跳到搜索


概述 / 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:

调试 / 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.

相关页面 / See also