openTCS-Kernel是调度核心,services是对服务层的封装,vehicles主要是在确定调度策略,确定行走路径后控制小车的,以及与外围设备的命令交互
openTCS-Strategies-Default是调度核心策略的默认实现,其中包含Dispatcher是分配运输订单与小车的对应关系,router是确定最短路径的,scheduler是主要是分配资源给小车
openTCS-API-Base主要是对涉及到的组件的一些抽象,drivers包下包含vechicle,peripherals,对应小车及外围设备
驱动器相关的包含小车和外围设备的
小车驱动适配器是VehicleCommAdapter
VehicleCommAdapter:其中包含了要发送的命令队列和已发送的命令队列
外围设备控制适配器为PeripheralCommAdapter
VehicleController 是小车的上层控制器,通过VehicleCommAdapter 来驱动小车
VehicleControllerPool用于维护车辆(Vehicle)和车辆控制器(VehicleController)之间的关联
LocalVehicleControllerPool用于管理控制器与车辆以及通信适配器的关系,用于管理Vehicle和VehicleCommAdapter
VehicleControllerComponentsFactory:创建PeripheralInteractor的工厂
VehicleControllerFactory:创建Vehicle的控制器工厂,同时设置对应的VehicleCommAdapter
DefaultVehicleController
- futureCommands:表示待发送到适配器的命令
- commandsSent:表示已经发送到适配器的命令
- lastCommandExecuted:表示最近一次适配器执行完的命令
在什么时候会触发Dispatcher的dispath方法呢?
- 在创建TransportOrder时
- 在调用ImplicitDispatchTrigger的onEvent方法中
- 即小车的IntegrationLevel为TO_BE_UTILIZED或者TO_BE_RESPECTED
- 小车的处理状态为IDLE,状态为IDLE或者CHARGING,小车的能量有变化
- 小车的处理状态有变化,新状态为IDLE或者AWAITING_ORDER
- 小车的订单次序从有变为无
- 周期调度任务PeriodicVehicleRedispatchingTask
在什么时候会触发外围设备任务PeripheralJobDispatcher的dispath方法呢?
- 在调用ImplicitDispatchTrigger的onEvent方法中
- TransportOrder的订单状态为变化,新状态为FAILED
- 周期调度任务PeriodicPeripheralRedispatchingTask
- 创建外围设备交互时PeripheralInteractor在执行前置或者后置交互,即在调用startPreMovementInteractions和startPostMovementInteractions方法时
资源调度
接口
DefaultVehicleController和Vehicle是一对一关系
实现
使用了组合模式
AllocationAdvisor:作为其它Module的组合,是模块的统一入口
PausedVehicleModule:不会对暂停的小车分配资源
ReservationPool:用于管理小车索要的资源以及分配的资源
AllocatorTask:处理资源分配的任务,具体依赖分配命令的子类
AllocatorCommand:分配的命令,包含资源分配,资源释放,资源的重新分配
ReservationPool:claimsByClient用来管理索要,即DefaultVehicleController小车索要了哪些资源,reservations表示分配成功后资源与小车的对应关系,即资源分配给了哪个小车
分配流程
分配时因为涉及到全局tcs资源及对象,使用了同步锁globalSyncObject
与小车命令交互控制了的,也使用了锁commAdapter
- DefaultVehicleController在更新DriveOrder时(setDriveOrder),根据DriveOrder判断需要的资源,然后调用Scheduler的claim索要资源
- 根据DriveOrder来创建命令集createFutureCommands
- 判断是否可以发送命令canSendNextCommand
- 看命令集是否不为空
- commAdapter通信适配器的队列容量是否可以接收命令
- 命令集中的第一条命令是否可以执行(即路径是否被锁)
- 是否在等待资源分配(waitingForAllocation)
- 是否有等待执行的命令(pendingCommand不为空)
- 交互命令是否有未完成的(isWaitingForMovementInteractionsToFinish)
- 在可以发送命令情况时,调用allocateForNextCommand,从命令集中取出第一条命令,判断命令所需要的资源,调用Scheduler的allocate方法异步分配资源,设置等待分配完成标识waitingForAllocation为true,同时设置等待执行命令pendingCommand
- 在异步分配成功后,会调用DefaultVehicleController#allocationSuccessful,执行pendingCommand的前置交互(prepareInteractions),然后发送命令sendCommand,将command添加到commAdapter的队列中,同时将命令记录到已发送队列commandsSent中,同时判断是否可以发下一个命令
- command添加到commAdapter时,会调用BasicVehicleCommAdapter#enqueueCommand,入队列成功会触发model的COMMAND_ENQUEUED事件,会调用BasicVehicleCommAdapter#propertyChange监听器方法,执行命令分发任务CommandDispatcherTask,调用BasicVehicleCommAdapter#sendCommand发送命令,同时将命令记录到已发送队列中sentQueue
- 命令执行完后,如果有触发COMMAND_EXECUTED,DefaultVehicleController会处理命令执行完事件(commandExecuted),将命令从已发送命令队列中删除,调用Scheduler#free删除为该命令分配的资源,同时执行该命令的后置操作(startPostMovementInteractions)
- 后置命令处理完后,checkForPendingCommands,判断是否还有待处理的命令,如果有,则继续下发命令,如果没有则将小车的状态设置为AWAITING_ORDER,此时会触发ImplicitDispatchTrigger
canSendNextCommand在什么时候会调用?
- 在调用
setDriveOrder
和updateDriveOrder
时 - 资源分配成功时
allocationSuccessful
- 小车控制器将移动命令放到通信适配器队列中时
sendCommand
- 命令执行完后检查是否还有命令要处理
checkForPendingCommands
第一种情况下小车的处理状态为AWAITING_ORDER
,后面三个是在 处理调度中,即小车的处理状态为处理中PROCESSING_ORDER
处理资源分配processAllocate
- tryAllocate尝试分配
- isNextInClaim首先看资源是否在索要池队列头
- resourcesAvailableForUser看资源是否可以被占用
- mayAllocate看分配顾问是否可能分配资源
- prepareAllocation为资源分配作准备工作
reservationPool.getReservationEntry(curRes).allocate(client)
分配资源reservationPool.unclaim(client, resources)
从索要池中删除
- checkAllocationsPrepared检查分配准备
- hasPreparedAllocation检查分配准备工作是否已经完成
- 分配成功后调用allocationSuccessful,生成移动命令,发送命令以及与外围设备作交互
- 如果allocationSuccessful失败,则调用undoAllocate释放分配的资源,调用scheduleRetryWaitingAllocations触发等待资源的处理
资源分配失败后的再次分配
在DefaultScheduler#free,DefaultScheduler#freeAll,DefaultScheduler#reschedule 时会再次分配
事件
EventBus:观察者模式的抽象,即作为Subject,也作为Observer,即继承了接口EventSource,EventHandler
SimpleEventBus :EventBus的实现类
TCSObjectManager:在修改属性时,会触发TCSObjectEvent事件
边权值计算
边计算也使用了组合模式,EdgeEvaluatorComposite集成了其它的EdgeEvaluator
重新路由
路由策略
重新路由处理
发起重新路由时机
1、拓扑结构发生变化
2、调用服务触发
3、界面上触发发起RerouteAction
4、在AssignNextDriveOrderPhase阶段,配置了DriveOrder完成时重新路由
状态变化
订单状态
小车状态
小车管理
VehicleCommAdapterRegistry注册小车工厂,来管理小车的创建
VehicleCommAdapterRegistry中factories使用的是TreeMap,排序规则为
- LoopbackCommunicationAdapterDescription类型排在最后
- 以VehicleCommAdapterDescription的getDescription返回字符串 作字典排序
VehicleCommAdapterRegistry是以VehicleCommAdapterFactory的getDescription返回的描述作为关键字来管理
外围设备管理
PeripheralCommAdapterRegistry注册外围设备工厂,来管理外围设备的创建
PeripheralCommAdapterRegistry是以PeripheralCommAdapterFactory的getDescription返回的描述来作为关键字来管理
分发
DefaultDispatcher#dispatch调用FullDispatchTask
-
CheckNewOrdersPhase
TransportOrder维度 -
FinishWithdrawalsPhase
小车Vehicle维度- 对于非立即取消订单的处理,在等待小车状态为AWAITING_ORDER时,检查是否有处于WITHDRAW的订单,则调用finishAbortion将小车的状态设置为IDLE
-
AssignNextDriveOrdersPhase
小车Vehicle维度 -
AssignSequenceSuccessorsPhase
小车Vehicle维度 -
分配订单
- AssignReservedOrdersPhase
小车Vehicle维度- 在AssignFreeOrdersPhase阶段分配没有成功时,在等待小车状态为Idle后,保留在订单预分配池中的订单OrderReservationPool开始计算路由处理
- AssignFreeOrdersPhase
TransportOrder维度和小车Vehicle维度。筛选小车的规则为IsAvailableForAnyOrder- 小车的状态为IDLE或者状态为PROCESSING_ORDER但是其对应的订单为可有可无的,即分配的订单为停车订单
- OrderReservationPool预订池中没有对应小车
在分配时,如果当前小车有订单要处理,则将订单与小车的关系添加到预分配池中。分配规则为 - 对Vechicle作CompositeVehicleSelectionFilter筛选
- 对运输订单作CompositeTransportOrderSelectionFilter筛选
- 小车数量小于订单数时
- 对小车作CompositeVehicleComparator排序
- 计算单个小车到所有订单的距离成本,对候选名单作CompositeAssignmentCandidateSelectionFilter筛选,对候选名单作CompositeOrderCandidateComparator排序,选择第一个
- 订单发数小于小车数时
- 对订单作CompositeOrderComparator排序
- 计算单个订单到所有小车的距离成本,对候选名单作CompositeAssignmentCandidateSelectionFilter筛选,对候选名单作CompositeVehicleCandidateComparator排序,选择第一个
在分配小车时,如果小车已经存在订单,则将小车与订单的对应关系添加到OrderReservationPool中,同时取消当前小车的订单TransportOrderUtil#abortOrder,内部调用abortAssignedOrder,其会更新订单状态为WITHDRAWN
- 对于需要立即取消订单的,会调用clearDriveOrder设置当前的DriveOrder为NULL, 等待分配标识waitingForAllocation为false, pendingResources为null,清空命令队列。同时将小车的处理状态设置为IDLE
- 对于无需立即取消订单的,调用abortDriveOrder,清除后一步的命令集
- AssignReservedOrdersPhase
-
小车充电(小车Vehicle维度)
- RechargeIdleVehiclesPhase
-
停靠小车(小车Vehicle维度)
- PrioritizedReparkPhase
- PrioritizedParkingPhase
- ParkIdleVehiclesPhase
停靠小车阶段创建的订单的属性dispensable(可有可无)为true,在下次AssignFreeOrdersPhase分发阶段时,为小车分配订单时,IsAvailableForAnyOrder在判断如果小车空闲时,或者当前小车分配的订单类型是可有可无时,可以为小车重新分配移动订单
模型
TCSObejct :主要是Dispatcher分发时使用
TCSResource :主要是Scheduler调度资源时使用
PeripheralInteractor
外围交互器,主要与外围设备交互,在VehicleContorller
的实现类DefaultVehicleController
中使用到,主要是执行移动命令的前处理和后处理
交互
preMovementInteractions
:移动命令对应的前置交互操作
postMovementInteractions
:移动命令对应的后置交互操作
prepareInteractions
:准备命令、订单对应的前后置操作
startPreMovementInteractions
:开始调起前置操作
startPostMovementInteractions
:开始调走后置操作
其中prepareInteractions
、startPreMovementInteractions
是在资源分配成功后allocationSuccessful
相继调用的
public boolean allocationSuccessful(@Nonnull Set<TCSResource<?>> resources) {
synchronized (commAdapter) {
....
peripheralInteractor.prepareInteractions(transportOrder.getReference(), command);
peripheralInteractor.startPreMovementInteractions(command,
() -> sendCommand(command),
this::onMovementInteractionFailed);
}
.....
}
而startPostMovementInteractions
是在命令执行完后commandExecuted
调用的
private void commandExecuted(MovementCommand executedCommand) {
synchronized (commAdapter) {
.....
peripheralInteractor.startPostMovementInteractions(executedCommand,
this::checkForPendingCommands,
this::onMovementInteractionFailed);
}
}
startPreMovementInteractions
和startPostMovementInteractions
是异步发起job的,即通过peripheralDispatcherService.dispatch()
,因此,交互操作成功失败是分别通过调用PeripheralInteraction
中的interactionSucceededCallback
,interactionFailedCallback
事件监听
PeripheralInteractor
实现了EventHandler
接口,主要是处理交互完成、失败的事件,最终是调用PeripheralInteraction
的完成回调onPeripheralJobFinished
和失败回调onPeripheralJobFailed
,即start
时设置的成功、失败回调interactionSucceededCallback
, interactionFailedCallback
public void onEvent(Object event) {
if (!(event instanceof TCSObjectEvent)) {
return;
}
TCSObjectEvent objectEvent = (TCSObjectEvent) event;
if (objectEvent.getType() != TCSObjectEvent.Type.OBJECT_MODIFIED) {
return;
}
if (objectEvent.getCurrentOrPreviousObjectState() instanceof PeripheralJob) {
onPeripheralJobChange(objectEvent);
}
}
private void onPeripheralJobChange(TCSObjectEvent event) {
PeripheralJob prevJobState = (PeripheralJob) event.getPreviousObjectState();
PeripheralJob currJobState = (PeripheralJob) event.getCurrentObjectState();
if (prevJobState.getState() != currJobState.getState()) {
switch (currJobState.getState()) {
case FINISHED:
onPeripheralJobFinished(currJobState);
break;
case FAILED:
onPeripheralJobFailed(currJobState);
break;
default: // Do nothing
}
}
}
private void onPeripheralJobFinished(PeripheralJob job) {
Stream.concat(preMovementInteractions.values().stream(),
postMovementInteractions.values().stream())
.forEach(interaction -> interaction.onPeripheralJobFinished(job));
Set<MovementCommand> preMovementsPrepared = preMovementInteractions.entrySet().stream()
.filter(entry -> entry.getValue().isFinished())
.map(entry -> entry.getKey())
.collect(Collectors.toSet());
Set<MovementCommand> postMovementsPrepared = postMovementInteractions.entrySet().stream()
.filter(entry -> entry.getValue().isFinished())
.map(entry -> entry.getKey())
.collect(Collectors.toSet());
preMovementsPrepared.forEach(
movementCommand -> preMovementInteractions.remove(movementCommand)
);
postMovementsPrepared.forEach(
movementCommand -> postMovementInteractions.remove(movementCommand)
);
}
private void onPeripheralJobFailed(PeripheralJob job) {
Stream.concat(preMovementInteractions.values().stream(),
postMovementInteractions.values().stream())
.forEach(interaction -> interaction.onPeripheralJobFailed(job));
}
初始化时会向eventBus订阅自己
public void initialize() {
if (isInitialized()) {
return;
}
eventSource.subscribe(this);
initialized = true;
}
DefaultVehicleController对资源的管理
其使用成员allocatedResources
来管理分配的资源
- 初始时null添加到
allocatedResources
队列中 - 首先根据DriveOrder创建命令集,即Route的构成steps,添加到
futureCommands
队列中 - 取出队列中的第一条命令,计算其所需要的资源,然后分配,分配成功后,将资源添加到
allocatedResources
队列 - 在发送完命令后,会判断
futureCommands
队列是否还有命令,如果有并且满足一定条件,会继续进行上一步 - 在命令执行完后,会从
allocatedResources
队列取出首元素释放,即释放上一步申请的资源(因为队列中的第一元素为null)
allocatedResources.add(null);
private void createFutureCommands(DriveOrder newOrder, Map<String, String> orderProperties) {
// Start processing the new order, i.e. fill futureCommands with corresponding command objects.
String op = newOrder.getDestination().getOperation();
Route orderRoute = newOrder.getRoute();
Point finalDestination = orderRoute.getFinalDestinationPoint();
Location finalDestinationLocation
= vehicleService.fetchObject(Location.class,
newOrder.getDestination().getDestination().getName());
Map<String, String> destProperties = newOrder.getDestination().getProperties();
Iterator<Step> stepIter = orderRoute.getSteps().iterator();
while (stepIter.hasNext()) {
Step curStep = stepIter.next();
// Ignore report positions on the route.
if (curStep.getDestinationPoint().isHaltingPosition()) {
boolean isFinalMovement = !stepIter.hasNext();
String operation = isFinalMovement ? op : MovementCommand.NO_OPERATION;
Location location = isFinalMovement ? finalDestinationLocation : null;
futureCommands.add(
new MovementCommandImpl(orderRoute,
curStep,
operation,
location,
isFinalMovement,
finalDestinationLocation,
finalDestination,
op,
mergeProperties(orderProperties, destProperties))
);
}
}
}
private void allocateForNextCommand() {
checkState(pendingCommand == null, "pendingCommand != null");
// Find out which resources are actually needed for the next command.
MovementCommand moveCmd = futureCommands.poll();
pendingResources = getNeededResources(moveCmd);
LOG.debug("{}: Allocating resources: {}", vehicle.getName(), pendingResources);
scheduler.allocate(this, pendingResources);
// Remember that we're waiting for an allocation. This ensures that we only
// wait for one allocation at a time, and that we get the resources from the
// scheduler in the right order.
waitingForAllocation = true;
pendingCommand = moveCmd;
}
private void commandExecuted(MovementCommand executedCommand) {
requireNonNull(executedCommand, "executedCommand");
synchronized (commAdapter) {
// Check if the executed command is the one we expect at this point.
MovementCommand expectedCommand = commandsSent.peek();
if (!Objects.equals(expectedCommand, executedCommand)) {
LOG.warn("{}: Communication adapter executed unexpected command: {} != {}",
vehicle.getName(),
executedCommand,
expectedCommand);
// XXX The communication adapter executed an unexpected command. Do something!
}
// Remove the command from the queue, since it has been processed successfully.
lastCommandExecuted = commandsSent.remove();
// Free resources allocated for the command before the one now executed.
Set<TCSResource<?>> oldResources = allocatedResources.poll();
if (oldResources != null) {
LOG.debug("{}: Freeing resources: {}", vehicle.getName(), oldResources);
scheduler.free(this, oldResources);
}
else {
LOG.debug("{}: Nothing to free.", vehicle.getName());
}
vehicleService.updateVehicleAllocatedResources(vehicle.getReference(),
toListOfResourceSets(allocatedResources));
peripheralInteractor.startPostMovementInteractions(executedCommand,
this::checkForPendingCommands,
this::onMovementInteractionFailed);
}
}
参考资料:
https://www.likecs.com/show-425956.html
官网