序言:
光灵的一生会遇上很多种人,很多种事,有的甜蜜,有的温馨,有的婉转成歌 ,有的绵延不息,在这些故事里,唯一的共通之处就是,某年、某月,某个波澜不惊的日子里,曾经很爱、很爱你。
帅气~内核的Java API
作为软件开发人员到底该做什么,心中要有认识~
使用内核API所需要的接口和类是opentcs-api-base JAR文件的一部分,所以我们应该将其添加到类路径/声明对它的依赖。
(参见可用的工件和API兼容性。)
您将经常遇到的工厂模型组件和运输任务的基本数据结构如图:
图1:基本数据结构
基本数据结构通常与获取和操作这些对象交互的服务接口是:
(我们开发人员需要的接口)
图2:接口名称
PS:在写类名的时候注意保持与上图一致
2.1、获取服务对象
要在内核JVM中运行的代码中使用服务,例如车辆驱动程序,只需请求一个实例,例如PlantModelService,通过依赖注入提供。您还可以使用这里的InternalPlantModelService实例,它提供了仅对内核应用程序组件可用的其他方法。
(这么帅的么~~可以啊,不外乎如此)
要从另一个JVM访问服务,例如,在一个应该创建传输任务或接收传输任务或车辆状态更新的客户机中,我们需要通过远程方法调用(RMI)连接到它们。
最简单的方法是创建一个实例:KernelServicePortalBuilder类,并让它为我们构建一个KernelServicePortal实例。
(目前,对用户管理的支持并不多,因此建议忽略需要用户凭据的方法。)
在创建KernelServicePortal实例之后,您可以使用它来获取服务实例并从中获取内核事件。
我们参阅基本API的JavaDoc文档中KernelServicePortalBuilder的类文档。
下面就是编写一个类的实例(KernelServicePortalBuilder)
KernelServicePortal servicePortal = new KernelServicePortalBuilder().build();
// 与某个内核连接并登录,端口号十分重要
servicePortal.login("someHost", 1099);
// 获得工厂模型服务的参考信息
PlantModelService plantModelService = servicePortal.getPlantModelService();
// 并找出当前加载的模型的名称
String modelName = plantModelService.getLoadedModelName();
// 轮回事件,如果当前没有,则等待一秒钟
// 这应该定期进行,并且可能在单独的线程中进行
List<Object> events = servicePortal.fetchEvents(1000);
2.2、处理运输任务
我们开发人员编写一个类,由TransportOrder类的实例表示的传输顺序描述由车辆执行的进程。通常,这个过程是货物从一个地点到另一个地点的实际运输。然而, TransportOrder也可以仅仅描述车辆移动到目的地位置和可选择的车辆操作。
下面所有的例子都是openTCS中假象有“运输任务”的例子,实际上什么也没有运输:
2.2.1、将货物从某处运到其他地方的经典任务命令:
- 移动到位置“A”,并在那里执行“装载货物”操作。
- 移动到位置“B”,在那里执行“卸载货物”操作。
2.2.2、操纵运输或固定物品:
a、移动到位置“A”,并在那里执行“钻孔”操作。
b、移动到位置“B”,在那里执行“锤打”操作。
2.2.3、将车辆移动到停车位置的命令:
a、移动到位置“Park 01”(不执行任何具体操作)。
2.2.3、给汽车电池充电的命令:
a、移动到位置“充电站”,并在那里执行“充电”操作。
2.2.4、一个运输任务指令的生命周期
a、在创建传输任务时,它的初始状态是RAW。
b、用户/客户端为应该影响传输过程的传输顺序设置参数。这些参数可能是运输任务的最后期限,即应该处理运输顺序的车辆或一组通用的,通常是特定于项目的属性。
c、传输顺序被激活,即参数设置完成,它的状态被设置为ACTIVE(活动状态)。
d、内核的路由器检查传输顺序的目的地之间的路由是否可能,如果是,则将其状态更改为DISPATCHABLE(可分派)。如果不可能进行路由,则将传输顺序标记为 UNROUTABLE (不可路由),且不进行任何进一步处理。
e、内核的dispatcher(调度程序)检查是否满足了执行传输任务的所有需求,并且可以使用车辆进行处理。只要还没有满足或没有车辆可以执行的要求,运输任务就会等待。
f、内核的dispatcher(调度程序)将传输顺序分配给一个用于处理的工具。它的状态改为BEING_PROCESSED。
•如果正在处理的传输任务被撤销(由客户/用户),它的状态将在车辆执行已发送到它的任何任务时发生更改。然后传输顺序的状态更改为FAILED,它不再被处理。
•如果运输任务的处理因任何原因而失败,则标记为失败,不再处理。
•如果车辆成功地处理了整个运输任务,则标记为已完成。
g、最终,在较长时间内或在最终状态下的大量传输任务在内核的任务池中积累时——内核删除了传输顺序,下面的状态机可视化了这个生命周期:
图:传输命令状态的可视化流程
2.3、运输任务的结构和处理
传输任务是通过调用TransportOrderService.createTransportOrder()创建的。
作为它的参数,它期望一个传输层的类(TransportOrderCreationTO)来包含访问目的地的序列,以及AGV应该在那里执行的操作。内核将每个目的地封装在一个新创建的驱动程序(DriveOrder)实例中。这些驱动程序(DriveOrders)本身是由内核在一个单独的、新创建的传输任务实例中包装的。
一旦一个运输任务被调度程序( Dispatcher,)分配给车辆,就会计算出每一个驱动程序的路线,然后这些路由被存储在相应的DriveOrders中。
PS、一旦AGV小车(“司机”)能够处理一个驱动程序,它的路线的单个步骤就被映射到移动命令。这些移动命令包含车辆驱动程序到达最终目的地所需的所有信息,并在那里执行所需的操作。
图、移动指令相关类
部分路线的移动命令被一点一点地发送给AGV小车的“司机”。内核只有在驱动程序正常运行时,才会预先发送许多移动命令。这样做是为了保持对所有AGV小车使用的路径/资源的精细控制,一个车辆驱动程序可以通过调整其命令队列的容量来设置它预先得到的移动命令的最大数量。一旦驱动程序完成,下一个驱动程序(DriveOrder)的路由就被映射到移动命令。一旦传输顺序的最后一个驱动程序完成,整个传输顺序也就完成了。
2.3.1、 如何创建一个新的运输命令
// 目前我们在编写的 transport order service 类
TransportOrderService transportOrderService = getATransportOrderServiceReference(
);
// 目前我们在编写的dispatcher service 类
DispatcherService dispatcherService = getADispatcherServiceReference();
// 车辆应该前往的运输任务目的地列表:
List<DestinationCreationTO> destinations = new LinkedList<>();
// 创建一个新的目标描述并将其添加到列表中。
// 每个目的地都由目的地的名称来描述
// 在工厂模型中的位置和AGV小车应该在那里执行的操作:
destinations.add(new DestinationCreationTO("Some location name",
"Some operation"));
// 根据需要向列表中添加尽可能多的目的地。然后,使用新传输任务的名称和目的地列表创建传输任务描述。
//注意,给定的名称必须是唯一的。
TransportOrderCreationTO orderTO
= new TransportOrderCreationTO("MyTransportOrder-" + UUID.randomUUID(),
destinations); //可选地,分配一个特定的车辆运输任务清单:
orderTO = orderTO.withIntendedVehicleName("Some vehicle name");
// 可选地,设置运输任务的最后期限:
orderTO = orderTO.withDeadline(Instant.now().plus(1, ChronoUnit.HOURS));
// 为给定的描述创建一个新的传输任务顺序~~
TransportOrder newOrder = transportOrderService.createTransportOrder(orderTO);
// 触发创建的传输任务顺序的分派过程,这就是我们程序员所知道的调用方法~~
dispatcherService.dispatch();
2.3.2、 如何创建将AGV小车移动到到点而不是位置的传输命令顺序
// 目前我们在编写的 transport order service 类
TransportOrderService transportOrderService = getATransportOrderServiceReference();
// 目前我们在编写的dispatcher service 类
DispatcherService dispatcherService = getADispatcherServiceReference();
// 创建一个列表,其中包含到某一点的单个目标。
// 使用Destination.OP_MOVE这个参数 作为将要执行的操作~~~,我们程序员对参数十分的敏感~
List<DestinationCreationTO> destinations = new LinkedList<>();
destinations.add(new DestinationCreationTO("Some point name",
Destination.OP_MOVE));
// 创建一个带有目的地和唯一名称的运输任务描述,并将其分配给特定的AGV小车
TransportOrderCreationTO orderTO
= new TransportOrderCreationTO("MyTransportOrder-" + UUID.randomUUID(),
destinations)
.withIntendedVehicleName("Some vehicle name");
// 使用描述创建一个传输任务
TransportOrder dummyOrder = transportOrderService.createTransportOrder(orderTO);
// 触发创建的传输任务顺序的分派过程,这就是我们程序员所知道的调用方法~~
dispatcherService.dispatch();
2.4、使用命令序列
任务序列可用于强制单个AGV小车在给定的任务中处理多个传输任务,在OrderSequence的API文档中描述了使用顺序序列的一些规则,但以下是我们开发人员通常要做的事情:
(我们开发人员需要另外查阅相关API文档来保证代码没有漏洞~~)
// 目前我们在编写的transport order service 类
TransportOrderService transportOrderService = getATransportOrderServiceReference(
);
// 目前我们在编写的 dispatcher service 类
DispatcherService dispatcherService = getADispatcherServiceReference();
// 创建一个唯一名称的任务序列描述:
OrderSequenceCreationTO sequenceTO
= new OrderSequenceCreationTO("MyOrderSequence-" + UUID.randomUUID());
// 可选地,设置任务序列的故障-报警标志
sequenceTO = sequenceTO.withFailureFatal(true);
// 创建顺序顺序
OrderSequence orderSequence = transportOrderService.createOrderSequence( sequenceTO);
// 像往常一样设置传输顺序,但添加包装序列的名称
List<DestinationCreationTO> destinations = new ArrayList<>();
destinations.add(new DestinationCreationTO("Some location name",
"Some operation"));
TransportOrderCreationTO orderTO
= new TransportOrderCreationTO("MyOrder-" + UUID.randomUUID(),
destinations)
.withWrappingSequence(orderSequence.getName());
// 创建运输任务
TransportOrder order = transportOrderService.createTransportOrder(orderTO);
// 根据需要创建和添加更多的任务命令~~~
// 最后,设置任务序列的完成(complete)标志,以指示不会向其添加更多的传输任务。
transportOrderService.markOrderSequenceComplete(orderSequence.getReference());
// 触发创建的传输任务顺序的分派过程,这就是我们程序员所知道的调用方法~~
dispatcherService.dispatch();
PS:只要序列没有被标记为完成或者完全完成,选择的第一个顺序的AGV小车将被绑定到这个序列。它不会处理任何不属于同一序列的命令,直到整个序列完成。一旦确定了序列的完成标志,并处理了属于它的所有传输任务,它的最终标记将由内核来设置。
(意思就是这一辆AGV小车已经被绑定了,直到完成整个任务序列,这在多辆AGV小车的情况下十分重要~~)
2.5、如何撤回目前正在处理的运输任务
// 目前我们正在编写 dispatcher service 类~
DispatcherService dispatcherService = getDispatcherServiceFromSomewhere();
// 获得运输命令被撤回
TransportOrder curOrder = getTransportOrderToWithdraw();
// 撤销任务
// 第二个参数表示车辆是否应该完成动作,我们开发人员应该注意每一个参数的含义
// 它已经被指定为(false)或立即中止(true)。
// 第三个参数表示车辆的处理状态是否应该更改为不可用,因此不能为其分配另一个传输任务~~
(我们开发人员应该注意每一个参数的正确与否,否则会造成漏洞)
// 就在撤销之后~~
dispatcherService.withdrawByTransportOrder(curOrder.getReference(), true, false);
2.6、 如何通过车辆处理参考命令来撤回运输任务
// 目前我们正在编写 object service类
TCSObjectService objectService = getTCSObjectServiceFromSomewhere();
// 得到运输命令应当撤回的AGV小车
Vehicle curVehicle = objectService.fetchObject(Vehicle.class,
getSampleVehicle());
// 目前我们正在编写dispatcher service类
DispatcherService dispatcherService = getDispatcherServiceFromSomewhere();
// 撤销任务
// 第二个参数表示车辆是否应该完成动作,我们开发人员应该注意每一个参数的含义
// 它已经被指定为(false)或立即中止(true)。
// 第三个参数表示车辆的处理状态是否应该更改为不可用,因此不能为其分配另一个传输任务~~
(我们开发人员应该注意每一个参数的正确与否,否则会造成漏洞)
// 就在撤销之后~~
dispatcherService.withdrawByVehicle(curVehicle.getReference(), true, false);