使用手册 - Simple:coder(路径编译器)机制详解:修订间差异

来自MDCS wiki
跳到导航 跳到搜索
无编辑摘要
无编辑摘要
 
第68行: 第68行:
其中,<code>templateString</code>、<code>useVerb</code>、<code>blockVerb</code>都是字符串,通过[https://github.com/sebastienros/jint Jint]将字符串视为JavaScript代码,进行解析执行。
其中,<code>templateString</code>、<code>useVerb</code>、<code>blockVerb</code>都是字符串,通过[https://github.com/sebastienros/jint Jint]将字符串视为JavaScript代码,进行解析执行。


<code>templateString</code>、<code>useVerb</code>、<code>blockVerb</code>中可以使用如下定义好的对象。'''注意,<code>TemplateCoderSittings</code>中不可使用<code>src</code>这一字段,<code>dst</code>代表当前站点。'''
<code>templateString</code>、<code>useVerb</code>、<code>blockVerb</code>中可以使用如下定义好的对象。'''注意,<code>TemplateSiteCoderSittings</code>中不可使用<code>src</code>这一字段,<code>dst</code>代表当前站点。'''
{| class="wikitable"
{| class="wikitable"
|+
|+

2023年11月4日 (六) 12:40的最新版本

1.概念

路径编译是MDCS架构中一个重要概念,它是上位调度系统与下位车端系统之间的桥梁。路径编译发生在寻路完成之后,路径编译的操作对象是由“站点、路径、站点、路径......”构成的序列,路径编译的结果是一串AGV可以执行的命令脚本,该脚本对应车端SimpleAGVInterface里面定义的接口。

2.机制介绍

我们将编译器称为“coder”,实际业务中,整个编译逻辑是由若干的coder构成的。也就是说,coder在同一个站点或者可以叠加使用。处理路径的,称为trackCoder;处理站点的,称为siteCoder。定义好某个车型的所有coder,就完成了该车型的业务层面的所有行为定义(即,在什么路径/站点上触发什么样的行为)。下面用一个具体的例子详细解释coder运行的机制。

我们将“小车从A到B”的过程称为一个“plan”。如上图所示,假设已经生成了`模拟车1`的一段plan。该plan包含6个site(站点)、5段track(路径),共11个segment。编译路径时,依据通行顺序检查所有segment。对于每一个segment,若它是站点,则检查所有的siteCoder;若它是路径,则检查所有的路径trackCoder。对于每一个被检查的coder,依次判断其是否能够触发,触发后生成AGV脚本,并判断是否继续检查剩余的coder。如下图所示,为车辆执行的脚本。

3.TemplateCoderSettings使用方法

3.1 CoderSettings中字段含义

TemplateTrackCoderSettingsTemplateSiteCoderSittings是分别实现trackCoder和SiteCoder的Attribute,通过在Simple工程中xxxCar.cs中小车类之前添加TemplateTrackCoderSettingsTemplateSiteCoderSittings定义站点编译器。下方DummyCar的示例,无条件地将所有路径编译为agv.Go()这一动作:

class DummyCarTrackField
{
    public int speed = -1;
    public bool reverse = false;
    public int reverseDst = -1;
}

[TemplateTrackCoderSettings(
    templateString = "agv.Go(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id},${track.id},${track.speed},${track.reverse || track.reverseDst == dst.id});",
    priority = 0,
    trackFields = typeof(DummyCarTrackField),
    useVerb = "true",
    blck)]

[CarType("模拟车")]
public class DummyCar : Car

CoderSitting中包含以下字段:

字段 类型 含义
templateString string 编译后执行的命令
priority int 优先级,数值越大越先被检查
siteFields Type 定义站点字段的类的Type
trackFields Type 定义路径字段的类的Type
planFields Type 定义车辆字段的类的Type
useVerb string 触发条件。默认为"true"
blockVerb string 触发后,若blockVerb为"true",则不再检查其他优先级更低的coder;若为"false",则继续检查剩余优先级更低的coder。默认为"false"

其中,templateStringuseVerbblockVerb都是字符串,通过Jint将字符串视为JavaScript代码,进行解析执行。

templateStringuseVerbblockVerb中可以使用如下定义好的对象。注意,TemplateSiteCoderSittings中不可使用src这一字段,dst代表当前站点。

对象 含义 包含字段
car 当前plan所使用的小车 car.id 小车id
carFields中定义的其他字段
src 路径起点 src.id 起点的id
src.x 起点的x坐标
src.y 起点的y坐标
siteFields中定义的其他字段
dst 路径终点 dst.id 终点的id
dst.x 终点的x坐标
dst.y 终点的y坐标
siteFields中定义的其他字段
plan 当前任务 segN 任务总元素数
curSeg 当前元素下标
src 任务起点
dst 任务终点
prev 前一个segment,若无返回null
next 后一个segment,若无返回null

templateStringuseVerbblockVerb中还预定义了如下方法:

方法 含义
getSite 返回指定id的站点
getTrack 返回指定id的路径
getCar 返回指定id的小车
getSegment 返回指定下标的segment

3.2 编写自己的coderSitting

以最常见的取放货为例,首席需要将使用的字段定义在类中,站点字段定义在SiteFields类中,路径字段定义在TrackFields类中。

下述coder的执行条件为plan.action=='put'&& dst.shelf,即站点上标记了shelf = true字段,并且这是放货plan时,才会下发templateString中的脚本,并且站点及路径上标记的响应字段的值也会跟随脚本下发。

class SiteFields
    {
        public bool shelf = false;
        public int obArea = -1;
        public int ASNub = -1;
        public float angle = 0;
        public float shelfWidth = 1030;
        public float distance = 500; 
    }
 [TemplateTrackCoderSettings(
        priority = 5,
        useVerb = "plan.action=='put' && dst.shelf ",
        templateString = "agv.Wait();agv.Put(${dst.ASNub},${dst.shelfWidth},${dst.obArea},${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id},${track.id},${track.speed},${dst.reverse},${dst.xbias},${dst.ybias},${dst.angle},${dst.distance});agv.Wait();",
        blockVerb = "true",
        siteFields = typeof(SiteFields),
        trackFields = typeof(TrackFields),
        planFields = typeof(PlanField))]

3.3 simple内置的coder

所有的小车类均继承了车体抽象类AbstractCar,抽象类中定义了如下几个coder:

队列动作。在站点的codeQueue字段上定义脚本,在该站点发生路径切换时执行。

[TemplateSiteCoderSettings(priority = 100,
    useVerb = "dst.codeQueue!=null",
    templateString = "agv.Queue(()=>{},()=>{${dst.codeQueue}});",
    blockVerb = "true",
    siteFields = typeof(StandardSiteFields))]

到达动作。在站点的codeArrive字段上定义AGV脚本,在AGV达到该站点发时(完全停车后)执行。

[TemplateSiteCoderSettings(priority = 100,
    useVerb = "dst.codeArrive!=null && plan.curSeg!=0",
    templateString = "agv.Wait(); ${dst.codeArrive};",
    siteFields = typeof(StandardSiteFields))]

离开动作。在站点的codeLeave字段上定义AGV脚本,在AGV从该站点离开之前执行。

[TemplateSiteCoderSettings(priority = 100, 
    useVerb = "dst.codeLeave!=null && plan.curSeg!=plan.segN-1",
    templateString = "agv.Wait(); ${dst.codeLeave};",
    siteFields = typeof(StandardSiteFields))]

空路径。即指定某段路径不执行具体动作(相当于直接跳过)。

[TemplateTrackCoderSettings(
    priority = 100,
    siteFields = typeof(StandardSiteFields),
    useVerb = "track.nop && (track.nopDst==-1 || track.nopDst==dst.id)",
    blockVerb = "true",
    templateString = "agv.Nop(${src.id},${dst.id},${track.id});",
    trackFields = typeof(StandardTrackFields))]

切换动作。在站点的switchString字段上定义脚本,在该站点发生路径切换时执行。一般用于进行障碍物切换。

[TemplateTrackCoderSettings(
    priority = 20,
    useVerb = "track.switcher",
    templateString = "agv.Queue(()=>{}, ()=>{${dst.switchString};});",
    siteFields = typeof(StandardSiteFields),
    trackFields = typeof(StandardTrackFields))]

4.ProgramCoderSettings使用方法

4.1 使用方法

比起TemplateCoderSettings,ProgramCoderSettings提供了更大的编译自由度。使用TemplateCoderSettings,只能访问到上一小节中提供的字段和方法,并且想要构建两个segment之间的上下文关系,是比较困难的。例如,考虑二维码导航的情况:小车在行进之前,需要根据当前朝向和目标行走方向的偏差,进行旋转。这条旋转指令用到了segment之间的上下文关系,且编译过程需要用到小车朝向、路径朝向的信息。

对于站点和路径,分别有ProgramTrackCoderSettingsProgramSiteCoderSettings两种编译器。两者均包含以下两个字段:

字段 类型 含义
priority int 优先级,数值越大越先被检查
program Type 定义coder逻辑的类

ProgramTrackCoderSettingsProgramSiteCoderSettingsprogram需赋值为ITrackCoderISiteCoder的派生类。这两个Interface定义了coder类的实现方法:

public interface ISiteCoder
{
    bool toBlock();
    bool Code(SegmentPlan plan, Site site, int i);
}

public interface ITrackCoder
{
    bool toBlock();
    bool Code(SegmentPlan plan, Track track, Site src, Site dst, int i);
}

其中的toBlock()函数的返回值决定是否屏蔽余下优先级更低的coder(与TemplateCoderSettings中的"blockVerb"类似,但toBlock()函数中开发者显然可以根据业务需求编写更复杂的逻辑);Code()函数的作用是生成当前segment对应的脚本字符串,其输入参数i对应当前segment的下标。生成的命令赋值给plan.codeArr中相应下标的元素,即得到整个执行脚本。

4.2 用例

示例如下,是二维码导航的trackCoder实现:

class QRTrackFields
{
    public int speed = -1;
    public bool reverse = false;
    public int reverseDst = -1;
}

public class QRGoCoder : ITrackCoder
{
    public bool toBlock()
    {
        return true;
    }

    public bool Code(SegmentPlan plan, Track track, Site src, Site dst, int i)
    {
        var trackInfo = StringDictConvert<QRTrackFields>.Convert(track.fields);
        var curPathDir = Math.Atan2(dst.y - src.y, dst.x - src.x) / Math.PI * 180;
        curPathDir = Math.Round(curPathDir / 90) * 90;

        var speed = trackInfo.speed;
        var reverse = trackInfo.reverse;
        if (trackInfo.reverseDst != -1 && trackInfo.reverseDst == dst.id) reverse = true;

        plan.codeArr[i] =
            $"agv.QRGo({curPathDir},{reverse.ToString().ToLower()},{src.id},{dst.id},{track.id},{speed});";

        return true;
    }
}