使用手册 - Simple:coder(路径编译器)机制详解:修订间差异
无编辑摘要 |
Huangzhaowei(讨论 | 贡献) 无编辑摘要 |
||
| 第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> | <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中字段含义
TemplateTrackCoderSettings和TemplateSiteCoderSittings是分别实现trackCoder和SiteCoder的Attribute,通过在Simple工程中xxxCar.cs中小车类之前添加TemplateTrackCoderSettings和TemplateSiteCoderSittings定义站点编译器。下方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" |
其中,templateString、useVerb、blockVerb都是字符串,通过Jint将字符串视为JavaScript代码,进行解析执行。
templateString、useVerb、blockVerb中可以使用如下定义好的对象。注意,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 | ||
templateString、useVerb、blockVerb中还预定义了如下方法:
| 方法 | 含义 |
|---|---|
| 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之间的上下文关系,且编译过程需要用到小车朝向、路径朝向的信息。
对于站点和路径,分别有ProgramTrackCoderSettings和ProgramSiteCoderSettings两种编译器。两者均包含以下两个字段:
| 字段 | 类型 | 含义 |
|---|---|---|
| priority | int | 优先级,数值越大越先被检查 |
| program | Type | 定义coder逻辑的类 |
ProgramTrackCoderSettings和ProgramSiteCoderSettings的program需赋值为ITrackCoder或ISiteCoder的派生类。这两个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;
}
}