Initial commit
Some checks failed
Gradle Build / build (push) Has been cancelled

This commit is contained in:
CaiXiang
2024-11-30 18:36:13 +08:00
commit aa56926258
2134 changed files with 232943 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
apply from: "${rootDir}/gradle/java-project.gradle"
apply from: "${rootDir}/gradle/java-codequality.gradle"
apply from: "${rootDir}/gradle/guice-project.gradle"
apply from: "${rootDir}/gradle/publishing-java.gradle"
dependencies {
api project(':opentcs-api-injection')
api project(':opentcs-common')
implementation group: 'org.jgrapht', name: 'jgrapht-core', version: '1.5.2'
implementation group: 'org.locationtech.jts', name: 'jts-core', version: '1.19.0'
}
task release {
dependsOn build
}

View File

@@ -0,0 +1,40 @@
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineMethodParams=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAfterDotInChainedMethodCalls=false
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineDisjunctiveCatchTypes=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineFor=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineImplements=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.sortMembersByVisibility=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.visibilityOrder=PUBLIC;PROTECTED;DEFAULT;PRIVATE
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.enable-indent=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineArrayInit=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineCallArgs=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.keepGettersAndSettersTogether=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classMembersOrder=STATIC FIELD;FIELD;STATIC_INIT;CONSTRUCTOR;INSTANCE_INIT;STATIC METHOD;METHOD;STATIC CLASS;CLASS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapCommentText=false
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.importGroupsOrder=*
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize=4
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineAnnotationArgs=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineTryResources=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.preserveNewLinesInComments=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineParenthesized=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineThrows=true
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=2
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=2
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=2
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=100
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true
netbeans.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project

View File

@@ -0,0 +1,231 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import jakarta.inject.Singleton;
import java.util.Comparator;
import org.opentcs.customizations.kernel.KernelInjectionModule;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.phase.parking.DefaultParkingPositionSupplier;
import org.opentcs.strategies.basic.dispatching.phase.parking.ParkingPositionSupplier;
import org.opentcs.strategies.basic.dispatching.phase.recharging.DefaultRechargePositionSupplier;
import org.opentcs.strategies.basic.dispatching.phase.recharging.RechargePositionSupplier;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeOrderCandidateComparator;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeOrderComparator;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeVehicleCandidateComparator;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeVehicleComparator;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByCompleteRoutingCosts;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByDeadline;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByEnergyLevel;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByInitialRoutingCosts;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByOrderAge;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByOrderName;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByVehicleName;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorDeadlineAtRiskFirst;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorIdleFirst;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByAge;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByDeadline;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByName;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorDeadlineAtRiskFirst;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorByEnergyLevel;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorByName;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorIdleFirst;
import org.opentcs.strategies.basic.dispatching.rerouting.ForcedReroutingStrategy;
import org.opentcs.strategies.basic.dispatching.rerouting.RegularDriveOrderMerger;
import org.opentcs.strategies.basic.dispatching.rerouting.RegularReroutingStrategy;
import org.opentcs.strategies.basic.dispatching.rerouting.ReroutingStrategy;
import org.opentcs.strategies.basic.dispatching.selection.AssignmentCandidateSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.ParkVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.RechargeVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.ReparkVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.TransportOrderSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.VehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.candidates.IsProcessable;
import org.opentcs.strategies.basic.dispatching.selection.orders.CompositeTransportOrderSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.orders.ContainsLockedTargetLocations;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeParkVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeRechargeVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeReparkVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.IsIdleAndDegraded;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.IsParkable;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.IsReparkable;
/**
* Guice configuration for the default dispatcher.
*/
public class DefaultDispatcherModule
extends
KernelInjectionModule {
/**
* Creates a new instance.
*/
public DefaultDispatcherModule() {
}
@Override
protected void configure() {
configureDispatcherDependencies();
bindDispatcher(DefaultDispatcher.class);
}
private void configureDispatcherDependencies() {
Multibinder.newSetBinder(binder(), VehicleSelectionFilter.class);
Multibinder.newSetBinder(binder(), TransportOrderSelectionFilter.class)
.addBinding().to(ContainsLockedTargetLocations.class);
Multibinder.newSetBinder(binder(), ParkVehicleSelectionFilter.class)
.addBinding().to(IsParkable.class);
Multibinder.newSetBinder(binder(), ReparkVehicleSelectionFilter.class)
.addBinding().to(IsReparkable.class);
Multibinder.newSetBinder(binder(), RechargeVehicleSelectionFilter.class)
.addBinding().to(IsIdleAndDegraded.class);
Multibinder.newSetBinder(binder(), AssignmentCandidateSelectionFilter.class)
.addBinding().to(IsProcessable.class);
bind(CompositeParkVehicleSelectionFilter.class)
.in(Singleton.class);
bind(CompositeReparkVehicleSelectionFilter.class)
.in(Singleton.class);
bind(CompositeRechargeVehicleSelectionFilter.class)
.in(Singleton.class);
bind(CompositeTransportOrderSelectionFilter.class)
.in(Singleton.class);
bind(CompositeVehicleSelectionFilter.class)
.in(Singleton.class);
bind(CompositeAssignmentCandidateSelectionFilter.class)
.in(Singleton.class);
bind(DefaultDispatcherConfiguration.class)
.toInstance(
getConfigBindingProvider().get(
DefaultDispatcherConfiguration.PREFIX,
DefaultDispatcherConfiguration.class
)
);
bind(OrderReservationPool.class)
.in(Singleton.class);
bind(ParkingPositionSupplier.class)
.to(DefaultParkingPositionSupplier.class)
.in(Singleton.class);
bind(RechargePositionSupplier.class)
.to(DefaultRechargePositionSupplier.class)
.in(Singleton.class);
MapBinder<String, Comparator<Vehicle>> vehicleComparatorBinder
= MapBinder.newMapBinder(
binder(),
new TypeLiteral<String>() {
},
new TypeLiteral<Comparator<Vehicle>>() {
}
);
vehicleComparatorBinder
.addBinding(VehicleComparatorByEnergyLevel.CONFIGURATION_KEY)
.to(VehicleComparatorByEnergyLevel.class);
vehicleComparatorBinder
.addBinding(VehicleComparatorByName.CONFIGURATION_KEY)
.to(VehicleComparatorByName.class);
vehicleComparatorBinder
.addBinding(VehicleComparatorIdleFirst.CONFIGURATION_KEY)
.to(VehicleComparatorIdleFirst.class);
MapBinder<String, Comparator<TransportOrder>> orderComparatorBinder
= MapBinder.newMapBinder(
binder(),
new TypeLiteral<String>() {
},
new TypeLiteral<Comparator<TransportOrder>>() {
}
);
orderComparatorBinder
.addBinding(TransportOrderComparatorByAge.CONFIGURATION_KEY)
.to(TransportOrderComparatorByAge.class);
orderComparatorBinder
.addBinding(TransportOrderComparatorByDeadline.CONFIGURATION_KEY)
.to(TransportOrderComparatorByDeadline.class);
orderComparatorBinder
.addBinding(TransportOrderComparatorDeadlineAtRiskFirst.CONFIGURATION_KEY)
.to(TransportOrderComparatorDeadlineAtRiskFirst.class);
orderComparatorBinder
.addBinding(TransportOrderComparatorByName.CONFIGURATION_KEY)
.to(TransportOrderComparatorByName.class);
MapBinder<String, Comparator<AssignmentCandidate>> candidateComparatorBinder
= MapBinder.newMapBinder(
binder(),
new TypeLiteral<String>() {
},
new TypeLiteral<Comparator<AssignmentCandidate>>() {
}
);
candidateComparatorBinder
.addBinding(CandidateComparatorByCompleteRoutingCosts.CONFIGURATION_KEY)
.to(CandidateComparatorByCompleteRoutingCosts.class);
candidateComparatorBinder
.addBinding(CandidateComparatorByDeadline.CONFIGURATION_KEY)
.to(CandidateComparatorByDeadline.class);
candidateComparatorBinder
.addBinding(CandidateComparatorDeadlineAtRiskFirst.CONFIGURATION_KEY)
.to(CandidateComparatorDeadlineAtRiskFirst.class);
candidateComparatorBinder
.addBinding(CandidateComparatorByEnergyLevel.CONFIGURATION_KEY)
.to(CandidateComparatorByEnergyLevel.class);
candidateComparatorBinder
.addBinding(CandidateComparatorByInitialRoutingCosts.CONFIGURATION_KEY)
.to(CandidateComparatorByInitialRoutingCosts.class);
candidateComparatorBinder
.addBinding(CandidateComparatorByOrderAge.CONFIGURATION_KEY)
.to(CandidateComparatorByOrderAge.class);
candidateComparatorBinder
.addBinding(CandidateComparatorByOrderName.CONFIGURATION_KEY)
.to(CandidateComparatorByOrderName.class);
candidateComparatorBinder
.addBinding(CandidateComparatorByVehicleName.CONFIGURATION_KEY)
.to(CandidateComparatorByVehicleName.class);
candidateComparatorBinder
.addBinding(CandidateComparatorIdleFirst.CONFIGURATION_KEY)
.to(CandidateComparatorIdleFirst.class);
bind(CompositeVehicleComparator.class)
.in(Singleton.class);
bind(CompositeOrderComparator.class)
.in(Singleton.class);
bind(CompositeOrderCandidateComparator.class)
.in(Singleton.class);
bind(CompositeVehicleCandidateComparator.class)
.in(Singleton.class);
bind(TransportOrderUtil.class)
.in(Singleton.class);
configureRerouteComponents();
}
private void configureRerouteComponents() {
bind(RerouteUtil.class).in(Singleton.class);
bind(RegularReroutingStrategy.class).in(Singleton.class);
bind(RegularDriveOrderMerger.class).in(Singleton.class);
MapBinder<ReroutingType, ReroutingStrategy> reroutingStrategies
= MapBinder.newMapBinder(
binder(),
ReroutingType.class,
ReroutingStrategy.class
);
reroutingStrategies
.addBinding(ReroutingType.REGULAR)
.to(RegularReroutingStrategy.class);
reroutingStrategies
.addBinding(ReroutingType.FORCED)
.to(ForcedReroutingStrategy.class);
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import org.opentcs.customizations.kernel.KernelInjectionModule;
import org.opentcs.drivers.peripherals.PeripheralJobCallback;
/**
* Guice configuration for the default peripheral job dispatcher.
*/
public class DefaultPeripheralJobDispatcherModule
extends
KernelInjectionModule {
/**
* Creates a new instance.
*/
public DefaultPeripheralJobDispatcherModule() {
}
@Override
protected void configure() {
configureDispatcherDependencies();
bindPeripheralJobDispatcher(DefaultPeripheralJobDispatcher.class);
}
private void configureDispatcherDependencies() {
bind(DefaultPeripheralJobDispatcherConfiguration.class)
.toInstance(
getConfigBindingProvider().get(
DefaultPeripheralJobDispatcherConfiguration.PREFIX,
DefaultPeripheralJobDispatcherConfiguration.class
)
);
bind(PeripheralJobCallback.class).to(DefaultPeripheralJobDispatcher.class);
bind(PeripheralReleaseStrategy.class).to(DefaultPeripheralReleaseStrategy.class);
bind(JobSelectionStrategy.class).to(DefaultJobSelectionStrategy.class);
}
}

View File

@@ -0,0 +1,125 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.routing;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import jakarta.inject.Singleton;
import org.opentcs.components.kernel.routing.GroupMapper;
import org.opentcs.customizations.kernel.KernelInjectionModule;
import org.opentcs.strategies.basic.routing.edgeevaluator.EdgeEvaluatorBoundingBox;
import org.opentcs.strategies.basic.routing.edgeevaluator.EdgeEvaluatorComposite;
import org.opentcs.strategies.basic.routing.edgeevaluator.EdgeEvaluatorDistance;
import org.opentcs.strategies.basic.routing.edgeevaluator.EdgeEvaluatorExplicitProperties;
import org.opentcs.strategies.basic.routing.edgeevaluator.EdgeEvaluatorHops;
import org.opentcs.strategies.basic.routing.edgeevaluator.EdgeEvaluatorTravelTime;
import org.opentcs.strategies.basic.routing.edgeevaluator.ExplicitPropertiesConfiguration;
import org.opentcs.strategies.basic.routing.jgrapht.BellmanFordPointRouterFactory;
import org.opentcs.strategies.basic.routing.jgrapht.DijkstraPointRouterFactory;
import org.opentcs.strategies.basic.routing.jgrapht.FloydWarshallPointRouterFactory;
import org.opentcs.strategies.basic.routing.jgrapht.GraphProvider;
import org.opentcs.strategies.basic.routing.jgrapht.MapperComponentsFactory;
import org.opentcs.strategies.basic.routing.jgrapht.ShortestPathConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Guice configuration for the default router.
*/
public class DefaultRouterModule
extends
KernelInjectionModule {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultRouterModule.class);
/**
* Creates a new instance.
*/
public DefaultRouterModule() {
}
@Override
protected void configure() {
configureRouterDependencies();
bindRouter(DefaultRouter.class);
}
private void configureRouterDependencies() {
bind(DefaultRouterConfiguration.class)
.toInstance(
getConfigBindingProvider().get(
DefaultRouterConfiguration.PREFIX,
DefaultRouterConfiguration.class
)
);
ShortestPathConfiguration spConfiguration
= getConfigBindingProvider().get(
ShortestPathConfiguration.PREFIX,
ShortestPathConfiguration.class
);
bind(ShortestPathConfiguration.class)
.toInstance(spConfiguration);
install(new FactoryModuleBuilder().build(MapperComponentsFactory.class));
bind(GraphProvider.class)
.in(Singleton.class);
switch (spConfiguration.algorithm()) {
case DIJKSTRA:
bind(PointRouterFactory.class)
.to(DijkstraPointRouterFactory.class);
break;
case BELLMAN_FORD:
bind(PointRouterFactory.class)
.to(BellmanFordPointRouterFactory.class);
break;
case FLOYD_WARSHALL:
bind(PointRouterFactory.class)
.to(FloydWarshallPointRouterFactory.class);
break;
default:
LOG.warn(
"Unhandled algorithm selected ({}), falling back to Dijkstra's algorithm.",
spConfiguration.algorithm()
);
bind(PointRouterFactory.class)
.to(DijkstraPointRouterFactory.class);
}
edgeEvaluatorBinder()
.addBinding(EdgeEvaluatorDistance.CONFIGURATION_KEY)
.to(EdgeEvaluatorDistance.class);
edgeEvaluatorBinder()
.addBinding(EdgeEvaluatorExplicitProperties.CONFIGURATION_KEY)
.to(EdgeEvaluatorExplicitProperties.class);
edgeEvaluatorBinder()
.addBinding(EdgeEvaluatorHops.CONFIGURATION_KEY)
.to(EdgeEvaluatorHops.class);
edgeEvaluatorBinder()
.addBinding(EdgeEvaluatorTravelTime.CONFIGURATION_KEY)
.to(EdgeEvaluatorTravelTime.class);
edgeEvaluatorBinder()
.addBinding(EdgeEvaluatorBoundingBox.CONFIGURATION_KEY)
.to(EdgeEvaluatorBoundingBox.class);
bind(EdgeEvaluatorComposite.class)
.in(Singleton.class);
bind(ExplicitPropertiesConfiguration.class)
.toInstance(
getConfigBindingProvider().get(
ExplicitPropertiesConfiguration.PREFIX,
ExplicitPropertiesConfiguration.class
)
);
bind(DefaultRoutingGroupMapper.class)
.in(Singleton.class);
bind(GroupMapper.class)
.to(DefaultRoutingGroupMapper.class);
}
}

View File

@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.scheduling;
import com.google.inject.multibindings.Multibinder;
import jakarta.inject.Singleton;
import org.opentcs.components.kernel.Scheduler;
import org.opentcs.customizations.kernel.KernelInjectionModule;
import org.opentcs.strategies.basic.scheduling.modules.PausedVehicleModule;
import org.opentcs.strategies.basic.scheduling.modules.SameDirectionBlockModule;
import org.opentcs.strategies.basic.scheduling.modules.SingleVehicleBlockModule;
import org.opentcs.strategies.basic.scheduling.modules.areaAllocation.AreaAllocationModule;
import org.opentcs.strategies.basic.scheduling.modules.areaAllocation.AreaAllocations;
import org.opentcs.strategies.basic.scheduling.modules.areaAllocation.AreaProvider;
import org.opentcs.strategies.basic.scheduling.modules.areaAllocation.CachingAreaProvider;
/**
* Guice configuration for the default scheduler.
*/
public class DefaultSchedulerModule
extends
KernelInjectionModule {
/**
* Creates a new instance.
*/
public DefaultSchedulerModule() {
}
@Override
protected void configure() {
configureSchedulerDependencies();
bindScheduler(DefaultScheduler.class);
}
private void configureSchedulerDependencies() {
bind(ReservationPool.class).in(Singleton.class);
Multibinder<Scheduler.Module> moduleBinder = schedulerModuleBinder();
moduleBinder.addBinding().to(SingleVehicleBlockModule.class);
moduleBinder.addBinding().to(SameDirectionBlockModule.class);
moduleBinder.addBinding().to(PausedVehicleModule.class);
moduleBinder.addBinding().to(AreaAllocationModule.class);
bind(AreaProvider.class)
.to(CachingAreaProvider.class)
.in(Singleton.class);
bind(AreaAllocations.class).in(Singleton.class);
}
}

View File

@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import java.util.List;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.TransportOrder;
/**
* Contains information for a potential assignment of a transport order to a vehicle.
*/
public class AssignmentCandidate {
/**
* The vehicle.
*/
private final Vehicle vehicle;
/**
* The transport order.
*/
private final TransportOrder transportOrder;
/**
* The route/drive orders to be executed upon assignment.
*/
private final List<DriveOrder> driveOrders;
/**
* The completeRoutingCosts for processing the whole order with the vehicle.
*/
private final long completeRoutingCosts;
/**
* Creates a new instance.
*
* @param vehicle The vehicle that would be assigned to the transport order.
* @param transportOrder The transport order that would be assigned to the vehicle.
* @param driveOrders The drive orders containing the computed route the vehicle would take.
* May not be empty and the route of each drive order may not be null.
*/
public AssignmentCandidate(
Vehicle vehicle,
TransportOrder transportOrder,
List<DriveOrder> driveOrders
) {
this.vehicle = requireNonNull(vehicle, "vehicle");
this.transportOrder = requireNonNull(transportOrder, "transportOrder");
this.driveOrders = requireNonNull(driveOrders, "driveOrders");
checkArgument(!driveOrders.isEmpty(), "driveOrders is empty");
driveOrders.forEach(
driveOrder -> checkArgument(
driveOrder.getRoute() != null,
"a drive order's route is null"
)
);
this.completeRoutingCosts = cumulatedCosts(driveOrders);
}
public Vehicle getVehicle() {
return vehicle;
}
public TransportOrder getTransportOrder() {
return transportOrder;
}
public List<DriveOrder> getDriveOrders() {
return driveOrders;
}
/**
* Returns the costs for travelling only the first drive order/reaching the first destination.
*
* @return The costs for travelling only the first drive order.
*/
public long getInitialRoutingCosts() {
return driveOrders.get(0).getRoute().getCosts();
}
/**
* Returns the costs for travelling all drive orders.
*
* @return The costs for travelling all drive orders.
*/
public long getCompleteRoutingCosts() {
return completeRoutingCosts;
}
private static long cumulatedCosts(List<DriveOrder> driveOrders) {
return driveOrders.stream().mapToLong(driveOrder -> driveOrder.getRoute().getCosts()).sum();
}
}

View File

@@ -0,0 +1,242 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.opentcs.components.kernel.Dispatcher;
import org.opentcs.components.kernel.dipatching.TransportOrderAssignmentException;
import org.opentcs.components.kernel.dipatching.TransportOrderAssignmentVeto;
import org.opentcs.components.kernel.services.InternalVehicleService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.phase.assignment.OrderAssigner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Dispatches transport orders and vehicles.
*/
public class DefaultDispatcher
implements
Dispatcher {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultDispatcher.class);
/**
* Stores reservations of transport orders for vehicles.
*/
private final OrderReservationPool orderReservationPool;
/**
* Provides services/utility methods for working with transport orders.
*/
private final TransportOrderUtil transportOrderUtil;
/**
* The vehicle service.
*/
private final InternalVehicleService vehicleService;
/**
* The kernel's executor.
*/
private final ScheduledExecutorService kernelExecutor;
private final FullDispatchTask fullDispatchTask;
private final Provider<PeriodicVehicleRedispatchingTask> periodicDispatchTaskProvider;
private final DefaultDispatcherConfiguration configuration;
private final RerouteUtil rerouteUtil;
private final OrderAssigner orderAssigner;
private final TransportOrderAssignmentChecker transportOrderAssignmentChecker;
private ScheduledFuture<?> periodicDispatchTaskFuture;
/**
* Indicates whether this component is enabled.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param orderReservationPool Stores reservations of transport orders for vehicles.
* @param transportOrderUtil Provides services for working with transport orders.
* @param vehicleService The vehicle service.
* @param kernelExecutor Executes dispatching tasks.
* @param fullDispatchTask The full dispatch task.
* @param periodicDispatchTaskProvider Provides the periodic vehicle redospatching task.
* @param configuration The dispatcher configuration.
* @param rerouteUtil The reroute util.
* @param orderAssigner Handles assignments of transport orders to vehicles.
* @param transportOrderAssignmentChecker Checks whether the assignment of transport orders to
* vehicles is possible.
*/
@Inject
public DefaultDispatcher(
OrderReservationPool orderReservationPool,
TransportOrderUtil transportOrderUtil,
InternalVehicleService vehicleService,
@KernelExecutor
ScheduledExecutorService kernelExecutor,
FullDispatchTask fullDispatchTask,
Provider<PeriodicVehicleRedispatchingTask> periodicDispatchTaskProvider,
DefaultDispatcherConfiguration configuration,
RerouteUtil rerouteUtil,
OrderAssigner orderAssigner,
TransportOrderAssignmentChecker transportOrderAssignmentChecker
) {
this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
this.fullDispatchTask = requireNonNull(fullDispatchTask, "fullDispatchTask");
this.periodicDispatchTaskProvider = requireNonNull(
periodicDispatchTaskProvider,
"periodicDispatchTaskProvider"
);
this.configuration = requireNonNull(configuration, "configuration");
this.rerouteUtil = requireNonNull(rerouteUtil, "rerouteUtil");
this.orderAssigner = requireNonNull(orderAssigner, "orderAssigner");
this.transportOrderAssignmentChecker = requireNonNull(
transportOrderAssignmentChecker,
"transportOrderAssignmentChecker"
);
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
LOG.debug("Initializing...");
transportOrderUtil.initialize();
orderReservationPool.clear();
fullDispatchTask.initialize();
LOG.debug(
"Scheduling periodic dispatch task with interval of {} ms...",
configuration.idleVehicleRedispatchingInterval()
);
periodicDispatchTaskFuture = kernelExecutor.scheduleAtFixedRate(
periodicDispatchTaskProvider.get(),
configuration.idleVehicleRedispatchingInterval(),
configuration.idleVehicleRedispatchingInterval(),
TimeUnit.MILLISECONDS
);
initialized = true;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
LOG.debug("Terminating...");
periodicDispatchTaskFuture.cancel(false);
periodicDispatchTaskFuture = null;
fullDispatchTask.terminate();
initialized = false;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void dispatch() {
LOG.debug("Executing dispatch task...");
fullDispatchTask.run();
}
@Override
public void withdrawOrder(TransportOrder order, boolean immediateAbort) {
requireNonNull(order, "order");
checkState(isInitialized(), "Not initialized");
LOG.debug(
"Withdrawing transport order '{}' (immediate={})...",
order.getName(),
immediateAbort
);
transportOrderUtil.abortOrder(order, immediateAbort);
}
@Override
public void withdrawOrder(Vehicle vehicle, boolean immediateAbort) {
requireNonNull(vehicle, "vehicle");
checkState(isInitialized(), "Not initialized");
LOG.debug(
"Withdrawing transport order for vehicle '{}' (immediate={})...",
vehicle.getName(),
immediateAbort
);
transportOrderUtil.abortOrder(vehicle, immediateAbort);
}
@Override
public void reroute(Vehicle vehicle, ReroutingType reroutingType) {
requireNonNull(vehicle, "vehicle");
requireNonNull(reroutingType, "reroutingType");
LOG.info(
"Rerouting vehicle '{}' from its current position '{}' using rerouting type '{}'...",
vehicle.getName(),
vehicle.getCurrentPosition() == null ? null : vehicle.getCurrentPosition().getName(),
reroutingType
);
rerouteUtil.reroute(vehicle, reroutingType);
}
@Override
public void rerouteAll(ReroutingType reroutingType) {
requireNonNull(reroutingType, "reroutingType");
LOG.info("Rerouting all vehicles using rerouting type '{}'...", reroutingType);
rerouteUtil.reroute(vehicleService.fetchObjects(Vehicle.class), reroutingType);
}
@Override
public void assignNow(TransportOrder transportOrder)
throws TransportOrderAssignmentException {
requireNonNull(transportOrder, "transportOrder");
TransportOrderAssignmentVeto assignmentVeto
= transportOrderAssignmentChecker.checkTransportOrderAssignment(transportOrder);
if (assignmentVeto != TransportOrderAssignmentVeto.NO_VETO) {
throw new TransportOrderAssignmentException(
transportOrder.getReference(),
transportOrder.getIntendedVehicle(),
assignmentVeto
);
}
orderAssigner.tryAssignments(
List.of(vehicleService.fetchObject(Vehicle.class, transportOrder.getIntendedVehicle())),
List.of(transportOrder)
);
}
}

View File

@@ -0,0 +1,182 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import java.util.List;
import org.opentcs.configuration.ConfigurationEntry;
import org.opentcs.configuration.ConfigurationPrefix;
/**
* Provides methods to configure the {@link DefaultDispatcher}.
*/
@ConfigurationPrefix(DefaultDispatcherConfiguration.PREFIX)
public interface DefaultDispatcherConfiguration {
/**
* This configuration's prefix.
*/
String PREFIX = "defaultdispatcher";
@ConfigurationEntry(
type = "Comma-separated list of strings",
description = {"Keys by which to prioritize transport orders for assignment.",
"Possible values:",
"BY_AGE: Sort by age, oldest first.",
"BY_DEADLINE: Sort by deadline, most urgent first.",
"DEADLINE_AT_RISK_FIRST: Sort orders with deadlines at risk first.",
"BY_NAME: Sort by name, lexicographically."},
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_assign"
)
List<String> orderPriorities();
@ConfigurationEntry(
type = "Comma-separated list of strings",
description = {"Keys by which to prioritize vehicles for assignment.",
"Possible values:",
"BY_ENERGY_LEVEL: Sort by energy level, highest first.",
"IDLE_FIRST: Sort vehicles with state IDLE first.",
"BY_NAME: Sort by name, lexicographically."},
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_assign"
)
List<String> vehiclePriorities();
@ConfigurationEntry(
type = "Comma-separated list of strings",
description = {"Keys by which to prioritize vehicle candidates for assignment.",
"Possible values:",
"BY_ENERGY_LEVEL: Sort by energy level of the vehicle, highest first.",
"IDLE_FIRST: Sort vehicles with state IDLE first.",
"BY_COMPLETE_ROUTING_COSTS: Sort by complete routing costs, lowest first.",
"BY_INITIAL_ROUTING_COSTS: Sort by routing costs for the first destination.",
"BY_VEHICLE_NAME: Sort by vehicle name, lexicographically."},
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_assign"
)
List<String> vehicleCandidatePriorities();
@ConfigurationEntry(
type = "Comma-separated list of strings",
description = {"Keys by which to prioritize transport order candidates for assignment.",
"Possible values:",
"BY_AGE: Sort by transport order age, oldest first.",
"BY_DEADLINE: Sort by transport order deadline, most urgent first.",
"DEADLINE_AT_RISK_FIRST: Sort orders with deadlines at risk first.",
"BY_COMPLETE_ROUTING_COSTS: Sort by complete routing costs, lowest first.",
"BY_INITIAL_ROUTING_COSTS: Sort by routing costs for the first destination.",
"BY_ORDER_NAME: Sort by transport order name, lexicographically."},
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_assign"
)
List<String> orderCandidatePriorities();
@ConfigurationEntry(
type = "Integer",
description = "The time window (in ms) before its deadline in which an order becomes urgent.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_assign_special_0"
)
long deadlineAtRiskPeriod();
@ConfigurationEntry(
type = "Boolean",
description = "Whether orders to the current position with no operation should be assigned.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "1_orders_special_0"
)
boolean assignRedundantOrders();
@ConfigurationEntry(
type = "Boolean",
description = "Whether unroutable incoming transport orders should be marked as UNROUTABLE.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "1_orders_special_1"
)
boolean dismissUnroutableTransportOrders();
@ConfigurationEntry(
type = "String",
description = {
"The strategy to use when rerouting of a vehicle results in no route at all.",
"The vehicle then continues to use the previous route in the configured way.",
"Possible values:",
"IGNORE_PATH_LOCKS: Stick to the previous route, ignoring path locks.",
"PAUSE_IMMEDIATELY: Do not send further orders to the vehicle; wait for another "
+ "rerouting opportunity.",
"PAUSE_AT_PATH_LOCK: Send further orders to the vehicle only until it reaches a locked "
+ "path; then wait for another rerouting opportunity."
},
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "1_orders_special_2"
)
ReroutingImpossibleStrategy reroutingImpossibleStrategy();
@ConfigurationEntry(
type = "Boolean",
description = "Whether to automatically create parking orders for idle vehicles.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "2_park_0"
)
boolean parkIdleVehicles();
@ConfigurationEntry(
type = "Boolean",
description = "Whether to consider parking position priorities when creating parking orders.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "2_park_1"
)
boolean considerParkingPositionPriorities();
@ConfigurationEntry(
type = "Boolean",
description = "Whether to repark vehicles to parking positions with higher priorities.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "2_park_2"
)
boolean reparkVehiclesToHigherPriorityPositions();
@ConfigurationEntry(
type = "Boolean",
description = "Whether to automatically create recharge orders for idle vehicles.",
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "3_recharge_0"
)
boolean rechargeIdleVehicles();
@ConfigurationEntry(
type = "Boolean",
description = {"Whether vehicles must be recharged until they are fully charged.",
"If false, vehicle must only be recharged until sufficiently charged."},
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
orderKey = "3_recharge_1"
)
boolean keepRechargingUntilFullyCharged();
@ConfigurationEntry(
type = "Integer",
description = "The interval between redispatching of vehicles.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
orderKey = "9_misc"
)
long idleVehicleRedispatchingInterval();
/**
* The available strategies for situations in which rerouting is not possible.
*/
enum ReroutingImpossibleStrategy {
/**
* Stick to the previous route, ignoring path locks.
*/
IGNORE_PATH_LOCKS,
/**
* Do not send further orders to the vehicle; wait for another rerouting opportunity.
*/
PAUSE_IMMEDIATELY,
/**
* Send further orders to the vehicle only until it reaches a locked path; then wait for another
* rerouting opportunity.
*/
PAUSE_AT_PATH_LOCK;
}
}

View File

@@ -0,0 +1,184 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.Lifecycle;
import org.opentcs.strategies.basic.dispatching.phase.AssignReservedOrdersPhase;
import org.opentcs.strategies.basic.dispatching.phase.AssignSequenceSuccessorsPhase;
import org.opentcs.strategies.basic.dispatching.phase.CheckNewOrdersPhase;
import org.opentcs.strategies.basic.dispatching.phase.FinishWithdrawalsPhase;
import org.opentcs.strategies.basic.dispatching.phase.assignment.AssignFreeOrdersPhase;
import org.opentcs.strategies.basic.dispatching.phase.assignment.AssignNextDriveOrdersPhase;
import org.opentcs.strategies.basic.dispatching.phase.parking.ParkIdleVehiclesPhase;
import org.opentcs.strategies.basic.dispatching.phase.parking.PrioritizedParkingPhase;
import org.opentcs.strategies.basic.dispatching.phase.parking.PrioritizedReparkPhase;
import org.opentcs.strategies.basic.dispatching.phase.recharging.RechargeIdleVehiclesPhase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Performs a full dispatch run.
*/
public class FullDispatchTask
implements
Runnable,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(FullDispatchTask.class);
private final CheckNewOrdersPhase checkNewOrdersPhase;
private final FinishWithdrawalsPhase finishWithdrawalsPhase;
private final AssignNextDriveOrdersPhase assignNextDriveOrdersPhase;
private final AssignReservedOrdersPhase assignReservedOrdersPhase;
private final AssignSequenceSuccessorsPhase assignSequenceSuccessorsPhase;
private final AssignFreeOrdersPhase assignFreeOrdersPhase;
private final RechargeIdleVehiclesPhase rechargeIdleVehiclesPhase;
private final PrioritizedReparkPhase prioritizedReparkPhase;
private final PrioritizedParkingPhase prioritizedParkingPhase;
private final ParkIdleVehiclesPhase parkIdleVehiclesPhase;
/**
* Indicates whether this component is enabled.
*/
private boolean initialized;
@Inject
public FullDispatchTask(
CheckNewOrdersPhase checkNewOrdersPhase,
FinishWithdrawalsPhase finishWithdrawalsPhase,
AssignNextDriveOrdersPhase assignNextDriveOrdersPhase,
AssignReservedOrdersPhase assignReservedOrdersPhase,
AssignSequenceSuccessorsPhase assignSequenceSuccessorsPhase,
AssignFreeOrdersPhase assignFreeOrdersPhase,
RechargeIdleVehiclesPhase rechargeIdleVehiclesPhase,
PrioritizedReparkPhase prioritizedReparkPhase,
PrioritizedParkingPhase prioritizedParkingPhase,
ParkIdleVehiclesPhase parkIdleVehiclesPhase
) {
this.checkNewOrdersPhase = requireNonNull(checkNewOrdersPhase, "checkNewOrdersPhase");
this.finishWithdrawalsPhase = requireNonNull(finishWithdrawalsPhase, "finishWithdrawalsPhase");
this.assignNextDriveOrdersPhase = requireNonNull(
assignNextDriveOrdersPhase,
"assignNextDriveOrdersPhase"
);
this.assignReservedOrdersPhase = requireNonNull(
assignReservedOrdersPhase,
"assignReservedOrdersPhase"
);
this.assignSequenceSuccessorsPhase = requireNonNull(
assignSequenceSuccessorsPhase,
"assignSequenceSuccessorsPhase"
);
this.assignFreeOrdersPhase = requireNonNull(assignFreeOrdersPhase, "assignFreeOrdersPhase");
this.rechargeIdleVehiclesPhase = requireNonNull(
rechargeIdleVehiclesPhase,
"rechargeIdleVehiclesPhase"
);
this.prioritizedReparkPhase = requireNonNull(prioritizedReparkPhase, "prioritizedReparkPhase");
this.prioritizedParkingPhase = requireNonNull(
prioritizedParkingPhase,
"prioritizedParkingPhase"
);
this.parkIdleVehiclesPhase = requireNonNull(parkIdleVehiclesPhase, "parkIdleVehiclesPhase");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
checkNewOrdersPhase.initialize();
finishWithdrawalsPhase.initialize();
assignNextDriveOrdersPhase.initialize();
assignReservedOrdersPhase.initialize();
assignSequenceSuccessorsPhase.initialize();
assignFreeOrdersPhase.initialize();
rechargeIdleVehiclesPhase.initialize();
prioritizedReparkPhase.initialize();
prioritizedParkingPhase.initialize();
parkIdleVehiclesPhase.initialize();
initialized = true;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
checkNewOrdersPhase.terminate();
finishWithdrawalsPhase.terminate();
assignNextDriveOrdersPhase.terminate();
assignReservedOrdersPhase.terminate();
assignSequenceSuccessorsPhase.terminate();
assignFreeOrdersPhase.terminate();
rechargeIdleVehiclesPhase.terminate();
prioritizedReparkPhase.terminate();
prioritizedParkingPhase.terminate();
parkIdleVehiclesPhase.terminate();
initialized = false;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public final void run() {
LOG.debug("Starting full dispatch run...");
checkNewOrdersPhase.run();
// Check what vehicles involved in a process should do.
finishWithdrawalsPhase.run();
assignNextDriveOrdersPhase.run();
assignSequenceSuccessorsPhase.run();
// Check what vehicles not already in a process should do.
assignOrders();
rechargeVehicles();
parkVehicles();
LOG.debug("Finished full dispatch run.");
}
/**
* Assignment of orders to vehicles.
* <p>
* Default: Assigns reserved and then free orders to vehicles.
* </p>
*/
protected void assignOrders() {
assignReservedOrdersPhase.run();
assignFreeOrdersPhase.run();
}
/**
* Recharging of vehicles.
* <p>
* Default: Sends idle vehicles with a degraded energy level to recharge locations.
* </p>
*/
protected void rechargeVehicles() {
rechargeIdleVehiclesPhase.run();
}
/**
* Parking of vehicles.
* <p>
* Default: Sends idle vehicles to parking positions.
* </p>
*/
protected void parkVehicles() {
prioritizedReparkPhase.run();
prioritizedParkingPhase.run();
parkIdleVehiclesPhase.run();
}
}

View File

@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
/**
* Stores reservations of orders for vehicles.
*/
public class OrderReservationPool {
/**
* Reservations of orders for vehicles.
*/
private final Map<TCSObjectReference<TransportOrder>, TCSObjectReference<Vehicle>> reservations
= Collections.synchronizedMap(new HashMap<>());
/**
* Creates a new instance.
*/
@Inject
public OrderReservationPool() {
}
/**
* Clears all reservations.
*/
public void clear() {
reservations.clear();
}
/**
* Checks whether there is a reservation of the given transport order for any vehicle.
*
* @param orderRef A reference to the transport order.
* @return <code>true</code> if, and only if, there is a reservation.
*/
public boolean isReserved(
@Nonnull
TCSObjectReference<TransportOrder> orderRef
) {
return reservations.containsKey(orderRef);
}
public void addReservation(
@Nonnull
TCSObjectReference<TransportOrder> orderRef,
@Nonnull
TCSObjectReference<Vehicle> vehicleRef
) {
reservations.put(orderRef, vehicleRef);
}
public void removeReservation(
@Nonnull
TCSObjectReference<TransportOrder> orderRef
) {
reservations.remove(orderRef);
}
public void removeReservations(
@Nonnull
TCSObjectReference<Vehicle> vehicleRef
) {
reservations.values().removeIf(value -> vehicleRef.equals(value));
}
public List<TCSObjectReference<TransportOrder>> findReservations(
@Nonnull
TCSObjectReference<Vehicle> vehicleRef
) {
return reservations.entrySet().stream()
.filter(entry -> vehicleRef.equals(entry.getValue()))
.map(entry -> entry.getKey())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.services.DispatcherService;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Periodically checks for idle vehicles that could process a transport order.
* The main purpose of doing this is retrying to dispatch vehicles that were not in a dispatchable
* state when dispatching them was last tried.
* A potential reason for this is that a vehicle temporarily reported an error because a safety
* sensor was triggered.
*/
public class PeriodicVehicleRedispatchingTask
implements
Runnable {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PeriodicVehicleRedispatchingTask.class);
private final DispatcherService dispatcherService;
private final TCSObjectService objectService;
/**
* Creates a new instance.
*
* @param dispatcherService The dispatcher service used to dispatch vehicles.
* @param objectService The object service.
*/
@Inject
public PeriodicVehicleRedispatchingTask(
DispatcherService dispatcherService,
TCSObjectService objectService
) {
this.dispatcherService = requireNonNull(dispatcherService, "dispatcherService");
this.objectService = requireNonNull(objectService, "objectService");
}
@Override
public void run() {
// If there are any vehicles that could process a transport order,
// trigger the dispatcher once.
objectService.fetchObjects(Vehicle.class, this::couldProcessTransportOrder).stream()
.findAny()
.ifPresent(vehicle -> {
LOG.debug("Vehicle {} could process transport order, triggering dispatcher ...", vehicle);
dispatcherService.dispatch();
});
}
private boolean couldProcessTransportOrder(Vehicle vehicle) {
return vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
&& vehicle.getCurrentPosition() != null
&& (processesNoOrder(vehicle)
|| processesDispensableOrder(vehicle));
}
private boolean processesNoOrder(Vehicle vehicle) {
return vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& (vehicle.hasState(Vehicle.State.IDLE)
|| vehicle.hasState(Vehicle.State.CHARGING));
}
private boolean processesDispensableOrder(Vehicle vehicle) {
return vehicle.hasProcState(Vehicle.ProcState.PROCESSING_ORDER)
&& objectService.fetchObject(TransportOrder.class, vehicle.getTransportOrder())
.isDispensable();
}
}

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import org.opentcs.components.Lifecycle;
/**
* Describes a reusable dispatching (sub-)task with a life cycle.
*/
public interface Phase
extends
Runnable,
Lifecycle {
}

View File

@@ -0,0 +1,362 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static java.util.Objects.requireNonNull;
import static org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration.ReroutingImpossibleStrategy.IGNORE_PATH_LOCKS;
import static org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration.ReroutingImpossibleStrategy.PAUSE_AT_PATH_LOCK;
import static org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration.ReroutingImpossibleStrategy.PAUSE_IMMEDIATELY;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.data.model.Path;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.Route;
import org.opentcs.data.order.Route.Step;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.data.peripherals.PeripheralJob;
import org.opentcs.drivers.vehicle.VehicleController;
import org.opentcs.drivers.vehicle.VehicleControllerPool;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration.ReroutingImpossibleStrategy;
import org.opentcs.strategies.basic.dispatching.rerouting.ReroutingStrategy;
import org.opentcs.strategies.basic.dispatching.rerouting.VehiclePositionResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides some utility methods used for rerouting vehicles.
*/
public class RerouteUtil {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(RerouteUtil.class);
/**
* The router.
*/
private final Router router;
/**
* The vehicle controller pool.
*/
private final VehicleControllerPool vehicleControllerPool;
/**
* The object service.
*/
private final InternalTransportOrderService transportOrderService;
private final DefaultDispatcherConfiguration configuration;
private final Map<ReroutingType, ReroutingStrategy> reroutingStrategies;
private final VehiclePositionResolver vehiclePositionResolver;
/**
* Creates a new instance.
*
* @param router The router.
* @param vehicleControllerPool The vehicle controller pool.
* @param transportOrderService The object service.
* @param configuration The configuration.
* @param reroutingStrategies The rerouting strategies to select from.
* @param vehiclePositionResolver Used to resolve the position of vehicles.
*/
@Inject
public RerouteUtil(
Router router,
VehicleControllerPool vehicleControllerPool,
InternalTransportOrderService transportOrderService,
DefaultDispatcherConfiguration configuration,
Map<ReroutingType, ReroutingStrategy> reroutingStrategies,
VehiclePositionResolver vehiclePositionResolver
) {
this.router = requireNonNull(router, "router");
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
this.transportOrderService = requireNonNull(transportOrderService, "transportOrderService");
this.configuration = requireNonNull(configuration, "configuration");
this.reroutingStrategies = requireNonNull(reroutingStrategies, "reroutingStrategies");
this.vehiclePositionResolver = requireNonNull(
vehiclePositionResolver,
"vehiclePositionResolver"
);
}
public void reroute(Collection<Vehicle> vehicles, ReroutingType reroutingType) {
for (Vehicle vehicle : vehicles) {
reroute(vehicle, reroutingType);
}
}
public void reroute(Vehicle vehicle, ReroutingType reroutingType) {
requireNonNull(vehicle, "vehicle");
LOG.debug("Trying to reroute vehicle '{}'...", vehicle.getName());
if (!vehicle.isProcessingOrder()) {
LOG.debug("{} can't be rerouted without processing a transport order.", vehicle.getName());
return;
}
TransportOrder originalOrder = transportOrderService.fetchObject(
TransportOrder.class,
vehicle.getTransportOrder()
);
if (reroutingType == ReroutingType.FORCED
&& isRelatedToUnfinishedPeripheralJobs(originalOrder)) {
LOG.warn(
"Cannot reroute {} when there are unfinished peripheral jobs "
+ "related to the current transport order.",
vehicle.getName()
);
return;
}
Optional<List<DriveOrder>> optOrders;
if (reroutingStrategies.containsKey(reroutingType)) {
optOrders = reroutingStrategies.get(reroutingType).reroute(vehicle);
}
else {
LOG.warn(
"Cannot reroute {} for unknown rerouting type: {}",
vehicle.getName(),
reroutingType.name()
);
optOrders = Optional.empty();
}
if (reroutingType == ReroutingType.FORCED && vehicle.getState() != Vehicle.State.IDLE) {
LOG.warn(
"Forcefully rerouting {} although its state is not 'IDLE' but '{}'.",
vehicle.getName(),
vehicle.getState().name()
);
}
// Get the drive order with the new route or stick to the old one
List<DriveOrder> newDriveOrders;
if (optOrders.isPresent()) {
newDriveOrders = optOrders.get();
}
else {
newDriveOrders = updatePathLocksAndRestrictions(vehicle, originalOrder);
}
LOG.debug("Updating transport order {}...", originalOrder.getName());
updateTransportOrder(originalOrder, newDriveOrders, vehicle);
}
private List<DriveOrder> updatePathLocksAndRestrictions(
Vehicle vehicle,
TransportOrder originalOrder
) {
LOG.debug(
"Couldn't find a new route for {}. Updating the current one...",
vehicle.getName()
);
// Get all unfinished drive order of the transport order the vehicle is processing
List<DriveOrder> unfinishedOrders = new ArrayList<>();
unfinishedOrders.add(originalOrder.getCurrentDriveOrder());
unfinishedOrders.addAll(originalOrder.getFutureDriveOrders());
unfinishedOrders = updatePathLocks(unfinishedOrders);
unfinishedOrders = markRestrictedSteps(
unfinishedOrders,
new ExecutionTest(
configuration.reroutingImpossibleStrategy(),
vehiclePositionResolver.getFutureOrCurrentPosition(vehicle)
)
);
return unfinishedOrders;
}
private void updateTransportOrder(
TransportOrder originalOrder,
List<DriveOrder> newDriveOrders,
Vehicle vehicle
) {
VehicleController controller = vehicleControllerPool.getVehicleController(vehicle.getName());
// Restore the transport order's history
List<DriveOrder> newOrders = new ArrayList<>();
newOrders.addAll(originalOrder.getPastDriveOrders());
newOrders.addAll(newDriveOrders);
// Update the transport order's drive orders with the re-routed ones
LOG.debug("{}: Updating drive orders with {}.", originalOrder.getName(), newOrders);
transportOrderService.updateTransportOrderDriveOrders(
originalOrder.getReference(),
newOrders
);
// If the vehicle is currently processing a (drive) order (and not waiting to get the next
// drive order) we need to update the vehicle's current drive order with the new one.
if (vehicle.hasProcState(Vehicle.ProcState.PROCESSING_ORDER)) {
controller.setTransportOrder(
transportOrderService.fetchObject(TransportOrder.class, originalOrder.getReference())
);
}
// Let the router know the vehicle selected another route
router.selectRoute(vehicle, newOrders);
}
private List<DriveOrder> updatePathLocks(List<DriveOrder> orders) {
List<DriveOrder> updatedOrders = new ArrayList<>();
for (DriveOrder order : orders) {
List<Step> updatedSteps = new ArrayList<>();
for (Step step : order.getRoute().getSteps()) {
if (step.getPath() != null) {
updatedSteps.add(
new Route.Step(
transportOrderService.fetchObject(Path.class, step.getPath().getReference()),
step.getSourcePoint(),
step.getDestinationPoint(),
step.getVehicleOrientation(),
step.getRouteIndex()
)
);
}
else {
// If the step doesn't have a path, there are no path locks to be updated and we can
// simply keep the step as it is.
updatedSteps.add(step);
}
}
Route updatedRoute = new Route(updatedSteps, order.getRoute().getCosts());
DriveOrder updatedOrder = new DriveOrder(order.getDestination())
.withRoute(updatedRoute)
.withState(order.getState())
.withTransportOrder(order.getTransportOrder());
updatedOrders.add(updatedOrder);
}
return updatedOrders;
}
private List<DriveOrder> markRestrictedSteps(
List<DriveOrder> orders,
Predicate<Step> executionTest
) {
if (configuration.reroutingImpossibleStrategy() == IGNORE_PATH_LOCKS) {
return orders;
}
if (!containsLockedPath(orders)) {
return orders;
}
List<DriveOrder> updatedOrders = new ArrayList<>();
for (DriveOrder order : orders) {
List<Step> updatedSteps = new ArrayList<>();
for (Step step : order.getRoute().getSteps()) {
boolean executionAllowed = executionTest.test(step);
LOG.debug("Marking path '{}' allowed: {}", step.getPath(), executionAllowed);
updatedSteps.add(
new Step(
step.getPath(),
step.getSourcePoint(),
step.getDestinationPoint(),
step.getVehicleOrientation(),
step.getRouteIndex(),
executionAllowed
)
);
}
Route updatedRoute = new Route(updatedSteps, order.getRoute().getCosts());
DriveOrder updatedOrder = new DriveOrder(order.getDestination())
.withRoute(updatedRoute)
.withState(order.getState())
.withTransportOrder(order.getTransportOrder());
updatedOrders.add(updatedOrder);
}
return updatedOrders;
}
private boolean containsLockedPath(List<DriveOrder> orders) {
return orders.stream()
.map(order -> order.getRoute().getSteps())
.flatMap(steps -> steps.stream())
.filter(step -> step.getPath() != null)
.anyMatch(step -> step.getPath().isLocked());
}
private boolean isRelatedToUnfinishedPeripheralJobs(TransportOrder transportOrder) {
return !transportOrderService.fetchObjects(
PeripheralJob.class,
job -> Objects.equals(job.getRelatedTransportOrder(), transportOrder.getReference())
&& job.getPeripheralOperation().isCompletionRequired()
&& !job.getState().isFinalState()
).isEmpty();
}
private class ExecutionTest
implements
Predicate<Step> {
/**
* The current fallback strategy.
*/
private final ReroutingImpossibleStrategy strategy;
/**
* The (earliest) point from which execution may not be allowed.
*/
private final Point source;
/**
* Whether execution of a step is allowed.
*/
private boolean executionAllowed = true;
/**
* Creates a new intance.
*
* @param strategy The current fallback strategy.
* @param source The (earliest) point from which execution may not be allowed.
*/
ExecutionTest(ReroutingImpossibleStrategy strategy, Point source) {
this.strategy = requireNonNull(strategy, "strategy");
this.source = requireNonNull(source, "source");
}
@Override
public boolean test(Step step) {
if (!executionAllowed) {
return false;
}
switch (strategy) {
case PAUSE_IMMEDIATELY:
if (Objects.equals(step.getSourcePoint(), source)) {
executionAllowed = false;
}
break;
case PAUSE_AT_PATH_LOCK:
if (step.getPath() != null && step.getPath().isLocked()) {
executionAllowed = false;
}
break;
default:
executionAllowed = true;
}
return executionAllowed;
}
}
}

View File

@@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.dipatching.TransportOrderAssignmentVeto;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
/**
* Provides methods to check if the assignment of a {@link TransportOrder} to a {@link Vehicle} is
* possible.
*/
public class TransportOrderAssignmentChecker {
private final TCSObjectService objectService;
private final OrderReservationPool orderReservationPool;
/**
* Creates a new instance.
*
* @param objectService The object service to use.
* @param orderReservationPool The pool of order reservations.
*/
@Inject
public TransportOrderAssignmentChecker(
TCSObjectService objectService,
OrderReservationPool orderReservationPool
) {
this.objectService = requireNonNull(objectService, "objectService");
this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");
}
/**
* Checks whether the assignment of the given transport order to its
* {@link TransportOrder#getIntendedVehicle() intented vehicle} is possible.
*
* @param transportOrder The transport order to check.
* @return A {@link TransportOrderAssignmentVeto} indicating whether the assignment of the given
* transport order is possible or not.
*/
public TransportOrderAssignmentVeto checkTransportOrderAssignment(TransportOrder transportOrder) {
if (!transportOrder.hasState(TransportOrder.State.DISPATCHABLE)) {
return TransportOrderAssignmentVeto.TRANSPORT_ORDER_STATE_INVALID;
}
if (transportOrder.getWrappingSequence() != null) {
return TransportOrderAssignmentVeto.TRANSPORT_ORDER_PART_OF_ORDER_SEQUENCE;
}
if (transportOrder.getIntendedVehicle() == null) {
return TransportOrderAssignmentVeto.TRANSPORT_ORDER_INTENDED_VEHICLE_NOT_SET;
}
Vehicle intendedVehicle = objectService.fetchObject(
Vehicle.class,
transportOrder.getIntendedVehicle()
);
if (!intendedVehicle.hasProcState(Vehicle.ProcState.IDLE)) {
return TransportOrderAssignmentVeto.VEHICLE_PROCESSING_STATE_INVALID;
}
if (!intendedVehicle.hasState(Vehicle.State.IDLE)
&& !intendedVehicle.hasState(Vehicle.State.CHARGING)) {
return TransportOrderAssignmentVeto.VEHICLE_STATE_INVALID;
}
if (intendedVehicle.getIntegrationLevel() != Vehicle.IntegrationLevel.TO_BE_UTILIZED) {
return TransportOrderAssignmentVeto.VEHICLE_INTEGRATION_LEVEL_INVALID;
}
if (intendedVehicle.getCurrentPosition() == null) {
return TransportOrderAssignmentVeto.VEHICLE_CURRENT_POSITION_UNKNOWN;
}
if (intendedVehicle.getOrderSequence() != null) {
return TransportOrderAssignmentVeto.VEHICLE_PROCESSING_ORDER_SEQUENCE;
}
if (!orderReservationPool.findReservations(intendedVehicle.getReference()).isEmpty()) {
return TransportOrderAssignmentVeto.GENERIC_VETO;
}
return TransportOrderAssignmentVeto.NO_VETO;
}
}

View File

@@ -0,0 +1,484 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.opentcs.components.Lifecycle;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.components.kernel.services.InternalVehicleService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.OrderSequence;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.drivers.vehicle.VehicleController;
import org.opentcs.drivers.vehicle.VehicleControllerPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides service functions for working with transport orders and their states.
*/
public class TransportOrderUtil
implements
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(TransportOrderUtil.class);
/**
* The transport order service.
*/
private final InternalTransportOrderService transportOrderService;
/**
* The vehicle service.
*/
private final InternalVehicleService vehicleService;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* The vehicle controller pool.
*/
private final VehicleControllerPool vehicleControllerPool;
/**
* This class's configuration.
*/
private final DefaultDispatcherConfiguration configuration;
/**
* Whether this instance is initialized.
*/
private boolean initialized;
@Inject
public TransportOrderUtil(
@Nonnull
InternalTransportOrderService transportOrderService,
@Nonnull
InternalVehicleService vehicleService,
@Nonnull
DefaultDispatcherConfiguration configuration,
@Nonnull
Router router,
@Nonnull
VehicleControllerPool vehicleControllerPool
) {
this.transportOrderService = requireNonNull(transportOrderService, "transportOrderService");
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
this.router = requireNonNull(router, "router");
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
/**
* Checks if a transport order's dependencies are completely satisfied or not.
*
* @param order A reference to the transport order to be checked.
* @return <code>false</code> if all the order's dependencies are finished (or
* don't exist any more), else <code>true</code>.
*/
public boolean hasUnfinishedDependencies(TransportOrder order) {
requireNonNull(order, "order");
// Assume that FINISHED orders do not have unfinished dependencies.
if (order.hasState(TransportOrder.State.FINISHED)) {
return false;
}
// Check if any transport order referenced as a an explicit dependency
// (really still exists and) is not finished.
if (order.getDependencies().stream()
.map(depRef -> transportOrderService.fetchObject(TransportOrder.class, depRef))
.anyMatch(dep -> dep != null && !dep.hasState(TransportOrder.State.FINISHED))) {
return true;
}
// Check if the transport order is part of an order sequence and if yes,
// if it's the next unfinished order in the sequence.
if (order.getWrappingSequence() != null) {
OrderSequence seq = transportOrderService.fetchObject(
OrderSequence.class,
order.getWrappingSequence()
);
if (!order.getReference().equals(seq.getNextUnfinishedOrder())) {
return true;
}
}
// All referenced transport orders either don't exist (any more) or have
// been finished already.
return false;
}
/**
* Finds transport orders that are ACTIVE and do not have any unfinished dependencies (any more),
* marking them as DISPATCHABLE.
*/
public void markNewDispatchableOrders() {
transportOrderService.fetchObjects(TransportOrder.class).stream()
.filter(order -> order.hasState(TransportOrder.State.ACTIVE))
.filter(order -> !hasUnfinishedDependencies(order))
.forEach(
order -> updateTransportOrderState(
order.getReference(),
TransportOrder.State.DISPATCHABLE
)
);
}
public void updateTransportOrderState(
@Nonnull
TCSObjectReference<TransportOrder> ref,
@Nonnull
TransportOrder.State newState
) {
requireNonNull(ref, "ref");
requireNonNull(newState, "newState");
LOG.debug("Updating state of transport order {} to {}...", ref.getName(), newState);
switch (newState) {
case FINISHED:
markOrderAndSequenceAsFinished(ref);
break;
case FAILED:
markOrderAndSequenceAsFailed(ref);
break;
default:
// Set the transport order's state.
transportOrderService.updateTransportOrderState(ref, newState);
}
}
/**
* Assigns a transport order to a vehicle, stores a route for the vehicle in
* the transport order, adjusts the state of vehicle and transport order
* and starts processing.
*
* @param vehicle The vehicle that is supposed to process the transport order.
* @param transportOrder The transport order to be processed.
* @param driveOrders The list of drive orders describing the route for the vehicle.
*/
public void assignTransportOrder(
Vehicle vehicle,
TransportOrder transportOrder,
List<DriveOrder> driveOrders
) {
requireNonNull(vehicle, "vehicle");
requireNonNull(transportOrder, "transportOrder");
requireNonNull(driveOrders, "driveOrders");
LOG.debug("Assigning vehicle {} to order {}.", vehicle.getName(), transportOrder.getName());
final TCSObjectReference<Vehicle> vehicleRef = vehicle.getReference();
final TCSObjectReference<TransportOrder> orderRef = transportOrder.getReference();
// Set the vehicle's and transport order's state.
vehicleService.updateVehicleProcState(vehicleRef, Vehicle.ProcState.PROCESSING_ORDER);
updateTransportOrderState(orderRef, TransportOrder.State.BEING_PROCESSED);
// Add cross references between vehicle and transport order/order sequence.
vehicleService.updateVehicleTransportOrder(vehicleRef, orderRef);
if (transportOrder.getWrappingSequence() != null) {
vehicleService.updateVehicleOrderSequence(vehicleRef, transportOrder.getWrappingSequence());
transportOrderService
.updateOrderSequenceProcessingVehicle(transportOrder.getWrappingSequence(), vehicleRef);
}
transportOrderService.updateTransportOrderProcessingVehicle(orderRef, vehicleRef, driveOrders);
// Let the router know about the route chosen.
router.selectRoute(vehicle, Collections.unmodifiableList(driveOrders));
// Update the transport order's copy.
TransportOrder updatedOrder = transportOrderService.fetchObject(TransportOrder.class, orderRef);
// If the drive order must be assigned, do so.
if (mustAssign(updatedOrder.getCurrentDriveOrder(), vehicle)) {
// Let the vehicle controller know about the first drive order.
vehicleControllerPool.getVehicleController(vehicle.getName())
.setTransportOrder(updatedOrder);
}
// If the drive order need not be assigned, let the kernel know that the
// vehicle is waiting for its next order - it will be dispatched again for
// the next drive order, then.
else {
vehicleService.updateVehicleProcState(vehicleRef, Vehicle.ProcState.AWAITING_ORDER);
}
} // void assignTransportOrder()
/**
* Checks if the given drive order must be processed or could/should be left out.
* Orders that should be left out are those with destinations at which the
* vehicle is already present and which require no destination operation.
*
* @param driveOrder The drive order to be processed.
* @param vehicle The vehicle that would process the order.
* @return <code>true</code> if, and only if, the given drive order must be
* processed; <code>false</code> if the order should/must be left out.
*/
public boolean mustAssign(DriveOrder driveOrder, Vehicle vehicle) {
requireNonNull(vehicle, "vehicle");
// Removing a vehicle's drive order is always allowed.
if (driveOrder == null) {
return true;
}
// Check if all orders are to be assigned.
if (configuration.assignRedundantOrders()) {
return true;
}
Point destPoint = driveOrder.getRoute().getFinalDestinationPoint();
String destOp = driveOrder.getDestination().getOperation();
// We use startsWith(OP_NOP) here because that makes it possible to have
// multiple different operations ("NOP.*") that all do nothing.
if (destPoint.getReference().equals(vehicle.getCurrentPosition())
&& (destOp.startsWith(DriveOrder.Destination.OP_NOP)
|| destOp.equals(DriveOrder.Destination.OP_MOVE))) {
return false;
}
return true;
}
/**
* Let a given vehicle abort any order it may currently be processing.
*
* @param vehicle The vehicle which should abort its order.
* @param immediateAbort Whether to abort the order immediately instead of
* just withdrawing it for a smooth abortion.
*/
public void abortOrder(Vehicle vehicle, boolean immediateAbort) {
requireNonNull(vehicle, "vehicle");
if (vehicle.getTransportOrder() == null) {
return;
}
abortAssignedOrder(
transportOrderService.fetchObject(TransportOrder.class, vehicle.getTransportOrder()),
vehicle,
immediateAbort
);
}
public void abortOrder(TransportOrder order, boolean immediateAbort) {
requireNonNull(order, "order");
if (order.getState().isFinalState()) {
LOG.info(
"Transport order '{}' already in final state '{}', skipping withdrawal.",
order.getName(),
order.getState()
);
return;
}
if (order.getProcessingVehicle() == null) {
updateTransportOrderState(order.getReference(), TransportOrder.State.FAILED);
}
else {
abortAssignedOrder(
order,
vehicleService.fetchObject(Vehicle.class, order.getProcessingVehicle()),
immediateAbort
);
}
}
public void finishAbortion(Vehicle vehicle) {
finishAbortion(vehicle.getTransportOrder(), vehicle);
}
private void finishAbortion(TCSObjectReference<TransportOrder> orderRef, Vehicle vehicle) {
requireNonNull(orderRef, "orderRef");
requireNonNull(vehicle, "vehicle");
LOG.debug("{}: Aborted order {}", vehicle.getName(), orderRef.getName());
// The current transport order has been aborted - update its state
// and that of the vehicle.
updateTransportOrderState(orderRef, TransportOrder.State.FAILED);
vehicleService.updateVehicleProcState(vehicle.getReference(), Vehicle.ProcState.IDLE);
vehicleService.updateVehicleTransportOrder(vehicle.getReference(), null);
// Let the router know that the vehicle doesn't have a route any more.
router.selectRoute(vehicle, null);
}
/**
* Aborts a given transport order known to be assigned to a given vehicle.
*
* @param vehicle The vehicle the order is assigned to.
* @param order The order.
* @param immediateAbort Whether to abort the order immediately instead of
* just withdrawing it for a smooth abortion.
*/
private void abortAssignedOrder(
TransportOrder order,
Vehicle vehicle,
boolean immediateAbort
) {
requireNonNull(order, "order");
requireNonNull(vehicle, "vehicle");
checkArgument(
!order.getState().isFinalState(),
"%s: Order already in final state: %s",
vehicle.getName(),
order.getName()
);
// Mark the order as withdrawn so we can react appropriately when the
// vehicle reports the remaining movements as finished.
updateTransportOrderState(order.getReference(), TransportOrder.State.WITHDRAWN);
VehicleController vehicleController
= vehicleControllerPool.getVehicleController(vehicle.getName());
if (immediateAbort) {
LOG.info("{}: Immediate abort of transport order {}...", vehicle.getName(), order.getName());
vehicleController.abortTransportOrder(true);
finishAbortion(order.getReference(), vehicle);
}
else {
vehicleController.abortTransportOrder(false);
}
}
/**
* Properly marks a transport order as FINISHED, also updating its wrapping sequence, if any.
*
* @param ref A reference to the transport order to be modified.
*/
private void markOrderAndSequenceAsFinished(TCSObjectReference<TransportOrder> ref) {
requireNonNull(ref, "ref");
TransportOrder order = transportOrderService.fetchObject(TransportOrder.class, ref);
Optional<OrderSequence> osOpt = extractWrappingSequence(order);
// Sanity check: The finished order must be the next one in the sequence.
osOpt.ifPresent(
seq -> checkArgument(
ref.equals(seq.getNextUnfinishedOrder()),
"TO %s != next unfinished TO %s in sequence %s",
ref,
seq.getNextUnfinishedOrder(),
seq
)
);
transportOrderService.updateTransportOrderState(ref, TransportOrder.State.FINISHED);
osOpt.ifPresent(seq -> {
transportOrderService.updateOrderSequenceFinishedIndex(
seq.getReference(),
seq.getFinishedIndex() + 1
);
// Finish the order sequence, using an up-to-date copy.
finishOrderSequence(
transportOrderService.fetchObject(
OrderSequence.class,
seq.getReference()
)
);
});
}
/**
* Properly marks a transport order as FAILED, also updating its wrapping sequence, if any.
*
* @param ref A reference to the transport order to be modified.
*/
private void markOrderAndSequenceAsFailed(TCSObjectReference<TransportOrder> ref) {
requireNonNull(ref, "ref");
TransportOrder failedOrder = transportOrderService.fetchObject(TransportOrder.class, ref);
transportOrderService.updateTransportOrderState(ref, TransportOrder.State.FAILED);
Optional<OrderSequence> osOpt = extractWrappingSequence(failedOrder);
osOpt.ifPresent(seq -> {
if (seq.isFailureFatal()) {
// Mark the sequence as complete to make sure no further orders are added.
transportOrderService.markOrderSequenceComplete(seq.getReference());
// Mark all orders of the sequence that are not in a final state as FAILED.
seq.getOrders().stream()
.map(curRef -> transportOrderService.fetchObject(TransportOrder.class, curRef))
.filter(o -> !o.getState().isFinalState())
.forEach(
o -> transportOrderService.updateTransportOrderState(
o.getReference(),
TransportOrder.State.FAILED
)
);
// Move the finished index of the sequence to its end.
transportOrderService.updateOrderSequenceFinishedIndex(
seq.getReference(),
seq.getOrders().size() - 1
);
}
else {
// Since failure of an order in the sequence is not fatal, increment the
// finished index of the sequence by one to move to the next order.
transportOrderService.updateOrderSequenceFinishedIndex(
seq.getReference(),
seq.getFinishedIndex() + 1
);
}
// Finish the order sequence, using an up-to-date copy.
finishOrderSequence(
transportOrderService.fetchObject(
OrderSequence.class,
failedOrder.getWrappingSequence()
)
);
});
}
private void finishOrderSequence(OrderSequence sequence) {
// Mark the sequence as finished if there's nothing more to do in it.
if (sequence.isComplete() && sequence.getNextUnfinishedOrder() == null) {
transportOrderService.markOrderSequenceFinished(sequence.getReference());
// If the sequence was assigned to a vehicle, reset its back reference
// on the sequence to make it available for orders again.
if (sequence.getProcessingVehicle() != null) {
vehicleService.updateVehicleOrderSequence(sequence.getProcessingVehicle(), null);
}
}
}
private Optional<OrderSequence> extractWrappingSequence(TransportOrder order) {
return order.getWrappingSequence() == null
? Optional.empty()
: Optional.of(
transportOrderService.fetchObject(
OrderSequence.class,
order.getWrappingSequence()
)
);
}
}

View File

@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Objects;
import java.util.Optional;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.OrderReservationPool;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
/**
* Assigns reserved transport orders (if any) to vehicles that have just finished their withdrawn
* ones.
*/
public class AssignReservedOrdersPhase
implements
Phase {
/**
* The object service
*/
private final TCSObjectService objectService;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* A collection of predicates for filtering assignment candidates.
*/
private final CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter;
/**
* Stores reservations of orders for vehicles.
*/
private final OrderReservationPool orderReservationPool;
private final TransportOrderUtil transportOrderUtil;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public AssignReservedOrdersPhase(
TCSObjectService objectService,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
OrderReservationPool orderReservationPool,
TransportOrderUtil transportOrderUtil
) {
this.router = requireNonNull(router, "router");
this.objectService = requireNonNull(objectService, "objectService");
this.assignmentCandidateSelectionFilter = requireNonNull(
assignmentCandidateSelectionFilter,
"assignmentCandidateSelectionFilter"
);
this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public void run() {
for (Vehicle vehicle : objectService.fetchObjects(Vehicle.class)) {
if (availableForReservedOrders(vehicle)) {
checkForReservedOrder(vehicle);
}
else if (unusableForReservedOrders(vehicle)) {
orderReservationPool.removeReservations(vehicle.getReference());
}
}
}
private void checkForReservedOrder(Vehicle vehicle) {
// Check if there's an order reserved for this vehicle that is in an assignable state. If yes,
// try to assign that.
// Note that we expect no more than a single reserved order, and remove ALL reservations if we
// find at least one, even if it cannot be processed by the vehicle in the end.
orderReservationPool.findReservations(vehicle.getReference()).stream()
.map(orderRef -> objectService.fetchObject(TransportOrder.class, orderRef))
.filter(order -> order.hasState(TransportOrder.State.DISPATCHABLE))
// A transport order's intended vehicle can change after its creation and also after
// reservation. Only handle orders where the intended vehicle (still) fits the reservation.
.filter(order -> hasNoOrMatchingIntendedVehicle(order, vehicle))
.limit(1)
.map(
order -> computeCandidate(
vehicle,
objectService.fetchObject(
Point.class,
vehicle.getCurrentPosition()
),
order
)
)
.filter(optCandidate -> optCandidate.isPresent())
.map(optCandidate -> optCandidate.get())
.filter(candidate -> assignmentCandidateSelectionFilter.apply(candidate).isEmpty())
.findFirst()
.ifPresent(
candidate -> transportOrderUtil.assignTransportOrder(
vehicle,
candidate.getTransportOrder(),
candidate.getDriveOrders()
)
);
// Regardless of whether a reserved order could be assigned to the vehicle or not, remove any
// reservations for the vehicle and allow it to be reserved (again) in the subsequent dispatcher
// phases.
orderReservationPool.removeReservations(vehicle.getReference());
}
private boolean availableForReservedOrders(Vehicle vehicle) {
return vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& (vehicle.hasState(Vehicle.State.IDLE)
|| vehicle.hasState(Vehicle.State.CHARGING))
&& vehicle.getCurrentPosition() != null
&& vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED;
}
private boolean unusableForReservedOrders(Vehicle vehicle) {
return vehicle.getIntegrationLevel() != Vehicle.IntegrationLevel.TO_BE_UTILIZED;
}
private boolean hasNoOrMatchingIntendedVehicle(TransportOrder order, Vehicle vehicle) {
return order.getIntendedVehicle() == null
|| Objects.equals(order.getIntendedVehicle(), vehicle.getReference());
}
private Optional<AssignmentCandidate> computeCandidate(
Vehicle vehicle,
Point vehiclePosition,
TransportOrder order
) {
return router.getRoute(vehicle, vehiclePosition, order)
.map(driveOrders -> new AssignmentCandidate(vehicle, order, driveOrders));
}
}

View File

@@ -0,0 +1,141 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Optional;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderSequence;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
/**
* Assigns vehicles to the next transport orders in their respective order sequences, if any.
*/
public class AssignSequenceSuccessorsPhase
implements
Phase {
/**
* The object service
*/
private final TCSObjectService objectService;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* A collection of predicates for filtering assignment candidates.
*/
private final CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter;
private final TransportOrderUtil transportOrderUtil;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public AssignSequenceSuccessorsPhase(
TCSObjectService objectService,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
TransportOrderUtil transportOrderUtil
) {
this.router = requireNonNull(router, "router");
this.objectService = requireNonNull(objectService, "objectService");
this.assignmentCandidateSelectionFilter = requireNonNull(
assignmentCandidateSelectionFilter,
"assignmentCandidateSelectionFilter"
);
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public void run() {
for (Vehicle vehicle : objectService.fetchObjects(
Vehicle.class,
this::readyForNextInSequence
)) {
tryAssignNextOrderInSequence(vehicle);
}
}
private void tryAssignNextOrderInSequence(Vehicle vehicle) {
nextOrderInCurrentSequence(vehicle)
.map(order -> computeCandidate(vehicle, order))
.filter(candidate -> assignmentCandidateSelectionFilter.apply(candidate).isEmpty())
.ifPresent(
candidate -> transportOrderUtil.assignTransportOrder(
vehicle,
candidate.getTransportOrder(),
candidate.getDriveOrders()
)
);
}
private AssignmentCandidate computeCandidate(Vehicle vehicle, TransportOrder order) {
return router.getRoute(
vehicle,
objectService.fetchObject(Point.class, vehicle.getCurrentPosition()),
order
)
.map(driveOrders -> new AssignmentCandidate(vehicle, order, driveOrders))
.orElse(null);
}
private Optional<TransportOrder> nextOrderInCurrentSequence(Vehicle vehicle) {
OrderSequence seq = objectService.fetchObject(OrderSequence.class, vehicle.getOrderSequence());
// If the order sequence's next order is not available, yet, the vehicle should wait for it.
if (seq.getNextUnfinishedOrder() == null) {
return Optional.empty();
}
// Return the next order to be processed for the sequence.
return Optional.of(
objectService.fetchObject(
TransportOrder.class,
seq.getNextUnfinishedOrder()
)
);
}
private boolean readyForNextInSequence(Vehicle vehicle) {
return vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
&& vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& vehicle.hasState(Vehicle.State.IDLE)
&& vehicle.getCurrentPosition() != null
&& vehicle.getOrderSequence() != null;
}
}

View File

@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
/**
* The result of trying to assign a set of vehicles/transport orders.
*/
public class AssignmentState {
private final List<AssignmentCandidate> assignedCandidates = new ArrayList<>();
private final List<AssignmentCandidate> reservedCandidates = new ArrayList<>();
private final Map<TransportOrder, OrderFilterResult> filteredOrders = new HashMap<>();
public AssignmentState() {
}
public List<AssignmentCandidate> getAssignedCandidates() {
return assignedCandidates;
}
public List<AssignmentCandidate> getReservedCandidates() {
return reservedCandidates;
}
public Map<TransportOrder, OrderFilterResult> getFilteredOrders() {
return filteredOrders;
}
public void addFilteredOrder(OrderFilterResult filterResult) {
TransportOrder order = filterResult.getOrder();
OrderFilterResult result
= filteredOrders.getOrDefault(
order,
new OrderFilterResult(order, new ArrayList<>())
);
result.getFilterReasons().addAll(filterResult.getFilterReasons());
filteredOrders.put(order, result);
}
/**
* Checks whether the given transport order is still assignable, taking into account the current
* assignment results.
*
* @param order The transport order to check.
* @return {@code true}, if the given transport order was not yet assigned or reserved, otherwise
* {@code false}.
*/
public boolean wasAssignedToVehicle(TransportOrder order) {
return Stream.concat(assignedCandidates.stream(), reservedCandidates.stream())
.anyMatch(candidate -> Objects.equals(candidate.getTransportOrder(), order));
}
/**
* Checks whether the given vehicle is still assignable, taking into account the current
* assignment results.
*
* @param vehicle The vehicle to check.
* @return {@code true}, if a transport order was not yet assigned to or reserved for the given
* vehicle, otherwise {@code false}.
*/
public boolean wasAssignedToOrder(Vehicle vehicle) {
return Stream.concat(assignedCandidates.stream(), reservedCandidates.stream())
.anyMatch(candidate -> Objects.equals(candidate.getVehicle(), vehicle));
}
public boolean wasFiltered(TransportOrder order) {
return filteredOrders.containsKey(order);
}
}

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import java.util.Collection;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
/**
* The result of an assignment candidate filter operation.
*/
public class CandidateFilterResult {
private final AssignmentCandidate candidate;
private final Collection<String> filterReasons;
public CandidateFilterResult(AssignmentCandidate candidate, Collection<String> filterReasons) {
this.candidate = requireNonNull(candidate, "candidate");
this.filterReasons = requireNonNull(filterReasons, "filterReasons");
}
public AssignmentCandidate getCandidate() {
return candidate;
}
public Collection<String> getFilterReasons() {
return filterReasons;
}
public boolean isFiltered() {
return !filterReasons.isEmpty();
}
public OrderFilterResult toFilterResult() {
return new OrderFilterResult(candidate.getTransportOrder(), filterReasons);
}
}

View File

@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
/**
* Checks for transport orders that are still in state RAW, and attempts to prepare them for
* assignment.
*/
public class CheckNewOrdersPhase
implements
Phase {
/**
* The object service
*/
private final TCSObjectService objectService;
/**
* The Router instance calculating route costs.
*/
private final Router router;
private final TransportOrderUtil transportOrderUtil;
/**
* The dispatcher configuration.
*/
private final DefaultDispatcherConfiguration configuration;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public CheckNewOrdersPhase(
TCSObjectService objectService,
Router router,
TransportOrderUtil transportOrderUtil,
DefaultDispatcherConfiguration configuration
) {
this.objectService = requireNonNull(objectService, "objectService");
this.router = requireNonNull(router, "router");
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public void run() {
objectService.fetchObjects(TransportOrder.class, this::inRawState).stream()
.forEach(order -> checkRawTransportOrder(order));
}
private void checkRawTransportOrder(TransportOrder order) {
requireNonNull(order, "order");
// Check if the transport order is routable.
if (configuration.dismissUnroutableTransportOrders()
&& !router.checkGeneralRoutability(order)) {
transportOrderUtil.updateTransportOrderState(
order.getReference(),
TransportOrder.State.UNROUTABLE
);
return;
}
transportOrderUtil.updateTransportOrderState(
order.getReference(),
TransportOrder.State.ACTIVE
);
// The transport order has been activated - dispatch it.
// Check if it has unfinished dependencies.
if (!transportOrderUtil.hasUnfinishedDependencies(order)) {
transportOrderUtil.updateTransportOrderState(
order.getReference(),
TransportOrder.State.DISPATCHABLE
);
}
}
private boolean inRawState(TransportOrder order) {
return order.hasState(TransportOrder.State.RAW);
}
}

View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
/**
* Finishes withdrawals of transport orders after the vehicle has come to a halt.
*/
public class FinishWithdrawalsPhase
implements
Phase {
/**
* The object service
*/
private final TCSObjectService objectService;
private final TransportOrderUtil transportOrderUtil;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public FinishWithdrawalsPhase(
TCSObjectService objectService,
TransportOrderUtil transportOrderUtil
) {
this.objectService = requireNonNull(objectService, "objectService");
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public void run() {
objectService.fetchObjects(Vehicle.class).stream()
.filter(vehicle -> vehicle.hasProcState(Vehicle.ProcState.AWAITING_ORDER))
.filter(vehicle -> hasWithdrawnTransportOrder(vehicle))
.forEach(vehicle -> transportOrderUtil.finishAbortion(vehicle));
}
private boolean hasWithdrawnTransportOrder(Vehicle vehicle) {
return objectService.fetchObject(TransportOrder.class, vehicle.getTransportOrder())
.hasState(TransportOrder.State.WITHDRAWN);
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import java.util.Collection;
import org.opentcs.data.order.TransportOrder;
/**
* The result of a transport order filter operation.
*/
public class OrderFilterResult {
private final TransportOrder order;
private final Collection<String> filterReasons;
public OrderFilterResult(TransportOrder order, Collection<String> filterReasons) {
this.order = requireNonNull(order, "order");
this.filterReasons = requireNonNull(filterReasons, "filterReasons");
}
public TransportOrder getOrder() {
return order;
}
public Collection<String> getFilterReasons() {
return filterReasons;
}
public boolean isFiltered() {
return !filterReasons.isEmpty();
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase;
import static java.util.Objects.requireNonNull;
import java.util.Collection;
import org.opentcs.data.model.Vehicle;
/**
* The result of a vehicle filter operation.
*/
public class VehicleFilterResult {
private final Vehicle vehicle;
private final Collection<String> filterReasons;
public VehicleFilterResult(Vehicle vehicle, Collection<String> filterReasons) {
this.vehicle = requireNonNull(vehicle, "vehicle");
this.filterReasons = requireNonNull(filterReasons, "filterReasons");
}
public Vehicle getVehicle() {
return vehicle;
}
public Collection<String> getFilterReasons() {
return filterReasons;
}
public boolean isFiltered() {
return !filterReasons.isEmpty();
}
}

View File

@@ -0,0 +1,160 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.assignment;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.phase.OrderFilterResult;
import org.opentcs.strategies.basic.dispatching.phase.VehicleFilterResult;
import org.opentcs.strategies.basic.dispatching.selection.orders.CompositeTransportOrderSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.orders.IsFreelyDispatchableToAnyVehicle;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeVehicleSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.IsAvailableForAnyOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Assigns transport orders to vehicles that are currently not processing any and are not bound to
* any order sequences.
*/
public class AssignFreeOrdersPhase
implements
Phase {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(AssignFreeOrdersPhase.class);
/**
* The object service.
*/
private final TCSObjectService objectService;
/**
* A collection of predicates for filtering vehicles.
*/
private final CompositeVehicleSelectionFilter vehicleSelectionFilter;
private final IsAvailableForAnyOrder isAvailableForAnyOrder;
private final IsFreelyDispatchableToAnyVehicle isFreelyDispatchableToAnyVehicle;
/**
* A collection of predicates for filtering transport orders.
*/
private final CompositeTransportOrderSelectionFilter transportOrderSelectionFilter;
/**
* Handles assignments of transport orders to vehicles.
*/
private final OrderAssigner orderAssigner;
/**
* Provides methods to check and update the dispatching status of transport orders.
*/
private final DispatchingStatusMarker dispatchingStatusMarker;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public AssignFreeOrdersPhase(
TCSObjectService objectService,
CompositeVehicleSelectionFilter vehicleSelectionFilter,
IsAvailableForAnyOrder isAvailableForAnyOrder,
IsFreelyDispatchableToAnyVehicle isFreelyDispatchableToAnyVehicle,
CompositeTransportOrderSelectionFilter transportOrderSelectionFilter,
OrderAssigner orderAssigner,
DispatchingStatusMarker dispatchingStatusMarker
) {
this.objectService = requireNonNull(objectService, "objectService");
this.vehicleSelectionFilter = requireNonNull(vehicleSelectionFilter, "vehicleSelectionFilter");
this.isAvailableForAnyOrder = requireNonNull(isAvailableForAnyOrder, "isAvailableForAnyOrder");
this.isFreelyDispatchableToAnyVehicle = requireNonNull(
isFreelyDispatchableToAnyVehicle,
"isFreelyDispatchableToAnyVehicle"
);
this.transportOrderSelectionFilter = requireNonNull(
transportOrderSelectionFilter,
"transportOrderSelectionFilter"
);
this.orderAssigner = requireNonNull(orderAssigner, "orderAssigner");
this.dispatchingStatusMarker = requireNonNull(
dispatchingStatusMarker,
"dispatchingStatusMarker"
);
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public void run() {
Map<Boolean, List<VehicleFilterResult>> vehiclesSplitByFilter
= objectService.fetchObjects(Vehicle.class, isAvailableForAnyOrder)
.stream()
.map(vehicle -> new VehicleFilterResult(vehicle, vehicleSelectionFilter.apply(vehicle)))
.collect(Collectors.partitioningBy(filterResult -> !filterResult.isFiltered()));
Collection<Vehicle> availableVehicles = vehiclesSplitByFilter.get(Boolean.TRUE).stream()
.map(VehicleFilterResult::getVehicle)
.collect(Collectors.toList());
if (availableVehicles.isEmpty()) {
LOG.debug("No vehicles available, skipping potentially expensive fetching of orders.");
return;
}
// Select only dispatchable orders first, then apply the composite filter, handle
// the orders that can be tried as usual and mark the others as filtered (if they aren't, yet).
Map<Boolean, List<OrderFilterResult>> ordersSplitByFilter
= objectService.fetchObjects(TransportOrder.class, isFreelyDispatchableToAnyVehicle)
.stream()
.map(order -> new OrderFilterResult(order, transportOrderSelectionFilter.apply(order)))
.collect(Collectors.partitioningBy(filterResult -> !filterResult.isFiltered()));
markNewlyFilteredOrders(ordersSplitByFilter.get(Boolean.FALSE));
orderAssigner.tryAssignments(
availableVehicles,
ordersSplitByFilter.get(Boolean.TRUE).stream()
.map(OrderFilterResult::getOrder)
.collect(Collectors.toList())
);
}
private void markNewlyFilteredOrders(Collection<OrderFilterResult> filterResults) {
filterResults.stream()
.filter(
filterResult -> (!dispatchingStatusMarker.isOrderMarkedAsDeferred(
filterResult.getOrder()
)
|| dispatchingStatusMarker.haveDeferralReasonsForOrderChanged(filterResult))
)
.forEach(dispatchingStatusMarker::markOrderAsDeferred);
}
}

View File

@@ -0,0 +1,147 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.assignment;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.components.kernel.services.InternalVehicleService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.drivers.vehicle.VehicleControllerPool;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Assigns the next drive order to each vehicle waiting for it, or finishes the respective transport
* order if the vehicle has finished its last drive order.
*/
public class AssignNextDriveOrdersPhase
implements
Phase {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(AssignNextDriveOrdersPhase.class);
private final InternalTransportOrderService transportOrderService;
private final InternalVehicleService vehicleService;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* The vehicle controller pool.
*/
private final VehicleControllerPool vehicleControllerPool;
private final TransportOrderUtil transportOrderUtil;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public AssignNextDriveOrdersPhase(
InternalTransportOrderService transportOrderService,
InternalVehicleService vehicleService,
Router router,
VehicleControllerPool vehicleControllerPool,
TransportOrderUtil transportOrderUtil
) {
this.transportOrderService = requireNonNull(transportOrderService, "transportOrderService");
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
this.router = requireNonNull(router, "router");
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public void run() {
transportOrderService.fetchObjects(Vehicle.class).stream()
.filter(vehicle -> vehicle.hasProcState(Vehicle.ProcState.AWAITING_ORDER))
.forEach(vehicle -> checkForNextDriveOrder(vehicle));
}
private void checkForNextDriveOrder(Vehicle vehicle) {
LOG.debug("Vehicle '{}' finished a drive order.", vehicle.getName());
// The vehicle is processing a transport order and has finished a drive order.
// See if there's another drive order to be processed.
transportOrderService.updateTransportOrderNextDriveOrder(vehicle.getTransportOrder());
TransportOrder vehicleOrder = transportOrderService.fetchObject(
TransportOrder.class,
vehicle.getTransportOrder()
);
if (vehicleOrder.getCurrentDriveOrder() == null) {
LOG.debug(
"Vehicle '{}' finished transport order '{}'",
vehicle.getName(),
vehicleOrder.getName()
);
// The current transport order has been finished - update its state and that of the vehicle.
transportOrderUtil.updateTransportOrderState(
vehicle.getTransportOrder(),
TransportOrder.State.FINISHED
);
// Update the vehicle's procState, implicitly dispatching it again.
vehicleService.updateVehicleProcState(vehicle.getReference(), Vehicle.ProcState.IDLE);
vehicleService.updateVehicleTransportOrder(vehicle.getReference(), null);
// Let the router know that the vehicle doesn't have a route any more.
router.selectRoute(vehicle, null);
// Update transport orders that are dispatchable now that this one has been finished.
transportOrderUtil.markNewDispatchableOrders();
}
else {
LOG.debug("Assigning next drive order to vehicle '{}'...", vehicle.getName());
if (transportOrderUtil.mustAssign(vehicleOrder.getCurrentDriveOrder(), vehicle)) {
// Get an up-to-date copy of the transport order in case the route changed.
vehicleOrder = transportOrderService.fetchObject(
TransportOrder.class,
vehicle.getTransportOrder()
);
// Let the vehicle controller know about the new drive order.
vehicleControllerPool.getVehicleController(vehicle.getName())
.setTransportOrder(vehicleOrder);
// The vehicle is still processing a transport order.
vehicleService.updateVehicleProcState(
vehicle.getReference(),
Vehicle.ProcState.PROCESSING_ORDER
);
}
// If the drive order need not be assigned, immediately check for another one.
else {
vehicleService.updateVehicleProcState(
vehicle.getReference(),
Vehicle.ProcState.AWAITING_ORDER
);
checkForNextDriveOrder(vehicle);
}
}
}
}

View File

@@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.assignment;
import static java.util.Objects.requireNonNull;
import static org.opentcs.data.order.TransportOrderHistoryCodes.ORDER_ASSIGNED_TO_VEHICLE;
import static org.opentcs.data.order.TransportOrderHistoryCodes.ORDER_DISPATCHING_DEFERRED;
import static org.opentcs.data.order.TransportOrderHistoryCodes.ORDER_DISPATCHING_RESUMED;
import static org.opentcs.data.order.TransportOrderHistoryCodes.ORDER_RESERVED_FOR_VEHICLE;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.ObjectHistory;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.phase.OrderFilterResult;
/**
* Provides methods to check and update the dispatching status of transport orders.
* <p>
* Examples for a transport order's dispatching status:
* </p>
* <ul>
* <li>Dispatching of a transport order has been deferred.</li>
* <li>A transport order has been assigned to a vehicle.</li>
* </ul>
*
* @author Martin Grzenia (Fraunhofer IML)
*/
public class DispatchingStatusMarker {
private final TCSObjectService objectService;
/**
* Creates a new instance.
*
* @param objectService The object service to use.
*/
@Inject
public DispatchingStatusMarker(TCSObjectService objectService) {
this.objectService = requireNonNull(objectService, "objectService");
}
/**
* Marks the {@link TransportOrder} referenced in the given {@link OrderFilterResult} as
* deferred.
*
* @param filterResult The filter result.
*/
public void markOrderAsDeferred(OrderFilterResult filterResult) {
objectService.appendObjectHistoryEntry(
filterResult.getOrder().getReference(),
new ObjectHistory.Entry(
ORDER_DISPATCHING_DEFERRED,
Collections.unmodifiableList(new ArrayList<>(filterResult.getFilterReasons()))
)
);
}
/**
* Marks the given {@link TransportOrder} as resumed.
*
* @param transportOrder The transport order.
*/
public void markOrderAsResumed(TransportOrder transportOrder) {
objectService.appendObjectHistoryEntry(
transportOrder.getReference(),
new ObjectHistory.Entry(
ORDER_DISPATCHING_RESUMED,
Collections.unmodifiableList(new ArrayList<>())
)
);
}
/**
* Checks whether the given {@link TransportOrder} is marked as deferred regarding dispatching.
*
* @param transportOrder The transport order.
* @return {@code true}, if the {@link ObjectHistory} of the given transport order indicates that
* the transport order is currently being deferred regarding dispatching, otherwise {@code false}.
*/
public boolean isOrderMarkedAsDeferred(TransportOrder transportOrder) {
return lastRelevantDeferredHistoryEntry(transportOrder).isPresent();
}
/**
* Marks the given {@link TransportOrder} as being assigned to the given {@link Vehicle}.
*
* @param transportOrder The transport order.
* @param vehicle The vehicle.
*/
public void markOrderAsAssigned(TransportOrder transportOrder, Vehicle vehicle) {
objectService.appendObjectHistoryEntry(
transportOrder.getReference(),
new ObjectHistory.Entry(ORDER_ASSIGNED_TO_VEHICLE, vehicle.getName())
);
}
/**
* Marks the given {@link TransportOrder} as being reserved for the given {@link Vehicle}.
*
* @param transportOrder The transport order.
* @param vehicle The vehicle.
*/
public void markOrderAsReserved(TransportOrder transportOrder, Vehicle vehicle) {
objectService.appendObjectHistoryEntry(
transportOrder.getReference(),
new ObjectHistory.Entry(ORDER_RESERVED_FOR_VEHICLE, vehicle.getName())
);
}
/**
* Checks whether the reasons for deferral of the {@link TransportOrder} referenced in the given
* {@link OrderFilterResult} have changed in comparison to the (new)
* {@link OrderFilterResult#getFilterReasons()}.
*
* @param filterResult The filter result.
* @return {@code true}, if the reasons for deferral have changed, otherwise {@code false}.
*/
@SuppressWarnings("unchecked")
public boolean haveDeferralReasonsForOrderChanged(OrderFilterResult filterResult) {
Collection<String> newReasons = filterResult.getFilterReasons();
Collection<String> oldReasons = lastRelevantDeferredHistoryEntry(filterResult.getOrder())
.map(entry -> (Collection<String>) entry.getSupplement())
.orElse(new ArrayList<>());
return newReasons.size() != oldReasons.size()
|| !newReasons.containsAll(oldReasons);
}
private Optional<ObjectHistory.Entry> lastRelevantDeferredHistoryEntry(
TransportOrder transportOrder
) {
return transportOrder.getHistory().getEntries().stream()
.filter(
entry -> equalsAny(
entry.getEventCode(),
ORDER_DISPATCHING_DEFERRED,
ORDER_DISPATCHING_RESUMED
)
)
.reduce((firstEntry, secondEntry) -> secondEntry)
.filter(entry -> entry.getEventCode().equals(ORDER_DISPATCHING_DEFERRED));
}
private boolean equalsAny(String string, String... others) {
return Arrays.asList(others).stream()
.anyMatch(other -> string.equals(other));
}
}

View File

@@ -0,0 +1,305 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.assignment;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.OrderReservationPool;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.phase.AssignmentState;
import org.opentcs.strategies.basic.dispatching.phase.CandidateFilterResult;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeOrderCandidateComparator;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeOrderComparator;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeVehicleCandidateComparator;
import org.opentcs.strategies.basic.dispatching.priorization.CompositeVehicleComparator;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles assignments of transport orders to vehicles.
*/
public class OrderAssigner {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(OrderAssigner.class);
/**
* The object service.
*/
private final TCSObjectService objectService;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* Stores reservations of orders for vehicles.
*/
private final OrderReservationPool orderReservationPool;
/**
* Defines the order of vehicles when there are less vehicles than transport orders.
*/
private final Comparator<Vehicle> vehicleComparator;
/**
* Defines the order of transport orders when there are less transport orders than vehicles.
*/
private final Comparator<TransportOrder> orderComparator;
/**
* Sorts candidates when looking for a transport order to be assigned to a vehicle.
*/
private final Comparator<AssignmentCandidate> orderCandidateComparator;
/**
* Sorts candidates when looking for a vehicle to be assigned to a transport order.
*/
private final Comparator<AssignmentCandidate> vehicleCandidateComparator;
/**
* A collection of predicates for filtering assignment candidates.
*/
private final CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter;
private final TransportOrderUtil transportOrderUtil;
/**
* Provides methods to check and update the dispatching status of transport orders.
*/
private final DispatchingStatusMarker dispatchingStatusMarker;
@Inject
public OrderAssigner(
TCSObjectService objectService,
Router router,
OrderReservationPool orderReservationPool,
CompositeVehicleComparator vehicleComparator,
CompositeOrderComparator orderComparator,
CompositeOrderCandidateComparator orderCandidateComparator,
CompositeVehicleCandidateComparator vehicleCandidateComparator,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
TransportOrderUtil transportOrderUtil,
DispatchingStatusMarker dispatchingStatusMarker
) {
this.router = requireNonNull(router, "router");
this.objectService = requireNonNull(objectService, "objectService");
this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");
this.vehicleComparator = requireNonNull(vehicleComparator, "vehicleComparator");
this.orderComparator = requireNonNull(orderComparator, "orderComparator");
this.orderCandidateComparator = requireNonNull(
orderCandidateComparator,
"orderCandidateComparator"
);
this.vehicleCandidateComparator = requireNonNull(
vehicleCandidateComparator,
"vehicleCandidateComparator"
);
this.assignmentCandidateSelectionFilter = requireNonNull(
assignmentCandidateSelectionFilter,
"assignmentCandidateSelectionFilter"
);
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
this.dispatchingStatusMarker = requireNonNull(
dispatchingStatusMarker,
"dispatchingStatusMarker"
);
}
/**
* Tries to assign the given tranpsort orders to the given vehicles.
*
* @param availableVehicles The vehicles available for order assignment.
* @param availableOrders The transport order available to be assigned to a vehicle.
*/
public void tryAssignments(
Collection<Vehicle> availableVehicles,
Collection<TransportOrder> availableOrders
) {
LOG.debug(
"Available for dispatching: {} transport orders and {} vehicles.",
availableOrders.size(),
availableVehicles.size()
);
AssignmentState assignmentState = new AssignmentState();
if (availableVehicles.size() < availableOrders.size()) {
availableVehicles.stream()
.sorted(vehicleComparator)
.forEach(vehicle -> tryAssignOrder(vehicle, availableOrders, assignmentState));
}
else {
availableOrders.stream()
.sorted(orderComparator)
.forEach(order -> tryAssignVehicle(order, availableVehicles, assignmentState));
}
assignmentState.getFilteredOrders().values().stream()
.filter(filterResult -> !assignmentState.wasAssignedToVehicle(filterResult.getOrder()))
.filter(dispatchingStatusMarker::haveDeferralReasonsForOrderChanged)
.forEach(dispatchingStatusMarker::markOrderAsDeferred);
availableOrders.stream()
.filter(
order -> (!assignmentState.wasFiltered(order)
&& !assignmentState.wasAssignedToVehicle(order))
)
.filter(dispatchingStatusMarker::isOrderMarkedAsDeferred)
.forEach(dispatchingStatusMarker::markOrderAsResumed);
}
private void tryAssignOrder(
Vehicle vehicle,
Collection<TransportOrder> availableOrders,
AssignmentState assignmentState
) {
LOG.debug("Trying to find transport order for vehicle '{}'...", vehicle.getName());
Point vehiclePosition = objectService.fetchObject(Point.class, vehicle.getCurrentPosition());
Map<Boolean, List<CandidateFilterResult>> ordersSplitByFilter
= availableOrders.stream()
.filter(
order -> (!assignmentState.wasAssignedToVehicle(order)
&& vehicleCanTakeOrder(vehicle, order)
&& orderAssignableToVehicle(order, vehicle))
)
.map(order -> computeCandidate(vehicle, vehiclePosition, order))
.filter(optCandidate -> optCandidate.isPresent())
.map(optCandidate -> optCandidate.get())
.map(
candidate -> new CandidateFilterResult(
candidate,
assignmentCandidateSelectionFilter.apply(candidate)
)
)
.collect(Collectors.partitioningBy(filterResult -> !filterResult.isFiltered()));
ordersSplitByFilter.get(Boolean.FALSE).stream()
.map(CandidateFilterResult::toFilterResult)
.forEach(filterResult -> assignmentState.addFilteredOrder(filterResult));
ordersSplitByFilter.get(Boolean.TRUE).stream()
.map(CandidateFilterResult::getCandidate)
.sorted(orderCandidateComparator)
.findFirst()
.ifPresent(candidate -> assignOrder(candidate, assignmentState));
}
private void tryAssignVehicle(
TransportOrder order,
Collection<Vehicle> availableVehicles,
AssignmentState assignmentState
) {
LOG.debug("Trying to find vehicle for transport order '{}'...", order.getName());
Map<Boolean, List<CandidateFilterResult>> ordersSplitByFilter
= availableVehicles.stream()
.filter(
vehicle -> (!assignmentState.wasAssignedToOrder(vehicle)
&& vehicleCanTakeOrder(vehicle, order)
&& orderAssignableToVehicle(order, vehicle))
)
.map(
vehicle -> computeCandidate(
vehicle,
objectService.fetchObject(Point.class, vehicle.getCurrentPosition()),
order
)
)
.filter(optCandidate -> optCandidate.isPresent())
.map(optCandidate -> optCandidate.get())
.map(
candidate -> new CandidateFilterResult(
candidate,
assignmentCandidateSelectionFilter.apply(candidate)
)
)
.collect(Collectors.partitioningBy(filterResult -> !filterResult.isFiltered()));
ordersSplitByFilter.get(Boolean.FALSE).stream()
.map(CandidateFilterResult::toFilterResult)
.forEach(filterResult -> assignmentState.addFilteredOrder(filterResult));
ordersSplitByFilter.get(Boolean.TRUE).stream()
.map(CandidateFilterResult::getCandidate)
.sorted(vehicleCandidateComparator)
.findFirst()
.ifPresent(candidate -> assignOrder(candidate, assignmentState));
}
private void assignOrder(AssignmentCandidate candidate, AssignmentState assignmentState) {
// If the vehicle currently has a (dispensable) order, we may not assign the new one here
// directly, but must abort the old one (DefaultDispatcher.abortOrder()) and wait for the
// vehicle's ProcState to become IDLE.
if (candidate.getVehicle().getTransportOrder() == null) {
LOG.debug(
"Assigning transport order '{}' to vehicle '{}'...",
candidate.getTransportOrder().getName(),
candidate.getVehicle().getName()
);
dispatchingStatusMarker.markOrderAsAssigned(
candidate.getTransportOrder(),
candidate.getVehicle()
);
transportOrderUtil.assignTransportOrder(
candidate.getVehicle(),
candidate.getTransportOrder(),
candidate.getDriveOrders()
);
assignmentState.getAssignedCandidates().add(candidate);
}
else {
LOG.debug(
"Reserving transport order '{}' for vehicle '{}'...",
candidate.getTransportOrder().getName(),
candidate.getVehicle().getName()
);
// Remember that the new order is reserved for this vehicle.
dispatchingStatusMarker.markOrderAsReserved(
candidate.getTransportOrder(),
candidate.getVehicle()
);
orderReservationPool.addReservation(
candidate.getTransportOrder().getReference(),
candidate.getVehicle().getReference()
);
assignmentState.getReservedCandidates().add(candidate);
transportOrderUtil.abortOrder(candidate.getVehicle(), false);
}
}
private Optional<AssignmentCandidate> computeCandidate(
Vehicle vehicle,
Point vehiclePosition,
TransportOrder order
) {
return router.getRoute(vehicle, vehiclePosition, order)
.map(driveOrders -> new AssignmentCandidate(vehicle, order, driveOrders));
}
private boolean vehicleCanTakeOrder(Vehicle vehicle, TransportOrder order) {
return !vehicle.isEnergyLevelCritical()
|| Objects.equals(
order.getAllDriveOrders().getFirst().getDestination().getOperation(),
vehicle.getRechargeOperation()
);
}
private boolean orderAssignableToVehicle(TransportOrder order, Vehicle vehicle) {
return (order.getIntendedVehicle() == null
|| Objects.equals(order.getIntendedVehicle(), vehicle.getReference()))
&& (vehicle.getAllowedOrderTypes().contains(order.getType())
|| vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_ANY));
}
}

View File

@@ -0,0 +1,169 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.opentcs.access.to.order.DestinationCreationTO;
import org.opentcs.access.to.order.TransportOrderCreationTO;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.components.kernel.services.TransportOrderService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The base class for parking phases.
*/
public abstract class AbstractParkingPhase
implements
Phase {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(AbstractParkingPhase.class);
/**
* The transport order service.
*/
private final InternalTransportOrderService orderService;
/**
* The strategy used for finding suitable parking positions.
*/
private final ParkingPositionSupplier parkingPosSupplier;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* A collection of predicates for filtering assignment candidates.
*/
private final CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter;
/**
* Provides service functions for working with transport orders.
*/
private final TransportOrderUtil transportOrderUtil;
/**
* The dispatcher configuration.
*/
private final DefaultDispatcherConfiguration configuration;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
public AbstractParkingPhase(
InternalTransportOrderService orderService,
ParkingPositionSupplier parkingPosSupplier,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
TransportOrderUtil transportOrderUtil,
DefaultDispatcherConfiguration configuration
) {
this.router = requireNonNull(router, "router");
this.orderService = requireNonNull(orderService, "orderService");
this.parkingPosSupplier = requireNonNull(parkingPosSupplier, "parkingPosSupplier");
this.assignmentCandidateSelectionFilter = requireNonNull(
assignmentCandidateSelectionFilter,
"assignmentCandidateSelectionFilter"
);
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
parkingPosSupplier.initialize();
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
parkingPosSupplier.terminate();
initialized = false;
}
public TransportOrderService getOrderService() {
return orderService;
}
public DefaultDispatcherConfiguration getConfiguration() {
return configuration;
}
protected void createParkingOrder(Vehicle vehicle) {
Point vehiclePosition = orderService.fetchObject(Point.class, vehicle.getCurrentPosition());
// Get a suitable parking position for the vehicle.
Optional<Point> parkPos = parkingPosSupplier.findParkingPosition(vehicle);
LOG.debug("Parking position for {}: {}", vehicle, parkPos);
// If we could not find a suitable parking position at all, just leave the vehicle where it is.
if (!parkPos.isPresent()) {
LOG.info("{}: Did not find a suitable parking position.", vehicle.getName());
return;
}
// Create a destination for the point.
List<DestinationCreationTO> parkDests = Arrays.asList(
new DestinationCreationTO(parkPos.get().getName(), DriveOrder.Destination.OP_PARK)
);
// Create a transport order for parking and verify its processability.
TransportOrder parkOrder = orderService.createTransportOrder(
new TransportOrderCreationTO("Park-", parkDests)
.withIncompleteName(true)
.withDispensable(true)
.withIntendedVehicleName(vehicle.getName())
.withType(OrderConstants.TYPE_PARK)
);
Optional<AssignmentCandidate> candidate = computeCandidate(vehicle, vehiclePosition, parkOrder)
.filter(c -> assignmentCandidateSelectionFilter.apply(c).isEmpty());
// XXX Change this to Optional.ifPresentOrElse() once we're at Java 9+.
if (candidate.isPresent()) {
transportOrderUtil.assignTransportOrder(
candidate.get().getVehicle(),
candidate.get().getTransportOrder(),
candidate.get().getDriveOrders()
);
}
else {
// Mark the order as failed, since the vehicle cannot execute it.
orderService.updateTransportOrderState(parkOrder.getReference(), TransportOrder.State.FAILED);
}
}
private Optional<AssignmentCandidate> computeCandidate(
Vehicle vehicle,
Point vehiclePosition,
TransportOrder order
) {
return router.getRoute(vehicle, vehiclePosition, order)
.map(driveOrders -> new AssignmentCandidate(vehicle, order, driveOrders));
}
}

View File

@@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nullable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalPlantModelService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
/**
* An abstract base class for parking position suppliers.
*/
public abstract class AbstractParkingPositionSupplier
implements
ParkingPositionSupplier {
/**
* The plant model service.
*/
private final InternalPlantModelService plantModelService;
/**
* A router for computing distances to parking positions.
*/
private final Router router;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param plantModelService The plant model service.
* @param router A router for computing distances to parking positions.
*/
protected AbstractParkingPositionSupplier(
InternalPlantModelService plantModelService,
Router router
) {
this.plantModelService = requireNonNull(plantModelService, "plantModelService");
this.router = requireNonNull(router, "router");
}
@Override
public void initialize() {
if (initialized) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!initialized) {
return;
}
initialized = false;
}
/**
* Returns the plant model service.
*
* @return The plant model service.
*/
public InternalPlantModelService getPlantModelService() {
return plantModelService;
}
/**
* Returns the system's router.
*
* @return The system's router.
*/
public Router getRouter() {
return router;
}
/**
* Returns a set of parking positions usable for the given vehicle (usable in the sense that these
* positions are not occupied by other vehicles).
*
* @param vehicle The vehicles to find parking positions for.
* @return The set of usable parking positions.
*/
protected Set<Point> findUsableParkingPositions(Vehicle vehicle) {
// Find out which points are destination points of the current routes of
// all vehicles, and keep them. (Multiple lookups ahead.)
Set<Point> targetedPoints = getRouter().getTargetedPoints();
return fetchAllParkingPositions().stream()
.filter(point -> isPointUnoccupiedFor(point, vehicle, targetedPoints))
.collect(Collectors.toSet());
}
/**
* Returns from the given set of points the one that is nearest to the given
* vehicle.
*
* @param vehicle The vehicle.
* @param points The set of points to select the nearest one from.
* @return The point nearest to the given vehicle.
*/
@Nullable
protected Point nearestPoint(Vehicle vehicle, Set<Point> points) {
requireNonNull(vehicle, "vehicle");
requireNonNull(points, "points");
if (vehicle.getCurrentPosition() == null) {
return null;
}
Point vehiclePos = plantModelService.fetchObject(Point.class, vehicle.getCurrentPosition());
return points.stream()
.map(point -> parkingPositionCandidate(vehicle, vehiclePos, point))
.filter(candidate -> candidate.costs < Long.MAX_VALUE)
.min(Comparator.comparingLong(candidate -> candidate.costs))
.map(candidate -> candidate.point)
.orElse(null);
}
/**
* Gathers a set of all points from all blocks that the given point is a member of.
*
* @param point The point to check.
* @return A set of all points from all blocks that the given point is a member of.
*/
protected Set<Point> expandPoints(Point point) {
return plantModelService.expandResources(Collections.singleton(point.getReference())).stream()
.filter(resource -> Point.class.equals(resource.getReference().getReferentClass()))
.map(resource -> (Point) resource)
.collect(Collectors.toSet());
}
protected Set<Point> fetchAllParkingPositions() {
return plantModelService.fetchObjects(Point.class, point -> point.isParkingPosition());
}
/**
* Checks if ALL points within the same block as the given access point are NOT occupied or
* targeted by any other vehicle than the given one.
*
* @param accessPoint The point to be checked.
* @param vehicle The vehicle to be checked for.
* @param targetedPoints All currently known targeted points.
* @return <code>true</code> if, and only if, ALL points within the same block as the given access
* point are NOT occupied or targeted by any other vehicle than the given one.
*/
private boolean isPointUnoccupiedFor(
Point accessPoint,
Vehicle vehicle,
Set<Point> targetedPoints
) {
return expandPoints(accessPoint).stream()
.allMatch(
point -> !pointOccupiedOrTargetedByOtherVehicle(
point,
vehicle,
targetedPoints
)
);
}
private boolean pointOccupiedOrTargetedByOtherVehicle(
Point pointToCheck,
Vehicle vehicle,
Set<Point> targetedPoints
) {
if (pointToCheck.getOccupyingVehicle() != null
&& !pointToCheck.getOccupyingVehicle().equals(vehicle.getReference())) {
return true;
}
else if (targetedPoints.contains(pointToCheck)) {
return true;
}
return false;
}
private PointCandidate parkingPositionCandidate(
Vehicle vehicle,
Point srcPosition,
Point destPosition
) {
return new PointCandidate(
destPosition,
router.getCosts(vehicle, srcPosition, destPosition, Set.of())
);
}
private static class PointCandidate {
private final Point point;
private final long costs;
PointCandidate(Point point, long costs) {
this.point = point;
this.costs = costs;
}
}
}

View File

@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import static org.opentcs.components.kernel.Dispatcher.PROPKEY_ASSIGNED_PARKING_POSITION;
import static org.opentcs.components.kernel.Dispatcher.PROPKEY_PREFERRED_PARKING_POSITION;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.Set;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalPlantModelService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A parking position supplier that tries to find parking positions that are unoccupied,
* not on the current route of any other vehicle and as close as possible to the
* parked vehicle's current position.
*/
public class DefaultParkingPositionSupplier
extends
AbstractParkingPositionSupplier {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultParkingPositionSupplier.class);
/**
* Creates a new instance.
*
* @param plantModelService The plant model service.
* @param router A router for computing travel costs to parking positions.
*/
@Inject
public DefaultParkingPositionSupplier(
InternalPlantModelService plantModelService,
Router router
) {
super(plantModelService, router);
}
@Override
public Optional<Point> findParkingPosition(final Vehicle vehicle) {
requireNonNull(vehicle, "vehicle");
if (vehicle.getCurrentPosition() == null) {
return Optional.empty();
}
Set<Point> parkingPosCandidates = findUsableParkingPositions(vehicle);
if (parkingPosCandidates.isEmpty()) {
LOG.debug("No parking position candidates found.");
return Optional.empty();
}
// Check if the vehicle has an assigned parking position.
// If yes, return either that (if it's with the available points) or none.
String assignedParkingPosName = vehicle.getProperty(PROPKEY_ASSIGNED_PARKING_POSITION);
if (assignedParkingPosName != null) {
return Optional.ofNullable(pickPointWithName(assignedParkingPosName, parkingPosCandidates));
}
// Check if the vehicle has a preferred parking position.
// If yes, and if it's with the available points, return that.
String preferredParkingPosName = vehicle.getProperty(PROPKEY_PREFERRED_PARKING_POSITION);
if (preferredParkingPosName != null) {
Point preferredPoint = pickPointWithName(preferredParkingPosName, parkingPosCandidates);
if (preferredPoint != null) {
return Optional.of(preferredPoint);
}
}
Point nearestPoint = nearestPoint(vehicle, parkingPosCandidates);
LOG.debug(
"Selected parking position {} for vehicle {} from candidates {}.",
nearestPoint,
vehicle.getName(),
parkingPosCandidates
);
return Optional.ofNullable(nearestPoint);
}
@Nullable
private Point pickPointWithName(String name, Set<Point> points) {
return points.stream()
.filter(point -> name.equals(point.getName()))
.findAny()
.orElse(null);
}
}

View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeParkVehicleSelectionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates parking orders for idle vehicles not already at a parking position considering all
* parking positions.
*/
public class ParkIdleVehiclesPhase
extends
AbstractParkingPhase {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(ParkIdleVehiclesPhase.class);
/**
* A filter for selecting vehicles that may be parked.
*/
private final CompositeParkVehicleSelectionFilter vehicleSelectionFilter;
@Inject
public ParkIdleVehiclesPhase(
InternalTransportOrderService orderService,
ParkingPositionSupplier parkingPosSupplier,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
TransportOrderUtil transportOrderUtil,
DefaultDispatcherConfiguration configuration,
CompositeParkVehicleSelectionFilter vehicleSelectionFilter
) {
super(
orderService,
parkingPosSupplier,
router,
assignmentCandidateSelectionFilter,
transportOrderUtil,
configuration
);
this.vehicleSelectionFilter = requireNonNull(vehicleSelectionFilter, "vehicleSelectionFilter");
}
@Override
public void run() {
if (!getConfiguration().parkIdleVehicles()) {
return;
}
LOG.debug("Looking for vehicles to send to parking positions...");
getOrderService().fetchObjects(Vehicle.class).stream()
.filter(vehicle -> vehicleSelectionFilter.apply(vehicle).isEmpty())
.forEach(vehicle -> createParkingOrder(vehicle));
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Comparator;
import org.opentcs.data.model.Point;
/**
* Compares parking positions by their priorities.
*/
public class ParkingPositionPriorityComparator
implements
Comparator<Point> {
/**
* A function computing the priority of a parking position.
*/
private final ParkingPositionToPriorityFunction priorityFunction;
/**
* Creates a new instance.
*
* @param priorityFunction A function computing the priority of a parking position.
*/
@Inject
public ParkingPositionPriorityComparator(ParkingPositionToPriorityFunction priorityFunction) {
this.priorityFunction = requireNonNull(priorityFunction, "priorityFunction");
}
@Override
public int compare(Point point1, Point point2) {
requireNonNull(point1, "point1");
requireNonNull(point2, "point2");
Integer point1Prio = priorityFunction.apply(point1);
Integer point2Prio = priorityFunction.apply(point2);
if (point1Prio != null && point2Prio == null) {
return -1;
}
else if (point1Prio == null && point2Prio != null) {
return 1;
}
else if (point1Prio == null && point2Prio == null) {
return point1.getName().compareTo(point2.getName());
}
return point1Prio.compareTo(point2Prio);
}
}

View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import jakarta.annotation.Nonnull;
import java.util.Optional;
import org.opentcs.components.Lifecycle;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
/**
* A strategy for finding parking positions for vehicles.
*/
public interface ParkingPositionSupplier
extends
Lifecycle {
/**
* Returns a suitable parking position for the given vehicle.
*
* @param vehicle The vehicle to find a parking position for.
* @return A parking position for the given vehicle, or an empty Optional, if no suitable parking
* position is available.
*/
@Nonnull
Optional<Point> findParkingPosition(
@Nonnull
Vehicle vehicle
);
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import com.google.common.base.Strings;
import java.util.function.Function;
import org.opentcs.components.kernel.Dispatcher;
import org.opentcs.data.model.Point;
/**
* Returns the priority of a parking position, if it has any, or <code>null</code>.
* <p>
* A priority is returned if the given point is a parking position and has a property with key
* {@link Dispatcher#PROPKEY_PARKING_POSITION_PRIORITY} and a numeric (decimal) value as understood
* by {@link Integer#parseInt(java.lang.String)}.
* If these prerequisites are not met, <code>null</code> is returned.
* </p>
*/
public class ParkingPositionToPriorityFunction
implements
Function<Point, Integer> {
/**
* Creates a new instance.
*/
public ParkingPositionToPriorityFunction() {
}
@Override
public Integer apply(Point point) {
if (!point.isParkingPosition()) {
return null;
}
String priorityString = point.getProperty(Dispatcher.PROPKEY_PARKING_POSITION_PRIORITY);
if (Strings.isNullOrEmpty(priorityString)) {
return null;
}
try {
return Integer.parseInt(priorityString);
}
catch (NumberFormatException e) {
return null;
}
}
}

View File

@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeParkVehicleSelectionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates parking orders for idle vehicles not already at a parking position considering only
* prioritized parking positions.
*/
public class PrioritizedParkingPhase
extends
AbstractParkingPhase {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PrioritizedParkingPhase.class);
/**
* A filter for selecting vehicles that may be parked.
*/
private final CompositeParkVehicleSelectionFilter vehicleSelectionFilter;
@Inject
public PrioritizedParkingPhase(
InternalTransportOrderService orderService,
PrioritizedParkingPositionSupplier parkingPosSupplier,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
TransportOrderUtil transportOrderUtil,
DefaultDispatcherConfiguration configuration,
CompositeParkVehicleSelectionFilter vehicleSelectionFilter
) {
super(
orderService,
parkingPosSupplier,
router,
assignmentCandidateSelectionFilter,
transportOrderUtil,
configuration
);
this.vehicleSelectionFilter = requireNonNull(vehicleSelectionFilter, "vehicleSelectionFilter");
}
@Override
public void run() {
if (!getConfiguration().parkIdleVehicles()
|| !getConfiguration().considerParkingPositionPriorities()) {
return;
}
LOG.debug("Looking for vehicles to send to prioritized parking positions...");
getOrderService().fetchObjects(Vehicle.class).stream()
.filter(vehicle -> vehicleSelectionFilter.apply(vehicle).isEmpty())
.forEach(vehicle -> createParkingOrder(vehicle));
}
}

View File

@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import jakarta.inject.Inject;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalPlantModelService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A parking position supplier that tries to find the parking position with the highest priority
* that is unoccupied, not on the current route of any other vehicle and as close as possible to the
* vehicle's current position.
*/
public class PrioritizedParkingPositionSupplier
extends
AbstractParkingPositionSupplier {
/**
* This class's Logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(PrioritizedParkingPositionSupplier.class);
/**
* A function computing the priority of a parking position.
*/
private final ParkingPositionToPriorityFunction priorityFunction;
/**
* Creates a new instance.
*
* @param plantModelService The plant model service.
* @param router A router for computing travel costs to parking positions.
* @param priorityFunction A function computing the priority of a parking position.
*/
@Inject
public PrioritizedParkingPositionSupplier(
InternalPlantModelService plantModelService,
Router router,
ParkingPositionToPriorityFunction priorityFunction
) {
super(plantModelService, router);
this.priorityFunction = requireNonNull(priorityFunction, "priorityFunction");
}
@Override
public Optional<Point> findParkingPosition(final Vehicle vehicle) {
requireNonNull(vehicle, "vehicle");
if (vehicle.getCurrentPosition() == null) {
return Optional.empty();
}
int currentPriority = priorityOfCurrentPosition(vehicle);
Set<Point> parkingPosCandidates = findUsableParkingPositions(vehicle).stream()
.filter(point -> hasHigherPriorityThan(point, currentPriority))
.collect(Collectors.toSet());
if (parkingPosCandidates.isEmpty()) {
LOG.debug("{}: No parking position candidates found.", vehicle.getName());
return Optional.empty();
}
LOG.debug(
"{}: Selecting parking position from candidates {}.",
vehicle.getName(),
parkingPosCandidates
);
parkingPosCandidates = filterPositionsWithHighestPriority(parkingPosCandidates);
Point parkingPos = nearestPoint(vehicle, parkingPosCandidates);
LOG.debug("{}: Selected parking position {}.", vehicle.getName(), parkingPos);
return Optional.ofNullable(parkingPos);
}
private int priorityOfCurrentPosition(Vehicle vehicle) {
Point currentPos = getPlantModelService().fetchObject(
Point.class,
vehicle.getCurrentPosition()
);
return priorityFunction
.andThen(priority -> priority != null ? priority : Integer.MAX_VALUE)
.apply(currentPos);
}
private boolean hasHigherPriorityThan(Point point, Integer priority) {
Integer pointPriority = priorityFunction.apply(point);
if (pointPriority == null) {
return false;
}
return pointPriority < priority;
}
private Set<Point> filterPositionsWithHighestPriority(Set<Point> positions) {
checkArgument(!positions.isEmpty(), "'positions' must not be empty");
Map<Integer, List<Point>> prioritiesToPositions = positions.stream()
.collect(Collectors.groupingBy(point -> priorityFunction.apply(point)));
Integer highestPriority = prioritiesToPositions.keySet().stream()
.reduce(Integer::min)
.get();
return new HashSet<>(prioritiesToPositions.get(highestPriority));
}
}

View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.parking;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeReparkVehicleSelectionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates parking orders for idle vehicles already at a parking position to send them to higher
* prioritized parking positions.
*/
public class PrioritizedReparkPhase
extends
AbstractParkingPhase {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PrioritizedReparkPhase.class);
private final CompositeReparkVehicleSelectionFilter vehicleSelectionFilter;
private final ParkingPositionPriorityComparator priorityComparator;
@Inject
public PrioritizedReparkPhase(
InternalTransportOrderService orderService,
PrioritizedParkingPositionSupplier parkingPosSupplier,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
TransportOrderUtil transportOrderUtil,
DefaultDispatcherConfiguration configuration,
CompositeReparkVehicleSelectionFilter vehicleSelectionFilter,
ParkingPositionPriorityComparator priorityComparator
) {
super(
orderService,
parkingPosSupplier,
router,
assignmentCandidateSelectionFilter,
transportOrderUtil,
configuration
);
this.vehicleSelectionFilter = requireNonNull(vehicleSelectionFilter, "vehicleSelectionFilter");
this.priorityComparator = requireNonNull(priorityComparator, "priorityComparator");
}
@Override
public void run() {
if (!getConfiguration().parkIdleVehicles()
|| !getConfiguration().considerParkingPositionPriorities()
|| !getConfiguration().reparkVehiclesToHigherPriorityPositions()) {
return;
}
LOG.debug("Looking for parking vehicles to send to higher prioritized parking positions...");
getOrderService().fetchObjects(Vehicle.class).stream()
.filter(vehicle -> vehicleSelectionFilter.apply(vehicle).isEmpty())
.sorted((vehicle1, vehicle2) -> {
// Sort the vehicles based on the priority of the parking position they occupy
Point point1 = getOrderService().fetchObject(Point.class, vehicle1.getCurrentPosition());
Point point2 = getOrderService().fetchObject(Point.class, vehicle2.getCurrentPosition());
return priorityComparator.compare(point1, point2);
})
.forEach(vehicle -> createParkingOrder(vehicle));
}
}

View File

@@ -0,0 +1,295 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.recharging;
import static java.util.Objects.requireNonNull;
import static org.opentcs.components.kernel.Dispatcher.PROPKEY_ASSIGNED_RECHARGE_LOCATION;
import static org.opentcs.components.kernel.Dispatcher.PROPKEY_PREFERRED_RECHARGE_LOCATION;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalPlantModelService;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.LocationType;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
/**
* Finds assigned, preferred or (routing-wise) cheapest recharge locations for vehicles.
*/
public class DefaultRechargePositionSupplier
implements
RechargePositionSupplier {
/**
* The plant model service.
*/
private final InternalPlantModelService plantModelService;
/**
* Our router.
*/
private final Router router;
/**
* Indicates whether this component is enabled.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param plantModelService The plant model service.
* @param router The router to use.
*/
@Inject
public DefaultRechargePositionSupplier(
InternalPlantModelService plantModelService,
Router router
) {
this.plantModelService = requireNonNull(plantModelService, "plantModelService");
this.router = requireNonNull(router, "router");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
initialized = false;
}
@Override
public List<DriveOrder.Destination> findRechargeSequence(Vehicle vehicle) {
requireNonNull(vehicle, "vehicle");
if (vehicle.getCurrentPosition() == null) {
return List.of();
}
Map<Location, Set<Point>> rechargeLocations
= findLocationsForOperation(
vehicle.getRechargeOperation(),
vehicle,
router.getTargetedPoints()
);
String assignedRechargeLocationName = vehicle.getProperty(PROPKEY_ASSIGNED_RECHARGE_LOCATION);
if (assignedRechargeLocationName != null) {
Location location = pickLocationWithName(
assignedRechargeLocationName,
rechargeLocations.keySet()
);
if (location == null) {
return List.of();
}
// XXX We should check whether there actually is a viable route to the location.
return List.of(createDestination(location, vehicle.getRechargeOperation()));
}
// XXX We should check whether there actually is a viable route to the chosen location.
return Optional.ofNullable(vehicle.getProperty(PROPKEY_PREFERRED_RECHARGE_LOCATION))
.map(name -> pickLocationWithName(name, rechargeLocations.keySet()))
.or(() -> Optional.ofNullable(findCheapestLocation(rechargeLocations, vehicle)))
.map(location -> List.of(createDestination(location, vehicle.getRechargeOperation())))
.orElse(List.of());
}
@Nullable
private Location findCheapestLocation(Map<Location, Set<Point>> locations, Vehicle vehicle) {
Point curPos = plantModelService.fetchObject(Point.class, vehicle.getCurrentPosition());
return locations.entrySet().stream()
.map(entry -> bestAccessPointCandidate(vehicle, curPos, entry.getKey(), entry.getValue()))
.filter(candidate -> candidate.isPresent())
.map(candidate -> candidate.get())
.min(Comparator.comparingLong(candidate -> candidate.costs))
.map(candidate -> candidate.location)
.orElse(null);
}
private DriveOrder.Destination createDestination(Location location, String operation) {
return new DriveOrder.Destination(location.getReference())
.withOperation(operation);
}
@Nullable
private Location pickLocationWithName(String name, Set<Location> locations) {
return locations.stream()
.filter(location -> name.equals(location.getName()))
.findAny()
.orElse(null);
}
/**
* Finds locations allowing the given operation, and the points they would be accessible from for
* the given vehicle.
*
* @param operation The operation.
* @param vehicle The vehicle.
* @param targetedPoints The points that are currently targeted by vehicles.
* @return The locations allowing the given operation, and the points they would be accessible
* from.
*/
private Map<Location, Set<Point>> findLocationsForOperation(
String operation,
Vehicle vehicle,
Set<Point> targetedPoints
) {
Map<Location, Set<Point>> result = new HashMap<>();
for (Location curLoc : plantModelService.fetchObjects(Location.class)) {
LocationType lType = plantModelService.fetchObject(LocationType.class, curLoc.getType());
if (lType.isAllowedOperation(operation)) {
Set<Point> points = findUnoccupiedAccessPointsForOperation(
curLoc,
operation,
vehicle,
targetedPoints
);
if (!points.isEmpty()) {
result.put(curLoc, points);
}
}
}
return result;
}
private Set<Point> findUnoccupiedAccessPointsForOperation(
Location location,
String rechargeOp,
Vehicle vehicle,
Set<Point> targetedPoints
) {
return location.getAttachedLinks().stream()
.filter(link -> allowsOperation(link, rechargeOp))
.map(link -> plantModelService.fetchObject(Point.class, link.getPoint()))
.filter(accessPoint -> isPointUnoccupiedFor(accessPoint, vehicle, targetedPoints))
.collect(Collectors.toSet());
}
/**
* Checks if the given link either does not define any allowed operations at all (meaning it does
* not override the allowed operations of the corresponding location's location type), or - if it
* does - explicitly allows the required recharge operation.
*
* @param link The link to be checked.
* @param operation The operation to be checked for.
* @return <code>true</code> if, and only if, the given link does not disallow the given
* operation.
*/
private boolean allowsOperation(Location.Link link, String operation) {
// This link is only interesting if it either does not define any allowed operations (does
// not override the allowed operations of the corresponding location) at all or, if it does,
// allows the required recharge operation.
return link.getAllowedOperations().isEmpty() || link.hasAllowedOperation(operation);
}
private Optional<LocationCandidate> bestAccessPointCandidate(
Vehicle vehicle,
Point srcPosition,
Location location,
Set<Point> destPositions
) {
return destPositions.stream()
.map(
point -> new LocationCandidate(
location,
router.getCosts(
vehicle,
srcPosition,
point,
Set.of()
)
)
)
.min(Comparator.comparingLong(candidate -> candidate.costs));
}
/**
* Checks if ALL points within the same block as the given access point are NOT occupied or
* targeted by any other vehicle than the given one.
*
* @param accessPoint The point to be checked.
* @param vehicle The vehicle to be checked for.
* @param targetedPoints All currently known targeted points.
* @return <code>true</code> if, and only if, ALL points within the same block as the given access
* point are NOT occupied or targeted by any other vehicle than the given one.
*/
private boolean isPointUnoccupiedFor(
Point accessPoint,
Vehicle vehicle,
Set<Point> targetedPoints
) {
return expandPoints(accessPoint).stream()
.noneMatch(
point -> pointOccupiedOrTargetedByOtherVehicle(
point,
vehicle,
targetedPoints
)
);
}
private boolean pointOccupiedOrTargetedByOtherVehicle(
Point pointToCheck,
Vehicle vehicle,
Set<Point> targetedPoints
) {
if (pointToCheck.getOccupyingVehicle() != null
&& !pointToCheck.getOccupyingVehicle().equals(vehicle.getReference())) {
return true;
}
else if (targetedPoints.contains(pointToCheck)) {
return true;
}
return false;
}
/**
* Gathers a set of all points from all blocks that the given point is a member of.
*
* @param point The point to check.
* @return A set of all points from all blocks that the given point is a member of.
*/
private Set<Point> expandPoints(Point point) {
return plantModelService.expandResources(Set.of(point.getReference())).stream()
.filter(resource -> Point.class.equals(resource.getReference().getReferentClass()))
.map(resource -> (Point) resource)
.collect(Collectors.toSet());
}
private static class LocationCandidate {
private final Location location;
private final long costs;
LocationCandidate(Location location, long costs) {
this.location = location;
this.costs = costs;
}
}
}

View File

@@ -0,0 +1,187 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.recharging;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.opentcs.access.to.order.DestinationCreationTO;
import org.opentcs.access.to.order.TransportOrderCreationTO;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.Phase;
import org.opentcs.strategies.basic.dispatching.TransportOrderUtil;
import org.opentcs.strategies.basic.dispatching.selection.candidates.CompositeAssignmentCandidateSelectionFilter;
import org.opentcs.strategies.basic.dispatching.selection.vehicles.CompositeRechargeVehicleSelectionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates recharging orders for any vehicles with a degraded energy level.
*/
public class RechargeIdleVehiclesPhase
implements
Phase {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(RechargeIdleVehiclesPhase.class);
/**
* The transport order service.
*/
private final InternalTransportOrderService orderService;
/**
* The strategy used for finding suitable recharge locations.
*/
private final RechargePositionSupplier rechargePosSupplier;
/**
* The Router instance calculating route costs.
*/
private final Router router;
/**
* A collection of predicates for filtering assignment candidates.
*/
private final CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter;
private final CompositeRechargeVehicleSelectionFilter vehicleSelectionFilter;
private final TransportOrderUtil transportOrderUtil;
/**
* The dispatcher configuration.
*/
private final DefaultDispatcherConfiguration configuration;
/**
* Indicates whether this component is initialized.
*/
private boolean initialized;
@Inject
public RechargeIdleVehiclesPhase(
InternalTransportOrderService orderService,
RechargePositionSupplier rechargePosSupplier,
Router router,
CompositeAssignmentCandidateSelectionFilter assignmentCandidateSelectionFilter,
CompositeRechargeVehicleSelectionFilter vehicleSelectionFilter,
TransportOrderUtil transportOrderUtil,
DefaultDispatcherConfiguration configuration
) {
this.router = requireNonNull(router, "router");
this.orderService = requireNonNull(orderService, "orderService");
this.rechargePosSupplier = requireNonNull(rechargePosSupplier, "rechargePosSupplier");
this.assignmentCandidateSelectionFilter = requireNonNull(
assignmentCandidateSelectionFilter,
"assignmentCandidateSelectionFilter"
);
this.vehicleSelectionFilter = requireNonNull(vehicleSelectionFilter, "vehicleSelectionFilter");
this.transportOrderUtil = requireNonNull(transportOrderUtil, "transportOrderUtil");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rechargePosSupplier.initialize();
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
rechargePosSupplier.terminate();
initialized = false;
}
@Override
public void run() {
if (!configuration.rechargeIdleVehicles()) {
return;
}
orderService.fetchObjects(Vehicle.class).stream()
.filter(vehicle -> vehicleSelectionFilter.apply(vehicle).isEmpty())
.forEach(vehicle -> createRechargeOrder(vehicle));
}
private void createRechargeOrder(Vehicle vehicle) {
List<DriveOrder.Destination> rechargeDests = rechargePosSupplier.findRechargeSequence(vehicle);
LOG.debug("Recharge sequence for {}: {}", vehicle, rechargeDests);
if (rechargeDests.isEmpty()) {
LOG.info("{}: Did not find a suitable recharge sequence.", vehicle.getName());
return;
}
List<DestinationCreationTO> chargeDests = new ArrayList<>(rechargeDests.size());
for (DriveOrder.Destination dest : rechargeDests) {
chargeDests.add(
new DestinationCreationTO(dest.getDestination().getName(), dest.getOperation())
.withProperties(dest.getProperties())
);
}
// Create a transport order for recharging and verify its processability.
// The recharge order may be withdrawn unless its energy level is critical.
TransportOrder rechargeOrder = orderService.createTransportOrder(
new TransportOrderCreationTO("Recharge-", chargeDests)
.withIncompleteName(true)
.withIntendedVehicleName(vehicle.getName())
.withDispensable(!vehicle.isEnergyLevelCritical())
.withType(OrderConstants.TYPE_CHARGE)
);
Point vehiclePosition = orderService.fetchObject(Point.class, vehicle.getCurrentPosition());
Optional<AssignmentCandidate> candidate = computeCandidate(
vehicle,
vehiclePosition,
rechargeOrder
)
.filter(c -> assignmentCandidateSelectionFilter.apply(c).isEmpty());
// XXX Change this to Optional.ifPresentOrElse() once we're at Java 9+.
if (candidate.isPresent()) {
transportOrderUtil.assignTransportOrder(
candidate.get().getVehicle(),
candidate.get().getTransportOrder(),
candidate.get().getDriveOrders()
);
}
else {
// Mark the order as failed, since the vehicle cannot execute it.
orderService.updateTransportOrderState(
rechargeOrder.getReference(),
TransportOrder.State.FAILED
);
}
}
private Optional<AssignmentCandidate> computeCandidate(
Vehicle vehicle,
Point vehiclePosition,
TransportOrder order
) {
return router.getRoute(vehicle, vehiclePosition, order)
.map(driveOrders -> new AssignmentCandidate(vehicle, order, driveOrders));
}
}

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.phase.recharging;
import jakarta.annotation.Nonnull;
import java.util.List;
import org.opentcs.components.Lifecycle;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
/**
* A strategy for finding locations suitable for recharging vehicles.
*/
public interface RechargePositionSupplier
extends
Lifecycle {
/**
* Returns a sequence of destinations for recharging the given vehicle.
* In most cases, the sequence will probably consist of only one destination. Some vehicles may
* require to visit more than one destination for recharging, though, e.g. to drop off a battery
* pack at one location and get a fresh one at another location.
*
* @param vehicle The vehicle to be recharged.
* @return A sequence of destinations including operations for recharging the given vehicle. If
* no suitable sequence was found, the returned sequence will be empty.
*/
@Nonnull
List<DriveOrder.Destination> findRechargeSequence(
@Nonnull
Vehicle vehicle
);
}

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization;
import static org.opentcs.util.Assertions.checkArgument;
import com.google.common.collect.Lists;
import jakarta.inject.Inject;
import java.util.Comparator;
import java.util.Map;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByOrderAge;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByOrderName;
/**
* A composite of all configured transport order candidate comparators.
*/
public class CompositeOrderCandidateComparator
implements
Comparator<AssignmentCandidate> {
/**
* A comparator composed of all configured comparators, in the configured order.
*/
private final Comparator<AssignmentCandidate> compositeComparator;
@Inject
public CompositeOrderCandidateComparator(
DefaultDispatcherConfiguration configuration,
Map<String, Comparator<AssignmentCandidate>> availableComparators
) {
// At the end, if all other comparators failed to see a difference, compare by age.
// As the age of two distinct transport orders may still be the same, finally compare by name.
// Add configured comparators before these two.
Comparator<AssignmentCandidate> composite
= new CandidateComparatorByOrderAge().thenComparing(new CandidateComparatorByOrderName());
for (String priorityKey : Lists.reverse(configuration.orderCandidatePriorities())) {
Comparator<AssignmentCandidate> configuredComparator = availableComparators.get(priorityKey);
checkArgument(
configuredComparator != null,
"Unknown order candidate priority key: '%s'",
priorityKey
);
composite = configuredComparator.thenComparing(composite);
}
this.compositeComparator = composite;
}
@Override
public int compare(AssignmentCandidate o1, AssignmentCandidate o2) {
return compositeComparator.compare(o1, o2);
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization;
import static org.opentcs.util.Assertions.checkArgument;
import com.google.common.collect.Lists;
import jakarta.inject.Inject;
import java.util.Comparator;
import java.util.Map;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByAge;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByName;
/**
* A composite of all configured transport order comparators.
*/
public class CompositeOrderComparator
implements
Comparator<TransportOrder> {
/**
* A comparator composed of all configured comparators, in the configured order.
*/
private final Comparator<TransportOrder> compositeComparator;
@Inject
public CompositeOrderComparator(
DefaultDispatcherConfiguration configuration,
Map<String, Comparator<TransportOrder>> availableComparators
) {
// At the end, if all other comparators failed to see a difference, compare by age.
// As the age of two distinct transport orders may still be the same, finally compare by name.
// Add configured comparators before these two.
Comparator<TransportOrder> composite
= new TransportOrderComparatorByAge().thenComparing(new TransportOrderComparatorByName());
for (String priorityKey : Lists.reverse(configuration.orderPriorities())) {
Comparator<TransportOrder> configuredComparator = availableComparators.get(priorityKey);
checkArgument(configuredComparator != null, "Unknown order priority key: '%s'", priorityKey);
composite = configuredComparator.thenComparing(composite);
}
this.compositeComparator = composite;
}
@Override
public int compare(TransportOrder o1, TransportOrder o2) {
return compositeComparator.compare(o1, o2);
}
}

View File

@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization;
import static org.opentcs.util.Assertions.checkArgument;
import com.google.common.collect.Lists;
import jakarta.inject.Inject;
import java.util.Comparator;
import java.util.Map;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByEnergyLevel;
import org.opentcs.strategies.basic.dispatching.priorization.candidate.CandidateComparatorByVehicleName;
/**
* A composite of all configured vehicle candidate comparators.
*/
public class CompositeVehicleCandidateComparator
implements
Comparator<AssignmentCandidate> {
/**
* A comparator composed of all configured comparators, in the configured order.
*/
private final Comparator<AssignmentCandidate> compositeComparator;
@Inject
public CompositeVehicleCandidateComparator(
DefaultDispatcherConfiguration configuration,
Map<String, Comparator<AssignmentCandidate>> availableComparators
) {
// At the end, if all other comparators failed to see a difference, compare by energy level.
// As the energy level of two distinct vehicles may still be the same, finally compare by name.
// Add configured comparators before these two.
Comparator<AssignmentCandidate> composite
= new CandidateComparatorByEnergyLevel()
.thenComparing(new CandidateComparatorByVehicleName());
for (String priorityKey : Lists.reverse(configuration.vehicleCandidatePriorities())) {
Comparator<AssignmentCandidate> configuredComparator = availableComparators.get(priorityKey);
checkArgument(
configuredComparator != null,
"Unknown vehicle candidate priority key: '%s'",
priorityKey
);
composite = configuredComparator.thenComparing(composite);
}
this.compositeComparator = composite;
}
@Override
public int compare(AssignmentCandidate o1, AssignmentCandidate o2) {
return compositeComparator.compare(o1, o2);
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization;
import static org.opentcs.util.Assertions.checkArgument;
import com.google.common.collect.Lists;
import jakarta.inject.Inject;
import java.util.Comparator;
import java.util.Map;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorByEnergyLevel;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorByName;
/**
* A composite of all configured vehicle comparators.
*/
public class CompositeVehicleComparator
implements
Comparator<Vehicle> {
/**
* A comparator composed of all configured comparators, in the configured order.
*/
private final Comparator<Vehicle> compositeComparator;
@Inject
public CompositeVehicleComparator(
DefaultDispatcherConfiguration configuration,
Map<String, Comparator<Vehicle>> availableComparators
) {
// At the end, if all other comparators failed to see a difference, compare by energy level.
// As the energy level of two distinct vehicles may still be the same, finally compare by name.
// Add configured comparators before these two.
Comparator<Vehicle> composite
= new VehicleComparatorByEnergyLevel().thenComparing(new VehicleComparatorByName());
for (String priorityKey : Lists.reverse(configuration.vehiclePriorities())) {
Comparator<Vehicle> configuredComparator = availableComparators.get(priorityKey);
checkArgument(
configuredComparator != null,
"Unknown vehicle priority key: '%s'",
priorityKey
);
composite = configuredComparator.thenComparing(composite);
}
this.compositeComparator = composite;
}
@Override
public int compare(Vehicle o1, Vehicle o2) {
return compositeComparator.compare(o1, o2);
}
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
/**
* Compares {@link AssignmentCandidate}s by routing costs.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorByCompleteRoutingCosts
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_COMPLETE_ROUTING_COSTS";
/**
* Creates a new instance.
*/
public CandidateComparatorByCompleteRoutingCosts() {
}
/**
* Compares two candidates by their routing costs.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candidate
* @param candidate2 The second candidate
* @return the value 0 if candidate1 and candidate2 have the same routing costs;
* a value less than 0 if candidate1 has a lower routing cost than candidate2;
* and a value greater than 0 otherwise.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
// Lower routing costs are better.
return Long.compare(candidate1.getCompleteRoutingCosts(), candidate2.getCompleteRoutingCosts());
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByDeadline;
/**
* Compares {@link AssignmentCandidate}s by deadline of the order.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorByDeadline
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_DEADLINE";
private final Comparator<TransportOrder> delegate = new TransportOrderComparatorByDeadline();
/**
* Creates a new instance.
*/
public CandidateComparatorByDeadline() {
}
/**
* Compares two candidates by the deadline of their transport order.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candidate
* @param candidate2 The second candidate
* @return the value 0 if candidate1 and candidate2 have the same deadline;
* a value less than 0 if candidate1 has an earlier deadline than candidate2;
* and a value greater than 0 otherwise.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getTransportOrder(), candidate2.getTransportOrder());
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorByEnergyLevel;
/**
* Compares {@link AssignmentCandidate}s by the energy level of their vehicles.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorByEnergyLevel
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_ENERGY_LEVEL";
private final Comparator<Vehicle> delegate = new VehicleComparatorByEnergyLevel();
/**
* Creates a new instance.
*/
public CandidateComparatorByEnergyLevel() {
}
/**
* Compares two candidates by the energy level of their vehicles.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candidate.
* @param candidate2 The second candidate.
* @return the value 0 if candidate1 and candidate2 have the same energy level;
* a value less than 0 if candidate1 has a higher energy level than candidate2;
* and a value greater than 0 otherwise.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getVehicle(), candidate2.getVehicle());
}
}

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
/**
* Compares {@link AssignmentCandidate}s by routing costs to the transport order's first
* destination.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorByInitialRoutingCosts
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_INITIAL_ROUTING_COSTS";
/**
* Creates a new instance.
*/
public CandidateComparatorByInitialRoutingCosts() {
}
/**
* Compares two candidates by their inital routing cost.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candidate.
* @param candidate2 The second candidate.
* @return the value 0 if candidate1 and candidate2 have the same routing cost;
* a value less than 0 if candidate1 has lower routing cost than candidate2;
* and a value greater than 0 otherwise.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
// Lower routing costs are better.
return Long.compare(candidate1.getInitialRoutingCosts(), candidate2.getInitialRoutingCosts());
}
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByAge;
/**
* Compares {@link AssignmentCandidate}s by age of the order.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorByOrderAge
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_AGE";
private final Comparator<TransportOrder> delegate = new TransportOrderComparatorByAge();
/**
* Creates a new instance.
*/
public CandidateComparatorByOrderAge() {
}
/**
* Compares two candidate by the age of the order.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candidate.
* @param candidate2 The second candidate.
* @return the value zero, if the transport order of
* candidate1 and candidate2 have the same creation time;
* a value less than zero, if candidate1 is older than candidate2.
* a value greater than zero otherwise.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getTransportOrder(), candidate2.getTransportOrder());
}
}

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorByName;
/**
* Compares {@link AssignmentCandidate}s by name of the order.
*/
public class CandidateComparatorByOrderName
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_ORDER_NAME";
private final Comparator<TransportOrder> delegate = new TransportOrderComparatorByName();
/**
* Creates a new instance.
*/
public CandidateComparatorByOrderName() {
}
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getTransportOrder(), candidate2.getTransportOrder());
}
}

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorByName;
/**
* Compares {@link AssignmentCandidate}s by name of the vehicle.
*/
public class CandidateComparatorByVehicleName
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_VEHICLE_NAME";
private final Comparator<Vehicle> delegate = new VehicleComparatorByName();
/**
* Creates a new instance.
*/
public CandidateComparatorByVehicleName() {
}
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getVehicle(), candidate2.getVehicle());
}
}

View File

@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.transportorder.TransportOrderComparatorDeadlineAtRiskFirst;
/**
* Compares {@link AssignmentCandidate}s by their transport order's deadlines, ordering those with a
* deadline at risk first.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorDeadlineAtRiskFirst
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "DEADLINE_AT_RISK_FIRST";
/**
* The comparator that compares the deadlines of transport orders, taking the critical threshold
* into account.
*/
private final Comparator<TransportOrder> delegate;
@Inject
public CandidateComparatorDeadlineAtRiskFirst(
TransportOrderComparatorDeadlineAtRiskFirst delegate
) {
this.delegate = requireNonNull(delegate, "delegate");
}
/**
* Compares two candidates by the deadline of their transport order and the given threshold
* indicating whether the remaining time for the deadline is considered critical
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candidate
* @param candidate2 The second candidate
* @return the value 0 if the deadlines of candidate1 and candidate2 are both at risk or not;
* a value less than 0 if only the deadline of candidate1 is at risk
* and a value greater than 0 in all other cases.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getTransportOrder(), candidate2.getTransportOrder());
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.candidate;
import java.util.Comparator;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.priorization.vehicle.VehicleComparatorIdleFirst;
/**
* Compares {@link AssignmentCandidate}s by vehicles' states, ordering IDLE vehicles first.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class CandidateComparatorIdleFirst
implements
Comparator<AssignmentCandidate> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "IDLE_FIRST";
private final Comparator<Vehicle> delegate = new VehicleComparatorIdleFirst();
/**
* Creates a new instance.
*/
public CandidateComparatorIdleFirst() {
}
/**
* Compares two candidates by the state of their vehicles.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param candidate1 The first candiate.
* @param candidate2 The second candidate.
* @return The value zero if the vehicles of candidate1 and candidate2 have the same state;
* a value grater zero, if the vehicle state of candidate1 is idle, unlike candidate2;
* a value less than zero otherwise.
*/
@Override
public int compare(AssignmentCandidate candidate1, AssignmentCandidate candidate2) {
return delegate.compare(candidate1.getVehicle(), candidate2.getVehicle());
}
}

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.transportorder;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
/**
* Compares {@link TransportOrder}s by age.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class TransportOrderComparatorByAge
implements
Comparator<TransportOrder> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_AGE";
/**
* Creates a new instance.
*/
public TransportOrderComparatorByAge() {
}
/**
* Compares two orders by their age.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param order1 The first order.
* @param order2 The second order.
* @return the value zero, if the transport order have the same creation time;
* a value less than zero, if order1 is older than order2.
* a value greater than zero otherwise.
*/
@Override
public int compare(TransportOrder order1, TransportOrder order2) {
return order1.getCreationTime().compareTo(order2.getCreationTime());
}
}

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.transportorder;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
/**
* Compares {@link TransportOrder}s by age.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class TransportOrderComparatorByDeadline
implements
Comparator<TransportOrder> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_DEADLINE";
/**
* Creates a new instance.
*/
public TransportOrderComparatorByDeadline() {
}
/**
* Compares two orders by their deadline.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param order1 The first order.
* @param order2 The second order.
* @return the value 0 if order1 and order2 have the same deadline;
* a value less than 0 if order1 has an earlier deadline than order2;
* and a value greater than 0 otherwise.
*/
@Override
public int compare(TransportOrder order1, TransportOrder order2) {
return order1.getDeadline().compareTo(order2.getDeadline());
}
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.transportorder;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
/**
* Compares {@link TransportOrder}s by their names.
*/
public class TransportOrderComparatorByName
implements
Comparator<TransportOrder> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_NAME";
/**
* Creates a new instance.
*/
public TransportOrderComparatorByName() {
}
@Override
public int compare(TransportOrder order1, TransportOrder order2) {
return order1.getName().compareTo(order2.getName());
}
}

View File

@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.transportorder;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.time.Instant;
import java.util.Comparator;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
/**
* Compares {@link TransportOrder}s by their deadlines, ordering those with a deadline at risk
* first.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class TransportOrderComparatorDeadlineAtRiskFirst
implements
Comparator<TransportOrder> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "DEADLINE_AT_RISK_FIRST";
/**
* The time window (in ms) before its deadline in which an order becomes urgent.
*/
private final long deadlineAtRiskPeriod;
@Inject
public TransportOrderComparatorDeadlineAtRiskFirst(DefaultDispatcherConfiguration configuration) {
requireNonNull(configuration, "configuration");
this.deadlineAtRiskPeriod = configuration.deadlineAtRiskPeriod();
}
/**
* Compares two orders by their deadline's criticality.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param order1 The first order.
* @param order2 The second order.
* @return the value 0 if the deadlines of order1 and order2 are both at risk or not,
* a value less than 0 if only the deadline of order1 is at risk
* and a value greater than 0 in all other cases.
*/
@Override
public int compare(TransportOrder order1, TransportOrder order2) {
boolean order1AtRisk = deadlineAtRisk(order1);
boolean order2AtRisk = deadlineAtRisk(order2);
if (order1AtRisk == order2AtRisk) {
return 0;
}
else if (order1AtRisk) {
return -1;
}
else {
return 1;
}
}
private boolean deadlineAtRisk(TransportOrder order) {
return order.getDeadline().minusMillis(deadlineAtRiskPeriod).isBefore(Instant.now());
}
}

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.vehicle;
import java.util.Comparator;
import org.opentcs.data.model.Vehicle;
/**
* Compares {@link Vehicle}s by energy level, sorting higher energy levels up.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class VehicleComparatorByEnergyLevel
implements
Comparator<Vehicle> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_ENERGY_LEVEL";
/**
* Creates a new instance.
*/
public VehicleComparatorByEnergyLevel() {
}
/**
* Compares two vehicles by their energy level.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param vehicle1 The first vehicle.
* @param vehicle2 The second vehicel.
* @return the value 0 if vehicle1 and vehicle2 have the same energy level;
* a value less than 0 if vehicle1 has a higher energy level than vehicle2;
* and a value greater than 0 otherwise.
*/
@Override
public int compare(Vehicle vehicle1, Vehicle vehicle2) {
return -Integer.compare(vehicle1.getEnergyLevel(), vehicle2.getEnergyLevel());
}
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.vehicle;
import java.util.Comparator;
import org.opentcs.data.model.Vehicle;
/**
* Compares {@link Vehicle}s by their names.
*/
public class VehicleComparatorByName
implements
Comparator<Vehicle> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "BY_NAME";
/**
* Creates a new instance.
*/
public VehicleComparatorByName() {
}
@Override
public int compare(Vehicle vehicle1, Vehicle vehicle2) {
return vehicle1.getName().compareTo(vehicle2.getName());
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.priorization.vehicle;
import java.util.Comparator;
import org.opentcs.data.model.Vehicle;
/**
* Compares {@link Vehicle}s by their states, ordering IDLE vehicles first.
* Note: this comparator imposes orderings that are inconsistent with equals.
*/
public class VehicleComparatorIdleFirst
implements
Comparator<Vehicle> {
/**
* A key used for selecting this comparator in a configuration setting.
* Should be unique among all keys.
*/
public static final String CONFIGURATION_KEY = "IDLE_FIRST";
/**
* Creates a new instance.
*/
public VehicleComparatorIdleFirst() {
}
/**
* Compares two vehicles by their state.
* Note: this comparator imposes orderings that are inconsistent with equals.
*
* @see Comparator#compare(java.lang.Object, java.lang.Object)
* @param vehicle1 The first vehicle.
* @param vehicle2 The second vehicle.
* @return The value zero if vehicle1 and vehicle2 have the same state;
* a value grater zero, if the state of vehicle1 is idle, unlike vehicle2;
* a value less than zero otherwise.
*/
@Override
public int compare(Vehicle vehicle1, Vehicle vehicle2) {
if (vehicle1.getState() == vehicle2.getState()) {
return 0;
}
else if (vehicle1.getState() == Vehicle.State.IDLE) {
return -1;
}
else {
return 1;
}
}
}

View File

@@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.Router;
import org.opentcs.data.model.Path;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.Route;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.routing.ResourceAvoidanceExtractor;
/**
* An abstract implementation of {@link DriveOrderMerger} defining the basic merging algorithm.
*/
public abstract class AbstractDriveOrderMerger
implements
DriveOrderMerger {
private final Router router;
private final ResourceAvoidanceExtractor resourceAvoidanceExtractor;
/**
* Creates a new instance.
*
* @param router The router to use.
* @param resourceAvoidanceExtractor Extracts resources to be avoided from transport orders.
*/
protected AbstractDriveOrderMerger(
Router router,
ResourceAvoidanceExtractor resourceAvoidanceExtractor
) {
this.router = requireNonNull(router, "router");
this.resourceAvoidanceExtractor
= requireNonNull(resourceAvoidanceExtractor, "resourceAvoidanceExtractor");
}
@Override
public DriveOrder mergeDriveOrders(
@Nonnull
DriveOrder orderA,
@Nonnull
DriveOrder orderB,
@Nonnull
TransportOrder originalOrder,
int currentRouteStepIndex,
Vehicle vehicle
) {
requireNonNull(orderA, "orderA");
requireNonNull(orderB, "orderB");
requireNonNull(originalOrder, "originalOrder");
return new DriveOrder(orderA.getDestination())
.withState(orderA.getState())
.withTransportOrder(orderA.getTransportOrder())
.withRoute(
mergeRoutes(
orderA.getRoute(),
orderB.getRoute(),
originalOrder,
currentRouteStepIndex,
vehicle
)
);
}
/**
* Merges the two given {@link Route}s.
*
* @param routeA A route.
* @param routeB A route to be merged with {@code routeA}.
* @param originalOrder The transport order to merge the drive orders for.
* @param currentRouteStepIndex The index of the last route step travelled for {@code routeA}.
* @param vehicle The {@link Vehicle} to merge the routes for.
* @return The (new) merged route.
*/
protected Route mergeRoutes(
Route routeA,
Route routeB,
TransportOrder originalOrder,
int currentRouteStepIndex,
Vehicle vehicle
) {
// Merge the route steps
List<Route.Step> mergedSteps = mergeSteps(
routeA.getSteps(),
routeB.getSteps(),
currentRouteStepIndex
);
// Calculate the costs for merged route
return new Route(
mergedSteps,
router.getCosts(
vehicle,
mergedSteps.get(0).getSourcePoint(),
mergedSteps.get(mergedSteps.size() - 1).getDestinationPoint(),
resourceAvoidanceExtractor
.extractResourcesToAvoid(originalOrder)
.toResourceReferenceSet()
)
);
}
/**
* Merges the two given lists of {@link Route.Step}s.
*
* @param stepsA A list of steps.
* @param stepsB A list of steps to be merged with {@code stepsA}.
* @param currentRouteStepIndex The index of the last route step travelled for {@code stepsA}.
* @return The (new) merged list of steps.
*/
protected abstract List<Route.Step> mergeSteps(
List<Route.Step> stepsA,
List<Route.Step> stepsB,
int currentRouteStepIndex
);
protected List<Route.Step> updateRouteIndices(List<Route.Step> steps) {
List<Route.Step> updatedSteps = new ArrayList<>();
for (int i = 0; i < steps.size(); i++) {
Route.Step currStep = steps.get(i);
updatedSteps.add(
new Route.Step(
currStep.getPath(),
currStep.getSourcePoint(),
currStep.getDestinationPoint(),
currStep.getVehicleOrientation(),
i,
currStep.isExecutionAllowed(),
currStep.getReroutingType()
)
);
}
return updatedSteps;
}
protected List<Path> stepsToPaths(List<Route.Step> steps) {
return steps.stream()
.map(step -> step.getPath())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,188 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import static java.util.Objects.requireNonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.TransportOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An abstract implementation of {@link ReroutingStrategy} defining the basic rerouting algorithm.
*/
public abstract class AbstractReroutingStrategy
implements
ReroutingStrategy {
private static final Logger LOG = LoggerFactory.getLogger(AbstractReroutingStrategy.class);
private final Router router;
private final TCSObjectService objectService;
private final DriveOrderMerger driveOrderMerger;
/**
* Creates a new instance.
*
* @param router The router to use.
* @param objectService The object service to use.
* @param driveOrderMerger Used to restore drive order history for a newly computed route.
*/
protected AbstractReroutingStrategy(
Router router,
TCSObjectService objectService,
DriveOrderMerger driveOrderMerger
) {
this.router = requireNonNull(router, "router");
this.objectService = requireNonNull(objectService, "objectService");
this.driveOrderMerger = requireNonNull(driveOrderMerger, "driveOrderMerger");
}
@Override
public Optional<List<DriveOrder>> reroute(Vehicle vehicle) {
TransportOrder currentTransportOrder = objectService.fetchObject(
TransportOrder.class,
vehicle.getTransportOrder()
);
LOG.debug("{}: Determining the reroute source...", vehicle.getName());
Optional<Point> optRerouteSource = determineRerouteSource(vehicle);
if (optRerouteSource.isEmpty()) {
LOG.warn(
"{}: Could not determine the reroute source. Not trying to reroute.",
vehicle.getName()
);
return Optional.empty();
}
Point rerouteSource = optRerouteSource.get();
// Get all unfinished drive order of the transport order the vehicle is processing.
List<DriveOrder> unfinishedOrders = getUnfinishedDriveOrders(currentTransportOrder);
// Try to get a new route for the unfinished drive orders from the reroute source.
Optional<List<DriveOrder>> optOrders = tryReroute(vehicle, unfinishedOrders, rerouteSource);
if (optOrders.isEmpty()) {
return Optional.empty();
}
List<DriveOrder> newDriveOrders = optOrders.get();
LOG.debug(
"Found a new route for {} from point {}: {}",
vehicle.getName(),
rerouteSource.getName(),
newDriveOrders
);
restoreCurrentDriveOrderHistory(newDriveOrders, vehicle, currentTransportOrder, rerouteSource);
return Optional.of(newDriveOrders);
}
protected TCSObjectService getObjectService() {
return objectService;
}
/**
* Determines the {@link Point} that should be the source point for the rerouting.
*
* @param vehicle The vehicle to determine the reroute source point for.
* @return The {@link Point} wrapped in an {@link Optional} or {@link Optional#EMPTY}, if a
* source point for the rerouting could not be determined.
*/
protected abstract Optional<Point> determineRerouteSource(Vehicle vehicle);
/**
* Returns a list of drive orders that haven't been finished for the given transport order, yet.
*
* @param order The transport order to get unfinished drive orders from.
* @return The list of unfinished drive orders.
*/
private List<DriveOrder> getUnfinishedDriveOrders(TransportOrder order) {
List<DriveOrder> result = new ArrayList<>();
result.add(order.getCurrentDriveOrder());
result.addAll(order.getFutureDriveOrders());
return result;
}
/**
* Tries to reroute the given vehicle for the given drive orders.
*
* @param vehicle The vehicle to reroute.
* @param driveOrders The drive orders for which to get a new route.
* @param sourcePoint The source point to reroute from.
* @return If rerouting is possible, an {@link Optional} containing the rerouted list of drive
* orders, otherwise {@link Optional#EMPTY}.
*/
private Optional<List<DriveOrder>> tryReroute(
Vehicle vehicle,
List<DriveOrder> driveOrders,
Point sourcePoint
) {
LOG.debug(
"Trying to reroute drive orders for {} from {}. Current drive orders: {}",
vehicle.getName(),
sourcePoint,
driveOrders
);
TransportOrder vehicleOrder = objectService.fetchObject(
TransportOrder.class,
vehicle.getTransportOrder()
);
return router.getRoute(
vehicle,
sourcePoint,
new TransportOrder("reroute-dummy", driveOrders)
.withProperties(vehicleOrder.getProperties())
);
}
private void restoreCurrentDriveOrderHistory(
List<DriveOrder> newDriveOrders,
Vehicle vehicle,
TransportOrder originalOrder,
Point rerouteSource
) {
// If the vehicle is currently not processing a (drive) order or waiting to get the next
// drive order (i.e. if it's idle) there is nothing to be restored.
if (vehicle.hasProcState(Vehicle.ProcState.IDLE)) {
return;
}
// XXX Is a distinction even necessary here, or could the else-part be performed in general?
if (isPointDestinationOfOrder(rerouteSource, originalOrder.getCurrentDriveOrder())) {
// The current drive order could not get rerouted, because the vehicle already
// received all commands for it. Therefore we want to keep the original drive order.
newDriveOrders.set(0, originalOrder.getCurrentDriveOrder());
}
else {
// Restore the current drive order's history
DriveOrder newCurrentOrder
= driveOrderMerger.mergeDriveOrders(
originalOrder.getCurrentDriveOrder(),
newDriveOrders.get(0),
originalOrder,
originalOrder.getCurrentRouteStepIndex(),
vehicle
);
newDriveOrders.set(0, newCurrentOrder);
}
}
private boolean isPointDestinationOfOrder(Point point, DriveOrder order) {
if (point == null || order == null) {
return false;
}
if (order.getRoute() == null) {
return false;
}
return Objects.equals(point, order.getRoute().getFinalDestinationPoint());
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.TransportOrder;
/**
* Provides a method to merge two {@link DriveOrder}s.
*/
public interface DriveOrderMerger {
/**
* Merges the two given {@link DriveOrder}s.
*
* @param orderA A drive order.
* @param orderB A drive order to be merged with {@code orderA}.
* @param originalOrder The transport order to merge the drive orders for.
* @param currentRouteStepIndex The index of the last route step travelled for {@code orderA}.
* @param vehicle The {@link Vehicle} to merge the drive orders for.
* @return The (new) merged drive order.
*/
DriveOrder mergeDriveOrders(
DriveOrder orderA,
DriveOrder orderB,
TransportOrder originalOrder,
int currentRouteStepIndex,
Vehicle vehicle
);
}

View File

@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import org.opentcs.components.kernel.Router;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.Route;
import org.opentcs.strategies.basic.routing.ResourceAvoidanceExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DriveOrderMerger} implementation for {@link ReroutingType#FORCED}.
* <p>
* Merges two drive orders so that the merged drive order follows the route of {@code orderA} up to
* the current route progress index reported by the vehicle that is processing the drive order. From
* there, the merged drive order follows the route of {@code orderB}. This means that the merged
* drive order may contain a gap/may not be continuous.
*/
public class ForcedDriveOrderMerger
extends
AbstractDriveOrderMerger {
private static final Logger LOG = LoggerFactory.getLogger(ForcedDriveOrderMerger.class);
/**
* Creates a new instance.
*
* @param router The router to use.
* @param resourceAvoidanceExtractor Extracts resources to be avoided from transport orders.
*/
@Inject
public ForcedDriveOrderMerger(
Router router,
ResourceAvoidanceExtractor resourceAvoidanceExtractor
) {
super(router, resourceAvoidanceExtractor);
}
@Override
protected List<Route.Step> mergeSteps(
List<Route.Step> stepsA,
List<Route.Step> stepsB,
int currentRouteStepIndex
) {
LOG.debug("Merging steps {} with {}", stepsToPaths(stepsA), stepsToPaths(stepsB));
List<Route.Step> mergedSteps = new ArrayList<>();
// Get the steps that the vehicle has already travelled.
mergedSteps.addAll(stepsA.subList(0, currentRouteStepIndex + 1));
// Set the rerouting type for the first step in the new route.
Route.Step firstStepOfNewRoute = stepsB.get(0);
List<Route.Step> modifiedStepsB = new ArrayList<>(stepsB);
modifiedStepsB.set(
0, new Route.Step(
firstStepOfNewRoute.getPath(),
firstStepOfNewRoute.getSourcePoint(),
firstStepOfNewRoute.getDestinationPoint(),
firstStepOfNewRoute.getVehicleOrientation(),
firstStepOfNewRoute.getRouteIndex(),
firstStepOfNewRoute.isExecutionAllowed(),
ReroutingType.FORCED
)
);
mergedSteps.addAll(modifiedStepsB);
// Update the steps route indices since they originate from two different drive orders
mergedSteps = updateRouteIndices(mergedSteps);
return mergedSteps;
}
}

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.Set;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.drivers.vehicle.VehicleController;
import org.opentcs.drivers.vehicle.VehicleControllerPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ReroutingStrategy} implementation for {@link ReroutingType#FORCED}.
* <p>
* Reroutes a {@link Vehicle} from its current position, but only if the vehicle is allowed to
* allocated the resources for that position.
*/
public class ForcedReroutingStrategy
extends
AbstractReroutingStrategy {
private static final Logger LOG = LoggerFactory.getLogger(ForcedReroutingStrategy.class);
private final VehicleControllerPool vehicleControllerPool;
private final InternalTransportOrderService transportOrderService;
@Inject
public ForcedReroutingStrategy(
Router router,
InternalTransportOrderService transportOrderService,
VehicleControllerPool vehicleControllerPool,
ForcedDriveOrderMerger driveOrderMerger
) {
super(router, transportOrderService, driveOrderMerger);
this.transportOrderService = requireNonNull(transportOrderService, "transportOrderService");
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
}
@Override
protected Optional<Point> determineRerouteSource(Vehicle vehicle) {
Point currentVehiclePosition = transportOrderService.fetchObject(
Point.class,
vehicle.getCurrentPosition()
);
if (currentVehiclePosition == null) {
return Optional.empty();
}
VehicleController vehicleController
= vehicleControllerPool.getVehicleController(vehicle.getName());
if (!vehicleController.mayAllocateNow(Set.of(currentVehiclePosition))) {
LOG.warn(
"{}: The resources for the current position are unavailable. "
+ "Unable to determine the reroute source.",
vehicle.getName()
);
return Optional.empty();
}
return Optional.of(currentVehiclePosition);
}
}

View File

@@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.opentcs.components.kernel.Router;
import org.opentcs.data.model.Point;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.Route;
import org.opentcs.strategies.basic.routing.ResourceAvoidanceExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DriveOrderMerger} implementation for {@link ReroutingType#REGULAR}.
* <p>
* Merges two drive orders so that the merged drive order follows the route of {@code orderA} up to
* the point where both drive orders ({@code orderA} and {@code orderB}) start to diverge. From
* there, the merged drive order follows the route of {@code orderB}.
*/
public class RegularDriveOrderMerger
extends
AbstractDriveOrderMerger {
private static final Logger LOG = LoggerFactory.getLogger(RegularDriveOrderMerger.class);
/**
* Creates a new instance.
*
* @param router The router to use.
* @param resourceAvoidanceExtractor Extracts resources to be avoided from transport orders.
*/
@Inject
public RegularDriveOrderMerger(
Router router,
ResourceAvoidanceExtractor resourceAvoidanceExtractor
) {
super(router, resourceAvoidanceExtractor);
}
@Override
protected List<Route.Step> mergeSteps(
List<Route.Step> stepsA,
List<Route.Step> stepsB,
int currentRouteStepIndex
) {
LOG.debug("Merging steps {} with {}", stepsToPaths(stepsA), stepsToPaths(stepsB));
List<Route.Step> mergedSteps = new ArrayList<>();
// Get the step where stepsB starts to diverge from stepsA (i.e. the step where routeA and
// routeB share the same source point).
Route.Step divergingStep = findStepWithSource(stepsB.get(0).getSourcePoint(), stepsA);
int divergingIndex = stepsA.indexOf(divergingStep);
mergedSteps.addAll(stepsA.subList(0, divergingIndex));
// Set the rerouting type for the first step in the new route.
Route.Step firstStepOfNewRoute = stepsB.get(0);
List<Route.Step> modifiedStepsB = new ArrayList<>(stepsB);
modifiedStepsB.set(
0, new Route.Step(
firstStepOfNewRoute.getPath(),
firstStepOfNewRoute.getSourcePoint(),
firstStepOfNewRoute.getDestinationPoint(),
firstStepOfNewRoute.getVehicleOrientation(),
firstStepOfNewRoute.getRouteIndex(),
firstStepOfNewRoute.isExecutionAllowed(),
ReroutingType.REGULAR
)
);
mergedSteps.addAll(modifiedStepsB);
// Update the steps route indices since they originate from two different drive orders.
mergedSteps = updateRouteIndices(mergedSteps);
return mergedSteps;
}
private Route.Step findStepWithSource(Point sourcePoint, List<Route.Step> steps) {
LOG.debug(
"Looking for a step with source point {} in {}",
sourcePoint,
stepsToPaths(steps)
);
return steps.stream()
.filter(step -> Objects.equals(step.getSourcePoint(), sourcePoint))
.findFirst()
.get();
}
}

View File

@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.opentcs.components.kernel.Router;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.Route;
import org.opentcs.data.order.TransportOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ReroutingStrategy} implementation for {@link ReroutingType#REGULAR}.
* <p>
* Reroutes a {@link Vehicle} from its future or current position according to
* {@link VehiclePositionResolver#getFutureOrCurrentPosition(org.opentcs.data.model.Vehicle)}.
*/
public class RegularReroutingStrategy
extends
AbstractReroutingStrategy
implements
ReroutingStrategy {
private static final Logger LOG = LoggerFactory.getLogger(RegularReroutingStrategy.class);
private final VehiclePositionResolver vehiclePositionResolver;
@Inject
public RegularReroutingStrategy(
Router router,
TCSObjectService objectService,
RegularDriveOrderMerger driveOrderMerger,
VehiclePositionResolver vehiclePositionResolver
) {
super(router, objectService, driveOrderMerger);
this.vehiclePositionResolver = requireNonNull(
vehiclePositionResolver,
"vehiclePositionResolver"
);
}
@Override
public Optional<List<DriveOrder>> reroute(Vehicle vehicle) {
if (!isVehicleAtExpectedPosition(vehicle)) {
LOG.warn(
"Can't perform regular rerouting for {} located at unexpected position.",
vehicle.getName()
);
return Optional.empty();
}
return super.reroute(vehicle);
}
@Override
protected Optional<Point> determineRerouteSource(Vehicle vehicle) {
return Optional.of(vehiclePositionResolver.getFutureOrCurrentPosition(vehicle));
}
private boolean isVehicleAtExpectedPosition(Vehicle vehicle) {
TransportOrder currentTransportOrder
= getObjectService().fetchObject(TransportOrder.class, vehicle.getTransportOrder());
TCSObjectReference<Point> currentVehiclePosition = vehicle.getCurrentPosition();
DriveOrder currentDriveOrder = currentTransportOrder.getCurrentDriveOrder();
if (currentVehiclePosition == null || currentDriveOrder == null) {
return false;
}
int routeProgressIndex = currentTransportOrder.getCurrentRouteStepIndex();
if (routeProgressIndex == TransportOrder.ROUTE_STEP_INDEX_DEFAULT) {
Route.Step step = currentDriveOrder.getRoute().getSteps().get(0);
Point expectedVehiclePosition
= step.getSourcePoint() != null ? step.getSourcePoint() : step.getDestinationPoint();
return Objects.equals(expectedVehiclePosition.getReference(), currentVehiclePosition);
}
Route.Step step = currentDriveOrder.getRoute().getSteps().get(routeProgressIndex);
Point expectedVehiclePosition = step.getDestinationPoint();
return Objects.equals(expectedVehiclePosition.getReference(), currentVehiclePosition);
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import java.util.List;
import java.util.Optional;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.TransportOrder;
/**
* A strategy for rerouting {@link Vehicle}s.
*/
public interface ReroutingStrategy {
/**
* Tries to calculate a new route for the given {@link Vehicle} and the {@link TransportOrder}
* it's currently processing.
* <p>
* The new route should consider the given vehicle's transport order progress so that the returned
* list of {@link DriveOrder}s doesn't contain any drive orders that the vehicle already finished.
*
* @param vehicle The vehicle to calculate a new route for.
* @return An {@link Optional} containing the new drive orders or {@link Optional#EMPTY}, if
* no new route could be calculated (e.g. because the given vehicle is not processing a transport
* order).
*/
Optional<List<DriveOrder>> reroute(Vehicle vehicle);
}

View File

@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.rerouting;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.MovementCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
import org.opentcs.drivers.vehicle.VehicleController;
import org.opentcs.drivers.vehicle.VehicleControllerPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides methods to resolve the position of a {@link Vehicle}.
*/
public class VehiclePositionResolver {
private static final Logger LOG = LoggerFactory.getLogger(VehiclePositionResolver.class);
private final VehicleControllerPool vehicleControllerPool;
private final TCSObjectService objectService;
/**
* Creates a new instance.
*
* @param vehicleControllerPool The pool of {@link VehicleController}s.
* @param objectService The object service to use.
*/
@Inject
public VehiclePositionResolver(
VehicleControllerPool vehicleControllerPool,
TCSObjectService objectService
) {
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
this.objectService = requireNonNull(objectService, "objectService");
}
/**
* Returns the position the given {@link Vehicle} will be at after processing all commands that
* have been or are to be sent to its {@link VehicleCommAdapter}, or its current position, if
* there are no such commands.
*
* @param vehicle The vehicle to get the position for.
* @return The position as a {@link Point}.
*/
public Point getFutureOrCurrentPosition(Vehicle vehicle) {
VehicleController controller = vehicleControllerPool.getVehicleController(vehicle.getName());
if (controller.getCommandsSent().isEmpty()
&& controller.getInteractionsPendingCommand().isEmpty()) {
LOG.debug(
"{}: No commands expected to be executed. Using current position: {}",
vehicle.getName(),
vehicle.getCurrentPosition()
);
return objectService.fetchObject(Point.class, vehicle.getCurrentPosition());
}
if (controller.getInteractionsPendingCommand().isPresent()) {
LOG.debug(
"{}: Command with pending peripheral operations present. Using its destination point: {}",
vehicle.getName(),
controller.getInteractionsPendingCommand().get().getStep().getDestinationPoint()
);
return controller.getInteractionsPendingCommand().get().getStep().getDestinationPoint();
}
List<MovementCommand> commandsSent = new ArrayList<>(controller.getCommandsSent());
MovementCommand lastCommandSent = commandsSent.get(commandsSent.size() - 1);
LOG.debug(
"{}: Using the last command sent to the communication adapter: {}",
vehicle.getName(),
lastCommandSent
);
return lastCommandSent.getStep().getDestinationPoint();
}
}

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection;
import java.util.Collection;
import java.util.function.Function;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
/**
* A filter for {@link AssignmentCandidate}s.
* Returns a collection of reasons for filtering the assignment candidate.
* If the returned collection is empty, no reason to filter it was encountered.
*/
public interface AssignmentCandidateSelectionFilter
extends
Function<AssignmentCandidate, Collection<String>> {
}

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection;
import java.util.Collection;
import java.util.function.Function;
import org.opentcs.data.model.Vehicle;
/**
* A filter for selecting {@link Vehicle}s for parking.
* Returns a collection of reasons for filtering the vehicle.
* If the returned collection is empty, no reason to filter it was encountered.
*/
public interface ParkVehicleSelectionFilter
extends
Function<Vehicle, Collection<String>> {
}

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection;
import java.util.Collection;
import java.util.function.Function;
import org.opentcs.data.model.Vehicle;
/**
* A filter for selecting {@link Vehicle}s for recharge orders.
* Returns a collection of reasons for filtering the vehicle.
* If the returned collection is empty, no reason to filter it was encountered.
*/
public interface RechargeVehicleSelectionFilter
extends
Function<Vehicle, Collection<String>> {
}

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection;
import java.util.Collection;
import java.util.function.Function;
import org.opentcs.data.model.Vehicle;
/**
* A filter for selecting {@link Vehicle}s for reparking.
* Returns a collection of reasons for filtering the vehicle.
* If the returned collection is empty, no reason to filter it was encountered.
*/
public interface ReparkVehicleSelectionFilter
extends
Function<Vehicle, Collection<String>> {
}

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection;
import java.util.Collection;
import java.util.function.Function;
import org.opentcs.data.order.TransportOrder;
/**
* A filter for {@link TransportOrder}s.
* Returns a collection of reasons for filtering the transport order.
* If the returned collection is empty, no reason to filter it was encountered.
*/
public interface TransportOrderSelectionFilter
extends
Function<TransportOrder, Collection<String>> {
}

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection;
import java.util.Collection;
import java.util.function.Function;
import org.opentcs.data.model.Vehicle;
/**
* A filter for {@link Vehicle}s.
* Returns a collection of reasons for filtering the vehicle.
* If the returned collection is empty, no reason to filter it was encountered.
*/
public interface VehicleSelectionFilter
extends
Function<Vehicle, Collection<String>> {
}

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.candidates;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.selection.AssignmentCandidateSelectionFilter;
/**
* A collection of {@link AssignmentCandidateSelectionFilter}s.
*/
public class CompositeAssignmentCandidateSelectionFilter
implements
AssignmentCandidateSelectionFilter {
/**
* The {@link AssignmentCandidateSelectionFilter}s.
*/
private final Set<AssignmentCandidateSelectionFilter> filters;
@Inject
public CompositeAssignmentCandidateSelectionFilter(
Set<AssignmentCandidateSelectionFilter> filters
) {
this.filters = requireNonNull(filters, "filters");
}
@Override
public Collection<String> apply(AssignmentCandidate candidate) {
return filters.stream()
.flatMap(filter -> filter.apply(candidate).stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.candidates;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.drivers.vehicle.VehicleControllerPool;
import org.opentcs.strategies.basic.dispatching.AssignmentCandidate;
import org.opentcs.strategies.basic.dispatching.selection.AssignmentCandidateSelectionFilter;
import org.opentcs.util.ExplainedBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Filters assignment candidates with which the transport order is actually processable by the
* vehicle.
*/
public class IsProcessable
implements
AssignmentCandidateSelectionFilter {
/**
* An error code indicating that there's a conflict between the type of a transport order and
* the types a vehicle is allowed to process.
*/
private static final String ORDER_TYPE_CONFLICT = "notAllowedOrderType";
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(IsProcessable.class);
/**
* The vehicle controller pool.
*/
private final VehicleControllerPool vehicleControllerPool;
/**
* Creates a new instance.
*
* @param vehicleControllerPool The controller pool to be worked with.
*/
@Inject
public IsProcessable(VehicleControllerPool vehicleControllerPool) {
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
}
@Override
public Collection<String> apply(AssignmentCandidate candidate) {
ExplainedBoolean result = checkProcessability(
candidate.getVehicle(),
candidate.getTransportOrder()
);
return result.getValue()
? new ArrayList<>()
: Arrays.asList(candidate.getVehicle().getName() + "(" + result.getReason() + ")");
}
/**
* Checks if the given vehicle could process the given order right now.
*
* @param vehicle The vehicle.
* @param order The order.
* @return <code>true</code> if, and only if, the given vehicle can process the given order.
*/
private ExplainedBoolean checkProcessability(Vehicle vehicle, TransportOrder order) {
requireNonNull(vehicle, "vehicle");
requireNonNull(order, "order");
// Check for matching order types
if (!vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_ANY)
&& !vehicle.getAllowedOrderTypes().contains(order.getType())) {
LOG.debug(
"Type '{}' of order '{}' not in allowed types '{}' of vehicle '{}'.",
order.getType(),
order.getName(),
vehicle.getAllowedOrderTypes(),
vehicle.getName()
);
return new ExplainedBoolean(false, ORDER_TYPE_CONFLICT);
}
return vehicleControllerPool.getVehicleController(vehicle.getName()).canProcess(order);
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.orders;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.selection.TransportOrderSelectionFilter;
/**
* A collection of {@link TransportOrderSelectionFilter}s.
*/
public class CompositeTransportOrderSelectionFilter
implements
TransportOrderSelectionFilter {
/**
* The {@link TransportOrderSelectionFilter}s.
*/
private final Set<TransportOrderSelectionFilter> filters;
@Inject
public CompositeTransportOrderSelectionFilter(Set<TransportOrderSelectionFilter> filters) {
this.filters = requireNonNull(filters, "filters");
}
@Override
public Collection<String> apply(TransportOrder order) {
return filters.stream()
.flatMap(filter -> filter.apply(order).stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.orders;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Objects;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Location;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.selection.TransportOrderSelectionFilter;
/**
* Filters transport orders that contain locked target locations.
*/
public class ContainsLockedTargetLocations
implements
TransportOrderSelectionFilter {
/**
* The object service.
*/
private final TCSObjectService objectService;
@Inject
public ContainsLockedTargetLocations(TCSObjectService objectService) {
this.objectService = requireNonNull(objectService, "objectService");
}
@Override
public Collection<String> apply(TransportOrder order) {
return !lockedLocations(order) ? new ArrayList<>() : Arrays.asList(getClass().getName());
}
@SuppressWarnings("unchecked")
private boolean lockedLocations(TransportOrder order) {
return order.getAllDriveOrders().stream()
.map(driveOrder -> driveOrder.getDestination().getDestination())
.filter(destination -> Objects.equal(destination.getReferentClass(), Location.class))
.map(destination -> (TCSObjectReference<Location>) destination)
.map(locationReference -> objectService.fetchObject(Location.class, locationReference))
.anyMatch(location -> location.isLocked());
}
}

View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.orders;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.function.Predicate;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.ObjectHistory;
import org.opentcs.data.order.OrderSequence;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.OrderReservationPool;
import org.opentcs.strategies.basic.dispatching.selection.TransportOrderSelectionFilter;
/**
* Filters transport orders that are dispatchable and available to <em>any</em> vehicle.
*
* <p>
* Note: This filter is not a {@link TransportOrderSelectionFilter} by intention, since it is not
* intended to be used in contexts where {@link ObjectHistory} entries are created.
* </p>
*/
public class IsFreelyDispatchableToAnyVehicle
implements
Predicate<TransportOrder> {
/**
* The order service.
*/
private final TCSObjectService objectService;
/**
* Stores reservations of orders for vehicles.
*/
private final OrderReservationPool orderReservationPool;
/**
* Creates a new isntance.
*
* @param objectService The order service.
* @param orderReservationPool Stores reservations of orders for vehicles.
*/
@Inject
public IsFreelyDispatchableToAnyVehicle(
TCSObjectService objectService,
OrderReservationPool orderReservationPool
) {
this.objectService = requireNonNull(objectService, "objectService");
this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");
}
@Override
public boolean test(TransportOrder order) {
// We only want to check dispatchable transport orders.
// Filter out transport orders that are intended for other vehicles.
// Also filter out all transport orders with reservations. We assume that a check for reserved
// orders has been performed already, and if any had been found, we wouldn't have been called.
return order.hasState(TransportOrder.State.DISPATCHABLE)
&& !partOfAnyVehiclesSequence(order)
&& !orderReservationPool.isReserved(order.getReference());
}
private boolean partOfAnyVehiclesSequence(TransportOrder order) {
if (order.getWrappingSequence() == null) {
return false;
}
OrderSequence seq = objectService.fetchObject(
OrderSequence.class,
order.getWrappingSequence()
);
return seq != null && seq.getProcessingVehicle() != null;
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.selection.ParkVehicleSelectionFilter;
/**
* A collection of {@link ParkVehicleSelectionFilter}s.
*/
public class CompositeParkVehicleSelectionFilter
implements
ParkVehicleSelectionFilter {
/**
* The {@link ParkVehicleSelectionFilter}s.
*/
private final Set<ParkVehicleSelectionFilter> filters;
@Inject
public CompositeParkVehicleSelectionFilter(Set<ParkVehicleSelectionFilter> filters) {
this.filters = requireNonNull(filters, "filters");
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return filters.stream()
.flatMap(filter -> filter.apply(vehicle).stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.selection.RechargeVehicleSelectionFilter;
/**
* A collection of {@link RechargeVehicleSelectionFilter}s.
*/
public class CompositeRechargeVehicleSelectionFilter
implements
RechargeVehicleSelectionFilter {
/**
* The {@link RechargeVehicleSelectionFilter}s.
*/
private final Set<RechargeVehicleSelectionFilter> filters;
@Inject
public CompositeRechargeVehicleSelectionFilter(Set<RechargeVehicleSelectionFilter> filters) {
this.filters = requireNonNull(filters, "filters");
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return filters.stream()
.flatMap(filter -> filter.apply(vehicle).stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.selection.ReparkVehicleSelectionFilter;
/**
* A collection of {@link ReparkVehicleSelectionFilter}s.
*/
public class CompositeReparkVehicleSelectionFilter
implements
ReparkVehicleSelectionFilter {
/**
* The {@link ParkVehicleSelectionFilter}s.
*/
private final Set<ReparkVehicleSelectionFilter> filters;
@Inject
public CompositeReparkVehicleSelectionFilter(Set<ReparkVehicleSelectionFilter> filters) {
this.filters = requireNonNull(filters, "filters");
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return filters.stream()
.flatMap(filter -> filter.apply(vehicle).stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.dispatching.selection.VehicleSelectionFilter;
/**
* A collection of {@link VehicleSelectionFilter}s.
*/
public class CompositeVehicleSelectionFilter
implements
VehicleSelectionFilter {
/**
* The {@link VehicleSelectionFilter}s.
*/
private final Set<VehicleSelectionFilter> filters;
@Inject
public CompositeVehicleSelectionFilter(Set<VehicleSelectionFilter> filters) {
this.filters = requireNonNull(filters, "filters");
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return filters.stream()
.flatMap(filter -> filter.apply(vehicle).stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.function.Predicate;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.ObjectHistory;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherConfiguration;
import org.opentcs.strategies.basic.dispatching.OrderReservationPool;
import org.opentcs.strategies.basic.dispatching.selection.VehicleSelectionFilter;
/**
* Filters vehicles that are generally available for transport orders.
*
* <p>
* Note: This filter is not a {@link VehicleSelectionFilter} by intention, since it is not
* intended to be used in contexts where {@link ObjectHistory} entries are created.
* </p>
*/
public class IsAvailableForAnyOrder
implements
Predicate<Vehicle> {
/**
* The object service.
*/
private final TCSObjectService objectService;
/**
* Stores reservations of orders for vehicles.
*/
private final OrderReservationPool orderReservationPool;
/**
* The default dispatcher configuration.
*/
private final DefaultDispatcherConfiguration configuration;
/**
* Creates a new instance.
*
* @param objectService The object service.
* @param orderReservationPool Stores reservations of orders for vehicles.
* @param configuration The default dispatcher configuration.
*/
@Inject
public IsAvailableForAnyOrder(
TCSObjectService objectService,
OrderReservationPool orderReservationPool,
DefaultDispatcherConfiguration configuration
) {
this.objectService = requireNonNull(objectService, "objectService");
this.orderReservationPool = requireNonNull(orderReservationPool, "orderReservationPool");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public boolean test(Vehicle vehicle) {
return vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
&& vehicle.getCurrentPosition() != null
&& vehicle.getOrderSequence() == null
&& !needsMoreCharging(vehicle)
&& (processesNoOrder(vehicle)
|| processesDispensableOrder(vehicle))
&& !hasOrderReservation(vehicle)
&& !vehicle.isPaused();
}
private boolean needsMoreCharging(Vehicle vehicle) {
return vehicle.hasState(Vehicle.State.CHARGING)
&& !rechargeThresholdReached(vehicle);
}
private boolean rechargeThresholdReached(Vehicle vehicle) {
return configuration.keepRechargingUntilFullyCharged()
? vehicle.isEnergyLevelFullyRecharged()
: vehicle.isEnergyLevelSufficientlyRecharged();
}
private boolean processesNoOrder(Vehicle vehicle) {
return vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& (vehicle.hasState(Vehicle.State.IDLE)
|| vehicle.hasState(Vehicle.State.CHARGING));
}
private boolean processesDispensableOrder(Vehicle vehicle) {
return vehicle.hasProcState(Vehicle.ProcState.PROCESSING_ORDER)
&& objectService.fetchObject(TransportOrder.class, vehicle.getTransportOrder())
.isDispensable();
}
private boolean hasOrderReservation(Vehicle vehicle) {
return !orderReservationPool.findReservations(vehicle.getReference()).isEmpty();
}
}

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.strategies.basic.dispatching.selection.RechargeVehicleSelectionFilter;
/**
* Filters vehicles that are idle and have a degraded energy level.
*/
public class IsIdleAndDegraded
implements
RechargeVehicleSelectionFilter {
/**
* Creates a new instance.
*/
public IsIdleAndDegraded() {
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return idleAndDegraded(vehicle) ? new ArrayList<>() : Arrays.asList(getClass().getName());
}
private boolean idleAndDegraded(Vehicle vehicle) {
return vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
&& vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& vehicle.hasState(Vehicle.State.IDLE)
&& vehicle.getCurrentPosition() != null
&& vehicle.getOrderSequence() == null
&& vehicle.isEnergyLevelDegraded()
&& hasAllowedOrderTypesForCharging(vehicle);
}
private boolean hasAllowedOrderTypesForCharging(Vehicle vehicle) {
return vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_CHARGE)
|| vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_ANY);
}
}

View File

@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.strategies.basic.dispatching.selection.ParkVehicleSelectionFilter;
/**
* Filters vehicles that are parkable.
*/
public class IsParkable
implements
ParkVehicleSelectionFilter {
/**
* The object service.
*/
private final TCSObjectService objectService;
/**
* Creates a new instance.
*
* @param objectService The object service.
*/
@Inject
public IsParkable(TCSObjectService objectService) {
this.objectService = requireNonNull(objectService, "objectService");
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return parkable(vehicle) ? new ArrayList<>() : Arrays.asList(getClass().getName());
}
private boolean parkable(Vehicle vehicle) {
return vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
&& vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& vehicle.hasState(Vehicle.State.IDLE)
&& !isParkingPosition(vehicle.getCurrentPosition())
&& vehicle.getOrderSequence() == null
&& hasAllowedOrderTypesForParking(vehicle);
}
private boolean isParkingPosition(TCSObjectReference<Point> positionRef) {
if (positionRef == null) {
return false;
}
Point position = objectService.fetchObject(Point.class, positionRef);
return position.isParkingPosition();
}
private boolean hasAllowedOrderTypesForParking(Vehicle vehicle) {
return vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_PARK)
|| vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_ANY);
}
}

View File

@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.dispatching.selection.vehicles;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderConstants;
import org.opentcs.strategies.basic.dispatching.selection.ReparkVehicleSelectionFilter;
/**
* Filters vehicles that are reparkable.
*/
public class IsReparkable
implements
ReparkVehicleSelectionFilter {
/**
* The object service.
*/
private final TCSObjectService objectService;
/**
* Creates a new instance.
*
* @param objectService The object service.
*/
@Inject
public IsReparkable(TCSObjectService objectService) {
this.objectService = requireNonNull(objectService, "objectService");
}
@Override
public Collection<String> apply(Vehicle vehicle) {
return reparkable(vehicle) ? new ArrayList<>() : Arrays.asList(getClass().getName());
}
private boolean reparkable(Vehicle vehicle) {
return vehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
&& vehicle.hasProcState(Vehicle.ProcState.IDLE)
&& vehicle.hasState(Vehicle.State.IDLE)
&& isParkingPosition(vehicle.getCurrentPosition())
&& vehicle.getOrderSequence() == null
&& hasAllowedOrderTypesForParking(vehicle);
}
private boolean isParkingPosition(TCSObjectReference<Point> positionRef) {
if (positionRef == null) {
return false;
}
Point position = objectService.fetchObject(Point.class, positionRef);
return position.isParkingPosition();
}
private boolean hasAllowedOrderTypesForParking(Vehicle vehicle) {
return vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_PARK)
|| vehicle.getAllowedOrderTypes().contains(OrderConstants.TYPE_ANY);
}
}

View File

@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import static org.opentcs.util.Assertions.checkArgument;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import org.opentcs.data.model.Location;
import org.opentcs.data.peripherals.PeripheralJob;
import org.opentcs.util.Comparators;
/**
* The default implementation of {@link JobSelectionStrategy}.
* Selects a job by applying the following rules:
* <ul>
* <li>The location of a job's operation has to match the given location.</li>
* <li>If this applies to multiple jobs, the oldest one is selected.</li>
* </ul>
*/
public class DefaultJobSelectionStrategy
implements
JobSelectionStrategy {
/**
* Creates a new instance.
*/
public DefaultJobSelectionStrategy() {
}
@Override
public Optional<PeripheralJob> select(Collection<PeripheralJob> jobs, Location location) {
checkArgument(
jobs.stream().allMatch(job -> matchesLocation(job, location)),
"All jobs are expected to match the given location: %s", location.getName()
);
return jobs.stream()
.sorted(Comparators.jobsByAge())
.findFirst();
}
private boolean matchesLocation(PeripheralJob job, Location location) {
return Objects.equals(job.getPeripheralOperation().getLocation(), location.getReference());
}
}

View File

@@ -0,0 +1,303 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.opentcs.components.kernel.PeripheralJobDispatcher;
import org.opentcs.components.kernel.services.InternalPeripheralJobService;
import org.opentcs.components.kernel.services.InternalPeripheralService;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.PeripheralInformation;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.data.peripherals.PeripheralJob;
import org.opentcs.drivers.peripherals.PeripheralControllerPool;
import org.opentcs.drivers.peripherals.PeripheralJobCallback;
import org.opentcs.util.event.EventSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Dispatches peripheral jobs and peripheral devices represented by locations.
*/
public class DefaultPeripheralJobDispatcher
implements
PeripheralJobDispatcher,
PeripheralJobCallback {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultPeripheralJobDispatcher.class);
/**
* The peripheral service to use.
*/
private final InternalPeripheralService peripheralService;
/**
* The peripheral job service to use.
*/
private final InternalPeripheralJobService peripheralJobService;
/**
* The controller pool.
*/
private final PeripheralControllerPool controllerPool;
/**
* Where we register for application events.
*/
private final EventSource eventSource;
/**
* The kernel's executor.
*/
private final ScheduledExecutorService kernelExecutor;
/**
* Performs a full dispatch run.
*/
private final FullDispatchTask fullDispatchTask;
/**
* A task to periodically trigger the job dispatcher.
*/
private final Provider<PeriodicPeripheralRedispatchingTask> periodicDispatchTaskProvider;
/**
* A provider for an event handler to trigger the job dispatcher on certain events.
*/
private final Provider<ImplicitDispatchTrigger> implicitDispatchTriggerProvider;
/**
* The peripheral job dispatcher's configuration.
*/
private final DefaultPeripheralJobDispatcherConfiguration configuration;
/**
* The future for the periodic dispatch task.
*/
private ScheduledFuture<?> periodicDispatchTaskFuture;
/**
* An event handler to trigger the job dispatcher on certain events.
*/
private ImplicitDispatchTrigger implicitDispatchTrigger;
/**
* Indicates whether this component is enabled.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param peripheralService The peripheral service to use.
* @param peripheralJobService The peripheral job service to use.
* @param controllerPool The controller pool.
* @param eventSource Where this instance registers for application events.
* @param kernelExecutor Executes dispatching tasks.
* @param fullDispatchTask Performs a full dispatch run.
* @param periodicDispatchTaskProvider A task to periodically trigger the job dispatcher.
* @param implicitDispatchTriggerProvider A provider for an event handler to trigger the job
* dispatcher on certain events.
* @param configuration The peripheral job dispatcher's configuration.
*/
@Inject
public DefaultPeripheralJobDispatcher(
InternalPeripheralService peripheralService,
InternalPeripheralJobService peripheralJobService,
PeripheralControllerPool controllerPool,
@ApplicationEventBus
EventSource eventSource,
@KernelExecutor
ScheduledExecutorService kernelExecutor,
FullDispatchTask fullDispatchTask,
Provider<PeriodicPeripheralRedispatchingTask> periodicDispatchTaskProvider,
Provider<ImplicitDispatchTrigger> implicitDispatchTriggerProvider,
DefaultPeripheralJobDispatcherConfiguration configuration
) {
this.peripheralService = requireNonNull(peripheralService, "peripheralService");
this.peripheralJobService = requireNonNull(peripheralJobService, "peripheralJobService");
this.controllerPool = requireNonNull(controllerPool, "controllerPool");
this.eventSource = requireNonNull(eventSource, "eventSource");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
this.fullDispatchTask = requireNonNull(fullDispatchTask, "fullDispatchTask");
this.periodicDispatchTaskProvider = requireNonNull(
periodicDispatchTaskProvider,
"periodicDispatchTaskProvider"
);
this.implicitDispatchTriggerProvider = requireNonNull(
implicitDispatchTriggerProvider,
"implicitDispatchTriggerProvider"
);
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
LOG.debug("Initializing...");
fullDispatchTask.initialize();
implicitDispatchTrigger = implicitDispatchTriggerProvider.get();
eventSource.subscribe(implicitDispatchTrigger);
LOG.debug(
"Scheduling periodic peripheral job dispatch task with interval of {} ms...",
configuration.idlePeripheralRedispatchingInterval()
);
periodicDispatchTaskFuture = kernelExecutor.scheduleAtFixedRate(
periodicDispatchTaskProvider.get(),
configuration.idlePeripheralRedispatchingInterval(),
configuration.idlePeripheralRedispatchingInterval(),
TimeUnit.MILLISECONDS
);
initialized = true;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
LOG.debug("Terminating...");
periodicDispatchTaskFuture.cancel(false);
periodicDispatchTaskFuture = null;
eventSource.unsubscribe(implicitDispatchTrigger);
implicitDispatchTrigger = null;
fullDispatchTask.terminate();
initialized = false;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void dispatch() {
LOG.debug("Scheduling dispatch task...");
fullDispatchTask.run();
}
@Override
public void withdrawJob(Location location) {
requireNonNull(location, "location");
checkState(isInitialized(), "Not initialized");
LOG.debug(
"Withdrawing peripheral job for location '{}' ({})...",
location.getName(),
location.getPeripheralInformation().getPeripheralJob()
);
if (location.getPeripheralInformation().getPeripheralJob() == null) {
return;
}
withdrawJob(
peripheralService.fetchObject(
PeripheralJob.class,
location.getPeripheralInformation().getPeripheralJob()
)
);
}
@Override
public void withdrawJob(PeripheralJob job) {
requireNonNull(job, "job");
checkState(isInitialized(), "Not initialized");
if (job.getState().isFinalState()) {
LOG.info(
"Peripheral job '{}' already in final state '{}', skipping withdrawal.",
job.getName(),
job.getState()
);
return;
}
checkArgument(
!isRelatedToNonFinalTransportOrder(job),
"Cannot withdraw job because it is related to transport order in non-final state: %s",
job.getName()
);
LOG.debug("Withdrawing peripheral job '{}'...", job.getName());
if (job.getState() == PeripheralJob.State.BEING_PROCESSED) {
controllerPool
.getPeripheralController(job.getPeripheralOperation().getLocation())
.abortJob();
}
finalizeJob(job, PeripheralJob.State.FAILED);
}
private boolean isRelatedToNonFinalTransportOrder(PeripheralJob job) {
return job.getRelatedTransportOrder() != null
&& !peripheralService.fetchObject(TransportOrder.class, job.getRelatedTransportOrder())
.getState().isFinalState();
}
@Override
public void peripheralJobFinished(
@Nonnull
TCSObjectReference<PeripheralJob> ref
) {
requireNonNull(ref, "ref");
PeripheralJob job = peripheralJobService.fetchObject(PeripheralJob.class, ref);
if (job.getState() != PeripheralJob.State.BEING_PROCESSED) {
LOG.info(
"Peripheral job not in state BEING_PROCESSED, ignoring: {} ({})",
job.getName(),
job.getState()
);
return;
}
finalizeJob(job, PeripheralJob.State.FINISHED);
dispatch();
}
@Override
public void peripheralJobFailed(
@Nonnull
TCSObjectReference<PeripheralJob> ref
) {
requireNonNull(ref, "ref");
PeripheralJob job = peripheralJobService.fetchObject(PeripheralJob.class, ref);
if (job.getState() != PeripheralJob.State.BEING_PROCESSED) {
LOG.info(
"Peripheral job not in state BEING_PROCESSED, ignoring: {} ({})",
job.getName(),
job.getState()
);
return;
}
finalizeJob(job, PeripheralJob.State.FAILED);
dispatch();
}
private void finalizeJob(PeripheralJob job, PeripheralJob.State state) {
if (job.getState() == PeripheralJob.State.BEING_PROCESSED) {
peripheralService.updatePeripheralProcState(
job.getPeripheralOperation().getLocation(),
PeripheralInformation.ProcState.IDLE
);
peripheralService.updatePeripheralJob(job.getPeripheralOperation().getLocation(), null);
}
peripheralJobService.updatePeripheralJobState(job.getReference(), state);
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import org.opentcs.configuration.ConfigurationEntry;
import org.opentcs.configuration.ConfigurationPrefix;
/**
* Provides methods to configure the {@link DefaultPeripheralJobDispatcher}
*/
@ConfigurationPrefix(DefaultPeripheralJobDispatcherConfiguration.PREFIX)
public interface DefaultPeripheralJobDispatcherConfiguration {
/**
* This configuration's prefix.
*/
String PREFIX = "defaultperipheraljobdispatcher";
@ConfigurationEntry(
type = "Integer",
description = "The interval between redispatching of peripheral devices.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
orderKey = "9_misc"
)
long idlePeripheralRedispatchingInterval();
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import java.util.Collection;
import java.util.stream.Collectors;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.PeripheralInformation;
/**
* The default implementation of {@link PeripheralReleaseStrategy}.
* Selects peripherals to be released by applying the following rules:
* <ul>
* <li>A peripheral's state must be {@link PeripheralInformation.State#IDLE}</li>
* <li>A peripheral's processing state must be {@link PeripheralInformation.ProcState#IDLE}</li>
* <li>A peripheral's reservation token must be set.</li>
* </ul>
*/
public class DefaultPeripheralReleaseStrategy
implements
PeripheralReleaseStrategy {
/**
* Creates a new instance.
*/
public DefaultPeripheralReleaseStrategy() {
}
@Override
public Collection<Location> selectPeripheralsToRelease(Collection<Location> locations) {
return locations.stream()
.filter(this::idleAndReserved)
.collect(Collectors.toSet());
}
private boolean idleAndReserved(Location location) {
return processesNoJob(location) && hasReservationToken(location);
}
private boolean processesNoJob(Location location) {
return location.getPeripheralInformation()
.getProcState() == PeripheralInformation.ProcState.IDLE
&& location.getPeripheralInformation().getState() == PeripheralInformation.State.IDLE;
}
private boolean hasReservationToken(Location location) {
return location.getPeripheralInformation().getReservationToken() != null;
}
}

View File

@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.Lifecycle;
import org.opentcs.strategies.basic.peripherals.dispatching.phase.AssignFreePeripheralsPhase;
import org.opentcs.strategies.basic.peripherals.dispatching.phase.AssignReservedPeripheralsPhase;
import org.opentcs.strategies.basic.peripherals.dispatching.phase.FinishWithdrawalsPhase;
import org.opentcs.strategies.basic.peripherals.dispatching.phase.ReleasePeripheralsPhase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Performs a full dispatch run.
*/
public class FullDispatchTask
implements
Runnable,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(FullDispatchTask.class);
private final FinishWithdrawalsPhase finishWithdrawalsPhase;
private final AssignReservedPeripheralsPhase assignReservedPeripheralsPhase;
private final ReleasePeripheralsPhase releasePeripheralsPhase;
private final AssignFreePeripheralsPhase assignFreePeripheralsPhase;
/**
* Indicates whether this component is enabled.
*/
private boolean initialized;
@Inject
public FullDispatchTask(
FinishWithdrawalsPhase finishWithdrawalsPhase,
AssignReservedPeripheralsPhase assignReservedPeripheralsPhase,
ReleasePeripheralsPhase releasePeripheralsPhase,
AssignFreePeripheralsPhase assignFreePeripheralsPhase
) {
this.finishWithdrawalsPhase = requireNonNull(finishWithdrawalsPhase, "finishWithdrawalsPhase");
this.assignReservedPeripheralsPhase = requireNonNull(
assignReservedPeripheralsPhase,
"assignReservedPeripheralsPhase"
);
this.releasePeripheralsPhase = requireNonNull(
releasePeripheralsPhase,
"releasePeripheralsPhase"
);
this.assignFreePeripheralsPhase = requireNonNull(
assignFreePeripheralsPhase,
"assignFreePeripheralsPhase"
);
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
finishWithdrawalsPhase.initialize();
assignReservedPeripheralsPhase.initialize();
releasePeripheralsPhase.initialize();
assignFreePeripheralsPhase.initialize();
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
assignFreePeripheralsPhase.terminate();
releasePeripheralsPhase.terminate();
assignReservedPeripheralsPhase.terminate();
finishWithdrawalsPhase.terminate();
initialized = false;
}
@Override
public void run() {
LOG.debug("Starting full dispatch run...");
finishWithdrawalsPhase.run();
assignReservedPeripheralsPhase.run();
releasePeripheralsPhase.run();
assignFreePeripheralsPhase.run();
LOG.debug("Finished full dispatch run.");
}
}

View File

@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.PeripheralJobDispatcher;
import org.opentcs.data.TCSObjectEvent;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.util.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An event listener that triggers the peripheral job dispatcher on certain events.
*/
public class ImplicitDispatchTrigger
implements
EventHandler {
/**
* This class's Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(ImplicitDispatchTrigger.class);
/**
* The dispatcher in use.
*/
private final PeripheralJobDispatcher dispatcher;
/**
* Creates a new instance.
*
* @param dispatcher The dispatcher in use.
*/
@Inject
public ImplicitDispatchTrigger(PeripheralJobDispatcher dispatcher) {
this.dispatcher = requireNonNull(dispatcher, "dispatcher");
}
@Override
public void onEvent(Object event) {
if (!(event instanceof TCSObjectEvent)) {
return;
}
TCSObjectEvent objectEvent = (TCSObjectEvent) event;
if (objectEvent.getType() == TCSObjectEvent.Type.OBJECT_MODIFIED
&& objectEvent.getCurrentOrPreviousObjectState() instanceof TransportOrder) {
checkTransportOrderChange(
(TransportOrder) objectEvent.getPreviousObjectState(),
(TransportOrder) objectEvent.getCurrentObjectState()
);
}
}
private void checkTransportOrderChange(TransportOrder oldOrder, TransportOrder newOrder) {
if (newOrder.getState() != oldOrder.getState()
&& newOrder.getState() == TransportOrder.State.FAILED) {
LOG.debug("Dispatching for {}...", newOrder);
dispatcher.dispatch();
}
}
}

View File

@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import java.util.Collection;
import java.util.Optional;
import org.opentcs.data.model.Location;
import org.opentcs.data.peripherals.PeripheralJob;
/**
* A strategy for selecting a peripheral job to be processed next.
*/
public interface JobSelectionStrategy {
/**
* Selects a peripheral job to be processed next by the given location out of the given
* collection of peripheral jobs.
*
* @param jobs The peripheral jobs to select from.
* @param location The location to select a peripheral job for.
* @return The selected peripheral job.
*/
Optional<PeripheralJob> select(Collection<PeripheralJob> jobs, Location location);
}

View File

@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.services.PeripheralDispatcherService;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.PeripheralInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Periodically checks for idle peripheral devices that could process a peripheral job.
*/
public class PeriodicPeripheralRedispatchingTask
implements
Runnable {
/**
* This class's Logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(PeriodicPeripheralRedispatchingTask.class);
private final PeripheralDispatcherService dispatcherService;
private final TCSObjectService objectService;
/**
* Creates a new instance.
*
* @param dispatcherService The dispatcher service used to dispatch peripheral devices.
* @param objectService The object service.
*/
@Inject
public PeriodicPeripheralRedispatchingTask(
PeripheralDispatcherService dispatcherService,
TCSObjectService objectService
) {
this.dispatcherService = requireNonNull(dispatcherService, "dispatcherService");
this.objectService = requireNonNull(objectService, "objectService");
}
@Override
public void run() {
// If there are any peripheral devices that could process a peripheral job,
// trigger the dispatcher once.
objectService.fetchObjects(Location.class, this::couldProcessJob).stream()
.findAny()
.ifPresent(location -> {
LOG.debug(
"Peripheral {} could process peripheral job, triggering dispatcher ...",
location
);
dispatcherService.dispatch();
});
}
private boolean couldProcessJob(Location loc) {
return loc.getPeripheralInformation().getState() != PeripheralInformation.State.NO_PERIPHERAL
&& processesNoJob(loc);
}
private boolean processesNoJob(Location location) {
return location.getPeripheralInformation()
.getProcState() == PeripheralInformation.ProcState.IDLE
&& location.getPeripheralInformation().getState() == PeripheralInformation.State.IDLE;
}
}

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import org.opentcs.components.Lifecycle;
/**
* Describes a reusable dispatching (sub-)task with a life cycle.
*/
public interface PeripheralDispatcherPhase
extends
Runnable,
Lifecycle {
}

View File

@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import org.opentcs.components.kernel.services.InternalPeripheralJobService;
import org.opentcs.components.kernel.services.InternalPeripheralService;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.PeripheralInformation;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.data.peripherals.PeripheralJob;
import org.opentcs.drivers.peripherals.PeripheralControllerPool;
import org.opentcs.drivers.peripherals.PeripheralJobCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides service functions for working with peripheral jobs and their states.
*/
public class PeripheralJobUtil {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PeripheralJobUtil.class);
/**
* The peripheral service to use.
*/
private final InternalPeripheralService peripheralService;
/**
* The peripheral job service to use.
*/
private final InternalPeripheralJobService peripheralJobService;
/**
* The peripheral controller pool.
*/
private final PeripheralControllerPool peripheralControllerPool;
/**
* The peripheral job callback to use.
*/
private final PeripheralJobCallback peripheralJobCallback;
@Inject
public PeripheralJobUtil(
InternalPeripheralService peripheralService,
InternalPeripheralJobService peripheralJobService,
PeripheralControllerPool peripheralControllerPool,
PeripheralJobCallback peripheralJobCallback
) {
this.peripheralService = requireNonNull(peripheralService, "peripheralService");
this.peripheralJobService = requireNonNull(peripheralJobService, "peripheralJobService");
this.peripheralControllerPool = requireNonNull(
peripheralControllerPool,
"peripheralControllerPool"
);
this.peripheralJobCallback = requireNonNull(peripheralJobCallback, "peripheralJobCallback");
}
public void assignPeripheralJob(Location location, PeripheralJob peripheralJob) {
requireNonNull(location, "location");
requireNonNull(peripheralJob, "peripheralJob");
LOG.debug("Assigning location {} to job {}.", location.getName(), peripheralJob.getName());
final TCSResourceReference<Location> locationRef = location.getReference();
final TCSObjectReference<PeripheralJob> jobRef = peripheralJob.getReference();
// Set the locations's and peripheral job's state.
peripheralService.updatePeripheralProcState(
locationRef,
PeripheralInformation.ProcState.PROCESSING_JOB
);
peripheralService.updatePeripheralReservationToken(
locationRef,
peripheralJob.getReservationToken()
);
peripheralService.updatePeripheralJob(locationRef, jobRef);
peripheralJobService.updatePeripheralJobState(jobRef, PeripheralJob.State.BEING_PROCESSED);
peripheralControllerPool.getPeripheralController(locationRef).process(
peripheralJobService.fetchObject(PeripheralJob.class, jobRef),
peripheralJobCallback
);
}
}

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.strategies.basic.peripherals.dispatching;
import java.util.Collection;
import org.opentcs.data.model.Location;
/**
* A strategy that determines peripherals whose reservations are to be released.
*/
public interface PeripheralReleaseStrategy {
/**
* Selects the peripherals whose reservations are to be released.
*
* @param locations The peripherals to select from.
* @return The selected peripherals.
*/
Collection<Location> selectPeripheralsToRelease(Collection<Location> locations);
}

Some files were not shown because too many files have changed in this diff Show More