org.opentcs.kernel.vehicles.PeripheralInteractor Maven / Gradle / Ivy
/**
* Copyright (c) The openTCS Authors.
*
* This program is free software and subject to the MIT license. (For details,
* see the licensing information (LICENSE.txt) you should have received with
* this copy of the software.)
*/
package org.opentcs.kernel.vehicles;
import static java.util.Objects.requireNonNull;
import com.google.inject.assistedinject.Assisted;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.opentcs.components.Lifecycle;
import org.opentcs.components.kernel.services.PeripheralDispatcherService;
import org.opentcs.components.kernel.services.PeripheralJobService;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.data.TCSObject;
import org.opentcs.data.TCSObjectEvent;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Path;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.data.peripherals.PeripheralJob;
import org.opentcs.data.peripherals.PeripheralOperation;
import org.opentcs.drivers.vehicle.MovementCommand;
import org.opentcs.util.event.EventHandler;
import org.opentcs.util.event.EventSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages interactions with peripheral devices that are to be performed before or after the
* execution of movement commands.
*/
public class PeripheralInteractor
implements
EventHandler,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(PeripheralInteractor.class);
/**
* The reference to the vehicle that's interacting with peripheral devices.
*/
private final TCSObjectReference vehicleRef;
/**
* The peripheral job service to use.
*/
private final PeripheralJobService peripheralJobService;
/**
* The peripheral dispatcher service to use.
*/
private final PeripheralDispatcherService peripheralDispatcherService;
/**
* The event source to register with.
*/
private final EventSource eventSource;
/**
* The peripheral interactions to be performed BEFORE the execution of a movement command mapped
* to the corresponding movement command.
*/
private final Map preMovementInteractions
= new HashMap<>();
/**
* The peripheral interactions to be performed AFTER the execution of a movement command mapped
* to the corresponding movement command.
*/
private final Map postMovementInteractions
= new HashMap<>();
/**
* Indicates whether this instance is initialized.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param vehicleRef The reference to the vehicle that's interacting with peripheral devices.
* @param peripheralJobService The peripheral job service to use.
* @param peripheralDispatcherService The peripheral dispatcher service to use.
* @param eventSource The event source to register with.
*/
@Inject
public PeripheralInteractor(
@Assisted
@Nonnull
TCSObjectReference vehicleRef,
@Nonnull
PeripheralJobService peripheralJobService,
@Nonnull
PeripheralDispatcherService peripheralDispatcherService,
@Nonnull
@ApplicationEventBus
EventSource eventSource
) {
this.vehicleRef = requireNonNull(vehicleRef, "vehicleRef");
this.peripheralJobService = requireNonNull(peripheralJobService, "peripheralJobService");
this.peripheralDispatcherService = requireNonNull(
peripheralDispatcherService,
"peripheralDispatcherService"
);
this.eventSource = requireNonNull(eventSource, "eventSource");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
eventSource.subscribe(this);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
eventSource.unsubscribe(this);
initialized = false;
}
@Override
public void onEvent(Object event) {
if (!(event instanceof TCSObjectEvent)) {
return;
}
TCSObjectEvent objectEvent = (TCSObjectEvent) event;
if (objectEvent.getType() != TCSObjectEvent.Type.OBJECT_MODIFIED) {
return;
}
TCSObject> currentOrPreviousObjectState = objectEvent.getCurrentOrPreviousObjectState();
if (!(currentOrPreviousObjectState instanceof PeripheralJob)) {
return;
}
// Since a PeripheralInteraction only keeps track of peripheral jobs where the completion
// required flag is set, we can ignore all peripheral jobs where this is not the case.
if (!hasCompletionRequiredFlagSet((PeripheralJob) currentOrPreviousObjectState)) {
return;
}
onPeripheralJobChange(objectEvent);
}
/**
* Prepares for peripheral interactions in the context of the given movement command by
* determining the interactions that have to be performed before and after the movement command
* is executed.
*
* @param orderRef A reference to the transport order that the movement command belongs to.
* @param movementCommand The movement command to prepare peripheral interactions for.
*/
public void prepareInteractions(
TCSObjectReference orderRef,
MovementCommand movementCommand
) {
Path path = movementCommand.getStep().getPath();
if (path == null) {
return;
}
Map> operations
= path.getPeripheralOperations().stream()
.collect(Collectors.groupingBy(t -> t.getExecutionTrigger()));
operations.computeIfAbsent(
PeripheralOperation.ExecutionTrigger.AFTER_ALLOCATION,
executionTrigger -> new ArrayList<>()
);
operations.computeIfAbsent(
PeripheralOperation.ExecutionTrigger.AFTER_MOVEMENT,
executionTrigger -> new ArrayList<>()
);
String reservationToken = determineReservationToken();
List preMovementOperations
= operations.get(PeripheralOperation.ExecutionTrigger.AFTER_ALLOCATION);
if (!preMovementOperations.isEmpty()) {
preMovementInteractions.put(
movementCommand,
new PeripheralInteraction(
vehicleRef,
orderRef,
movementCommand,
preMovementOperations,
peripheralJobService,
reservationToken
)
);
}
List postMovementOperations
= operations.get(PeripheralOperation.ExecutionTrigger.AFTER_MOVEMENT);
if (!postMovementOperations.isEmpty()) {
postMovementInteractions.put(
movementCommand,
new PeripheralInteraction(
vehicleRef,
orderRef,
movementCommand,
postMovementOperations,
peripheralJobService,
reservationToken
)
);
}
}
/**
* Starts the peripheral interactions that have to be performed before the given movement command
* is executed.
*
* @param movementCommand The movement command.
* @param succeededCallback The callback that is executed if the interactions succeeds (i.e. once
* all required interactions are finished).
* @param failedCallback The callback that is executed if the interactions fails (i.e. if a
* single interaction failed).
*/
public void startPreMovementInteractions(
@Nonnull
MovementCommand movementCommand,
@Nonnull
Runnable succeededCallback,
@Nonnull
Runnable failedCallback
) {
requireNonNull(movementCommand, "movementCommand");
requireNonNull(succeededCallback, "succeededCallback");
requireNonNull(failedCallback, "failedCallback");
if (!preMovementInteractions.containsKey(movementCommand)) {
LOG.debug(
"{}: No interactions to be performed before movement to {}...",
vehicleRef.getName(),
movementCommand.getStep().getDestinationPoint().getName()
);
succeededCallback.run();
return;
}
LOG.debug(
"{}: There are interactions to be performed before movement to {}...",
vehicleRef.getName(),
movementCommand.getStep().getDestinationPoint().getName()
);
preMovementInteractions.get(movementCommand).start(succeededCallback, failedCallback);
// In case there are only operations with the completion required flag not set, the interaction
// is immediately finished and we can remove it right away.
if (preMovementInteractions.get(movementCommand).isFinished()) {
preMovementInteractions.remove(movementCommand);
}
// Peripheral jobs have been created. Dispatch them.
peripheralDispatcherService.dispatch();
}
/**
* Starts the peripheral interactions that have to be performed after the given movement command
* is executed.
*
* @param movementCommand The movement command.
* @param succeededCallback The callback that is executed if the interactions succeeds (i.e. once
* all required interactions are finished).
* @param failedCallback The callback that is executed if the interactions fails (i.e. if a
* single interaction failed).
*/
public void startPostMovementInteractions(
@Nonnull
MovementCommand movementCommand,
@Nonnull
Runnable succeededCallback,
@Nonnull
Runnable failedCallback
) {
requireNonNull(movementCommand, "movementCommand");
requireNonNull(succeededCallback, "succeededCallback");
requireNonNull(failedCallback, "failedCallback");
if (!postMovementInteractions.containsKey(movementCommand)) {
LOG.debug(
"{}: No interactions to be performed after movement to {}...",
vehicleRef.getName(),
movementCommand.getStep().getDestinationPoint().getName()
);
succeededCallback.run();
return;
}
LOG.debug(
"{}: There are interactions to be performed after movement to {}...",
vehicleRef.getName(),
movementCommand.getStep().getDestinationPoint().getName()
);
postMovementInteractions.get(movementCommand).start(succeededCallback, failedCallback);
// In case there are only operations with the completion required flag not set, the interaction
// is immediately finished and we can remove it right away.
if (postMovementInteractions.get(movementCommand).isFinished()) {
postMovementInteractions.remove(movementCommand);
}
// Peripheral jobs have been created. Dispatch them.
peripheralDispatcherService.dispatch();
}
/**
* Returns whether there are any required (pre or post movement) interactions that have not been
* finished yet.
* In case there's a required interaction that has failed (and therefore not finished), this
* method returns {@code true}.
*
* @return Whether there are any required interactions that have not been finished yet.
*/
public boolean isWaitingForMovementInteractionsToFinish() {
return isWaitingForPreMovementInteractionsToFinish()
|| isWaitingForPostMovementInteractionsToFinish();
}
/**
* Returns whether there are any required (pre movement) interactions that have not been finished
* yet.
* In case there's a required interaction that has failed (and therefore not finished), this
* method returns {@code true}.
*
* @return Whether there are any required (pre movement) interactions that have not been finished
* yet.
*/
public boolean isWaitingForPreMovementInteractionsToFinish() {
return !preMovementInteractions.values().stream()
.filter(PeripheralInteraction::hasRequiredOperations)
.allMatch(PeripheralInteraction::isFinished);
}
/**
* Returns whether there are any required (post movement) interactions that have not been finished
* yet.
* In case there's a required interaction that has failed (and therefore not finished), this
* method returns {@code true}.
*
* @return Whether there are any required (post movement) interactions that have not been finished
* yet.
*/
public boolean isWaitingForPostMovementInteractionsToFinish() {
return !postMovementInteractions.values().stream()
.filter(PeripheralInteraction::hasRequiredOperations)
.allMatch(PeripheralInteraction::isFinished);
}
/**
* Returns a list of required operations that are still to be completed mapped to the associated
* movement command's destination point.
*
* @return A list of required operations that are still to be completed mapped to the associated
* movement command's destination point.
*/
public Map> pendingRequiredInteractionsByDestination() {
return Stream.concat(
preMovementInteractions.entrySet().stream(),
postMovementInteractions.entrySet().stream()
)
.map(entry -> entry.getValue())
.filter(interaction -> interaction.hasRequiredOperations())
// We're working with two streams from two maps which can each contain the same keys.
// Therefore we have to use the groupingBy collector and need to flat map each interaction's
// pending required operations.
.collect(
Collectors.groupingBy(
interact -> interact.getMovementCommand().getStep().getDestinationPoint().getName(),
Collectors.flatMapping(
interaction -> interaction.getPendingRequiredOperations().stream(),
Collectors.toList()
)
)
);
}
private void onPeripheralJobChange(TCSObjectEvent event) {
PeripheralJob prevJobState = (PeripheralJob) event.getPreviousObjectState();
PeripheralJob currJobState = (PeripheralJob) event.getCurrentObjectState();
if (prevJobState.getState() != currJobState.getState()) {
switch (currJobState.getState()) {
case FINISHED:
onPeripheralJobFinished(currJobState);
break;
case FAILED:
onPeripheralJobFailed(currJobState);
break;
default: // Do nothing
}
}
}
private void onPeripheralJobFinished(PeripheralJob job) {
Stream.concat(
preMovementInteractions.values().stream(),
postMovementInteractions.values().stream()
)
.forEach(interaction -> interaction.onPeripheralJobFinished(job));
// With a peripheral job finished, an associated interaction might now be finished as well. If
// that's the case, forget the interaction.
preMovementInteractions.entrySet().removeIf(entry -> entry.getValue().isFinished());
postMovementInteractions.entrySet().removeIf(entry -> entry.getValue().isFinished());
}
private void onPeripheralJobFailed(PeripheralJob job) {
Stream.concat(
preMovementInteractions.values().stream(),
postMovementInteractions.values().stream()
)
.forEach(interaction -> interaction.onPeripheralJobFailed(job));
// With a peripheral job failed, an associated interaction might now be failed as well. If
// that's the case, forget the interaction.
preMovementInteractions.entrySet().removeIf(entry -> entry.getValue().isFailed());
postMovementInteractions.entrySet().removeIf(entry -> entry.getValue().isFailed());
}
/**
* Clears the interactions.
*/
public void clear() {
preMovementInteractions.clear();
postMovementInteractions.clear();
}
private String determineReservationToken() {
Vehicle vehicle = peripheralJobService.fetchObject(Vehicle.class, vehicleRef);
if (vehicle.getTransportOrder() != null) {
TransportOrder transportOrder = peripheralJobService.fetchObject(
TransportOrder.class,
vehicle.getTransportOrder()
);
if (transportOrder.getPeripheralReservationToken() != null) {
return transportOrder.getPeripheralReservationToken();
}
}
return vehicle.getName();
}
private boolean hasCompletionRequiredFlagSet(PeripheralJob job) {
return job.getPeripheralOperation().isCompletionRequired();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy