使用手册 - Simple:coder(路径编译器)机制详解:修订间差异
(创建页面,内容为“== 1.概念 == 路径编译是MDCS架构中一个重要概念,它是上位调度系统与下位车端系统之间的桥梁。路径编译发生在寻路完成之后,路径编译的操作对象是由“站点、路径、站点、路径......”构成的序列,路径编译的结果是一串AGV可以执行的命令脚本,该脚本对应车端SimpleAGVInterface里面定义的接口。 == 2.机制介绍 == 我们将编译器称为“coder”,实际业务…”) |
Huangzhaowei(讨论 | 贡献) 无编辑摘要 |
||
| (未显示1个用户的2个中间版本) | |||
| 第13行: | 第13行: | ||
== 3.TemplateCoderSettings使用方法 == | == 3.TemplateCoderSettings使用方法 == | ||
=== 3.1 | === 3.1 CoderSettings中字段含义 === | ||
<code>TemplateTrackCoderSettings</code>和<code>TemplateSiteCoderSittings</code>是分别实现trackCoder和SiteCoder的[https://learn.microsoft.com/zh-cn/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial Attribute],通过在Simple工程中xxxCar.cs中小车类之前添加<code>TemplateTrackCoderSettings</code>和<code>TemplateSiteCoderSittings</code>定义站点编译器。下方DummyCar的示例,无条件地将所有路径编译为agv.Go()这一动作:<syntaxhighlight lang="c#" line="1"> | <code>TemplateTrackCoderSettings</code>和<code>TemplateSiteCoderSittings</code>是分别实现trackCoder和SiteCoder的[https://learn.microsoft.com/zh-cn/dotnet/csharp/advanced-topics/reflection-and-attributes/attribute-tutorial Attribute],通过在Simple工程中xxxCar.cs中小车类之前添加<code>TemplateTrackCoderSettings</code>和<code>TemplateSiteCoderSittings</code>定义站点编译器。下方DummyCar的示例,无条件地将所有路径编译为agv.Go()这一动作:<syntaxhighlight lang="c#" line="1"> | ||
class DummyCarTrackField | class DummyCarTrackField | ||
| 第66行: | 第66行: | ||
|触发后,若blockVerb为"true",则不再检查其他优先级更低的coder;若为"false",则继续检查剩余优先级更低的coder。默认为"false" | |触发后,若blockVerb为"true",则不再检查其他优先级更低的coder;若为"false",则继续检查剩余优先级更低的coder。默认为"false" | ||
|} | |} | ||
其中,<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>TemplateSiteCoderSittings</code>中不可使用<code>src</code>这一字段,<code>dst</code>代表当前站点。''' | |||
{| class="wikitable" | |||
|+ | |||
!对象 | |||
!含义 | |||
! colspan="2" |包含字段 | |||
|- | |||
| rowspan="2" |car | |||
| rowspan="2" |当前plan所使用的小车 | |||
|car.id | |||
|小车id | |||
|- | |||
| colspan="2" |carFields中定义的其他字段 | |||
|- | |||
| rowspan="4" |src | |||
| rowspan="4" |路径起点 | |||
|src.id | |||
|起点的id | |||
|- | |||
|src.x | |||
|起点的x坐标 | |||
|- | |||
|src.y | |||
|起点的y坐标 | |||
|- | |||
| colspan="2" |siteFields中定义的其他字段 | |||
|- | |||
| rowspan="4" |dst | |||
| rowspan="4" |路径终点 | |||
|dst.id | |||
|终点的id | |||
|- | |||
|dst.x | |||
|终点的x坐标 | |||
|- | |||
|dst.y | |||
|终点的y坐标 | |||
|- | |||
| colspan="2" |siteFields中定义的其他字段 | |||
|- | |||
| rowspan="4" |plan | |||
| rowspan="4" |当前任务 | |||
|segN | |||
|任务总元素数 | |||
|- | |||
|curSeg | |||
|当前元素下标 | |||
|- | |||
|src | |||
|任务起点 | |||
|- | |||
|dst | |||
|任务终点 | |||
|- | |||
|prev | |||
|前一个segment,若无返回null | |||
| | |||
| | |||
|- | |||
|next | |||
|后一个segment,若无返回null | |||
| | |||
| | |||
|} | |||
<code>templateString</code>、<code>useVerb</code>、<code>blockVerb</code>中还预定义了如下方法: | |||
{| class="wikitable" | |||
|+ | |||
!'''方法''' | |||
!'''含义''' | |||
|- | |||
|getSite | |||
|返回指定id的站点 | |||
|- | |||
|getTrack | |||
|返回指定id的路径 | |||
|- | |||
|getCar | |||
|返回指定id的小车 | |||
|- | |||
|getSegment | |||
|返回指定下标的segment | |||
|} | |||
=== 3.2 编写自己的coderSitting === | |||
以最常见的取放货为例,首席需要将使用的字段定义在类中,站点字段定义在SiteFields类中,路径字段定义在TrackFields类中。 | |||
下述coder的执行条件为<code>plan.action=='put'&& dst.shelf</code>,即站点上标记了<code>shelf = true</code>字段,并且这是放货plan时,才会下发<code>templateString</code>中的脚本,并且站点及路径上标记的响应字段的值也会跟随脚本下发。<syntaxhighlight lang="c#" line="1"> | |||
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))] | |||
</syntaxhighlight> | |||
=== 3.3 simple内置的coder === | |||
所有的小车类均继承了车体抽象类<code>AbstractCar</code>,抽象类中定义了如下几个coder: | |||
队列动作。在站点的codeQueue字段上定义脚本,在该站点发生路径切换时执行。<syntaxhighlight lang="c#" line="1"> | |||
[TemplateSiteCoderSettings(priority = 100, | |||
useVerb = "dst.codeQueue!=null", | |||
templateString = "agv.Queue(()=>{},()=>{${dst.codeQueue}});", | |||
blockVerb = "true", | |||
siteFields = typeof(StandardSiteFields))] | |||
</syntaxhighlight>到达动作。在站点的codeArrive字段上定义AGV脚本,在AGV达到该站点发时(完全停车后)执行。<syntaxhighlight lang="c#" line="1"> | |||
[TemplateSiteCoderSettings(priority = 100, | |||
useVerb = "dst.codeArrive!=null && plan.curSeg!=0", | |||
templateString = "agv.Wait(); ${dst.codeArrive};", | |||
siteFields = typeof(StandardSiteFields))] | |||
</syntaxhighlight>离开动作。在站点的codeLeave字段上定义AGV脚本,在AGV从该站点离开之前执行。<syntaxhighlight lang="c#" line="1"> | |||
[TemplateSiteCoderSettings(priority = 100, | |||
useVerb = "dst.codeLeave!=null && plan.curSeg!=plan.segN-1", | |||
templateString = "agv.Wait(); ${dst.codeLeave};", | |||
siteFields = typeof(StandardSiteFields))] | |||
</syntaxhighlight>空路径。即指定某段路径不执行具体动作(相当于直接跳过)。<syntaxhighlight lang="c#" line="1"> | |||
[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))] | |||
</syntaxhighlight>切换动作。在站点的switchString字段上定义脚本,在该站点发生路径切换时执行。一般用于进行障碍物切换。<syntaxhighlight lang="c#" line="1"> | |||
[TemplateTrackCoderSettings( | |||
priority = 20, | |||
useVerb = "track.switcher", | |||
templateString = "agv.Queue(()=>{}, ()=>{${dst.switchString};});", | |||
siteFields = typeof(StandardSiteFields), | |||
trackFields = typeof(StandardTrackFields))] | |||
</syntaxhighlight> | |||
== 4.ProgramCoderSettings使用方法 == | |||
=== 4.1 使用方法 === | |||
比起TemplateCoderSettings,ProgramCoderSettings提供了更大的编译自由度。使用TemplateCoderSettings,只能访问到上一小节中提供的字段和方法,并且想要构建两个segment之间的上下文关系,是比较困难的。例如,考虑二维码导航的情况:小车在行进之前,需要根据当前朝向和目标行走方向的偏差,进行旋转。这条旋转指令用到了segment之间的上下文关系,且编译过程需要用到小车朝向、路径朝向的信息。 | |||
对于站点和路径,分别有<code>ProgramTrackCoderSettings</code>和<code>ProgramSiteCoderSettings</code>两种编译器。两者均包含以下两个字段: | |||
{| class="wikitable" | |||
|+ | |||
!'''字段''' | |||
!'''类型''' | |||
!'''含义''' | |||
|- | |||
|priority | |||
|int | |||
|优先级,数值越大越先被检查 | |||
|- | |||
|program | |||
|Type | |||
|定义coder逻辑的类 | |||
|} | |||
<code>ProgramTrackCoderSettings</code>和<code>ProgramSiteCoderSettings</code>的<code>program</code>需赋值为<code>ITrackCoder</code>或<code>ISiteCoder</code>的派生类。这两个Interface定义了coder类的实现方法:<syntaxhighlight lang="c#" line="1"> | |||
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); | |||
} | |||
</syntaxhighlight>其中的<code>toBlock()</code>函数的返回值决定是否屏蔽余下优先级更低的coder(与<code>TemplateCoderSettings</code>中的"blockVerb"类似,但<code>toBlock()</code>函数中开发者显然可以根据业务需求编写更复杂的逻辑);<code>Code()</code>函数的作用是生成当前segment对应的脚本字符串,其输入参数<code>i</code>对应当前segment的下标。生成的命令赋值给<code>plan.codeArr</code>中相应下标的元素,即得到整个执行脚本。 | |||
=== 4.2 用例 === | |||
示例如下,是二维码导航的trackCoder实现:<syntaxhighlight lang="c#" line="1"> | |||
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; | |||
} | |||
} | |||
</syntaxhighlight> | |||
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;
}
}