All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentcs.drivers.vehicle.BasicVehicleCommAdapter Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/**
 * 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.drivers.vehicle;

import static java.util.Objects.requireNonNull;
import static org.opentcs.drivers.vehicle.VehicleProcessModel.Attribute.COMMAND_ENQUEUED;
import static org.opentcs.drivers.vehicle.VehicleProcessModel.Attribute.COMMAND_EXECUTED;
import static org.opentcs.util.Assertions.checkInRange;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import org.opentcs.data.model.Vehicle;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A base class for communication adapters mainly providing command queue processing.
 *
 * 

* Implementation notes: *

*
    *
  • Accessing the queues of {@link #getUnsentCommands() unsent} and * {@link #getSentCommands() sent} commands from outside should always be protected by * synchronization on the {@link BasicVehicleCommAdapter} instance.
  • *
*/ public abstract class BasicVehicleCommAdapter implements VehicleCommAdapter, PropertyChangeListener { /** * This class's Logger. */ private static final Logger LOG = LoggerFactory.getLogger(BasicVehicleCommAdapter.class); /** * An observable model of the vehicle's and its comm adapter's attributes. */ private final VehicleProcessModel vehicleModel; /** * The number of commands this comm adapter accepts. Must be at least 1. */ private final int commandsCapacity; /** * The string to recognize as a recharge operation. */ private final String rechargeOperation; /** * The executor to run tasks on. */ private final ScheduledExecutorService executor; /** * Indicates whether this adapter is initialized. */ private boolean initialized; /** * This adapter's enabled flag. */ private boolean enabled; /** * This adapter's current command dispatcher task. */ private final Runnable commandDispatcherTask = new CommandDispatcherTask(); /** * This adapter's command queue. */ private final Queue commandQueue = new LinkedBlockingQueue<>(); /** * Contains the orders which have been sent to the vehicle but which haven't * been executed by it, yet. */ private final Queue sentQueue = new LinkedBlockingQueue<>(); /** * Creates a new instance. * * @param vehicleModel An observable model of the vehicle's and its comm adapter's attributes. * @param commandsCapacity The number of commands this comm adapter accepts. Must be at least 1. * @param rechargeOperation The string to recognize as a recharge operation. * @param executor The executor to run tasks on. */ public BasicVehicleCommAdapter( VehicleProcessModel vehicleModel, int commandsCapacity, String rechargeOperation, ScheduledExecutorService executor ) { this.vehicleModel = requireNonNull(vehicleModel, "vehicleModel"); this.commandsCapacity = checkInRange( commandsCapacity, 1, Integer.MAX_VALUE, "commandsCapacity" ); this.rechargeOperation = requireNonNull(rechargeOperation, "rechargeOperation"); this.executor = requireNonNull(executor, "executor"); } /** * {@inheritDoc} *

* Overriding methods are expected to call this implementation, too. *

*/ @Override public void initialize() { if (initialized) { LOG.debug("{}: Already initialized.", getName()); return; } getProcessModel().addPropertyChangeListener(this); this.initialized = true; } /** * {@inheritDoc} *

* Overriding methods are expected to call this implementation, too. *

*/ @Override public void terminate() { if (!initialized) { LOG.debug("{}: Not initialized.", getName()); return; } getProcessModel().removePropertyChangeListener(this); this.initialized = false; } @Override public boolean isInitialized() { return initialized; } /** * {@inheritDoc} *

* Overriding methods are expected to call this implementation, too. *

*/ @Override public synchronized void enable() { if (isEnabled()) { return; } LOG.info("Vehicle comm adapter is being enabled: {}", getName()); connectVehicle(); enabled = true; getProcessModel().setCommAdapterEnabled(true); } /** * {@inheritDoc} *

* Overriding methods are expected to call this implementation, too. *

*/ @Override public synchronized void disable() { if (!isEnabled()) { return; } LOG.info("Vehicle comm adapter is being disabled: {}", getName()); disconnectVehicle(); enabled = false; // Update the vehicle's state for the rest of the system. getProcessModel().setCommAdapterEnabled(false); getProcessModel().setState(Vehicle.State.UNKNOWN); } @Override public synchronized boolean isEnabled() { return enabled; } @Override public VehicleProcessModel getProcessModel() { return vehicleModel; } @Override public VehicleProcessModelTO createTransferableProcessModel() { return createCustomTransferableProcessModel() .setName(getProcessModel().getName()) .setCommAdapterConnected(getProcessModel().isCommAdapterConnected()) .setCommAdapterEnabled(getProcessModel().isCommAdapterEnabled()) .setEnergyLevel(getProcessModel().getEnergyLevel()) .setLoadHandlingDevices(getProcessModel().getLoadHandlingDevices()) .setNotifications(getProcessModel().getNotifications()) .setOrientationAngle(getProcessModel().getOrientationAngle()) .setPrecisePosition(getProcessModel().getPrecisePosition()) .setPosition(getProcessModel().getPosition()) .setState(getProcessModel().getState()) .setBoundingBox(getProcessModel().getBoundingBox()); } @Override public synchronized Queue getUnsentCommands() { return commandQueue; } @Override public synchronized Queue getSentCommands() { return sentQueue; } @Override public int getCommandsCapacity() { return commandsCapacity; } @Override public boolean canAcceptNextCommand() { return (getUnsentCommands().size() + getSentCommands().size()) < getCommandsCapacity(); } @Override public String getRechargeOperation() { return rechargeOperation; } @Override public synchronized boolean enqueueCommand(MovementCommand newCommand) { requireNonNull(newCommand, "newCommand"); if (!canAcceptNextCommand()) { return false; } LOG.debug("{}: Adding command: {}", getName(), newCommand); getUnsentCommands().add(newCommand); getProcessModel().commandEnqueued(newCommand); return true; } @Override public synchronized void clearCommandQueue() { getUnsentCommands().clear(); getSentCommands().clear(); } @Override public void execute(AdapterCommand command) { command.execute(this); } /** * Processes updates of the {@link VehicleProcessModel}. * *

* Overriding methods should also call this. *

* * @param evt The property change event published by the model. */ @Override public void propertyChange(PropertyChangeEvent evt) { if (Objects.equals(evt.getPropertyName(), COMMAND_ENQUEUED.name()) || Objects.equals(evt.getPropertyName(), COMMAND_EXECUTED.name())) { executor.execute(commandDispatcherTask); } } /** * Returns this communication adapter's name. * * @return This communication adapter's name. */ public String getName() { return getProcessModel().getName(); } /** * Returns the executor to run tasks on. * * @return The executor to run tasks on. */ public ScheduledExecutorService getExecutor() { return executor; } /** * Converts the given command to something the vehicle can understand and sends the resulting data * to the vehicle. *

* Note that this method is called from the kernel executor and thus should not block. *

* * @param cmd The command to be sent. * @throws IllegalArgumentException If there was a problem with interpreting the command or * communicating it to the vehicle. */ public abstract void sendCommand(MovementCommand cmd) throws IllegalArgumentException; /** * Checks whether a new command can be sent to the vehicle. *

* This method returns true only if there is at least one command in the * {@link #getUnsentCommands() queue of unsent commands} waiting to be sent. *

* * @return true if, and only if, a new command can be sent to the vehicle. */ protected synchronized boolean canSendNextCommand() { // Since canAcceptNextCommand() already ensures that the combined sizes of the queues of unsent // and sent commands don't exceeed the number of commands the comm adapters can accept, the only // thing to do here is to check if there are any commands to be sent. return !getUnsentCommands().isEmpty(); } // Abstract methods start here. /** * Initiates a communication channel to the vehicle. * This method should not block, i.e. it should not wait for the actual * connection to be established, as the vehicle could be temporarily absent * or not responding at all. If that's the case, the communication adapter * should continue trying to establish a connection until successful or until * disconnectVehicle is called. */ protected abstract void connectVehicle(); /** * Closes the communication channel to the vehicle. */ protected abstract void disconnectVehicle(); /** * Checks whether the communication channel to the vehicle is open. *

* Note that the return value of this method does not indicate * whether communication with the vehicle is currently alive and/or if the * vehicle is considered to be working/responding correctly. *

* * @return true if, and only if, the communication channel to the * vehicle is open. */ protected abstract boolean isVehicleConnected(); /** * Creates a transferable process model with the specific attributes of this comm adapter's * process model set. *

* This method should be overriden by implementing classes. *

* * @return A transferable process model. */ protected VehicleProcessModelTO createCustomTransferableProcessModel() { return new VehicleProcessModelTO(); } /** * The task processing the command queue. */ private class CommandDispatcherTask implements Runnable { CommandDispatcherTask() { } @Override public void run() { synchronized (BasicVehicleCommAdapter.this) { if (!isEnabled()) { LOG.debug("{}: Not enabled, skipping.", getName()); return; } if (!canSendNextCommand()) { LOG.debug("{}: Cannot send another command, skipping.", getName()); return; } MovementCommand curCmd = getUnsentCommands().poll(); if (curCmd == null) { LOG.debug("{}: Nothing to send, skipping.", getName()); return; } try { LOG.debug("{}: Sending command: {}", getName(), curCmd); sendCommand(curCmd); // Remember that we sent this command to the vehicle. getSentCommands().add(curCmd); // Notify listeners that this command was sent. getProcessModel().commandSent(curCmd); } catch (IllegalArgumentException exc) { // Notify listeners that this command failed. LOG.warn("{}: Failed sending command {}", getName(), curCmd, exc); getProcessModel().commandFailed(curCmd); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy