This commit is contained in:
19
opentcs-strategies-default/build.gradle
Normal file
19
opentcs-strategies-default/build.gradle
Normal 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
|
||||
}
|
||||
40
opentcs-strategies-default/gradle.properties
Normal file
40
opentcs-strategies-default/gradle.properties
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
@@ -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>> {
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user