This commit is contained in:
BIN
opentcs-kernel/src/dist/bin/splash-image.gif
vendored
Normal file
BIN
opentcs-kernel/src/dist/bin/splash-image.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
2
opentcs-kernel/src/dist/bin/splash-image.gif.license
vendored
Normal file
2
opentcs-kernel/src/dist/bin/splash-image.gif.license
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: CC-BY-4.0
|
||||
70
opentcs-kernel/src/dist/config/logging.config
vendored
Normal file
70
opentcs-kernel/src/dist/config/logging.config
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: CC-BY-4.0
|
||||
|
||||
############################################################
|
||||
# Default Logging Configuration File
|
||||
#
|
||||
# You can use a different file by specifying a filename
|
||||
# with the java.util.logging.config.file system property.
|
||||
# For example java -Djava.util.logging.config.file=myfile
|
||||
############################################################
|
||||
|
||||
############################################################
|
||||
# Global properties
|
||||
############################################################
|
||||
|
||||
# "handlers" specifies a comma separated list of log Handler
|
||||
# classes. These handlers will be installed during VM startup.
|
||||
# Note that these classes must be on the system classpath.
|
||||
# By default we only configure a ConsoleHandler, which will only
|
||||
# show messages at the INFO and above levels.
|
||||
#handlers= java.util.logging.ConsoleHandler
|
||||
|
||||
# To also add the FileHandler, use the following line instead.
|
||||
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
|
||||
|
||||
# Default global logging level.
|
||||
# This specifies which kinds of events are logged across
|
||||
# all loggers. For any given facility this global level
|
||||
# can be overriden by a facility specific level
|
||||
# Note that the ConsoleHandler also has a separate level
|
||||
# setting to limit messages printed to the console.
|
||||
.level= INFO
|
||||
|
||||
############################################################
|
||||
# Handler specific properties.
|
||||
# Describes specific configuration info for Handlers.
|
||||
############################################################
|
||||
|
||||
# default file output is in user's home directory.
|
||||
java.util.logging.FileHandler.pattern = ./log/opentcs-kernel.%g.log
|
||||
java.util.logging.FileHandler.limit = 500000
|
||||
java.util.logging.FileHandler.count = 10
|
||||
#java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
|
||||
java.util.logging.FileHandler.formatter = org.opentcs.util.logging.SingleLineFormatter
|
||||
java.util.logging.FileHandler.append = true
|
||||
java.util.logging.FileHandler.level = FINE
|
||||
|
||||
# Limit the message that are printed on the console to INFO and above.
|
||||
java.util.logging.ConsoleHandler.level = FINE
|
||||
#java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||
java.util.logging.ConsoleHandler.formatter = org.opentcs.util.logging.SingleLineFormatter
|
||||
|
||||
# Our own handler for the GUI:
|
||||
#org.opentcs.kernel.controlcenter.ControlCenterInfoHandler.level = WARNING
|
||||
|
||||
|
||||
############################################################
|
||||
# Facility specific properties.
|
||||
# Provides extra control for each logger.
|
||||
############################################################
|
||||
|
||||
# For example, set the com.xyz.foo logger to only log SEVERE
|
||||
# messages:
|
||||
#com.xyz.foo.level = SEVERE
|
||||
|
||||
# Logging configuration for single classes. Remember that you might also have to
|
||||
# adjust handler levels!
|
||||
|
||||
#org.opentcs.strategies.basic.dispatching.DefaultDispatcher.level = FINE
|
||||
#org.opentcs.kernel.KernelStateOperating.level = FINE
|
||||
2
opentcs-kernel/src/dist/config/opentcs-kernel.properties
vendored
Normal file
2
opentcs-kernel/src/dist/config/opentcs-kernel.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: CC-BY-4.0
|
||||
62
opentcs-kernel/src/dist/generateKeystores.bat
vendored
Normal file
62
opentcs-kernel/src/dist/generateKeystores.bat
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
@echo off
|
||||
rem SPDX-FileCopyrightText: The openTCS Authors
|
||||
rem SPDX-License-Identifier: MIT
|
||||
rem
|
||||
rem Start SSL keystore and truststore generation.
|
||||
rem
|
||||
|
||||
rem Don't export variables to the parent shell.
|
||||
setlocal
|
||||
|
||||
rem Set the path to where keytool is located. Keytool comes with the Java JRE.
|
||||
set KEYTOOL_PATH="keytool"
|
||||
|
||||
rem Try to execute keytool
|
||||
%KEYTOOL_PATH% 2>nul
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
if %KEYTOOL_PATH% equ "keytool" (
|
||||
echo Error: Could not find keytool in PATH.
|
||||
) else (
|
||||
echo Error: Could not find keytool in %KEYTOOL_PATH%.
|
||||
)
|
||||
exit /B %ERRORLEVEL%
|
||||
)
|
||||
|
||||
rem Set base directory names.
|
||||
set OPENTCS_HOME=.
|
||||
set OPENTCS_CONFIGDIR=%OPENTCS_HOME%\config
|
||||
set OUTPUTDIR=%OPENTCS_CONFIGDIR%
|
||||
|
||||
rem Set paths to generate files at.
|
||||
set KEYSTORE_FILEPATH=%OUTPUTDIR%\keystore.p12
|
||||
set TRUSTSTORE_FILEPATH=%OUTPUTDIR%\truststore.p12
|
||||
set CERTIFICATE_FILEPATH=%OUTPUTDIR%\certificate.cer
|
||||
|
||||
rem Set the password used for generating the stores.
|
||||
set PASSWORD=password
|
||||
|
||||
echo Deleting previously generated keystore and truststore...
|
||||
del %KEYSTORE_FILEPATH% 2>nul
|
||||
del %TRUSTSTORE_FILEPATH% 2>nul
|
||||
del %CERTIFICATE_FILEPATH% 2>nul
|
||||
|
||||
rem Generates a keypair wrapped in a self-signed (X.509) certificate.
|
||||
rem Some defaults of the -genkeypair command: -alias "mykey" -keyalg "DSA" -keysize 1024 -validity 90
|
||||
echo Generating a new keystore in %KEYSTORE_FILEPATH%...
|
||||
%KEYTOOL_PATH% -genkeypair -alias openTCS -keyalg RSA -dname "c=DE" -storepass %PASSWORD% -keypass %PASSWORD% -validity 365 -storetype PKCS12 -keystore %KEYSTORE_FILEPATH%
|
||||
|
||||
rem Exports the (wrapping) self-signed certificate from the generated keypair.
|
||||
rem '-rfc' - Output the certificate in a printable encoding format (by default the -export command outputs a certificate in binary encoding)
|
||||
rem '2>nul' - Suppress output of this command
|
||||
%KEYTOOL_PATH% -exportcert -alias openTCS -file %CERTIFICATE_FILEPATH% -keystore %KEYSTORE_FILEPATH% -storepass %PASSWORD% -rfc 2>nul
|
||||
|
||||
rem Adds the exported certificate to a new keystore and its trusted certificates.
|
||||
echo Generating a new truststore in %TRUSTSTORE_FILEPATH%...
|
||||
%KEYTOOL_PATH% -importcert -alias openTCS -file %CERTIFICATE_FILEPATH% -storepass %PASSWORD% -storetype PKCS12 -keystore %TRUSTSTORE_FILEPATH% -noprompt 2>nul
|
||||
|
||||
rem Delete the exported certificate since it's not really needed.
|
||||
del %CERTIFICATE_FILEPATH%
|
||||
|
||||
echo Copy the generated truststore to the openTCS PlantOverview's \config folder or a corresponding location of your application.
|
||||
|
||||
pause
|
||||
57
opentcs-kernel/src/dist/generateKeystores.sh
vendored
Normal file
57
opentcs-kernel/src/dist/generateKeystores.sh
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Start SSL keystore and truststore generation.
|
||||
#
|
||||
|
||||
# Set the path to where keytool is located. Keytool comes with the Java JRE.
|
||||
export KEYTOOL_PATH="keytool"
|
||||
|
||||
# Try to execute keytool
|
||||
${KEYTOOL_PATH} 2>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
if [ "${KEYTOOL_PATH}" = "keytool" ]; then
|
||||
echo Error: Could not find keytool in PATH.
|
||||
else
|
||||
echo Error: Could not find keytool in ${KEYTOOL_PATH}.
|
||||
fi
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Set base directory names.
|
||||
export OPENTCS_HOME=.
|
||||
export OPENTCS_CONFIGDIR="${OPENTCS_HOME}/config"
|
||||
export OUTPUTDIR="${OPENTCS_CONFIGDIR}"
|
||||
|
||||
# Set paths to generate files at.
|
||||
export KEYSTORE_FILEPATH="${OUTPUTDIR}/keystore.p12"
|
||||
export TRUSTSTORE_FILEPATH="${OUTPUTDIR}/truststore.p12"
|
||||
export CERTIFICATE_FILEPATH="${OUTPUTDIR}/certificate.cer"
|
||||
|
||||
# Set the password used for generating the stores.
|
||||
export PASSWORD=password
|
||||
|
||||
echo Deleting previously generated keystore and truststore...
|
||||
rm ${KEYSTORE_FILEPATH} 2>/dev/null
|
||||
rm ${TRUSTSTORE_FILEPATH} 2>/dev/null
|
||||
rm ${CERTIFICATE_FILEPATH} 2>/dev/null
|
||||
|
||||
# Generates a keypair wrapped in a self-signed (X.509) certificate.
|
||||
# Some defaults of the -genkeypair command: -alias "mykey" -keyalg "DSA" -keysize 1024 -validity 90
|
||||
echo Generating a new keystore in ${KEYSTORE_FILEPATH}...
|
||||
${KEYTOOL_PATH} -genkeypair -alias openTCS -keyalg RSA -dname "c=DE" -storepass ${PASSWORD} -keypass ${PASSWORD} -validity 365 -storetype PKCS12 -keystore ${KEYSTORE_FILEPATH}
|
||||
|
||||
# Exports the (wrapping) self-signed certificate from the generated keypair.
|
||||
# '-rfc' - Output the certificate in a printable encoding format (by default the -export command outputs a certificate in binary encoding)
|
||||
# '2>nul' - Suppress output of this command
|
||||
${KEYTOOL_PATH} -exportcert -alias openTCS -file ${CERTIFICATE_FILEPATH} -keystore ${KEYSTORE_FILEPATH} -storepass ${PASSWORD} -rfc 2>/dev/null
|
||||
|
||||
# Adds the exported certificate to a new keystore and its trusted certificates.
|
||||
echo Generating a new truststore in ${TRUSTSTORE_FILEPATH}...
|
||||
${KEYTOOL_PATH} -importcert -alias openTCS -file ${CERTIFICATE_FILEPATH} -storepass ${PASSWORD} -storetype PKCS12 -keystore ${TRUSTSTORE_FILEPATH} -noprompt 2>/dev/null
|
||||
|
||||
# Delete the exported certificate since it's not really needed.
|
||||
rm ${CERTIFICATE_FILEPATH}
|
||||
|
||||
echo Copy the generated truststore to the openTCS PlantOverview\'s /config folder or a corresponding location of your application.
|
||||
0
opentcs-kernel/src/dist/lib/openTCS-extensions/.keepme
vendored
Normal file
0
opentcs-kernel/src/dist/lib/openTCS-extensions/.keepme
vendored
Normal file
0
opentcs-kernel/src/dist/log/statistics/.keepme
vendored
Normal file
0
opentcs-kernel/src/dist/log/statistics/.keepme
vendored
Normal file
20
opentcs-kernel/src/dist/shutdownKernel.bat
vendored
Normal file
20
opentcs-kernel/src/dist/shutdownKernel.bat
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
rem SPDX-FileCopyrightText: The openTCS Authors
|
||||
rem SPDX-License-Identifier: MIT
|
||||
rem
|
||||
rem Shut down the openTCS kernel.
|
||||
rem
|
||||
|
||||
rem Don't export variables to the parent shell
|
||||
setlocal
|
||||
|
||||
rem Set base directory names.
|
||||
set OPENTCS_BASE=.
|
||||
set OPENTCS_LIBDIR=%OPENTCS_BASE%\lib
|
||||
|
||||
rem Set the class path
|
||||
set OPENTCS_CP=%OPENTCS_LIBDIR%\*;
|
||||
set OPENTCS_CP=%OPENTCS_CP%;%OPENTCS_LIBDIR%\openTCS-extensions\*;
|
||||
|
||||
java -classpath "%OPENTCS_CP%" ^
|
||||
org.opentcs.kernel.ShutdownKernel localhost 55100
|
||||
24
opentcs-kernel/src/dist/shutdownKernel.sh
vendored
Normal file
24
opentcs-kernel/src/dist/shutdownKernel.sh
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Shut down the openTCS kernel.
|
||||
#
|
||||
|
||||
# Set base directory names.
|
||||
export OPENTCS_BASE=.
|
||||
export OPENTCS_LIBDIR="${OPENTCS_BASE}/lib"
|
||||
|
||||
# Set the class path
|
||||
export OPENTCS_CP="${OPENTCS_LIBDIR}/*"
|
||||
export OPENTCS_CP="${OPENTCS_CP}:${OPENTCS_LIBDIR}/openTCS-extensions/*"
|
||||
|
||||
if [ -n "${OPENTCS_JAVAVM}" ]; then
|
||||
export JAVA="${OPENTCS_JAVAVM}"
|
||||
else
|
||||
# XXX Be a bit more clever to find out the name of the JVM runtime.
|
||||
export JAVA="java"
|
||||
fi
|
||||
|
||||
${JAVA} -classpath "${OPENTCS_CP}" \
|
||||
org.opentcs.kernel.ShutdownKernel localhost 55100
|
||||
36
opentcs-kernel/src/dist/startKernel.bat
vendored
Normal file
36
opentcs-kernel/src/dist/startKernel.bat
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
@echo off
|
||||
rem SPDX-FileCopyrightText: The openTCS Authors
|
||||
rem SPDX-License-Identifier: MIT
|
||||
rem
|
||||
rem Start the openTCS kernel.
|
||||
rem
|
||||
|
||||
rem Set window title
|
||||
title Kernel (openTCS)
|
||||
|
||||
rem Don't export variables to the parent shell
|
||||
setlocal
|
||||
|
||||
rem Set base directory names.
|
||||
set OPENTCS_BASE=.
|
||||
set OPENTCS_HOME=.
|
||||
set OPENTCS_CONFIGDIR=%OPENTCS_HOME%\config
|
||||
set OPENTCS_LIBDIR=%OPENTCS_BASE%\lib
|
||||
|
||||
rem Set the class path
|
||||
set OPENTCS_CP=%OPENTCS_LIBDIR%\*;
|
||||
set OPENTCS_CP=%OPENTCS_CP%;%OPENTCS_LIBDIR%\openTCS-extensions\*;
|
||||
|
||||
rem XXX Be a bit more clever to find out the name of the JVM runtime.
|
||||
set JAVA=java
|
||||
|
||||
rem Start kernel
|
||||
%JAVA% -enableassertions ^
|
||||
-Dopentcs.base="%OPENTCS_BASE%" ^
|
||||
-Dopentcs.home="%OPENTCS_HOME%" ^
|
||||
-Dopentcs.configuration.provider=gestalt ^
|
||||
-Dopentcs.configuration.reload.interval=10000 ^
|
||||
-Djava.util.logging.config.file="%OPENTCS_CONFIGDIR%\logging.config" ^
|
||||
-XX:-OmitStackTraceInFastThrow ^
|
||||
-classpath "%OPENTCS_CP%" ^
|
||||
org.opentcs.kernel.RunKernel
|
||||
34
opentcs-kernel/src/dist/startKernel.sh
vendored
Normal file
34
opentcs-kernel/src/dist/startKernel.sh
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Start the openTCS kernel.
|
||||
#
|
||||
|
||||
# Set base directory names.
|
||||
export OPENTCS_BASE=.
|
||||
export OPENTCS_HOME=.
|
||||
export OPENTCS_CONFIGDIR="${OPENTCS_HOME}/config"
|
||||
export OPENTCS_LIBDIR="${OPENTCS_BASE}/lib"
|
||||
|
||||
# Set the class path
|
||||
export OPENTCS_CP="${OPENTCS_LIBDIR}/*"
|
||||
export OPENTCS_CP="${OPENTCS_CP}:${OPENTCS_LIBDIR}/openTCS-extensions/*"
|
||||
|
||||
if [ -n "${OPENTCS_JAVAVM}" ]; then
|
||||
export JAVA="${OPENTCS_JAVAVM}"
|
||||
else
|
||||
# XXX Be a bit more clever to find out the name of the JVM runtime.
|
||||
export JAVA="java"
|
||||
fi
|
||||
|
||||
# Start kernel
|
||||
${JAVA} -enableassertions \
|
||||
-Dopentcs.base="${OPENTCS_BASE}" \
|
||||
-Dopentcs.home="${OPENTCS_HOME}" \
|
||||
-Dopentcs.configuration.provider=gestalt \
|
||||
-Dopentcs.configuration.reload.interval=10000 \
|
||||
-Djava.util.logging.config.file=${OPENTCS_CONFIGDIR}/logging.config \
|
||||
-XX:-OmitStackTraceInFastThrow \
|
||||
-classpath "${OPENTCS_CP}" \
|
||||
org.opentcs.kernel.RunKernel
|
||||
@@ -0,0 +1,338 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import com.google.inject.multibindings.MapBinder;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.access.LocalKernel;
|
||||
import org.opentcs.access.SslParameterSet;
|
||||
import org.opentcs.common.LoggingScheduledThreadPoolExecutor;
|
||||
import org.opentcs.components.kernel.ObjectNameProvider;
|
||||
import org.opentcs.components.kernel.services.DispatcherService;
|
||||
import org.opentcs.components.kernel.services.InternalPeripheralJobService;
|
||||
import org.opentcs.components.kernel.services.InternalPeripheralService;
|
||||
import org.opentcs.components.kernel.services.InternalPlantModelService;
|
||||
import org.opentcs.components.kernel.services.InternalQueryService;
|
||||
import org.opentcs.components.kernel.services.InternalTransportOrderService;
|
||||
import org.opentcs.components.kernel.services.InternalVehicleService;
|
||||
import org.opentcs.components.kernel.services.NotificationService;
|
||||
import org.opentcs.components.kernel.services.PeripheralDispatcherService;
|
||||
import org.opentcs.components.kernel.services.PeripheralJobService;
|
||||
import org.opentcs.components.kernel.services.PeripheralService;
|
||||
import org.opentcs.components.kernel.services.PlantModelService;
|
||||
import org.opentcs.components.kernel.services.QueryService;
|
||||
import org.opentcs.components.kernel.services.RouterService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.components.kernel.services.TransportOrderService;
|
||||
import org.opentcs.components.kernel.services.VehicleService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.customizations.ApplicationHome;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.opentcs.customizations.kernel.KernelInjectionModule;
|
||||
import org.opentcs.drivers.peripherals.PeripheralControllerPool;
|
||||
import org.opentcs.drivers.vehicle.VehicleControllerPool;
|
||||
import org.opentcs.kernel.extensions.controlcenter.vehicles.AttachmentManager;
|
||||
import org.opentcs.kernel.extensions.controlcenter.vehicles.VehicleEntryPool;
|
||||
import org.opentcs.kernel.extensions.watchdog.Watchdog;
|
||||
import org.opentcs.kernel.extensions.watchdog.WatchdogConfiguration;
|
||||
import org.opentcs.kernel.peripherals.DefaultPeripheralControllerPool;
|
||||
import org.opentcs.kernel.peripherals.LocalPeripheralControllerPool;
|
||||
import org.opentcs.kernel.peripherals.PeripheralAttachmentManager;
|
||||
import org.opentcs.kernel.peripherals.PeripheralCommAdapterRegistry;
|
||||
import org.opentcs.kernel.peripherals.PeripheralControllerFactory;
|
||||
import org.opentcs.kernel.peripherals.PeripheralEntryPool;
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.persistence.XMLFileModelPersister;
|
||||
import org.opentcs.kernel.services.StandardDispatcherService;
|
||||
import org.opentcs.kernel.services.StandardNotificationService;
|
||||
import org.opentcs.kernel.services.StandardPeripheralDispatcherService;
|
||||
import org.opentcs.kernel.services.StandardPeripheralJobService;
|
||||
import org.opentcs.kernel.services.StandardPeripheralService;
|
||||
import org.opentcs.kernel.services.StandardPlantModelService;
|
||||
import org.opentcs.kernel.services.StandardQueryService;
|
||||
import org.opentcs.kernel.services.StandardRouterService;
|
||||
import org.opentcs.kernel.services.StandardTCSObjectService;
|
||||
import org.opentcs.kernel.services.StandardTransportOrderService;
|
||||
import org.opentcs.kernel.services.StandardVehicleService;
|
||||
import org.opentcs.kernel.vehicles.DefaultVehicleControllerPool;
|
||||
import org.opentcs.kernel.vehicles.LocalVehicleControllerPool;
|
||||
import org.opentcs.kernel.vehicles.VehicleCommAdapterRegistry;
|
||||
import org.opentcs.kernel.vehicles.VehicleControllerComponentsFactory;
|
||||
import org.opentcs.kernel.vehicles.VehicleControllerFactory;
|
||||
import org.opentcs.kernel.vehicles.transformers.CoordinateSystemTransformerFactory;
|
||||
import org.opentcs.kernel.vehicles.transformers.DefaultVehicleDataTransformerFactory;
|
||||
import org.opentcs.kernel.workingset.CreationTimeThreshold;
|
||||
import org.opentcs.kernel.workingset.NotificationBuffer;
|
||||
import org.opentcs.kernel.workingset.PeripheralJobPoolManager;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
import org.opentcs.kernel.workingset.PrefixedUlidObjectNameProvider;
|
||||
import org.opentcs.kernel.workingset.TCSObjectManager;
|
||||
import org.opentcs.kernel.workingset.TCSObjectRepository;
|
||||
import org.opentcs.kernel.workingset.TransportOrderPoolManager;
|
||||
import org.opentcs.util.event.EventBus;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.opentcs.util.event.SimpleEventBus;
|
||||
import org.opentcs.util.logging.UncaughtExceptionLogger;
|
||||
|
||||
/**
|
||||
* A Guice module for the openTCS kernel application.
|
||||
*/
|
||||
public class DefaultKernelInjectionModule
|
||||
extends
|
||||
KernelInjectionModule {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public DefaultKernelInjectionModule() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
configureEventHub();
|
||||
configureKernelExecutor();
|
||||
|
||||
// Ensure that the application's home directory can be used everywhere.
|
||||
File applicationHome = new File(System.getProperty("opentcs.home", "."));
|
||||
bind(File.class)
|
||||
.annotatedWith(ApplicationHome.class)
|
||||
.toInstance(applicationHome);
|
||||
|
||||
// A single global synchronization object for the kernel.
|
||||
bind(Object.class)
|
||||
.annotatedWith(GlobalSyncObject.class)
|
||||
.to(Object.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
// The kernel's data pool structures.
|
||||
bind(TCSObjectRepository.class).in(Singleton.class);
|
||||
bind(TCSObjectManager.class).in(Singleton.class);
|
||||
bind(PlantModelManager.class).in(Singleton.class);
|
||||
bind(TransportOrderPoolManager.class).in(Singleton.class);
|
||||
bind(PeripheralJobPoolManager.class).in(Singleton.class);
|
||||
bind(NotificationBuffer.class).in(Singleton.class);
|
||||
|
||||
bind(ObjectNameProvider.class)
|
||||
.to(PrefixedUlidObjectNameProvider.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
configurePersistence();
|
||||
|
||||
bind(VehicleCommAdapterRegistry.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
configureVehicleControllers();
|
||||
|
||||
bind(AttachmentManager.class)
|
||||
.in(Singleton.class);
|
||||
bind(VehicleEntryPool.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
configurePeripheralControllers();
|
||||
|
||||
bind(PeripheralCommAdapterRegistry.class)
|
||||
.in(Singleton.class);
|
||||
bind(PeripheralAttachmentManager.class)
|
||||
.in(Singleton.class);
|
||||
bind(PeripheralEntryPool.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
bind(StandardKernel.class)
|
||||
.in(Singleton.class);
|
||||
bind(LocalKernel.class)
|
||||
.to(StandardKernel.class);
|
||||
|
||||
vehicleDataTransformersBinder().addBinding().to(DefaultVehicleDataTransformerFactory.class);
|
||||
// tag::documentation_registerTransformerFactory[]
|
||||
vehicleDataTransformersBinder().addBinding().to(CoordinateSystemTransformerFactory.class);
|
||||
// end::documentation_registerTransformerFactory[]
|
||||
|
||||
configureKernelStatesDependencies();
|
||||
configureKernelStarterDependencies();
|
||||
configureSslParameters();
|
||||
configureKernelServicesDependencies();
|
||||
|
||||
// Ensure all of these binders are initialized.
|
||||
extensionsBinderAllModes();
|
||||
extensionsBinderModelling();
|
||||
extensionsBinderOperating();
|
||||
vehicleCommAdaptersBinder();
|
||||
peripheralCommAdaptersBinder();
|
||||
|
||||
configureWatchdogExtension();
|
||||
}
|
||||
|
||||
private void configureKernelServicesDependencies() {
|
||||
bind(StandardPlantModelService.class).in(Singleton.class);
|
||||
bind(PlantModelService.class).to(StandardPlantModelService.class);
|
||||
bind(InternalPlantModelService.class).to(StandardPlantModelService.class);
|
||||
|
||||
bind(StandardTransportOrderService.class).in(Singleton.class);
|
||||
bind(TransportOrderService.class).to(StandardTransportOrderService.class);
|
||||
bind(InternalTransportOrderService.class).to(StandardTransportOrderService.class);
|
||||
|
||||
bind(StandardVehicleService.class).in(Singleton.class);
|
||||
bind(VehicleService.class).to(StandardVehicleService.class);
|
||||
bind(InternalVehicleService.class).to(StandardVehicleService.class);
|
||||
|
||||
bind(StandardTCSObjectService.class).in(Singleton.class);
|
||||
bind(TCSObjectService.class).to(StandardTCSObjectService.class);
|
||||
|
||||
bind(StandardNotificationService.class).in(Singleton.class);
|
||||
bind(NotificationService.class).to(StandardNotificationService.class);
|
||||
|
||||
bind(StandardRouterService.class).in(Singleton.class);
|
||||
bind(RouterService.class).to(StandardRouterService.class);
|
||||
|
||||
bind(StandardDispatcherService.class).in(Singleton.class);
|
||||
bind(DispatcherService.class).to(StandardDispatcherService.class);
|
||||
|
||||
bind(StandardQueryService.class).in(Singleton.class);
|
||||
bind(QueryService.class).to(StandardQueryService.class);
|
||||
bind(InternalQueryService.class).to(StandardQueryService.class);
|
||||
|
||||
bind(StandardPeripheralService.class).in(Singleton.class);
|
||||
bind(PeripheralService.class).to(StandardPeripheralService.class);
|
||||
bind(InternalPeripheralService.class).to(StandardPeripheralService.class);
|
||||
|
||||
bind(StandardPeripheralJobService.class).in(Singleton.class);
|
||||
bind(PeripheralJobService.class).to(StandardPeripheralJobService.class);
|
||||
bind(InternalPeripheralJobService.class).to(StandardPeripheralJobService.class);
|
||||
|
||||
bind(StandardPeripheralDispatcherService.class).in(Singleton.class);
|
||||
bind(PeripheralDispatcherService.class).to(StandardPeripheralDispatcherService.class);
|
||||
}
|
||||
|
||||
private void configureVehicleControllers() {
|
||||
install(new FactoryModuleBuilder().build(VehicleControllerFactory.class));
|
||||
install(new FactoryModuleBuilder().build(VehicleControllerComponentsFactory.class));
|
||||
|
||||
bind(DefaultVehicleControllerPool.class)
|
||||
.in(Singleton.class);
|
||||
bind(VehicleControllerPool.class)
|
||||
.to(DefaultVehicleControllerPool.class);
|
||||
bind(LocalVehicleControllerPool.class)
|
||||
.to(DefaultVehicleControllerPool.class);
|
||||
}
|
||||
|
||||
private void configurePeripheralControllers() {
|
||||
install(new FactoryModuleBuilder().build(PeripheralControllerFactory.class));
|
||||
|
||||
bind(DefaultPeripheralControllerPool.class)
|
||||
.in(Singleton.class);
|
||||
bind(PeripheralControllerPool.class)
|
||||
.to(DefaultPeripheralControllerPool.class);
|
||||
bind(LocalPeripheralControllerPool.class)
|
||||
.to(DefaultPeripheralControllerPool.class);
|
||||
}
|
||||
|
||||
private void configurePersistence() {
|
||||
bind(ModelPersister.class).to(XMLFileModelPersister.class);
|
||||
}
|
||||
|
||||
private void configureEventHub() {
|
||||
EventBus newEventBus = new SimpleEventBus();
|
||||
bind(EventHandler.class)
|
||||
.annotatedWith(ApplicationEventBus.class)
|
||||
.toInstance(newEventBus);
|
||||
bind(org.opentcs.util.event.EventSource.class)
|
||||
.annotatedWith(ApplicationEventBus.class)
|
||||
.toInstance(newEventBus);
|
||||
bind(EventBus.class)
|
||||
.annotatedWith(ApplicationEventBus.class)
|
||||
.toInstance(newEventBus);
|
||||
}
|
||||
|
||||
private void configureKernelStatesDependencies() {
|
||||
// A map for KernelState instances to be provided at runtime.
|
||||
MapBinder<Kernel.State, KernelState> stateMapBinder
|
||||
= MapBinder.newMapBinder(binder(), Kernel.State.class, KernelState.class);
|
||||
stateMapBinder.addBinding(Kernel.State.SHUTDOWN).to(KernelStateShutdown.class);
|
||||
stateMapBinder.addBinding(Kernel.State.MODELLING).to(KernelStateModelling.class);
|
||||
stateMapBinder.addBinding(Kernel.State.OPERATING).to(KernelStateOperating.class);
|
||||
|
||||
bind(OrderPoolConfiguration.class)
|
||||
.toInstance(
|
||||
getConfigBindingProvider().get(
|
||||
OrderPoolConfiguration.PREFIX,
|
||||
OrderPoolConfiguration.class
|
||||
)
|
||||
);
|
||||
|
||||
bind(CreationTimeThreshold.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
transportOrderCleanupApprovalBinder();
|
||||
orderSequenceCleanupApprovalBinder();
|
||||
peripheralJobCleanupApprovalBinder();
|
||||
}
|
||||
|
||||
private void configureKernelStarterDependencies() {
|
||||
bind(KernelApplicationConfiguration.class)
|
||||
.toInstance(
|
||||
getConfigBindingProvider().get(
|
||||
KernelApplicationConfiguration.PREFIX,
|
||||
KernelApplicationConfiguration.class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void configureSslParameters() {
|
||||
SslConfiguration configuration
|
||||
= getConfigBindingProvider().get(
|
||||
SslConfiguration.PREFIX,
|
||||
SslConfiguration.class
|
||||
);
|
||||
SslParameterSet sslParamSet = new SslParameterSet(
|
||||
SslParameterSet.DEFAULT_KEYSTORE_TYPE,
|
||||
new File(configuration.keystoreFile()),
|
||||
configuration.keystorePassword(),
|
||||
new File(configuration.truststoreFile()),
|
||||
configuration.truststorePassword()
|
||||
);
|
||||
bind(SslParameterSet.class).toInstance(sslParamSet);
|
||||
}
|
||||
|
||||
private void configureKernelExecutor() {
|
||||
ScheduledExecutorService executor
|
||||
= new LoggingScheduledThreadPoolExecutor(
|
||||
1,
|
||||
runnable -> {
|
||||
Thread thread = new Thread(runnable, "kernelExecutor");
|
||||
thread.setUncaughtExceptionHandler(new UncaughtExceptionLogger(false));
|
||||
return thread;
|
||||
}
|
||||
);
|
||||
bind(ScheduledExecutorService.class)
|
||||
.annotatedWith(KernelExecutor.class)
|
||||
.toInstance(executor);
|
||||
bind(ExecutorService.class)
|
||||
.annotatedWith(KernelExecutor.class)
|
||||
.toInstance(executor);
|
||||
bind(Executor.class)
|
||||
.annotatedWith(KernelExecutor.class)
|
||||
.toInstance(executor);
|
||||
}
|
||||
|
||||
private void configureWatchdogExtension() {
|
||||
extensionsBinderOperating().addBinding()
|
||||
.to(Watchdog.class)
|
||||
.in(Singleton.class);
|
||||
|
||||
bind(WatchdogConfiguration.class)
|
||||
.toInstance(
|
||||
getConfigBindingProvider().get(
|
||||
WatchdogConfiguration.PREFIX,
|
||||
WatchdogConfiguration.class
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.util.Modules;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import org.opentcs.configuration.ConfigurationBindingProvider;
|
||||
import org.opentcs.configuration.gestalt.GestaltConfigurationBindingProvider;
|
||||
import org.opentcs.customizations.kernel.KernelInjectionModule;
|
||||
import org.opentcs.strategies.basic.dispatching.DefaultDispatcherModule;
|
||||
import org.opentcs.strategies.basic.peripherals.dispatching.DefaultPeripheralJobDispatcherModule;
|
||||
import org.opentcs.strategies.basic.routing.DefaultRouterModule;
|
||||
import org.opentcs.strategies.basic.scheduling.DefaultSchedulerModule;
|
||||
import org.opentcs.util.Environment;
|
||||
import org.opentcs.util.logging.UncaughtExceptionLogger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The kernel process's default entry point.
|
||||
*/
|
||||
public class RunKernel {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RunKernel.class);
|
||||
|
||||
/**
|
||||
* Prevents external instantiation.
|
||||
*/
|
||||
private RunKernel() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the system and starts the openTCS kernel including modules.
|
||||
*
|
||||
* @param args The command line arguments.
|
||||
* @throws Exception If there was a problem starting the kernel.
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
throws Exception {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(false));
|
||||
|
||||
Environment.logSystemInfo();
|
||||
|
||||
LOG.debug("Setting up openTCS kernel {}...", Environment.getBaselineVersion());
|
||||
Injector injector = Guice.createInjector(customConfigurationModule());
|
||||
injector.getInstance(KernelStarter.class).startKernel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a Guice module containing the custom configuration for the kernel
|
||||
* application, including additions and overrides by the user.
|
||||
*
|
||||
* @return The custom configuration module.
|
||||
*/
|
||||
private static Module customConfigurationModule() {
|
||||
List<KernelInjectionModule> defaultModules
|
||||
= Arrays.asList(
|
||||
new DefaultKernelInjectionModule(),
|
||||
new DefaultDispatcherModule(),
|
||||
new DefaultRouterModule(),
|
||||
new DefaultSchedulerModule(),
|
||||
new DefaultPeripheralJobDispatcherModule()
|
||||
);
|
||||
|
||||
ConfigurationBindingProvider bindingProvider = configurationBindingProvider();
|
||||
for (KernelInjectionModule defaultModule : defaultModules) {
|
||||
defaultModule.setConfigBindingProvider(bindingProvider);
|
||||
}
|
||||
|
||||
return Modules.override(defaultModules)
|
||||
.with(findRegisteredModules(bindingProvider));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns all Guice modules registered via ServiceLoader.
|
||||
*
|
||||
* @return The registered/found modules.
|
||||
*/
|
||||
private static List<KernelInjectionModule> findRegisteredModules(
|
||||
ConfigurationBindingProvider bindingProvider
|
||||
) {
|
||||
List<KernelInjectionModule> registeredModules = new ArrayList<>();
|
||||
for (KernelInjectionModule module : ServiceLoader.load(KernelInjectionModule.class)) {
|
||||
LOG.info(
|
||||
"Integrating injection module {} (source: {})",
|
||||
module.getClass().getName(),
|
||||
module.getClass().getProtectionDomain().getCodeSource()
|
||||
);
|
||||
module.setConfigBindingProvider(bindingProvider);
|
||||
registeredModules.add(module);
|
||||
}
|
||||
return registeredModules;
|
||||
}
|
||||
|
||||
private static ConfigurationBindingProvider configurationBindingProvider() {
|
||||
String chosenProvider = System.getProperty("opentcs.configuration.provider", "gestalt");
|
||||
switch (chosenProvider) {
|
||||
case "gestalt":
|
||||
default:
|
||||
LOG.info("Using gestalt as the configuration provider.");
|
||||
return gestaltConfigurationBindingProvider();
|
||||
}
|
||||
}
|
||||
|
||||
private static ConfigurationBindingProvider gestaltConfigurationBindingProvider() {
|
||||
return new GestaltConfigurationBindingProvider(
|
||||
Paths.get(
|
||||
System.getProperty("opentcs.base", "."),
|
||||
"config",
|
||||
"opentcs-kernel-defaults-baseline.properties"
|
||||
)
|
||||
.toAbsolutePath(),
|
||||
Paths.get(
|
||||
System.getProperty("opentcs.base", "."),
|
||||
"config",
|
||||
"opentcs-kernel-defaults-custom.properties"
|
||||
)
|
||||
.toAbsolutePath(),
|
||||
Paths.get(
|
||||
System.getProperty("opentcs.home", "."),
|
||||
"config",
|
||||
"opentcs-kernel.properties"
|
||||
)
|
||||
.toAbsolutePath()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Shuts down a running kernel via its administration interface.
|
||||
*/
|
||||
public class ShutdownKernel {
|
||||
|
||||
private ShutdownKernel() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Java main.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length > 2) {
|
||||
System.err.println("ShutdownKernel [<host>] [<port>]");
|
||||
return;
|
||||
}
|
||||
|
||||
String hostName = args.length > 0 ? args[0] : "localhost";
|
||||
int port = args.length > 1 ? Integer.parseInt(args[1]) : 55001;
|
||||
|
||||
try {
|
||||
URL url = new URI("http://" + hostName + ":" + port + "/v1/kernel").toURL();
|
||||
System.err.println("Calling to " + url + "...");
|
||||
HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
|
||||
httpCon.setRequestMethod("DELETE");
|
||||
httpCon.connect();
|
||||
httpCon.getInputStream();
|
||||
}
|
||||
catch (IOException | URISyntaxException exc) {
|
||||
System.err.println("Exception accessing admin interface:");
|
||||
exc.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
@@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import org.opentcs.configuration.ConfigurationEntry;
|
||||
import org.opentcs.configuration.ConfigurationPrefix;
|
||||
|
||||
/**
|
||||
* Provides common kernel configuration entries.
|
||||
*/
|
||||
@ConfigurationPrefix(KernelApplicationConfiguration.PREFIX)
|
||||
public interface KernelApplicationConfiguration {
|
||||
|
||||
/**
|
||||
* This configuration's prefix.
|
||||
*/
|
||||
String PREFIX = "kernelapp";
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether to automatically enable drivers on startup.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
|
||||
orderKey = "1_startup_0"
|
||||
)
|
||||
boolean autoEnableDriversOnStartup();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether to automatically enable peripheral drivers on startup.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
|
||||
orderKey = "1_startup_1"
|
||||
)
|
||||
boolean autoEnablePeripheralDriversOnStartup();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether to implicitly save the model when leaving modelling state.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
|
||||
orderKey = "2_autosave"
|
||||
)
|
||||
boolean saveModelOnTerminateModelling();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether to implicitly save the model when leaving operating state.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL,
|
||||
orderKey = "2_autosave"
|
||||
)
|
||||
boolean saveModelOnTerminateOperating();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether to implicitly update the router's topology when a path is (un)locked.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
|
||||
orderKey = "3_topologyUpdate"
|
||||
)
|
||||
boolean updateRoutingTopologyOnPathLockChange();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether vehicles should be rerouted immediately on topology changes.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
|
||||
orderKey = "4_reroute_1"
|
||||
)
|
||||
boolean rerouteOnRoutingTopologyUpdate();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Boolean",
|
||||
description = "Whether vehicles should be rerouted as soon as they finish a drive order.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
|
||||
orderKey = "4_reroute_2"
|
||||
)
|
||||
boolean rerouteOnDriveOrderFinished();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "String",
|
||||
description = {
|
||||
"The type of how vehicle resources (i.e., paths, points and locations allocated by "
|
||||
+ "vehicles) are managed.",
|
||||
"Possible values:",
|
||||
"LENGTH_IGNORED: Resources are _always_ released up to (excluding) a vehicle's current"
|
||||
+ "position. This type can be useful when you primarily want to utilize vehicle "
|
||||
+ "envelopes for traffic management.",
|
||||
"""
|
||||
LENGTH_RESPECTED: Only resources that are no longer "covered" by a vehicle (according
|
||||
to the length of the vehicle and the length of the paths behind it) are released. This
|
||||
is the "classic" way resources were managed before vehicle envelopes were introduced.
|
||||
"""
|
||||
},
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
|
||||
orderKey = "5_resource_management_1"
|
||||
)
|
||||
VehicleResourceManagementType vehicleResourceManagementType();
|
||||
|
||||
/**
|
||||
* Defines the different types of how vehicle resources (i.e., paths, points and locations
|
||||
* allocated by vehicles) are managed.
|
||||
*/
|
||||
enum VehicleResourceManagementType {
|
||||
/**
|
||||
* When releasing resources, the length of a vehicle is ignored.
|
||||
* <p>
|
||||
* Resources are <em>always</em> released up to (excluding) a vehicle's current position.
|
||||
* </p>
|
||||
* <p>
|
||||
* This type can be useful when you primarily want to utilize vehicle envelopes for traffic
|
||||
* management.
|
||||
* </p>
|
||||
*/
|
||||
LENGTH_IGNORED,
|
||||
/**
|
||||
* When releasing resources, the length of a vehicle is respected.
|
||||
* <p>
|
||||
* Only resources that are no longer "covered" by a vehicle (according to the length of the
|
||||
* vehicle and the length of the paths behind it) are released. This is the "classic" way
|
||||
* resources were managed before vehicle envelopes were introduced.
|
||||
* </p>
|
||||
*/
|
||||
LENGTH_RESPECTED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.access.LocalKernel;
|
||||
import org.opentcs.components.kernel.KernelExtension;
|
||||
import org.opentcs.components.kernel.services.InternalPlantModelService;
|
||||
import org.opentcs.customizations.kernel.ActiveInAllModes;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Initializes an openTCS kernel instance.
|
||||
*/
|
||||
public class KernelStarter {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KernelStarter.class);
|
||||
/**
|
||||
* The kernel we're working with.
|
||||
*/
|
||||
private final LocalKernel kernel;
|
||||
/**
|
||||
* The plant model service.
|
||||
*/
|
||||
private final InternalPlantModelService plantModelService;
|
||||
/**
|
||||
* The kernel extensions to be registered.
|
||||
*/
|
||||
private final Set<KernelExtension> extensions;
|
||||
/**
|
||||
* The kernel's executor service.
|
||||
*/
|
||||
private final ScheduledExecutorService kernelExecutor;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param kernel The kernel we're working with.
|
||||
* @param plantModelService The plant model service.
|
||||
* @param extensions The kernel extensions to be registered.
|
||||
* @param kernelExecutor The kernel's executor service.
|
||||
*/
|
||||
@Inject
|
||||
protected KernelStarter(
|
||||
LocalKernel kernel,
|
||||
InternalPlantModelService plantModelService,
|
||||
@ActiveInAllModes
|
||||
Set<KernelExtension> extensions,
|
||||
@KernelExecutor
|
||||
ScheduledExecutorService kernelExecutor
|
||||
) {
|
||||
this.kernel = requireNonNull(kernel, "kernel");
|
||||
this.plantModelService = requireNonNull(plantModelService, "plantModelService");
|
||||
this.extensions = requireNonNull(extensions, "extensions");
|
||||
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the system and starts the openTCS kernel including modules.
|
||||
*
|
||||
* @throws IOException If there was a problem loading model data.
|
||||
*/
|
||||
public void startKernel()
|
||||
throws IOException {
|
||||
kernelExecutor.submit(() -> {
|
||||
// Register kernel extensions.
|
||||
for (KernelExtension extension : extensions) {
|
||||
kernel.addKernelExtension(extension);
|
||||
}
|
||||
|
||||
// Start local kernel.
|
||||
kernel.initialize();
|
||||
LOG.debug("Kernel initialized.");
|
||||
|
||||
plantModelService.loadPlantModel();
|
||||
|
||||
kernel.setState(Kernel.State.OPERATING);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import org.opentcs.access.Kernel.State;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
|
||||
/**
|
||||
* The abstract base class for classes that implement state specific kernel
|
||||
* behaviour.
|
||||
*/
|
||||
public abstract class KernelState
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The model facade to the object pool.
|
||||
*/
|
||||
private final PlantModelManager plantModelManager;
|
||||
/**
|
||||
* The persister loading and storing model data.
|
||||
*/
|
||||
private final ModelPersister modelPersister;
|
||||
|
||||
/**
|
||||
* Creates a new state.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param modelPersister The model persister to be used.
|
||||
*/
|
||||
public KernelState(
|
||||
Object globalSyncObject,
|
||||
PlantModelManager plantModelManager,
|
||||
ModelPersister modelPersister
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.plantModelManager = requireNonNull(plantModelManager, "plantModelManager");
|
||||
this.modelPersister = requireNonNull(modelPersister, "modelPersister");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state.
|
||||
*
|
||||
* @return The current state.
|
||||
*/
|
||||
public abstract State getState();
|
||||
|
||||
protected Object getGlobalSyncObject() {
|
||||
return globalSyncObject;
|
||||
}
|
||||
|
||||
protected ModelPersister getModelPersister() {
|
||||
return modelPersister;
|
||||
}
|
||||
|
||||
protected PlantModelManager getPlantModelManager() {
|
||||
return plantModelManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.components.kernel.KernelExtension;
|
||||
import org.opentcs.customizations.kernel.ActiveInModellingMode;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the standard openTCS kernel in modelling mode.
|
||||
*/
|
||||
public class KernelStateModelling
|
||||
extends
|
||||
KernelStateOnline {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KernelStateModelling.class);
|
||||
/**
|
||||
* This kernel state's local extensions.
|
||||
*/
|
||||
private final Set<KernelExtension> extensions;
|
||||
/**
|
||||
* This instance's <em>initialized</em> flag.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param modelPersister The model persister to be used.
|
||||
* @param configuration This class's configuration.
|
||||
* @param extensions The kernel extensions to be used.
|
||||
*/
|
||||
@Inject
|
||||
public KernelStateModelling(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
PlantModelManager plantModelManager,
|
||||
ModelPersister modelPersister,
|
||||
KernelApplicationConfiguration configuration,
|
||||
@ActiveInModellingMode
|
||||
Set<KernelExtension> extensions
|
||||
) {
|
||||
super(
|
||||
globalSyncObject,
|
||||
plantModelManager,
|
||||
modelPersister,
|
||||
configuration.saveModelOnTerminateModelling()
|
||||
);
|
||||
this.extensions = requireNonNull(extensions, "extensions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Already initialized");
|
||||
}
|
||||
LOG.debug("Initializing modelling state...");
|
||||
|
||||
// Start kernel extensions.
|
||||
for (KernelExtension extension : extensions) {
|
||||
LOG.debug("Initializing kernel extension '{}'...", extension);
|
||||
extension.initialize();
|
||||
}
|
||||
LOG.debug("Finished initializing kernel extensions.");
|
||||
|
||||
initialized = true;
|
||||
|
||||
LOG.debug("Modelling state initialized.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!initialized) {
|
||||
throw new IllegalStateException("Not initialized, cannot terminate");
|
||||
}
|
||||
LOG.debug("Terminating modelling state...");
|
||||
super.terminate();
|
||||
|
||||
// Terminate everything that may still use resources.
|
||||
for (KernelExtension extension : extensions) {
|
||||
LOG.debug("Terminating kernel extension '{}'...", extension);
|
||||
extension.terminate();
|
||||
}
|
||||
LOG.debug("Terminated kernel extensions.");
|
||||
|
||||
initialized = false;
|
||||
|
||||
LOG.debug("Modelling state terminated.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kernel.State getState() {
|
||||
return Kernel.State.MODELLING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
|
||||
/**
|
||||
* The base class for the kernel's online states.
|
||||
*/
|
||||
public abstract class KernelStateOnline
|
||||
extends
|
||||
KernelState {
|
||||
|
||||
/**
|
||||
* Whether to save the model when this state is terminated.
|
||||
*/
|
||||
private final boolean saveModelOnTerminate;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param modelPersister The model persister to be used.
|
||||
* @param saveModelOnTerminate Whether to save the model when this state is terminated.
|
||||
*/
|
||||
public KernelStateOnline(
|
||||
Object globalSyncObject,
|
||||
PlantModelManager plantModelManager,
|
||||
ModelPersister modelPersister,
|
||||
boolean saveModelOnTerminate
|
||||
) {
|
||||
super(globalSyncObject, plantModelManager, modelPersister);
|
||||
this.saveModelOnTerminate = saveModelOnTerminate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (saveModelOnTerminate) {
|
||||
savePlantModel();
|
||||
}
|
||||
}
|
||||
|
||||
private void savePlantModel()
|
||||
throws IllegalStateException {
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
getModelPersister().saveModel(getPlantModelManager().createPlantModelCreationTO());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.components.kernel.Dispatcher;
|
||||
import org.opentcs.components.kernel.KernelExtension;
|
||||
import org.opentcs.components.kernel.PeripheralJobDispatcher;
|
||||
import org.opentcs.components.kernel.Router;
|
||||
import org.opentcs.components.kernel.Scheduler;
|
||||
import org.opentcs.components.kernel.services.InternalVehicleService;
|
||||
import org.opentcs.customizations.kernel.ActiveInOperatingMode;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.kernel.extensions.controlcenter.vehicles.AttachmentManager;
|
||||
import org.opentcs.kernel.peripherals.LocalPeripheralControllerPool;
|
||||
import org.opentcs.kernel.peripherals.PeripheralAttachmentManager;
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.vehicles.LocalVehicleControllerPool;
|
||||
import org.opentcs.kernel.workingset.PeripheralJobPoolManager;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
import org.opentcs.kernel.workingset.TransportOrderPoolManager;
|
||||
import org.opentcs.kernel.workingset.WorkingSetCleanupTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the standard openTCS kernel in normal operation.
|
||||
*/
|
||||
public class KernelStateOperating
|
||||
extends
|
||||
KernelStateOnline {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KernelStateOperating.class);
|
||||
/**
|
||||
* The order pool manager.
|
||||
*/
|
||||
private final TransportOrderPoolManager orderPoolManager;
|
||||
/**
|
||||
* The job pool manager.
|
||||
*/
|
||||
private final PeripheralJobPoolManager jobPoolManager;
|
||||
/**
|
||||
* This kernel's router.
|
||||
*/
|
||||
private final Router router;
|
||||
/**
|
||||
* This kernel's scheduler.
|
||||
*/
|
||||
private final Scheduler scheduler;
|
||||
/**
|
||||
* This kernel's dispatcher.
|
||||
*/
|
||||
private final Dispatcher dispatcher;
|
||||
/**
|
||||
* This kernel's peripheral job dispatcher.
|
||||
*/
|
||||
private final PeripheralJobDispatcher peripheralJobDispatcher;
|
||||
/**
|
||||
* A pool of vehicle controllers.
|
||||
*/
|
||||
private final LocalVehicleControllerPool vehicleControllerPool;
|
||||
/**
|
||||
* A pool of peripheral controllers.
|
||||
*/
|
||||
private final LocalPeripheralControllerPool peripheralControllerPool;
|
||||
/**
|
||||
* The kernel's executor.
|
||||
*/
|
||||
private final ScheduledExecutorService kernelExecutor;
|
||||
/**
|
||||
* A task for periodically getting rid of old orders, order sequences and peripheral jobs.
|
||||
*/
|
||||
private final WorkingSetCleanupTask workingSetCleanupTask;
|
||||
/**
|
||||
* This kernel state's local extensions.
|
||||
*/
|
||||
private final Set<KernelExtension> extensions;
|
||||
/**
|
||||
* The kernel's attachment manager.
|
||||
*/
|
||||
private final AttachmentManager attachmentManager;
|
||||
/**
|
||||
* The kernel's peripheral attachment manager.
|
||||
*/
|
||||
private final PeripheralAttachmentManager peripheralAttachmentManager;
|
||||
/**
|
||||
* The vehicle service.
|
||||
*/
|
||||
private final InternalVehicleService vehicleService;
|
||||
/**
|
||||
* Listens to path lock events and updates the routing topology.
|
||||
*/
|
||||
private final PathLockEventListener pathLockListener;
|
||||
/**
|
||||
* Triggers dispatching of vehicles and transport orders on certain events.
|
||||
*/
|
||||
private final VehicleDispatchTrigger vehicleDispatchTrigger;
|
||||
/**
|
||||
* A handle for the cleaner task.
|
||||
*/
|
||||
private ScheduledFuture<?> cleanerTaskFuture;
|
||||
/**
|
||||
* This instance's <em>initialized</em> flag.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject kernel threads' global synchronization object.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param orderPoolManager The order pool manager to be used.
|
||||
* @param jobPoolManager The job pool manager to be used.
|
||||
* @param modelPersister The model persister to be used.
|
||||
* @param configuration This class's configuration.
|
||||
* @param router The router to be used.
|
||||
* @param scheduler The scheduler to be used.
|
||||
* @param dispatcher The dispatcher to be used.
|
||||
* @param peripheralJobDispatcher The peripheral job dispatcher to be used.
|
||||
* @param controllerPool The vehicle controller pool to be used.
|
||||
* @param peripheralControllerPool The peripheral controller pool to be used.
|
||||
* @param kernelExecutor The kernel executer to be used.
|
||||
* @param workingSetCleanupTask The workingset cleanup task to be used.
|
||||
* @param extensions The kernel extensions to load.
|
||||
* @param attachmentManager The attachment manager to be used.
|
||||
* @param peripheralAttachmentManager The peripheral attachment manager to be used.
|
||||
* @param vehicleService The vehicle service to be used.
|
||||
* @param pathLockListener Listens to path lock events and updates the routing topology.
|
||||
* @param vehicleDispatchTrigger Triggers dispatching of vehicles and transport orders on certain
|
||||
* events.
|
||||
*/
|
||||
@Inject
|
||||
public KernelStateOperating(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
PlantModelManager plantModelManager,
|
||||
TransportOrderPoolManager orderPoolManager,
|
||||
PeripheralJobPoolManager jobPoolManager,
|
||||
ModelPersister modelPersister,
|
||||
KernelApplicationConfiguration configuration,
|
||||
Router router,
|
||||
Scheduler scheduler,
|
||||
Dispatcher dispatcher,
|
||||
PeripheralJobDispatcher peripheralJobDispatcher,
|
||||
LocalVehicleControllerPool controllerPool,
|
||||
LocalPeripheralControllerPool peripheralControllerPool,
|
||||
@KernelExecutor
|
||||
ScheduledExecutorService kernelExecutor,
|
||||
WorkingSetCleanupTask workingSetCleanupTask,
|
||||
@ActiveInOperatingMode
|
||||
Set<KernelExtension> extensions,
|
||||
AttachmentManager attachmentManager,
|
||||
PeripheralAttachmentManager peripheralAttachmentManager,
|
||||
InternalVehicleService vehicleService,
|
||||
PathLockEventListener pathLockListener,
|
||||
VehicleDispatchTrigger vehicleDispatchTrigger
|
||||
) {
|
||||
super(
|
||||
globalSyncObject,
|
||||
plantModelManager,
|
||||
modelPersister,
|
||||
configuration.saveModelOnTerminateOperating()
|
||||
);
|
||||
this.orderPoolManager = requireNonNull(orderPoolManager, "orderPoolManager");
|
||||
this.jobPoolManager = requireNonNull(jobPoolManager, "jobPoolManager");
|
||||
this.router = requireNonNull(router, "router");
|
||||
this.scheduler = requireNonNull(scheduler, "scheduler");
|
||||
this.dispatcher = requireNonNull(dispatcher, "dispatcher");
|
||||
this.peripheralJobDispatcher = requireNonNull(
|
||||
peripheralJobDispatcher,
|
||||
"peripheralJobDispatcher"
|
||||
);
|
||||
this.vehicleControllerPool = requireNonNull(controllerPool, "controllerPool");
|
||||
this.peripheralControllerPool = requireNonNull(
|
||||
peripheralControllerPool,
|
||||
"peripheralControllerPool"
|
||||
);
|
||||
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
|
||||
this.workingSetCleanupTask = requireNonNull(workingSetCleanupTask, "workingSetCleanupTask");
|
||||
this.extensions = requireNonNull(extensions, "extensions");
|
||||
this.attachmentManager = requireNonNull(attachmentManager, "attachmentManager");
|
||||
this.peripheralAttachmentManager = requireNonNull(
|
||||
peripheralAttachmentManager,
|
||||
"peripheralAttachmentManager"
|
||||
);
|
||||
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
|
||||
this.pathLockListener = requireNonNull(pathLockListener, "pathLockListener");
|
||||
this.vehicleDispatchTrigger = requireNonNull(vehicleDispatchTrigger, "vehicleDispatchTrigger");
|
||||
}
|
||||
|
||||
// Implementation of interface Kernel starts here.
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (initialized) {
|
||||
LOG.debug("Already initialized.");
|
||||
return;
|
||||
}
|
||||
LOG.debug("Initializing operating state...");
|
||||
|
||||
// Reset vehicle states to ensure vehicles are not dispatchable initially.
|
||||
for (Vehicle curVehicle : vehicleService.fetchObjects(Vehicle.class)) {
|
||||
vehicleService.updateVehicleProcState(curVehicle.getReference(), Vehicle.ProcState.IDLE);
|
||||
vehicleService.updateVehicleIntegrationLevel(
|
||||
curVehicle.getReference(),
|
||||
Vehicle.IntegrationLevel.TO_BE_RESPECTED
|
||||
);
|
||||
vehicleService.updateVehicleState(curVehicle.getReference(), Vehicle.State.UNKNOWN);
|
||||
vehicleService.updateVehicleTransportOrder(curVehicle.getReference(), null);
|
||||
vehicleService.updateVehicleOrderSequence(curVehicle.getReference(), null);
|
||||
}
|
||||
|
||||
LOG.debug("Initializing scheduler '{}'...", scheduler);
|
||||
scheduler.initialize();
|
||||
LOG.debug("Initializing router '{}'...", router);
|
||||
router.initialize();
|
||||
LOG.debug("Initializing dispatcher '{}'...", dispatcher);
|
||||
dispatcher.initialize();
|
||||
LOG.debug("Initializing peripheral job dispatcher '{}'...", peripheralJobDispatcher);
|
||||
peripheralJobDispatcher.initialize();
|
||||
LOG.debug("Initializing vehicle controller pool '{}'...", vehicleControllerPool);
|
||||
vehicleControllerPool.initialize();
|
||||
LOG.debug("Initializing peripheral controller pool '{}'...", peripheralControllerPool);
|
||||
peripheralControllerPool.initialize();
|
||||
LOG.debug("Initializing attachment manager '{}'...", attachmentManager);
|
||||
attachmentManager.initialize();
|
||||
LOG.debug("Initializing peripheral attachment manager '{}'...", peripheralAttachmentManager);
|
||||
peripheralAttachmentManager.initialize();
|
||||
|
||||
pathLockListener.initialize();
|
||||
vehicleDispatchTrigger.initialize();
|
||||
|
||||
// Start a task for cleaning up old orders periodically.
|
||||
cleanerTaskFuture = kernelExecutor.scheduleAtFixedRate(
|
||||
workingSetCleanupTask,
|
||||
workingSetCleanupTask.getSweepInterval(),
|
||||
workingSetCleanupTask.getSweepInterval(),
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
|
||||
// Start kernel extensions.
|
||||
for (KernelExtension extension : extensions) {
|
||||
LOG.debug("Initializing kernel extension '{}'...", extension);
|
||||
extension.initialize();
|
||||
}
|
||||
LOG.debug("Finished initializing kernel extensions.");
|
||||
|
||||
initialized = true;
|
||||
|
||||
LOG.debug("Operating state initialized.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!initialized) {
|
||||
LOG.debug("Not initialized.");
|
||||
return;
|
||||
}
|
||||
LOG.debug("Terminating operating state...");
|
||||
super.terminate();
|
||||
|
||||
// Terminate everything that may still use resources.
|
||||
for (KernelExtension extension : extensions) {
|
||||
LOG.debug("Terminating kernel extension '{}'...", extension);
|
||||
extension.terminate();
|
||||
}
|
||||
LOG.debug("Terminated kernel extensions.");
|
||||
|
||||
// No need to clean up any more - it's all going to be cleaned up very soon.
|
||||
cleanerTaskFuture.cancel(false);
|
||||
cleanerTaskFuture = null;
|
||||
|
||||
// Terminate strategies.
|
||||
LOG.debug("Terminating peripheral job dispatcher '{}'...", peripheralJobDispatcher);
|
||||
peripheralJobDispatcher.terminate();
|
||||
LOG.debug("Terminating dispatcher '{}'...", dispatcher);
|
||||
dispatcher.terminate();
|
||||
LOG.debug("Terminating router '{}'...", router);
|
||||
router.terminate();
|
||||
LOG.debug("Terminating scheduler '{}'...", scheduler);
|
||||
scheduler.terminate();
|
||||
LOG.debug("Terminating peripheral controller pool '{}'...", peripheralControllerPool);
|
||||
peripheralControllerPool.terminate();
|
||||
LOG.debug("Terminating vehicle controller pool '{}'...", vehicleControllerPool);
|
||||
vehicleControllerPool.terminate();
|
||||
LOG.debug("Terminating attachment manager '{}'...", attachmentManager);
|
||||
attachmentManager.terminate();
|
||||
LOG.debug("Terminating peripheral attachment manager '{}'...", peripheralAttachmentManager);
|
||||
peripheralAttachmentManager.terminate();
|
||||
|
||||
pathLockListener.terminate();
|
||||
vehicleDispatchTrigger.terminate();
|
||||
|
||||
// Grant communication adapters etc. some time to settle things.
|
||||
Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Ensure that vehicles do not reference orders any more.
|
||||
for (Vehicle curVehicle : vehicleService.fetchObjects(Vehicle.class)) {
|
||||
vehicleService.updateVehicleProcState(curVehicle.getReference(), Vehicle.ProcState.IDLE);
|
||||
vehicleService.updateVehicleIntegrationLevel(
|
||||
curVehicle.getReference(),
|
||||
Vehicle.IntegrationLevel.TO_BE_RESPECTED
|
||||
);
|
||||
vehicleService.updateVehicleState(curVehicle.getReference(), Vehicle.State.UNKNOWN);
|
||||
vehicleService.updateVehicleTransportOrder(curVehicle.getReference(), null);
|
||||
vehicleService.updateVehicleOrderSequence(curVehicle.getReference(), null);
|
||||
}
|
||||
|
||||
// Remove all orders and order sequences from the pool.
|
||||
orderPoolManager.clear();
|
||||
// Remove all peripheral jobs from the pool.
|
||||
jobPoolManager.clear();
|
||||
|
||||
initialized = false;
|
||||
|
||||
LOG.debug("Operating state terminated.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kernel.State getState() {
|
||||
return Kernel.State.OPERATING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
|
||||
/**
|
||||
* This class implements the standard openTCS kernel when it's shut down.
|
||||
*/
|
||||
public class KernelStateShutdown
|
||||
extends
|
||||
KernelState {
|
||||
|
||||
/**
|
||||
* Indicates whether this component is enabled.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new StandardKernelShutdownState.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param modelPersister The model persister to be used.
|
||||
*/
|
||||
@Inject
|
||||
public KernelStateShutdown(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
PlantModelManager plantModelManager,
|
||||
ModelPersister modelPersister
|
||||
) {
|
||||
super(
|
||||
globalSyncObject,
|
||||
plantModelManager,
|
||||
modelPersister
|
||||
);
|
||||
}
|
||||
|
||||
// Methods that HAVE to be implemented/overridden start here.
|
||||
@Override
|
||||
public void initialize() {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kernel.State getState() {
|
||||
return Kernel.State.SHUTDOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import org.opentcs.configuration.ConfigurationEntry;
|
||||
import org.opentcs.configuration.ConfigurationPrefix;
|
||||
import org.opentcs.kernel.workingset.WorkingSetCleanupTask;
|
||||
|
||||
/**
|
||||
* Provides methods to configure the {@link WorkingSetCleanupTask}.
|
||||
*/
|
||||
@ConfigurationPrefix(OrderPoolConfiguration.PREFIX)
|
||||
public interface OrderPoolConfiguration {
|
||||
|
||||
/**
|
||||
* This configuration's prefix.
|
||||
*/
|
||||
String PREFIX = "orderpool";
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Long",
|
||||
description = "The interval between sweeps (in ms).",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_NEW_PLANT_MODEL
|
||||
)
|
||||
long sweepInterval();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Integer",
|
||||
description = "The minimum age of orders or peripheral jobs to remove in a sweep (in ms).",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY
|
||||
)
|
||||
int sweepAge();
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.DispatcherService;
|
||||
import org.opentcs.components.kernel.services.RouterService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.TCSObjectEvent;
|
||||
import org.opentcs.data.model.Path;
|
||||
import org.opentcs.data.order.ReroutingType;
|
||||
import org.opentcs.util.event.EventBus;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
|
||||
/**
|
||||
* Listens to path lock events and updates the routing topology.
|
||||
*/
|
||||
public class PathLockEventListener
|
||||
implements
|
||||
EventHandler,
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* The kernel configuration.
|
||||
*/
|
||||
private final KernelApplicationConfiguration configuration;
|
||||
/**
|
||||
* The router service.
|
||||
*/
|
||||
private final RouterService routerService;
|
||||
/**
|
||||
* The event bus.
|
||||
*/
|
||||
private final EventBus eventBus;
|
||||
/**
|
||||
* The dispatcher.
|
||||
*/
|
||||
private final DispatcherService dispatcher;
|
||||
/**
|
||||
* This instance's <em>initialized</em> flag.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param configuration The kernel configuration.
|
||||
* @param routerService The router service.
|
||||
* @param eventBus The event bus.
|
||||
* @param dispatcher The dispatcher.
|
||||
*/
|
||||
@Inject
|
||||
public PathLockEventListener(
|
||||
KernelApplicationConfiguration configuration,
|
||||
RouterService routerService,
|
||||
@ApplicationEventBus
|
||||
EventBus eventBus,
|
||||
DispatcherService dispatcher
|
||||
) {
|
||||
this.configuration = requireNonNull(configuration, "configuration");
|
||||
this.routerService = requireNonNull(routerService, "routerService");
|
||||
this.eventBus = requireNonNull(eventBus, "eventBus");
|
||||
this.dispatcher = requireNonNull(dispatcher, "dispatcher");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
eventBus.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
initialized = false;
|
||||
eventBus.unsubscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Object eventObject) {
|
||||
if (!configuration.updateRoutingTopologyOnPathLockChange()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(eventObject instanceof TCSObjectEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TCSObjectEvent event = (TCSObjectEvent) eventObject;
|
||||
if (hasPathLockChanged(event)) {
|
||||
routerService.updateRoutingTopology(
|
||||
Set.of(((Path) event.getCurrentObjectState()).getReference())
|
||||
);
|
||||
|
||||
if (configuration.rerouteOnRoutingTopologyUpdate()) {
|
||||
dispatcher.rerouteAll(ReroutingType.REGULAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasPathLockChanged(TCSObjectEvent event) {
|
||||
return event.getCurrentObjectState() instanceof Path
|
||||
&& event.getType() == TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
&& ((Path) event.getCurrentObjectState()).isLocked() != ((Path) event
|
||||
.getPreviousObjectState()).isLocked();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import org.opentcs.configuration.ConfigurationEntry;
|
||||
import org.opentcs.configuration.ConfigurationPrefix;
|
||||
|
||||
/**
|
||||
* Provides methods to configure the ssl connection.
|
||||
*/
|
||||
@ConfigurationPrefix(SslConfiguration.PREFIX)
|
||||
public interface SslConfiguration {
|
||||
|
||||
/**
|
||||
* This configuration's prefix.
|
||||
*/
|
||||
String PREFIX = "ssl";
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "String",
|
||||
description = {"The file url of the keystore."},
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
|
||||
orderKey = "0_connection_0"
|
||||
)
|
||||
String keystoreFile();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "String",
|
||||
description = {"The password for the keystore."},
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
|
||||
orderKey = "0_connection_1"
|
||||
)
|
||||
String keystorePassword();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "String",
|
||||
description = {"The file url of the truststore."},
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
|
||||
orderKey = "0_connection_2"
|
||||
)
|
||||
String truststoreFile();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "String",
|
||||
description = {"The password for the truststore."},
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
|
||||
orderKey = "0_connection_3"
|
||||
)
|
||||
String truststorePassword();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
import com.google.inject.Provider;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.access.Kernel.State;
|
||||
import org.opentcs.access.KernelStateTransitionEvent;
|
||||
import org.opentcs.access.LocalKernel;
|
||||
import org.opentcs.components.kernel.KernelExtension;
|
||||
import org.opentcs.components.kernel.services.NotificationService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.opentcs.data.notification.UserNotification;
|
||||
import org.opentcs.util.event.EventBus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the standard openTCS kernel.
|
||||
*/
|
||||
public class StandardKernel
|
||||
implements
|
||||
LocalKernel,
|
||||
Runnable {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StandardKernel.class);
|
||||
/**
|
||||
* A map to state providers used when switching kernel states.
|
||||
*/
|
||||
private final Map<Kernel.State, Provider<KernelState>> stateProviders;
|
||||
/**
|
||||
* The application's event bus.
|
||||
*/
|
||||
private final EventBus eventBus;
|
||||
/**
|
||||
* Our executor.
|
||||
*/
|
||||
private final ScheduledExecutorService kernelExecutor;
|
||||
/**
|
||||
* This kernel's order receivers.
|
||||
*/
|
||||
private final Set<KernelExtension> kernelExtensions = new HashSet<>();
|
||||
/**
|
||||
* Functions as a barrier for the kernel's {@link #run() run()} method.
|
||||
*/
|
||||
private final Semaphore terminationSemaphore = new Semaphore(0);
|
||||
/**
|
||||
* The notification service.
|
||||
*/
|
||||
private final NotificationService notificationService;
|
||||
/**
|
||||
* This kernel's <em>initialized</em> flag.
|
||||
*/
|
||||
private volatile boolean initialized;
|
||||
/**
|
||||
* The kernel implementing the actual functionality for the current mode.
|
||||
*/
|
||||
private KernelState kernelState;
|
||||
|
||||
/**
|
||||
* Creates a new kernel.
|
||||
*
|
||||
* @param eventBus The central event bus to be used.
|
||||
* @param kernelExecutor An executor for this kernel's tasks.
|
||||
* @param stateProviders The state map to be used.
|
||||
* @param notificationService The notification service to be used.
|
||||
*/
|
||||
@Inject
|
||||
public StandardKernel(
|
||||
@ApplicationEventBus
|
||||
EventBus eventBus,
|
||||
@KernelExecutor
|
||||
ScheduledExecutorService kernelExecutor,
|
||||
Map<Kernel.State, Provider<KernelState>> stateProviders,
|
||||
NotificationService notificationService
|
||||
) {
|
||||
this.eventBus = requireNonNull(eventBus, "eventBus");
|
||||
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
|
||||
this.stateProviders = requireNonNull(stateProviders, "stateProviders");
|
||||
this.notificationService = requireNonNull(notificationService, "notificationService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
// First of all, start all kernel extensions that are already registered.
|
||||
for (KernelExtension extension : kernelExtensions) {
|
||||
LOG.debug("Initializing extension: {}", extension.getClass().getName());
|
||||
extension.initialize();
|
||||
}
|
||||
|
||||
// Initial state is modelling.
|
||||
setState(State.MODELLING);
|
||||
|
||||
initialized = true;
|
||||
LOG.debug("Starting kernel thread");
|
||||
Thread kernelThread = new Thread(this, "kernelThread");
|
||||
kernelThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
// Note that the actual shutdown of extensions should happen when the kernel
|
||||
// thread (see run()) finishes, not here.
|
||||
// Set the terminated flag and wake up this kernel's thread for termination.
|
||||
initialized = false;
|
||||
terminationSemaphore.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Wait until terminated.
|
||||
terminationSemaphore.acquireUninterruptibly();
|
||||
LOG.info("Terminating...");
|
||||
// Sleep a bit so clients have some time to receive an event for the
|
||||
// SHUTDOWN state change and shut down gracefully themselves.
|
||||
Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
|
||||
// Shut down all kernel extensions.
|
||||
LOG.debug("Shutting down kernel extensions...");
|
||||
for (KernelExtension extension : kernelExtensions) {
|
||||
extension.terminate();
|
||||
}
|
||||
kernelExecutor.shutdown();
|
||||
LOG.info("Kernel thread finished.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return kernelState.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(State newState)
|
||||
throws IllegalArgumentException {
|
||||
requireNonNull(newState, "newState");
|
||||
|
||||
final Kernel.State oldState;
|
||||
if (kernelState != null) {
|
||||
oldState = kernelState.getState();
|
||||
// Don't do anything if the new state is the same as the current one.
|
||||
if (oldState == newState) {
|
||||
LOG.debug("Already in state '{}', doing nothing.", newState.name());
|
||||
return;
|
||||
}
|
||||
// Let listeners know we're in transition.
|
||||
emitStateEvent(oldState, newState, false);
|
||||
// Terminate previous state.
|
||||
kernelState.terminate();
|
||||
}
|
||||
else {
|
||||
oldState = null;
|
||||
}
|
||||
LOG.info("Switching kernel to state '{}'", newState.name());
|
||||
switch (newState) {
|
||||
case SHUTDOWN:
|
||||
kernelState = stateProviders.get(Kernel.State.SHUTDOWN).get();
|
||||
kernelState.initialize();
|
||||
terminate();
|
||||
break;
|
||||
case MODELLING:
|
||||
kernelState = stateProviders.get(Kernel.State.MODELLING).get();
|
||||
kernelState.initialize();
|
||||
break;
|
||||
case OPERATING:
|
||||
kernelState = stateProviders.get(Kernel.State.OPERATING).get();
|
||||
kernelState.initialize();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected state: " + newState);
|
||||
}
|
||||
emitStateEvent(oldState, newState, true);
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
"Kernel is now in state " + newState,
|
||||
UserNotification.Level.INFORMATIONAL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addKernelExtension(final KernelExtension newExtension) {
|
||||
requireNonNull(newExtension, "newExtension");
|
||||
|
||||
kernelExtensions.add(newExtension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeKernelExtension(final KernelExtension rmExtension) {
|
||||
requireNonNull(rmExtension, "rmExtension");
|
||||
|
||||
kernelExtensions.remove(rmExtension);
|
||||
}
|
||||
|
||||
// Methods not declared in any interface start here.
|
||||
/**
|
||||
* Generates an event for a state change.
|
||||
*
|
||||
* @param leftState The state left.
|
||||
* @param enteredState The state entered.
|
||||
* @param transitionFinished Whether the transition is finished or not.
|
||||
*/
|
||||
private void emitStateEvent(State leftState, State enteredState, boolean transitionFinished) {
|
||||
eventBus.onEvent(new KernelStateTransitionEvent(leftState, enteredState, transitionFinished));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.concurrent.Executor;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.DispatcherService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.opentcs.data.TCSObjectEvent;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.order.ReroutingType;
|
||||
import org.opentcs.util.event.EventBus;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Triggers dispatching of vehicles and transport orders on certain events.
|
||||
*/
|
||||
public class VehicleDispatchTrigger
|
||||
implements
|
||||
EventHandler,
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VehicleDispatchTrigger.class);
|
||||
/**
|
||||
* The dispatcher in use.
|
||||
*/
|
||||
private final DispatcherService dispatcher;
|
||||
/**
|
||||
* The event bus.
|
||||
*/
|
||||
private final EventBus eventBus;
|
||||
/**
|
||||
* The app configuration.
|
||||
*/
|
||||
private final KernelApplicationConfiguration configuration;
|
||||
/**
|
||||
* The kernel executor.
|
||||
*/
|
||||
private final Executor kernelExecutor;
|
||||
/**
|
||||
* This instance's <em>initialized</em> flag.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param kernelExecutor The kernel executor to use.
|
||||
* @param eventBus The event bus.
|
||||
* @param dispatcher The dispatcher in use.
|
||||
* @param configuration The application configuration.
|
||||
*/
|
||||
@Inject
|
||||
public VehicleDispatchTrigger(
|
||||
@KernelExecutor
|
||||
Executor kernelExecutor,
|
||||
@ApplicationEventBus
|
||||
EventBus eventBus,
|
||||
DispatcherService dispatcher,
|
||||
KernelApplicationConfiguration configuration
|
||||
) {
|
||||
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
|
||||
this.eventBus = requireNonNull(eventBus, "eventBus");
|
||||
this.dispatcher = requireNonNull(dispatcher, "dispatcher");
|
||||
this.configuration = requireNonNull(configuration, "configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
eventBus.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
initialized = false;
|
||||
eventBus.unsubscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Object event) {
|
||||
if (!(event instanceof TCSObjectEvent)) {
|
||||
return;
|
||||
}
|
||||
TCSObjectEvent objectEvent = (TCSObjectEvent) event;
|
||||
if (objectEvent.getCurrentOrPreviousObjectState() instanceof Vehicle) {
|
||||
checkVehicleChange(
|
||||
(Vehicle) objectEvent.getPreviousObjectState(),
|
||||
(Vehicle) objectEvent.getCurrentObjectState()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVehicleChange(Vehicle oldVehicle, Vehicle newVehicle) {
|
||||
if (driveOrderFinished(oldVehicle, newVehicle)
|
||||
&& configuration.rerouteOnDriveOrderFinished()) {
|
||||
LOG.debug("Rerouting vehicle {}...", newVehicle);
|
||||
dispatcher.reroute(newVehicle.getReference(), ReroutingType.REGULAR);
|
||||
}
|
||||
|
||||
if ((newVehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_UTILIZED
|
||||
|| newVehicle.getIntegrationLevel() == Vehicle.IntegrationLevel.TO_BE_RESPECTED)
|
||||
&& (idleAndEnergyLevelChanged(oldVehicle, newVehicle)
|
||||
|| awaitingNextOrder(oldVehicle, newVehicle)
|
||||
|| orderSequenceNulled(oldVehicle, newVehicle))) {
|
||||
LOG.debug("Dispatching for {}...", newVehicle);
|
||||
// Dispatching may result in changes to the vehicle and thus trigger this code, which would
|
||||
// then lead to a second dispatch run before the first one is completed. To avoid this, we
|
||||
// ensure dispatching is done at some later point by scheduling it to be executed on the
|
||||
// kernel executor (so it does not trigger itself in a loop).
|
||||
kernelExecutor.execute(() -> dispatcher.dispatch());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean idleAndEnergyLevelChanged(Vehicle oldVehicle, Vehicle newVehicle) {
|
||||
// If the vehicle is idle and its energy level changed, we may want to order it to recharge.
|
||||
return newVehicle.hasProcState(Vehicle.ProcState.IDLE)
|
||||
&& (newVehicle.hasState(Vehicle.State.IDLE) || newVehicle.hasState(Vehicle.State.CHARGING))
|
||||
&& newVehicle.getEnergyLevel() != oldVehicle.getEnergyLevel();
|
||||
}
|
||||
|
||||
private boolean awaitingNextOrder(Vehicle oldVehicle, Vehicle newVehicle) {
|
||||
// If the vehicle's processing state changed to IDLE or AWAITING_ORDER, it is waiting for
|
||||
// its next order, so look for one.
|
||||
return newVehicle.getProcState() != oldVehicle.getProcState()
|
||||
&& (newVehicle.hasProcState(Vehicle.ProcState.IDLE)
|
||||
|| newVehicle.hasProcState(Vehicle.ProcState.AWAITING_ORDER));
|
||||
}
|
||||
|
||||
private boolean orderSequenceNulled(Vehicle oldVehicle, Vehicle newVehicle) {
|
||||
// If the vehicle's order sequence reference has become null, the vehicle has just been released
|
||||
// from an order sequence, so we may look for new assignments.
|
||||
return newVehicle.getOrderSequence() == null
|
||||
&& oldVehicle.getOrderSequence() != null;
|
||||
}
|
||||
|
||||
private boolean driveOrderFinished(Vehicle oldVehicle, Vehicle newVehicle) {
|
||||
// If the vehicle's processing state changes to AWAITING_ORDER, it has finished its current
|
||||
// drive order.
|
||||
return newVehicle.getProcState() != oldVehicle.getProcState()
|
||||
&& newVehicle.hasProcState(Vehicle.ProcState.AWAITING_ORDER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.controlcenter.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterFactory;
|
||||
import org.opentcs.drivers.vehicle.management.ProcessModelEvent;
|
||||
import org.opentcs.drivers.vehicle.management.VehicleAttachmentEvent;
|
||||
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
|
||||
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
|
||||
import org.opentcs.kernel.KernelApplicationConfiguration;
|
||||
import org.opentcs.kernel.vehicles.LocalVehicleControllerPool;
|
||||
import org.opentcs.kernel.vehicles.VehicleCommAdapterRegistry;
|
||||
import org.opentcs.util.Assertions;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages attachment and detachment of communication adapters to vehicles.
|
||||
*/
|
||||
public class AttachmentManager
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AttachmentManager.class);
|
||||
/**
|
||||
* This class's configuration.
|
||||
*/
|
||||
private final KernelApplicationConfiguration configuration;
|
||||
/**
|
||||
* The object service.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
/**
|
||||
* The vehicle controller pool.
|
||||
*/
|
||||
private final LocalVehicleControllerPool controllerPool;
|
||||
/**
|
||||
* The comm adapter registry.
|
||||
*/
|
||||
private final VehicleCommAdapterRegistry commAdapterRegistry;
|
||||
/**
|
||||
* The pool of vehicle entries.
|
||||
*/
|
||||
private final VehicleEntryPool vehicleEntryPool;
|
||||
/**
|
||||
* The handler to send events to.
|
||||
*/
|
||||
private final EventHandler eventHandler;
|
||||
/**
|
||||
* The pool of comm adapter attachments.
|
||||
*/
|
||||
private final Map<String, VehicleAttachmentInformation> attachmentPool = new HashMap<>();
|
||||
/**
|
||||
* Whether the attachment manager is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The object service.
|
||||
* @param controllerPool The vehicle controller pool.
|
||||
* @param commAdapterRegistry The comm adapter registry.
|
||||
* @param vehicleEntryPool The pool of vehicle entries.
|
||||
* @param eventHandler The handler to send events to.
|
||||
* @param configuration This class's configuration.
|
||||
*/
|
||||
@Inject
|
||||
public AttachmentManager(
|
||||
@Nonnull
|
||||
TCSObjectService objectService,
|
||||
@Nonnull
|
||||
LocalVehicleControllerPool controllerPool,
|
||||
@Nonnull
|
||||
VehicleCommAdapterRegistry commAdapterRegistry,
|
||||
@Nonnull
|
||||
VehicleEntryPool vehicleEntryPool,
|
||||
@Nonnull
|
||||
@ApplicationEventBus
|
||||
EventHandler eventHandler,
|
||||
@Nonnull
|
||||
KernelApplicationConfiguration configuration
|
||||
) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
this.controllerPool = requireNonNull(controllerPool, "controllerPool");
|
||||
this.commAdapterRegistry = requireNonNull(commAdapterRegistry, "commAdapterRegistry");
|
||||
this.vehicleEntryPool = requireNonNull(vehicleEntryPool, "vehicleEntryPool");
|
||||
this.eventHandler = requireNonNull(eventHandler, "eventHandler");
|
||||
this.configuration = requireNonNull(configuration, "configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
LOG.debug("Already initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
commAdapterRegistry.initialize();
|
||||
vehicleEntryPool.initialize();
|
||||
|
||||
initAttachmentPool();
|
||||
|
||||
autoAttachAllAdapters();
|
||||
|
||||
if (configuration.autoEnableDriversOnStartup()) {
|
||||
autoEnableAllAdapters();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
LOG.debug("Not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Detach all attached drivers to clean up.
|
||||
detachAllAdapters();
|
||||
vehicleEntryPool.terminate();
|
||||
commAdapterRegistry.terminate();
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches an adapter to a vehicle.
|
||||
*
|
||||
* @param vehicleName The vehicle name.
|
||||
* @param factory The factory that provides the adapter to be assigned.
|
||||
*/
|
||||
public void attachAdapterToVehicle(
|
||||
@Nonnull
|
||||
String vehicleName,
|
||||
@Nonnull
|
||||
VehicleCommAdapterFactory factory
|
||||
) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
requireNonNull(factory, "factory");
|
||||
|
||||
LOG.info(
|
||||
"Attaching vehicle comm adapter: '{}' -- '{}'...",
|
||||
vehicleName,
|
||||
factory.getClass().getName()
|
||||
);
|
||||
|
||||
VehicleEntry vehicleEntry = vehicleEntryPool.getEntryFor(vehicleName);
|
||||
if (vehicleEntry == null) {
|
||||
LOG.warn(
|
||||
"No vehicle entry found for '{}'. Entries: {}",
|
||||
vehicleName,
|
||||
vehicleEntryPool
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
VehicleCommAdapter commAdapter = factory.getAdapterFor(vehicleEntry.getVehicle());
|
||||
if (commAdapter == null) {
|
||||
LOG.warn(
|
||||
"Factory {} did not provide adapter for vehicle {}, ignoring.",
|
||||
factory,
|
||||
vehicleEntry.getVehicle().getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform a cleanup for the old adapter.
|
||||
disableAndTerminateAdapter(vehicleEntry);
|
||||
controllerPool.detachVehicleController(vehicleEntry.getVehicle().getName());
|
||||
|
||||
commAdapter.initialize();
|
||||
controllerPool.attachVehicleController(vehicleEntry.getVehicle().getName(), commAdapter);
|
||||
|
||||
vehicleEntry.setCommAdapterFactory(factory);
|
||||
vehicleEntry.setCommAdapter(commAdapter);
|
||||
vehicleEntry.setProcessModel(commAdapter.getProcessModel());
|
||||
|
||||
objectService.updateObjectProperty(
|
||||
vehicleEntry.getVehicle().getReference(),
|
||||
Vehicle.PREFERRED_ADAPTER,
|
||||
factory.getClass().getName()
|
||||
);
|
||||
|
||||
updateAttachmentInformation(vehicleEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically attach a vehicle to an adapter.
|
||||
*
|
||||
* @param vehicleName The name of the vehicle to attach.
|
||||
*/
|
||||
public void autoAttachAdapterToVehicle(
|
||||
@Nonnull
|
||||
String vehicleName
|
||||
) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
|
||||
VehicleEntry vehicleEntry = vehicleEntryPool.getEntryFor(vehicleName);
|
||||
if (vehicleEntry == null) {
|
||||
LOG.warn(
|
||||
"No vehicle entry found for '{}'. Entries: {}",
|
||||
vehicleName,
|
||||
vehicleEntryPool
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not auto-attach if there is already a comm adapter attached to the vehicle.
|
||||
if (vehicleEntry.getCommAdapter() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vehicle vehicle = getUpdatedVehicle(vehicleEntry.getVehicle());
|
||||
String prefAdapter = vehicle.getProperties().get(Vehicle.PREFERRED_ADAPTER);
|
||||
VehicleCommAdapterFactory factory = findFactoryWithName(prefAdapter);
|
||||
if (factory != null && factory.providesAdapterFor(vehicle)) {
|
||||
attachAdapterToVehicle(vehicleName, factory);
|
||||
}
|
||||
else {
|
||||
if (!Strings.isNullOrEmpty(prefAdapter)) {
|
||||
LOG.warn(
|
||||
"Couldn't attach preferred adapter {} to {}. Attaching first available adapter.",
|
||||
prefAdapter,
|
||||
vehicleEntry.getVehicle().getName()
|
||||
);
|
||||
}
|
||||
List<VehicleCommAdapterFactory> factories
|
||||
= commAdapterRegistry.findFactoriesFor(vehicleEntry.getVehicle());
|
||||
if (!factories.isEmpty()) {
|
||||
attachAdapterToVehicle(vehicleName, factories.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically attach all vehicles to an adapter.
|
||||
*/
|
||||
public void autoAttachAllAdapters() {
|
||||
vehicleEntryPool.getEntries().forEach((vehicleName, entry) -> {
|
||||
autoAttachAdapterToVehicle(vehicleName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachment information for a vehicle.
|
||||
*
|
||||
* @param vehicleName The name of the vehicle.
|
||||
* @return Attachment information about the vehicle.
|
||||
*/
|
||||
public VehicleAttachmentInformation getAttachmentInformation(String vehicleName) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
Assertions.checkArgument(
|
||||
attachmentPool.get(vehicleName) != null,
|
||||
"No attachment information for vehicle %s",
|
||||
vehicleName
|
||||
);
|
||||
|
||||
return attachmentPool.get(vehicleName);
|
||||
}
|
||||
|
||||
public Map<String, VehicleAttachmentInformation> getAttachmentPool() {
|
||||
return attachmentPool;
|
||||
}
|
||||
|
||||
private void disableAndTerminateAdapter(
|
||||
@Nonnull
|
||||
VehicleEntry vehicleEntry
|
||||
) {
|
||||
requireNonNull(vehicleEntry, "vehicleEntry");
|
||||
|
||||
VehicleCommAdapter commAdapter = vehicleEntry.getCommAdapter();
|
||||
if (commAdapter != null) {
|
||||
commAdapter.disable();
|
||||
// Let the adapter know cleanup time is here.
|
||||
commAdapter.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
private void initAttachmentPool() {
|
||||
vehicleEntryPool.getEntries().forEach((vehicleName, entry) -> {
|
||||
List<VehicleCommAdapterDescription> availableCommAdapters
|
||||
= commAdapterRegistry.getFactories().stream()
|
||||
.filter(f -> f.providesAdapterFor(entry.getVehicle()))
|
||||
.map(f -> f.getDescription())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
attachmentPool.put(
|
||||
vehicleName,
|
||||
new VehicleAttachmentInformation(
|
||||
entry.getVehicle().getReference(),
|
||||
availableCommAdapters,
|
||||
new NullVehicleCommAdapterDescription()
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateAttachmentInformation(VehicleEntry entry) {
|
||||
String vehicleName = entry.getVehicleName();
|
||||
VehicleCommAdapterFactory factory = entry.getCommAdapterFactory();
|
||||
VehicleAttachmentInformation newAttachment = attachmentPool.get(vehicleName)
|
||||
.withAttachedCommAdapter(factory.getDescription());
|
||||
|
||||
attachmentPool.put(vehicleName, newAttachment);
|
||||
|
||||
eventHandler.onEvent(new VehicleAttachmentEvent(vehicleName, newAttachment));
|
||||
if (entry.getCommAdapter() == null) {
|
||||
// In case we are detached
|
||||
eventHandler.onEvent(new ProcessModelEvent(vehicleName, new VehicleProcessModelTO()));
|
||||
}
|
||||
else {
|
||||
eventHandler.onEvent(
|
||||
new ProcessModelEvent(
|
||||
vehicleName,
|
||||
entry.getCommAdapter()
|
||||
.createTransferableProcessModel()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fresh copy of a vehicle from the kernel.
|
||||
*
|
||||
* @param vehicle The old vehicle instance.
|
||||
* @return The fresh vehicle instance.
|
||||
*/
|
||||
private Vehicle getUpdatedVehicle(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return objectService.fetchObjects(Vehicle.class).stream()
|
||||
.filter(updatedVehicle -> Objects.equals(updatedVehicle.getName(), vehicle.getName()))
|
||||
.findFirst().orElse(vehicle);
|
||||
}
|
||||
|
||||
private void autoEnableAllAdapters() {
|
||||
vehicleEntryPool.getEntries().values().stream()
|
||||
.map(entry -> entry.getCommAdapter())
|
||||
.filter(adapter -> adapter != null)
|
||||
.filter(adapter -> !adapter.isEnabled())
|
||||
.forEach(adapter -> adapter.enable());
|
||||
}
|
||||
|
||||
private void detachAllAdapters() {
|
||||
LOG.debug("Detaching vehicle communication adapters...");
|
||||
vehicleEntryPool.getEntries().forEach((vehicleName, entry) -> {
|
||||
disableAndTerminateAdapter(entry);
|
||||
});
|
||||
LOG.debug("Detached vehicle communication adapters");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VehicleCommAdapterFactory findFactoryWithName(
|
||||
@Nullable
|
||||
String name
|
||||
) {
|
||||
return commAdapterRegistry.getFactories().stream()
|
||||
.filter(factory -> factory.getClass().getName().equals(name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.controlcenter.vehicles;
|
||||
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
|
||||
|
||||
/**
|
||||
* A {@link VehicleCommAdapterDescription} for no comm adapter.
|
||||
*/
|
||||
public class NullVehicleCommAdapterDescription
|
||||
extends
|
||||
VehicleCommAdapterDescription {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public NullVehicleCommAdapterDescription() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "-";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSimVehicleCommAdapter() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.controlcenter.vehicles;
|
||||
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterFactory;
|
||||
|
||||
/**
|
||||
* A Vehicle adapter factory that creates no vehicles adapters.
|
||||
*/
|
||||
public class NullVehicleCommAdapterFactory
|
||||
implements
|
||||
VehicleCommAdapterFactory {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public NullVehicleCommAdapterFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleCommAdapterDescription getDescription() {
|
||||
return new NullVehicleCommAdapterDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean providesAdapterFor(Vehicle vehicle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleCommAdapter getAdapterFor(Vehicle vehicle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.controlcenter.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterFactory;
|
||||
import org.opentcs.drivers.vehicle.VehicleProcessModel;
|
||||
|
||||
/**
|
||||
* Represents a {@link Vehicle} in the {@link VehicleEntryPool}.
|
||||
*/
|
||||
public class VehicleEntry
|
||||
implements
|
||||
PropertyChangeListener {
|
||||
|
||||
/**
|
||||
* The vehicle this entry represents.
|
||||
*/
|
||||
private final Vehicle vehicle;
|
||||
/**
|
||||
* Used for implementing property change events.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
|
||||
/**
|
||||
* The process model for the vehicle.
|
||||
*/
|
||||
private VehicleProcessModel processModel;
|
||||
/**
|
||||
* The comm adapter factory for this vehicle.
|
||||
*/
|
||||
private VehicleCommAdapterFactory commAdapterFactory = new NullVehicleCommAdapterFactory();
|
||||
/**
|
||||
* The comm adapter that is attached to this vehicle.
|
||||
*/
|
||||
private VehicleCommAdapter commAdapter;
|
||||
|
||||
/**
|
||||
* Creates a vehicle entry.
|
||||
*
|
||||
* @param vehicle The vehicle this entry represents.
|
||||
*/
|
||||
public VehicleEntry(Vehicle vehicle) {
|
||||
this.vehicle = requireNonNull(vehicle, "vehicle");
|
||||
this.processModel = new VehicleProcessModel(vehicle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (!(evt.getSource() instanceof VehicleProcessModel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pcs.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property change listener.
|
||||
*
|
||||
* @param listener The listener to add.
|
||||
*/
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property change listener.
|
||||
*
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vehicle that is represented by this entry.
|
||||
*
|
||||
* @return The vehicle that is represented by this entry.
|
||||
*/
|
||||
@Nonnull
|
||||
public Vehicle getVehicle() {
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the vehicle that is represented by this entry.
|
||||
*
|
||||
* @return The name of the vehicle that is represented by this entry.
|
||||
*/
|
||||
@Nonnull
|
||||
public String getVehicleName() {
|
||||
return vehicle.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the process model of the vehicle that is represented by this entry.
|
||||
*
|
||||
* @return The process model of the vehicle that is represented by this entry.
|
||||
*/
|
||||
@Nonnull
|
||||
public VehicleProcessModel getProcessModel() {
|
||||
return processModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the process model for the vehicle represented by this entry.
|
||||
*
|
||||
* @param processModel The new process model for the vehicle.
|
||||
*/
|
||||
public void setProcessModel(
|
||||
@Nonnull
|
||||
VehicleProcessModel processModel
|
||||
) {
|
||||
VehicleProcessModel oldProcessModel = this.processModel;
|
||||
this.processModel = requireNonNull(processModel, "processModel");
|
||||
|
||||
oldProcessModel.removePropertyChangeListener(this);
|
||||
processModel.addPropertyChangeListener(this);
|
||||
|
||||
pcs.firePropertyChange(Attribute.PROCESS_MODEL.name(), oldProcessModel, processModel);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public VehicleCommAdapterFactory getCommAdapterFactory() {
|
||||
return commAdapterFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comm adapter factory for this entry.
|
||||
*
|
||||
* @param commAdapterFactory The new comm adapter factory.
|
||||
*/
|
||||
public void setCommAdapterFactory(
|
||||
@Nonnull
|
||||
VehicleCommAdapterFactory commAdapterFactory
|
||||
) {
|
||||
VehicleCommAdapterFactory oldValue = this.commAdapterFactory;
|
||||
this.commAdapterFactory = commAdapterFactory;
|
||||
|
||||
pcs.firePropertyChange(Attribute.COMM_ADAPTER_FACTORY.name(), oldValue, commAdapterFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the comm adapter factory for this entry.
|
||||
*
|
||||
* @return The comm adapter factory for this entry
|
||||
*/
|
||||
@Nullable
|
||||
public VehicleCommAdapter getCommAdapter() {
|
||||
return commAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comm adapter for this entry.
|
||||
*
|
||||
* @param commAdapter The new comm adapter.
|
||||
*/
|
||||
public void setCommAdapter(
|
||||
@Nullable
|
||||
VehicleCommAdapter commAdapter
|
||||
) {
|
||||
VehicleCommAdapter oldValue = this.commAdapter;
|
||||
this.commAdapter = commAdapter;
|
||||
|
||||
pcs.firePropertyChange(Attribute.COMM_ADAPTER.name(), oldValue, commAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum elements used as notification arguments to specify which argument changed.
|
||||
*/
|
||||
public enum Attribute {
|
||||
/**
|
||||
* Indicates a change of the process model reference.
|
||||
*/
|
||||
PROCESS_MODEL,
|
||||
/**
|
||||
* Indicates a change of the comm adapter factory reference.
|
||||
*/
|
||||
COMM_ADAPTER_FACTORY,
|
||||
/**
|
||||
* Indicates a change of the comm adapter reference.
|
||||
*/
|
||||
COMM_ADAPTER
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.controlcenter.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides a pool of {@link VehicleEntry}s with an entry for every {@link Vehicle} object in the
|
||||
* kernel.
|
||||
*/
|
||||
public class VehicleEntryPool
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VehicleEntryPool.class);
|
||||
/**
|
||||
* The object service.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
/**
|
||||
* The entries of this pool.
|
||||
*/
|
||||
private final Map<String, VehicleEntry> entries = new TreeMap<>();
|
||||
/**
|
||||
* Whether the pool is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The object service.
|
||||
*/
|
||||
@Inject
|
||||
public VehicleEntryPool(
|
||||
@Nonnull
|
||||
TCSObjectService objectService
|
||||
) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
LOG.debug("Already initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
objectService.fetchObjects(Vehicle.class).stream()
|
||||
.forEach(vehicle -> entries.put(vehicle.getName(), new VehicleEntry(vehicle)));
|
||||
LOG.debug("Initialized vehicle entry pool: {}", entries);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
LOG.debug("Not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
entries.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all entries in the pool.
|
||||
*
|
||||
* @return Map of vehicle names to their vehicle entries.
|
||||
*/
|
||||
@Nonnull
|
||||
public Map<String, VehicleEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link VehicleEntry} for the given vehicle name.
|
||||
*
|
||||
* @param vehicleName The vehicle name to get the entry for.
|
||||
* @return the vehicle entry for the given vehicle name.
|
||||
*/
|
||||
@Nullable
|
||||
public VehicleEntry getEntryFor(
|
||||
@Nonnull
|
||||
String vehicleName
|
||||
) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
return entries.get(vehicleName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
/**
|
||||
* Classes of the kernel control center concerning vehicles/vehicle drivers.
|
||||
* The GUI parts of the driver framework.
|
||||
*/
|
||||
package org.opentcs.kernel.extensions.controlcenter.vehicles;
|
||||
@@ -0,0 +1,232 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.watchdog;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.data.model.Vehicle.IntegrationLevel.TO_BE_RESPECTED;
|
||||
import static org.opentcs.data.model.Vehicle.IntegrationLevel.TO_BE_UTILIZED;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.NotificationService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Block;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.notification.UserNotification;
|
||||
|
||||
/**
|
||||
* Periodically checks the occupancy status of single-vehicle blocks.
|
||||
*
|
||||
* This check will publish a user notification if a single-vehicle block is occupied by more than
|
||||
* one vehicle.
|
||||
* A single notification will be published when the violation is first detected.
|
||||
* A second notification will be published when the violation changed or is resolved.
|
||||
*
|
||||
* The exact rules for when a violation notification should be sent are:
|
||||
*
|
||||
* <ul>
|
||||
* <li> A violation should trigger a notification if a block is occupied by more than one vehicle
|
||||
* and the set of vehicles occupying the block is different to the one in the previous iteration.
|
||||
* The order of the occupants does not matter.
|
||||
* (V1, V2) is the same as (V2, V1).</li>
|
||||
* <li> If a block was previously occupied by more than one vehicle and is now occupied by one or no
|
||||
* vehicle, a notification about the resultion of the situation is sent.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* <ul>
|
||||
* <li> A block previously occupied by (V1, V2) that is now occupied by (V1, V2, V3) should
|
||||
* trigger a new violation notification.</li>
|
||||
* <li> A block previously occupied by (V1, V2) that is now occupied by (V1) should trigger a
|
||||
* resolution notification.</li>
|
||||
* <li> A block previously occupied by (V1, V2) that is now still occupied by (V1, V2) or (V2, V1)
|
||||
* should not trigger a new notification.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class BlockConsistencyCheck
|
||||
implements
|
||||
Runnable,
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* Notification source.
|
||||
*/
|
||||
private static final String NOTIFICATION_SOURCE = "Watchdog - Block consistency check";
|
||||
/**
|
||||
* Object service to access the model.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
/**
|
||||
* The service to send out user notifications.
|
||||
*/
|
||||
private final NotificationService notificationService;
|
||||
/**
|
||||
* The kernel executor.
|
||||
*/
|
||||
private final ScheduledExecutorService kernelExecutor;
|
||||
/**
|
||||
* The configuration.
|
||||
*/
|
||||
private final WatchdogConfiguration configuration;
|
||||
/**
|
||||
* Whether this check is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
/**
|
||||
* The Future created for the block check task.
|
||||
*/
|
||||
private ScheduledFuture<?> scheduledFuture;
|
||||
/**
|
||||
* Holds currently known block occupations.
|
||||
* Maps a block reference to a set of vehicles contained in that block.
|
||||
*/
|
||||
private Map<TCSResourceReference<Block>, Set<TCSObjectReference<Vehicle>>> occupations
|
||||
= new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param kernelExecutor The kernel executor.
|
||||
* @param objectService The object service.
|
||||
* @param notificationService The notification service.
|
||||
* @param configuration The watchdog configuration.
|
||||
*/
|
||||
@Inject
|
||||
public BlockConsistencyCheck(
|
||||
@KernelExecutor
|
||||
ScheduledExecutorService kernelExecutor,
|
||||
TCSObjectService objectService,
|
||||
NotificationService notificationService,
|
||||
WatchdogConfiguration configuration
|
||||
) {
|
||||
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
this.notificationService = requireNonNull(notificationService, "notificationService");
|
||||
this.configuration = requireNonNull(configuration, "configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduledFuture = kernelExecutor.scheduleAtFixedRate(
|
||||
this,
|
||||
configuration.blockConsistencyCheckInterval(),
|
||||
configuration.blockConsistencyCheckInterval(),
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduledFuture != null) {
|
||||
scheduledFuture.cancel(true);
|
||||
scheduledFuture = null;
|
||||
}
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Map<TCSResourceReference<Block>, Set<TCSObjectReference<Vehicle>>> currentOccupations
|
||||
= findCurrentOccupations();
|
||||
|
||||
// Find new violations.
|
||||
currentOccupations.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().size() > 1)
|
||||
.filter(entry -> {
|
||||
return !occupations.containsKey(entry.getKey())
|
||||
|| !occupations.get(entry.getKey()).equals(entry.getValue());
|
||||
})
|
||||
.forEach(entry -> {
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
NOTIFICATION_SOURCE,
|
||||
String.format(
|
||||
"Block %s is overfull. Occupied by vehicles: %s",
|
||||
entry.getKey().getName(),
|
||||
entry.getValue().stream()
|
||||
.map(vehicle -> vehicle.getName())
|
||||
.collect(Collectors.joining(", "))
|
||||
),
|
||||
UserNotification.Level.IMPORTANT
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// Find resolved violations
|
||||
occupations.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().size() > 1)
|
||||
.filter(entry -> {
|
||||
return !currentOccupations.containsKey(entry.getKey())
|
||||
|| currentOccupations.get(entry.getKey()).size() <= 1;
|
||||
})
|
||||
.forEach(entry -> {
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
NOTIFICATION_SOURCE,
|
||||
String.format("Block %s is not overfull any more.", entry.getKey().getName()),
|
||||
UserNotification.Level.IMPORTANT
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
occupations = currentOccupations;
|
||||
}
|
||||
|
||||
private Map<TCSResourceReference<Block>, Set<TCSObjectReference<Vehicle>>>
|
||||
findCurrentOccupations() {
|
||||
Map<TCSResourceReference<Block>, Set<TCSObjectReference<Vehicle>>> currentOccupations
|
||||
= new HashMap<>();
|
||||
|
||||
Set<Block> blocks = objectService.fetchObjects(Block.class);
|
||||
|
||||
objectService.fetchObjects(Vehicle.class)
|
||||
.stream()
|
||||
.filter(vehicle -> {
|
||||
return vehicle.getIntegrationLevel() == TO_BE_RESPECTED
|
||||
|| vehicle.getIntegrationLevel() == TO_BE_UTILIZED;
|
||||
})
|
||||
.filter(vehicle -> vehicle.getCurrentPosition() != null)
|
||||
.forEach(vehicle -> {
|
||||
Point currentPoint = objectService.fetchObject(Point.class, vehicle.getCurrentPosition());
|
||||
|
||||
blocks.stream()
|
||||
.filter(block -> block.getType() == Block.Type.SINGLE_VEHICLE_ONLY)
|
||||
.filter(block -> block.getMembers().contains(currentPoint.getReference()))
|
||||
.forEach(block -> {
|
||||
currentOccupations.putIfAbsent(block.getReference(), new HashSet<>());
|
||||
currentOccupations.get(block.getReference()).add(vehicle.getReference());
|
||||
});
|
||||
});
|
||||
|
||||
return currentOccupations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.watchdog;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.NotificationService;
|
||||
import org.opentcs.customizations.kernel.KernelExecutor;
|
||||
import org.opentcs.data.notification.UserNotification;
|
||||
import org.opentcs.kernel.extensions.watchdog.StrandedVehicles.VehicleSnapshot;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Checks for vehicles that are stranded for too long.
|
||||
* <p>
|
||||
* A vehicle is considered "stranded" if one of the following conditions applies and lasts longer
|
||||
* than a configurable time period (see
|
||||
* {@link WatchdogConfiguration#strandedVehicleDurationThreshold()}):
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>The vehicle is idle and at a position that is <em>not</em> a parking position.</li>
|
||||
* <li>The vehicle is idle and has a transport order assigned to it.</li>
|
||||
* </ul>
|
||||
* This check publishes a user notification when a vehicle is considered stranded and another user
|
||||
* notification once a vehicle is no longer considered stranded.
|
||||
*/
|
||||
public class StrandedVehicleCheck
|
||||
implements
|
||||
Runnable,
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StrandedVehicleCheck.class);
|
||||
/**
|
||||
* Source for notifications.
|
||||
*/
|
||||
private static final String NOTIFICATION_SOURCE = "Watchdog - Stranded vehicle check";
|
||||
/**
|
||||
* A formatter for timestamps.
|
||||
*/
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMATTER
|
||||
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
|
||||
/**
|
||||
* The service to send out user notifications.
|
||||
*/
|
||||
private final NotificationService notificationService;
|
||||
/**
|
||||
* The kernel executor.
|
||||
*/
|
||||
private final ScheduledExecutorService kernelExecutor;
|
||||
/**
|
||||
* The configuration.
|
||||
*/
|
||||
private final WatchdogConfiguration configuration;
|
||||
/**
|
||||
* Keeps track of stranded vehicles.
|
||||
*/
|
||||
private final StrandedVehicles stranded;
|
||||
/**
|
||||
* Whether this check is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
/**
|
||||
* The Future created for the block check task.
|
||||
*/
|
||||
private ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param kernelExecutor The kernel executor.
|
||||
* @param notificationService The notification service.
|
||||
* @param configuration The watchdog configuration.
|
||||
* @param stranded Keeps track of stranded vehicles.
|
||||
*/
|
||||
@Inject
|
||||
public StrandedVehicleCheck(
|
||||
@KernelExecutor
|
||||
ScheduledExecutorService kernelExecutor,
|
||||
NotificationService notificationService,
|
||||
WatchdogConfiguration configuration,
|
||||
StrandedVehicles stranded
|
||||
) {
|
||||
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
|
||||
this.notificationService = requireNonNull(notificationService, "notificationService");
|
||||
this.configuration = requireNonNull(configuration, "configuration");
|
||||
this.stranded = requireNonNull(stranded, "stranded");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduledFuture = kernelExecutor.scheduleAtFixedRate(
|
||||
this,
|
||||
configuration.strandedVehicleCheckInterval(),
|
||||
configuration.strandedVehicleCheckInterval(),
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
|
||||
stranded.initialize();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduledFuture != null) {
|
||||
scheduledFuture.cancel(true);
|
||||
scheduledFuture = null;
|
||||
}
|
||||
|
||||
stranded.terminate();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long strandedTimeThreshold = configuration.strandedVehicleDurationThreshold();
|
||||
|
||||
stranded.identifyStrandedVehicles(currentTime, strandedTimeThreshold);
|
||||
Set<VehicleSnapshot> newlyStrandedVehicles = stranded.newlyStrandedVehicles();
|
||||
Set<VehicleSnapshot> noLongerStrandedVehicles = stranded.noLongerStrandedVehicles();
|
||||
|
||||
newlyStrandedVehicles
|
||||
.forEach(vehicleSnapshot -> {
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
NOTIFICATION_SOURCE,
|
||||
String.format(
|
||||
"Vehicle '%s' is stranded since: %s",
|
||||
vehicleSnapshot.getVehicle().getName(),
|
||||
TIMESTAMP_FORMATTER.format(
|
||||
Instant.ofEpochMilli(vehicleSnapshot.getLastRelevantStateChange())
|
||||
)
|
||||
),
|
||||
UserNotification.Level.INFORMATIONAL
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
noLongerStrandedVehicles
|
||||
.forEach(vehicleSnapshot -> {
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
NOTIFICATION_SOURCE,
|
||||
String.format(
|
||||
"Vehicle '%s' is no longer stranded.",
|
||||
vehicleSnapshot.getVehicle().getName()
|
||||
),
|
||||
UserNotification.Level.INFORMATIONAL
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.watchdog;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class to find out stranded vehicles.
|
||||
*/
|
||||
public class StrandedVehicles
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StrandedVehicles.class);
|
||||
/**
|
||||
* Object service to access the model.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
/**
|
||||
* Provider to get the current time.
|
||||
*/
|
||||
private final TimeProvider timeProvider;
|
||||
/**
|
||||
* Map to store the current snapshot for each vehicle.
|
||||
*/
|
||||
private final Map<String, VehicleSnapshot> currentSnapshots = new HashMap<>();
|
||||
/**
|
||||
* Map to store the previous snapshot for each vehicle.
|
||||
*/
|
||||
private final Map<String, VehicleSnapshot> previousSnapshots = new HashMap<>();
|
||||
/**
|
||||
* Whether this instance is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The object service.
|
||||
* @param timeProvider Provider to get the current time.
|
||||
*/
|
||||
@Inject
|
||||
public StrandedVehicles(
|
||||
TCSObjectService objectService,
|
||||
TimeProvider timeProvider
|
||||
) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
this.timeProvider = requireNonNull(timeProvider, "timeProvider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long currentTime = timeProvider.getCurrentTime();
|
||||
objectService.fetchObjects(Vehicle.class).forEach(vehicle -> {
|
||||
VehicleSnapshot vehicleSnapshot = new VehicleSnapshot(vehicle);
|
||||
vehicleSnapshot.setLastRelevantStateChange(currentTime);
|
||||
currentSnapshots.put(vehicle.getName(), vehicleSnapshot);
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSnapshots.clear();
|
||||
previousSnapshots.clear();
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies stranded vehicles.
|
||||
*
|
||||
* @param currentTime The current time.
|
||||
* @param strandedDurationThreshold The duration that a vehicle must be in a stranded state to
|
||||
* actually be considered stranded.
|
||||
*/
|
||||
public void identifyStrandedVehicles(
|
||||
long currentTime,
|
||||
long strandedDurationThreshold
|
||||
) {
|
||||
LOG.debug("Identifying stranded vehicles...");
|
||||
previousSnapshots.clear();
|
||||
previousSnapshots.putAll(currentSnapshots);
|
||||
currentSnapshots.clear();
|
||||
|
||||
objectService.fetchObjects(Vehicle.class)
|
||||
.stream()
|
||||
.forEach(vehicle -> {
|
||||
VehicleSnapshot previousSnapshot = previousSnapshots.get(vehicle.getName());
|
||||
VehicleSnapshot currentSnapshot = new VehicleSnapshot(vehicle);
|
||||
|
||||
if (vehicle.getState() != Vehicle.State.IDLE
|
||||
|| relevantPropertiesChanged(previousSnapshot, currentSnapshot)) {
|
||||
currentSnapshot.setLastRelevantStateChange(currentTime);
|
||||
currentSnapshot.setStranded(false);
|
||||
}
|
||||
else if (isInStrandedState(vehicle)) {
|
||||
LOG.debug("Checking if vehicle '{}' is stranded long enough...", vehicle);
|
||||
long lastRelevantStateChange = previousSnapshot.getLastRelevantStateChange();
|
||||
currentSnapshot.setLastRelevantStateChange(lastRelevantStateChange);
|
||||
|
||||
long strandedDuration = currentTime - lastRelevantStateChange;
|
||||
currentSnapshot.setStranded(strandedDuration >= strandedDurationThreshold);
|
||||
}
|
||||
else {
|
||||
// The state of the vehicle has not effectively changed. Therefore, apply values from
|
||||
// the previous snapshot.
|
||||
currentSnapshot.setLastRelevantStateChange(
|
||||
previousSnapshot.getLastRelevantStateChange()
|
||||
);
|
||||
currentSnapshot.setStranded(previousSnapshot.isStranded());
|
||||
}
|
||||
|
||||
LOG.debug("Snapshot of vehicle '{}': {}", vehicle.getName(), currentSnapshot);
|
||||
currentSnapshots.put(vehicle.getName(), currentSnapshot);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns vehicles that are currently considered newly stranded (i.e., vehicles that were
|
||||
* previously not considered stranded, but are now considered stranded).
|
||||
*
|
||||
* @return The set of vehicles that are considered newly stranded.
|
||||
*/
|
||||
public Set<VehicleSnapshot> newlyStrandedVehicles() {
|
||||
return currentSnapshots.values().stream()
|
||||
.filter(
|
||||
vehicleSnapshot -> !previousSnapshots.get(vehicleSnapshot.getVehicle().getName())
|
||||
.isStranded()
|
||||
&& vehicleSnapshot.isStranded()
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns vehicles that are currently considered no longer stranded (i.e., vehicles that were
|
||||
* previously considered stranded, but are now not considered stranded).
|
||||
*
|
||||
* @return The set of vehicles that are no longer considered stranded.
|
||||
*/
|
||||
public Set<VehicleSnapshot> noLongerStrandedVehicles() {
|
||||
return currentSnapshots.values().stream()
|
||||
.filter(
|
||||
vehicleSnapshot -> previousSnapshots.get(vehicleSnapshot.getVehicle().getName())
|
||||
.isStranded()
|
||||
&& !vehicleSnapshot.isStranded()
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isInStrandedState(Vehicle vehicle) {
|
||||
return isIdleAtNoParkingPosition(vehicle) || isIdleWithTransportOrder(vehicle);
|
||||
}
|
||||
|
||||
private boolean isIdleAtNoParkingPosition(Vehicle vehicle) {
|
||||
return vehicle.hasState(Vehicle.State.IDLE)
|
||||
&& vehicle.getCurrentPosition() != null
|
||||
&& objectService.fetchObject(Point.class, vehicle.getCurrentPosition())
|
||||
.getType() != Point.Type.PARK_POSITION;
|
||||
}
|
||||
|
||||
private boolean isIdleWithTransportOrder(Vehicle vehicle) {
|
||||
return vehicle.hasState(Vehicle.State.IDLE)
|
||||
&& vehicle.getTransportOrder() != null;
|
||||
}
|
||||
|
||||
private boolean relevantPropertiesChanged(
|
||||
VehicleSnapshot previousSnapshot,
|
||||
VehicleSnapshot currentSnapshot
|
||||
) {
|
||||
return previousSnapshot.getLastState() != currentSnapshot.getLastState()
|
||||
|| !Objects.equals(previousSnapshot.getLastPosition(), currentSnapshot.getLastPosition())
|
||||
|| !Objects.equals(
|
||||
previousSnapshot.getVehicle().getTransportOrder(),
|
||||
currentSnapshot.getVehicle().getTransportOrder()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A snapshot of a vehicle's state with additional information on whether the given vehicle is
|
||||
* considered stranded and when the last state change (with regard to the "stranded" state)
|
||||
* occurred.
|
||||
*/
|
||||
public static class VehicleSnapshot {
|
||||
|
||||
private final Vehicle vehicle;
|
||||
private long lastRelevantStateChange;
|
||||
private boolean stranded;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param vehicle Vehicle to be stored.
|
||||
*/
|
||||
public VehicleSnapshot(Vehicle vehicle) {
|
||||
this.vehicle = requireNonNull(vehicle, "vehicle");
|
||||
}
|
||||
|
||||
public boolean isStranded() {
|
||||
return stranded;
|
||||
}
|
||||
|
||||
public void setStranded(boolean stranded) {
|
||||
this.stranded = stranded;
|
||||
}
|
||||
|
||||
public Vehicle getVehicle() {
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
public long getLastRelevantStateChange() {
|
||||
return lastRelevantStateChange;
|
||||
}
|
||||
|
||||
public void setLastRelevantStateChange(long lastRelevantStateChange) {
|
||||
this.lastRelevantStateChange = lastRelevantStateChange;
|
||||
}
|
||||
|
||||
public TCSObjectReference<Point> getLastPosition() {
|
||||
return vehicle.getCurrentPosition();
|
||||
}
|
||||
|
||||
public Vehicle.State getLastState() {
|
||||
return vehicle.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleSnapshot{"
|
||||
+ "stranded=" + stranded
|
||||
+ ", lastRelevantStateChange=" + lastRelevantStateChange
|
||||
+ ", vehicle=" + vehicle
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.watchdog;
|
||||
|
||||
/**
|
||||
* Provides the current time.
|
||||
*/
|
||||
public class TimeProvider {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public TimeProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time.
|
||||
*
|
||||
* @return The current time.
|
||||
*/
|
||||
public long getCurrentTime() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.watchdog;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.components.kernel.KernelExtension;
|
||||
|
||||
/**
|
||||
* A kernel extension to periodicly monitor the state of the kernel with check tasks.
|
||||
*/
|
||||
public class Watchdog
|
||||
implements
|
||||
KernelExtension {
|
||||
|
||||
/**
|
||||
* Whether this kernel extension is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
/**
|
||||
* The task to check for consistency of blocks.
|
||||
*/
|
||||
private final BlockConsistencyCheck blockCheck;
|
||||
/**
|
||||
* The task to check for stranded vehicles.
|
||||
*/
|
||||
private final StrandedVehicleCheck strandedVehicleCheck;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param blockCheck The block check task.
|
||||
* @param strandedVehicleCheck The stranded vehicle check task.
|
||||
*/
|
||||
@Inject
|
||||
public Watchdog(
|
||||
BlockConsistencyCheck blockCheck,
|
||||
StrandedVehicleCheck strandedVehicleCheck
|
||||
) {
|
||||
this.blockCheck = requireNonNull(blockCheck, "blockCheck");
|
||||
this.strandedVehicleCheck = requireNonNull(strandedVehicleCheck, "strandedVehicleCheck");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
blockCheck.initialize();
|
||||
strandedVehicleCheck.initialize();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
blockCheck.terminate();
|
||||
strandedVehicleCheck.terminate();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.extensions.watchdog;
|
||||
|
||||
import org.opentcs.configuration.ConfigurationEntry;
|
||||
import org.opentcs.configuration.ConfigurationPrefix;
|
||||
|
||||
/**
|
||||
* Configuration for the watchdog extension.
|
||||
*/
|
||||
@ConfigurationPrefix(WatchdogConfiguration.PREFIX)
|
||||
public interface WatchdogConfiguration {
|
||||
|
||||
/**
|
||||
* This configuration's prefix.
|
||||
*/
|
||||
String PREFIX = "watchdog";
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Integer",
|
||||
description = "The interval (in milliseconds) in which to check for block consistency.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
|
||||
orderKey = "1_block"
|
||||
)
|
||||
int blockConsistencyCheckInterval();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Integer",
|
||||
description = "The interval (in milliseconds) in which to check for stranded vehicles.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
|
||||
orderKey = "2_stranded_vehicle_0"
|
||||
)
|
||||
int strandedVehicleCheckInterval();
|
||||
|
||||
@ConfigurationEntry(
|
||||
type = "Integer",
|
||||
description = "The duration (in milliseconds) that a vehicle must be in a _stranded_ state "
|
||||
+ "to actually be considered stranded.",
|
||||
changesApplied = ConfigurationEntry.ChangesApplied.INSTANTLY,
|
||||
orderKey = "2_stranded_vehicle_1"
|
||||
)
|
||||
int strandedVehicleDurationThreshold();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
/**
|
||||
* The openTCS kernel, its interface for client applications and closely related
|
||||
* functionality.
|
||||
*/
|
||||
package org.opentcs.kernel;
|
||||
@@ -0,0 +1,178 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkState;
|
||||
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Objects;
|
||||
import org.opentcs.components.kernel.services.InternalPeripheralService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
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.PeripheralAdapterCommand;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralController;
|
||||
import org.opentcs.drivers.peripherals.PeripheralJobCallback;
|
||||
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
|
||||
import org.opentcs.drivers.peripherals.management.PeripheralProcessModelEvent;
|
||||
import org.opentcs.util.ExplainedBoolean;
|
||||
import org.opentcs.util.event.EventBus;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Realizes a bidirectional connection between the kernel and a comm adapter controlling a
|
||||
* peripheral device.
|
||||
*/
|
||||
public class DefaultPeripheralController
|
||||
implements
|
||||
PeripheralController,
|
||||
EventHandler {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultPeripheralController.class);
|
||||
/**
|
||||
* The location representing the peripheral device controlled by this controller/the comm adapter.
|
||||
*/
|
||||
private final TCSResourceReference<Location> location;
|
||||
/**
|
||||
* The comm adapter controling the peripheral device.
|
||||
*/
|
||||
private final PeripheralCommAdapter commAdapter;
|
||||
/**
|
||||
* The peripheral service to use.
|
||||
*/
|
||||
private final InternalPeripheralService peripheralService;
|
||||
/**
|
||||
* The event bus we should register with and send events to.
|
||||
*/
|
||||
private final EventBus eventBus;
|
||||
/**
|
||||
* Indicates whether this controller is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new DefaultPeripheralController.
|
||||
*
|
||||
* @param location The location representing the peripheral device.
|
||||
* @param commAdapter The comm adapter that controls the peripheral device.
|
||||
* @param peripheralService The peripheral service to be used.
|
||||
* @param eventBus The event bus to be used.
|
||||
*/
|
||||
@Inject
|
||||
public DefaultPeripheralController(
|
||||
@Assisted
|
||||
@Nonnull
|
||||
TCSResourceReference<Location> location,
|
||||
@Assisted
|
||||
@Nonnull
|
||||
PeripheralCommAdapter commAdapter,
|
||||
@Nonnull
|
||||
InternalPeripheralService peripheralService,
|
||||
@Nonnull
|
||||
@ApplicationEventBus
|
||||
EventBus eventBus
|
||||
) {
|
||||
this.location = requireNonNull(location, "location");
|
||||
this.commAdapter = requireNonNull(commAdapter, "commAdapter");
|
||||
this.peripheralService = requireNonNull(peripheralService, "peripheralService");
|
||||
this.eventBus = requireNonNull(eventBus, "eventBus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventBus.subscribe(this);
|
||||
|
||||
updatePeripheralState(commAdapter.getProcessModel().getState());
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updatePeripheralState(PeripheralInformation.State.UNKNOWN);
|
||||
|
||||
eventBus.unsubscribe(this);
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Object event) {
|
||||
if (!(event instanceof PeripheralProcessModelEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PeripheralProcessModelEvent processModelEvent = (PeripheralProcessModelEvent) event;
|
||||
if (Objects.equals(
|
||||
processModelEvent.getAttributeChanged(),
|
||||
PeripheralProcessModel.Attribute.STATE.name()
|
||||
)
|
||||
&& Objects.equals(processModelEvent.getLocation(), location)) {
|
||||
updatePeripheralState(processModelEvent.getProcessModel().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(PeripheralJob job, PeripheralJobCallback callback)
|
||||
throws IllegalStateException {
|
||||
requireNonNull(job, "job");
|
||||
requireNonNull(callback, "callback");
|
||||
|
||||
ExplainedBoolean canProcess = canProcess(job);
|
||||
checkState(
|
||||
canProcess.getValue(),
|
||||
"%s: Can't process job: %s",
|
||||
location.getName(),
|
||||
canProcess.getReason()
|
||||
);
|
||||
|
||||
LOG.debug("{}: Handing job to comm adapter: {}", location.getName(), job);
|
||||
commAdapter.process(job, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abortJob() {
|
||||
commAdapter.abortJob();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplainedBoolean canProcess(PeripheralJob job) {
|
||||
requireNonNull(job, "job");
|
||||
return commAdapter.canProcess(job);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommAdapterCommand(PeripheralAdapterCommand command) {
|
||||
requireNonNull(command, "command");
|
||||
commAdapter.execute(command);
|
||||
}
|
||||
|
||||
private void updatePeripheralState(PeripheralInformation.State newState) {
|
||||
requireNonNull(newState, "newState");
|
||||
peripheralService.updatePeripheralState(location, newState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Maintains associations of {@link Location}, {@link PeripheralController} and
|
||||
* {@link PeripheralCommAdapter}.
|
||||
*/
|
||||
public class DefaultPeripheralControllerPool
|
||||
implements
|
||||
LocalPeripheralControllerPool {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultPeripheralControllerPool.class);
|
||||
/**
|
||||
* The object service to use.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
/**
|
||||
* A factory for peripheral controllers.
|
||||
*/
|
||||
private final PeripheralControllerFactory controllerFactory;
|
||||
/**
|
||||
* The entries of this pool mapped to the corresponding locations.
|
||||
*/
|
||||
private final Map<TCSResourceReference<Location>, PoolEntry> poolEntries = new HashMap<>();
|
||||
/**
|
||||
* Indicates whether this component is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new DefaultPeripheralControllerPool.
|
||||
*
|
||||
* @param objectService The object service to be used.
|
||||
* @param controllerFactory The controller factory to be used.
|
||||
*/
|
||||
@Inject
|
||||
public DefaultPeripheralControllerPool(
|
||||
TCSObjectService objectService,
|
||||
PeripheralControllerFactory controllerFactory
|
||||
) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
this.controllerFactory = requireNonNull(controllerFactory, "controllerFactory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
LOG.debug("Already initialized, doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
LOG.debug("Not initialized, doing nothing.");
|
||||
return;
|
||||
}
|
||||
// Detach all peripherals.
|
||||
for (PoolEntry curEntry : poolEntries.values()) {
|
||||
curEntry.controller.terminate();
|
||||
}
|
||||
poolEntries.clear();
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralController getPeripheralController(TCSResourceReference<Location> locationRef)
|
||||
throws IllegalArgumentException {
|
||||
requireNonNull(locationRef, "locationRef");
|
||||
checkArgument(
|
||||
poolEntries.containsKey(locationRef),
|
||||
"No controller present for %s",
|
||||
locationRef.getName()
|
||||
);
|
||||
|
||||
return poolEntries.get(locationRef).getController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachPeripheralController(
|
||||
TCSResourceReference<Location> locationRef,
|
||||
PeripheralCommAdapter commAdapter
|
||||
)
|
||||
throws IllegalArgumentException {
|
||||
requireNonNull(locationRef, "locationRef");
|
||||
requireNonNull(commAdapter, "commAdapter");
|
||||
|
||||
if (poolEntries.containsKey(locationRef)) {
|
||||
LOG.warn("{}: Peripheral controller already attached, doing nothing.", locationRef.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = objectService.fetchObject(Location.class, locationRef);
|
||||
checkArgument(location != null, "No such location: %s", locationRef.getName());
|
||||
|
||||
LOG.debug("{}: Attaching controller...", locationRef.getName());
|
||||
PeripheralController controller = controllerFactory.createPeripheralController(
|
||||
locationRef,
|
||||
commAdapter
|
||||
);
|
||||
poolEntries.put(locationRef, new PoolEntry(locationRef, controller, commAdapter));
|
||||
controller.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachPeripheralController(TCSResourceReference<Location> locationRef) {
|
||||
requireNonNull(locationRef, "locationRef");
|
||||
|
||||
if (!poolEntries.containsKey(locationRef)) {
|
||||
LOG.debug("{}: No peripheral controller attached, doing nothing.", locationRef.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("{}: Detaching controller...", locationRef.getName());
|
||||
poolEntries.remove(locationRef).getController().terminate();
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry in this controller pool.
|
||||
*/
|
||||
private static class PoolEntry {
|
||||
|
||||
/**
|
||||
* The location.
|
||||
*/
|
||||
private final TCSResourceReference<Location> location;
|
||||
/**
|
||||
* The peripheral controller associated with the location.
|
||||
*/
|
||||
private final PeripheralController controller;
|
||||
/**
|
||||
* The comm adapter associated with the location.
|
||||
*/
|
||||
private final PeripheralCommAdapter commAdapter;
|
||||
|
||||
/**
|
||||
* Creates a new pool entry.
|
||||
*
|
||||
* @param location The location.
|
||||
* @param controller The peripheral controller associated with the location.
|
||||
* @param cmmmAdapter The comm adapter associated with the location.
|
||||
*/
|
||||
private PoolEntry(
|
||||
TCSResourceReference<Location> location,
|
||||
PeripheralController controller,
|
||||
PeripheralCommAdapter cmmmAdapter
|
||||
) {
|
||||
this.location = requireNonNull(location, "location");
|
||||
this.controller = requireNonNull(controller, "controller");
|
||||
this.commAdapter = requireNonNull(cmmmAdapter, "cmmmAdapter");
|
||||
}
|
||||
|
||||
public TCSResourceReference<Location> getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public PeripheralController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
public PeripheralCommAdapter getCommAdapter() {
|
||||
return commAdapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralControllerPool;
|
||||
|
||||
/**
|
||||
* Manages the attachment of peripheral controllers to locations and peripheral comm adapters.
|
||||
*/
|
||||
public interface LocalPeripheralControllerPool
|
||||
extends
|
||||
PeripheralControllerPool,
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* Associates a peripheral controller with a location and a comm adapter.
|
||||
*
|
||||
* @param location The reference to the location.
|
||||
* @param commAdapter The comm adapter that is going to control the peripheral deivce.
|
||||
* @throws IllegalArgumentException If the referenced location does not exist.
|
||||
*/
|
||||
void attachPeripheralController(
|
||||
TCSResourceReference<Location> location,
|
||||
PeripheralCommAdapter commAdapter
|
||||
)
|
||||
throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Disassociates a peripheral controller and a comm adapter from a location.
|
||||
*
|
||||
* @param location The reference to the location.
|
||||
*/
|
||||
void detachPeripheralController(TCSResourceReference<Location> location);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.data.peripherals.PeripheralJob;
|
||||
import org.opentcs.drivers.peripherals.PeripheralAdapterCommand;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralJobCallback;
|
||||
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
|
||||
import org.opentcs.util.ExplainedBoolean;
|
||||
|
||||
/**
|
||||
* A {@link PeripheralCommAdapter} implementation that is doing nothing.
|
||||
*/
|
||||
public class NullPeripheralCommAdapter
|
||||
implements
|
||||
PeripheralCommAdapter {
|
||||
|
||||
/**
|
||||
* The process model.
|
||||
*/
|
||||
private final PeripheralProcessModel processModel;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param location The reference to the location this adapter is attached to.
|
||||
*/
|
||||
public NullPeripheralCommAdapter(
|
||||
@Nonnull
|
||||
TCSResourceReference<Location> location
|
||||
) {
|
||||
this.processModel = new PeripheralProcessModel(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralProcessModel getProcessModel() {
|
||||
return processModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplainedBoolean canProcess(PeripheralJob job) {
|
||||
return new ExplainedBoolean(false, "Can't process any jobs.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(PeripheralJob job, PeripheralJobCallback callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abortJob() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(PeripheralAdapterCommand command) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import org.opentcs.common.peripherals.NullPeripheralCommAdapterDescription;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterFactory;
|
||||
|
||||
/**
|
||||
* A factory for {@link NullPeripheralCommAdapter}s.
|
||||
*/
|
||||
public class NullPeripheralCommAdapterFactory
|
||||
implements
|
||||
PeripheralCommAdapterFactory {
|
||||
|
||||
/**
|
||||
* Creates a new NullPeripheralCommAdapterFactory.
|
||||
*/
|
||||
public NullPeripheralCommAdapterFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralCommAdapterDescription getDescription() {
|
||||
return new NullPeripheralCommAdapterDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean providesAdapterFor(Location location) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralCommAdapter getAdapterFor(Location location) {
|
||||
return new NullPeripheralCommAdapter(location.getReference());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.InternalPeripheralService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterFactory;
|
||||
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
|
||||
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentEvent;
|
||||
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentInformation;
|
||||
import org.opentcs.drivers.peripherals.management.PeripheralProcessModelEvent;
|
||||
import org.opentcs.kernel.KernelApplicationConfiguration;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages attachment and detachment of peripheral communication adapters to location.
|
||||
*/
|
||||
public class PeripheralAttachmentManager
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PeripheralAttachmentManager.class);
|
||||
/**
|
||||
* This class's configuration.
|
||||
*/
|
||||
private final KernelApplicationConfiguration configuration;
|
||||
/**
|
||||
* The peripheral service.
|
||||
*/
|
||||
private final InternalPeripheralService peripheralService;
|
||||
/**
|
||||
* The peripheral controller pool.
|
||||
*/
|
||||
private final LocalPeripheralControllerPool controllerPool;
|
||||
/**
|
||||
* The peripheral comm adapter registry.
|
||||
*/
|
||||
private final PeripheralCommAdapterRegistry commAdapterRegistry;
|
||||
/**
|
||||
* The pool of peripheral entries.
|
||||
*/
|
||||
private final PeripheralEntryPool peripheralEntryPool;
|
||||
/**
|
||||
* The handler to send events to.
|
||||
*/
|
||||
private final EventHandler eventHandler;
|
||||
/**
|
||||
* Whether the attachment manager is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param peripheralService The peripheral service.
|
||||
* @param controllerPool The peripheral controller pool.
|
||||
* @param commAdapterRegistry The peripheral comm adapter registry.
|
||||
* @param peripheralEntryPool The pool of peripheral entries.
|
||||
* @param eventHandler The handler to send events to.
|
||||
* @param configuration This class's configuration.
|
||||
*/
|
||||
@Inject
|
||||
public PeripheralAttachmentManager(
|
||||
@Nonnull
|
||||
InternalPeripheralService peripheralService,
|
||||
@Nonnull
|
||||
LocalPeripheralControllerPool controllerPool,
|
||||
@Nonnull
|
||||
PeripheralCommAdapterRegistry commAdapterRegistry,
|
||||
@Nonnull
|
||||
PeripheralEntryPool peripheralEntryPool,
|
||||
@Nonnull
|
||||
@ApplicationEventBus
|
||||
EventHandler eventHandler,
|
||||
@Nonnull
|
||||
KernelApplicationConfiguration configuration
|
||||
) {
|
||||
this.peripheralService = requireNonNull(peripheralService, "peripheralService");
|
||||
this.controllerPool = requireNonNull(controllerPool, "controllerPool");
|
||||
this.commAdapterRegistry = requireNonNull(commAdapterRegistry, "commAdapterRegistry");
|
||||
this.peripheralEntryPool = requireNonNull(peripheralEntryPool, "peripheralEntryPool");
|
||||
this.eventHandler = requireNonNull(eventHandler, "eventHandler");
|
||||
this.configuration = requireNonNull(configuration, "configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
commAdapterRegistry.initialize();
|
||||
peripheralEntryPool.initialize();
|
||||
|
||||
autoAttachAllAdapters();
|
||||
LOG.debug("Locations attached: {}", peripheralEntryPool.getEntries());
|
||||
|
||||
if (configuration.autoEnablePeripheralDriversOnStartup()) {
|
||||
autoEnableAllAdapters();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
LOG.debug("Not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable and terminate all attached drivers to clean up.
|
||||
disableAndTerminateAllAdapters();
|
||||
peripheralEntryPool.terminate();
|
||||
commAdapterRegistry.terminate();
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a peripheral comm adapter to a location.
|
||||
*
|
||||
* @param location The location to attach to.
|
||||
* @param description The description of the comm adapter to attach.
|
||||
*/
|
||||
public void attachAdapterToLocation(
|
||||
@Nonnull
|
||||
TCSResourceReference<Location> location,
|
||||
@Nonnull
|
||||
PeripheralCommAdapterDescription description
|
||||
) {
|
||||
requireNonNull(location, "location");
|
||||
requireNonNull(description, "description");
|
||||
|
||||
attachAdapterToLocation(
|
||||
peripheralEntryPool.getEntryFor(location),
|
||||
commAdapterRegistry.findFactoryFor(description)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachment information for a location.
|
||||
*
|
||||
* @param location The location to get attachment information about.
|
||||
* @return The attachment information for a location.
|
||||
*/
|
||||
@Nonnull
|
||||
public PeripheralAttachmentInformation getAttachmentInformation(
|
||||
@Nonnull
|
||||
TCSResourceReference<Location> location
|
||||
) {
|
||||
requireNonNull(location, "location");
|
||||
|
||||
PeripheralEntry entry = peripheralEntryPool.getEntryFor(location);
|
||||
return new PeripheralAttachmentInformation(
|
||||
entry.getLocation(),
|
||||
entry.getAvailableCommAdapters(),
|
||||
entry.getCommAdapterFactory().getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
private void attachAdapterToLocation(
|
||||
PeripheralEntry entry,
|
||||
PeripheralCommAdapterFactory factory
|
||||
) {
|
||||
requireNonNull(entry, "entry");
|
||||
requireNonNull(factory, "factory");
|
||||
|
||||
LOG.info(
|
||||
"Attaching peripheral comm adapter: '{}' -- '{}'...",
|
||||
entry.getLocation().getName(),
|
||||
factory.getClass().getName()
|
||||
);
|
||||
|
||||
Location location = peripheralService.fetchObject(Location.class, entry.getLocation());
|
||||
PeripheralCommAdapter commAdapter = factory.getAdapterFor(location);
|
||||
if (commAdapter == null) {
|
||||
LOG.warn(
|
||||
"Factory {} did not provide adapter for location {}, ignoring.",
|
||||
factory,
|
||||
entry.getLocation().getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform a cleanup for the old adapter.
|
||||
disableAndTerminateAdapter(entry);
|
||||
controllerPool.detachPeripheralController(entry.getLocation());
|
||||
|
||||
commAdapter.initialize();
|
||||
controllerPool.attachPeripheralController(entry.getLocation(), commAdapter);
|
||||
|
||||
entry.setCommAdapterFactory(factory);
|
||||
entry.setCommAdapter(commAdapter);
|
||||
|
||||
// Publish events about the new attached adapter.
|
||||
eventHandler.onEvent(
|
||||
new PeripheralAttachmentEvent(
|
||||
entry.getLocation(),
|
||||
new PeripheralAttachmentInformation(
|
||||
entry.getLocation(),
|
||||
entry.getAvailableCommAdapters(),
|
||||
entry.getCommAdapterFactory().getDescription()
|
||||
)
|
||||
)
|
||||
);
|
||||
eventHandler.onEvent(
|
||||
new PeripheralProcessModelEvent(
|
||||
entry.getLocation(),
|
||||
PeripheralProcessModel.Attribute.LOCATION.name(),
|
||||
entry.getProcessModel()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void autoAttachAdapterToLocation(PeripheralEntry peripheralEntry) {
|
||||
// Do not auto-attach if there is already a (real) comm adapter attached to the location.
|
||||
if (!(peripheralEntry.getCommAdapter() instanceof NullPeripheralCommAdapter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = peripheralService.fetchObject(
|
||||
Location.class,
|
||||
peripheralEntry.getLocation()
|
||||
);
|
||||
List<PeripheralCommAdapterFactory> factories = commAdapterRegistry.findFactoriesFor(location);
|
||||
if (!factories.isEmpty()) {
|
||||
LOG.debug(
|
||||
"Attaching {} to first available adapter: {}.",
|
||||
peripheralEntry.getLocation().getName(),
|
||||
factories.get(0).getDescription().getDescription()
|
||||
);
|
||||
attachAdapterToLocation(peripheralEntry, factories.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void autoAttachAllAdapters() {
|
||||
peripheralEntryPool.getEntries().forEach((location, entry) -> {
|
||||
autoAttachAdapterToLocation(entry);
|
||||
});
|
||||
}
|
||||
|
||||
private void disableAndTerminateAdapter(PeripheralEntry peripheralEntry) {
|
||||
peripheralEntry.getCommAdapter().disable();
|
||||
peripheralEntry.getCommAdapter().terminate();
|
||||
}
|
||||
|
||||
private void autoEnableAllAdapters() {
|
||||
peripheralEntryPool.getEntries().values().stream()
|
||||
.map(entry -> entry.getCommAdapter())
|
||||
.filter(adapter -> !adapter.isEnabled())
|
||||
.forEach(adapter -> adapter.enable());
|
||||
}
|
||||
|
||||
private void disableAndTerminateAllAdapters() {
|
||||
LOG.debug("Detaching peripheral communication adapters...");
|
||||
peripheralEntryPool.getEntries().forEach((location, entry) -> {
|
||||
disableAndTerminateAdapter(entry);
|
||||
});
|
||||
LOG.debug("Detached peripheral communication adapters");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
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.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A registry for all peripheral communication adapters in the system.
|
||||
*/
|
||||
public class PeripheralCommAdapterRegistry
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PeripheralCommAdapterRegistry.class);
|
||||
/**
|
||||
* The registered factories.
|
||||
*/
|
||||
private final Map<PeripheralCommAdapterDescription, PeripheralCommAdapterFactory> factories
|
||||
= new HashMap<>();
|
||||
/**
|
||||
* Indicates whether this component is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new registry.
|
||||
*
|
||||
* @param factories The peripheral comm adapter factories.
|
||||
*/
|
||||
@Inject
|
||||
public PeripheralCommAdapterRegistry(Set<PeripheralCommAdapterFactory> factories) {
|
||||
requireNonNull(factories, "factories");
|
||||
for (PeripheralCommAdapterFactory factory : factories) {
|
||||
LOG.info(
|
||||
"Setting up peripheral communication adapter factory: {}",
|
||||
factory.getClass().getName()
|
||||
);
|
||||
this.factories.put(factory.getDescription(), factory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (PeripheralCommAdapterFactory factory : factories.values()) {
|
||||
factory.initialize();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (PeripheralCommAdapterFactory factory : factories.values()) {
|
||||
factory.terminate();
|
||||
}
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered factories that can provide peripheral communication adapters.
|
||||
*
|
||||
* @return All registered factories that can provide peripheral communication adapters.
|
||||
*/
|
||||
public List<PeripheralCommAdapterFactory> getFactories() {
|
||||
return new ArrayList<>(factories.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the factory for the given description.
|
||||
*
|
||||
* @param description The description to get the factory for.
|
||||
* @return The factory for the given description.
|
||||
*/
|
||||
@Nonnull
|
||||
public PeripheralCommAdapterFactory findFactoryFor(
|
||||
@Nonnull
|
||||
PeripheralCommAdapterDescription description
|
||||
) {
|
||||
requireNonNull(description, "description");
|
||||
checkArgument(
|
||||
factories.get(description) != null,
|
||||
"No factory for description %s",
|
||||
description
|
||||
);
|
||||
|
||||
return factories.get(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of factories that can provide communication adapters for the given location.
|
||||
*
|
||||
* @param location The location to find communication adapters/factories for.
|
||||
* @return A set of factories that can provide communication adapters for the given location.
|
||||
*/
|
||||
public List<PeripheralCommAdapterFactory> findFactoriesFor(Location location) {
|
||||
requireNonNull(location, "location");
|
||||
|
||||
return factories.values().stream()
|
||||
.filter(factory -> factory.providesAdapterFor(location))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralController;
|
||||
|
||||
/**
|
||||
* A factory for {@link PeripheralController} instances.
|
||||
*/
|
||||
public interface PeripheralControllerFactory {
|
||||
|
||||
/**
|
||||
* Creates a new peripheral controller for the given location and communication adapter.
|
||||
*
|
||||
* @param location The location.
|
||||
* @param commAdapter The communication adapter.
|
||||
* @return A new peripheral controller.
|
||||
*/
|
||||
DefaultPeripheralController createPeripheralController(
|
||||
TCSResourceReference<Location> location,
|
||||
PeripheralCommAdapter commAdapter
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapter;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterFactory;
|
||||
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
|
||||
|
||||
/**
|
||||
* An entry for a peripheral device represented by a {@link Location}.
|
||||
*/
|
||||
public class PeripheralEntry {
|
||||
|
||||
/**
|
||||
* The available comm adapters for this entry.
|
||||
*/
|
||||
private final List<PeripheralCommAdapterDescription> availableCommAdapters;
|
||||
/**
|
||||
* The peripheral comm adapter factory that created this entry's comm adapter instance.
|
||||
*/
|
||||
private PeripheralCommAdapterFactory commAdapterFactory = new NullPeripheralCommAdapterFactory();
|
||||
/**
|
||||
* The comm adapter instance for this entry.
|
||||
*/
|
||||
private PeripheralCommAdapter commAdapter;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param location The location representing the peripheral device.
|
||||
* @param availableCommAdapters The available comm adapters for this entry.
|
||||
*/
|
||||
public PeripheralEntry(
|
||||
@Nonnull
|
||||
Location location,
|
||||
@Nonnull
|
||||
List<PeripheralCommAdapterDescription> availableCommAdapters
|
||||
) {
|
||||
requireNonNull(location, "location");
|
||||
this.availableCommAdapters = requireNonNull(availableCommAdapters, "availableCommAdapters");
|
||||
this.commAdapter = commAdapterFactory.getAdapterFor(location);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public PeripheralProcessModel getProcessModel() {
|
||||
return commAdapter.getProcessModel();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public TCSResourceReference<Location> getLocation() {
|
||||
return getProcessModel().getLocation();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<PeripheralCommAdapterDescription> getAvailableCommAdapters() {
|
||||
return availableCommAdapters;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public PeripheralCommAdapterFactory getCommAdapterFactory() {
|
||||
return commAdapterFactory;
|
||||
}
|
||||
|
||||
public void setCommAdapterFactory(
|
||||
@Nonnull
|
||||
PeripheralCommAdapterFactory commAdapterFactory
|
||||
) {
|
||||
this.commAdapterFactory = requireNonNull(commAdapterFactory, "commAdapterFactory");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public PeripheralCommAdapter getCommAdapter() {
|
||||
return commAdapter;
|
||||
}
|
||||
|
||||
public void setCommAdapter(
|
||||
@Nonnull
|
||||
PeripheralCommAdapter commAdapter
|
||||
) {
|
||||
this.commAdapter = requireNonNull(commAdapter, "commAdapter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PeripheralEntry{"
|
||||
+ "availableCommAdapters=" + availableCommAdapters + ", "
|
||||
+ "commAdapterFactory=" + commAdapterFactory + ", "
|
||||
+ "commAdapter=" + commAdapter + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.peripherals;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides a pool of {@link PeripheralEntry}s with an entry for every {@link Location} object in
|
||||
* the kernel.
|
||||
*/
|
||||
public class PeripheralEntryPool
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PeripheralEntryPool.class);
|
||||
/**
|
||||
* The object service.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
/**
|
||||
* The peripheral comm adapter registry.
|
||||
*/
|
||||
private final PeripheralCommAdapterRegistry commAdapterRegistry;
|
||||
/**
|
||||
* The entries of this pool.
|
||||
*/
|
||||
private final Map<TCSResourceReference<Location>, PeripheralEntry> entries = new HashMap<>();
|
||||
/**
|
||||
* Whether the pool is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The object service.
|
||||
* @param commAdapterRegistry The peripheral comm adapter registry.
|
||||
*/
|
||||
@Inject
|
||||
public PeripheralEntryPool(
|
||||
@Nonnull
|
||||
TCSObjectService objectService,
|
||||
@Nonnull
|
||||
PeripheralCommAdapterRegistry commAdapterRegistry
|
||||
) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
this.commAdapterRegistry = requireNonNull(commAdapterRegistry, "commAdapterRegistry");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Location location : objectService.fetchObjects(Location.class)) {
|
||||
entries.put(
|
||||
location.getReference(),
|
||||
new PeripheralEntry(
|
||||
location,
|
||||
commAdapterRegistry.findFactoriesFor(location).stream()
|
||||
.map(factory -> factory.getDescription())
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
LOG.debug("Initialized peripheral entry pool: {}", entries);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entries.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Map<TCSResourceReference<Location>, PeripheralEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PeripheralEntry} for the given location.
|
||||
*
|
||||
* @param location The reference to the location.
|
||||
* @return The entry for the given location.
|
||||
* @throws IllegalArgumentException If no entry is present for the given location.
|
||||
*/
|
||||
@Nonnull
|
||||
public PeripheralEntry getEntryFor(
|
||||
@Nonnull
|
||||
TCSResourceReference<Location> location
|
||||
)
|
||||
throws IllegalArgumentException {
|
||||
requireNonNull(location, "location");
|
||||
checkArgument(
|
||||
entries.containsKey(location),
|
||||
"No peripheral entry present for %s",
|
||||
location.getName()
|
||||
);
|
||||
return entries.get(location);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.persistence;
|
||||
|
||||
import org.opentcs.access.to.model.PlantModelCreationTO;
|
||||
|
||||
/**
|
||||
* Provides methods to persist and load models.
|
||||
* Only a single model is persisted at a time.
|
||||
*/
|
||||
public interface ModelPersister {
|
||||
|
||||
/**
|
||||
* Find out if there is a persisted model at the moment.
|
||||
*
|
||||
* @return True if a model is saved.
|
||||
*/
|
||||
boolean hasSavedModel();
|
||||
|
||||
/**
|
||||
* Persists a model according to the actual implementation of this method.
|
||||
*
|
||||
* @param model The model to be persisted.
|
||||
* @throws IllegalStateException If persisting the model is not possible for some reason.
|
||||
*/
|
||||
void saveModel(PlantModelCreationTO model)
|
||||
throws IllegalStateException;
|
||||
|
||||
/**
|
||||
* Reads the model and returns it as a <Code>PlantModelCreationTO</Code>.
|
||||
*
|
||||
* @return The <Code>PlantModelCreationTO</Code> that contains the data that was read.
|
||||
* @throws IllegalStateException If reading the model is not possible for some reason.
|
||||
*/
|
||||
PlantModelCreationTO readModel()
|
||||
throws IllegalStateException;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.persistence;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkState;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import org.opentcs.access.to.model.PlantModelCreationTO;
|
||||
import org.opentcs.customizations.ApplicationHome;
|
||||
import org.opentcs.util.persistence.ModelParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link ModelPersister} implementation using an XML file.
|
||||
*/
|
||||
public class XMLFileModelPersister
|
||||
implements
|
||||
ModelPersister {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XMLFileModelPersister.class);
|
||||
/**
|
||||
* The name of the model file in the model directory.
|
||||
*/
|
||||
private static final String MODEL_FILE_NAME = "model.xml";
|
||||
/**
|
||||
* The directory path for the persisted model.
|
||||
*/
|
||||
private final File dataDirectory;
|
||||
/**
|
||||
* The model file.
|
||||
*/
|
||||
private final File modelFile;
|
||||
/**
|
||||
* Reads and writes models into xml files.
|
||||
*/
|
||||
private final ModelParser modelParser;
|
||||
|
||||
/**
|
||||
* Creates a new XMLFileModelPersister.
|
||||
*
|
||||
* @param directory The application's home directory.
|
||||
* @param modelParser Reads and writes into the xml file.
|
||||
*/
|
||||
@Inject
|
||||
public XMLFileModelPersister(
|
||||
@ApplicationHome
|
||||
File directory,
|
||||
ModelParser modelParser
|
||||
) {
|
||||
this.modelParser = requireNonNull(modelParser, "modelParser");
|
||||
this.dataDirectory = new File(requireNonNull(directory, "directory"), "data");
|
||||
|
||||
this.modelFile = new File(dataDirectory, MODEL_FILE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveModel(PlantModelCreationTO model)
|
||||
throws IllegalStateException {
|
||||
requireNonNull(model, "model");
|
||||
|
||||
LOG.debug("Saving model '{}'.", model.getName());
|
||||
|
||||
// Check if writing the model is possible.
|
||||
checkState(
|
||||
dataDirectory.isDirectory() || dataDirectory.mkdirs(),
|
||||
"%s is not an existing directory and could not be created, either.",
|
||||
dataDirectory.getPath()
|
||||
);
|
||||
checkState(
|
||||
!modelFile.exists() || modelFile.isFile(),
|
||||
"%s exists, but is not a regular file",
|
||||
modelFile.getPath()
|
||||
);
|
||||
try {
|
||||
if (modelFile.exists()) {
|
||||
createBackup();
|
||||
}
|
||||
|
||||
modelParser.writeModel(model, modelFile);
|
||||
}
|
||||
catch (IOException exc) {
|
||||
throw new IllegalStateException("Exception saving model", exc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlantModelCreationTO readModel()
|
||||
throws IllegalStateException {
|
||||
// Return empty model if there is no saved model
|
||||
if (!hasSavedModel()) {
|
||||
return new PlantModelCreationTO("empty model");
|
||||
}
|
||||
|
||||
// Read the model from the file.
|
||||
return readXMLModel(modelFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSavedModel() {
|
||||
return modelFileExists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a backup of the currently saved model file by copying it to the
|
||||
* "backups" subdirectory.
|
||||
*
|
||||
* Assumes that the model file exists.
|
||||
*
|
||||
* @throws IOException If the backup directory is not accessible or copying
|
||||
* the file fails.
|
||||
*/
|
||||
private void createBackup()
|
||||
throws IOException {
|
||||
// Generate backup file name
|
||||
Calendar cal = Calendar.getInstance();
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss-SSS");
|
||||
String time = sdf.format(cal.getTime());
|
||||
String modelBackupName = MODEL_FILE_NAME + "_backup_" + time;
|
||||
// Make sure backup directory exists
|
||||
File modelBackupDirectory = new File(dataDirectory, "backups");
|
||||
if (modelBackupDirectory.exists()) {
|
||||
if (!modelBackupDirectory.isDirectory()) {
|
||||
throw new IOException(
|
||||
modelBackupDirectory.getPath() + " exists, but is not a directory"
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!modelBackupDirectory.mkdir()) {
|
||||
throw new IOException(
|
||||
"Could not create model directory " + modelBackupDirectory.getPath()
|
||||
);
|
||||
}
|
||||
}
|
||||
// Backup the model file
|
||||
Files.copy(
|
||||
modelFile.toPath(),
|
||||
new File(modelBackupDirectory, modelBackupName).toPath()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the data directory with a model file exist. If not, throw an
|
||||
* exception.
|
||||
*
|
||||
* @throws IOException If check failed.
|
||||
*/
|
||||
private boolean modelFileExists() {
|
||||
if (!modelFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
if (!modelFile.isFile()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a model from a given InputStream.
|
||||
*
|
||||
* @param modelFile The file containing the model.
|
||||
* @param model The model to be built.
|
||||
* @throws IOException If an exception occured while loading
|
||||
*/
|
||||
private PlantModelCreationTO readXMLModel(File modelFile)
|
||||
throws IllegalStateException {
|
||||
try {
|
||||
return modelParser.readModel(modelFile);
|
||||
}
|
||||
catch (IOException exc) {
|
||||
LOG.error("Exception parsing input", exc);
|
||||
throw new IllegalStateException("Exception parsing input", exc);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
/**
|
||||
* Classes for persisting and materializing openTCS data.
|
||||
*/
|
||||
package org.opentcs.kernel.persistence;
|
||||
@@ -0,0 +1,112 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import org.opentcs.access.CredentialsException;
|
||||
import org.opentcs.access.KernelRuntimeException;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.data.ObjectHistory;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObject;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
|
||||
/**
|
||||
* Delegate method calls to the {@link TCSObjectService} implementation.
|
||||
*/
|
||||
public abstract class AbstractTCSObjectService
|
||||
implements
|
||||
TCSObjectService {
|
||||
|
||||
/**
|
||||
* The tcs object service to delegate method calls to.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The service to delegate method calls to.
|
||||
*/
|
||||
public AbstractTCSObjectService(TCSObjectService objectService) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> T fetchObject(Class<T> clazz, TCSObjectReference<T> ref)
|
||||
throws CredentialsException {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
return getObjectService().fetchObject(clazz, ref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> T fetchObject(Class<T> clazz, String name)
|
||||
throws CredentialsException {
|
||||
requireNonNull(clazz, "clazz");
|
||||
|
||||
return getObjectService().fetchObject(clazz, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> Set<T> fetchObjects(Class<T> clazz)
|
||||
throws CredentialsException {
|
||||
requireNonNull(clazz, "clazz");
|
||||
|
||||
return getObjectService().fetchObjects(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> Set<T> fetchObjects(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
Predicate<? super T> predicate
|
||||
)
|
||||
throws CredentialsException {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(predicate, "predicate");
|
||||
|
||||
return getObjectService().fetchObjects(clazz, predicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjectProperty(
|
||||
TCSObjectReference<?> ref,
|
||||
String key,
|
||||
@Nullable
|
||||
String value
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
CredentialsException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(key, "key");
|
||||
|
||||
getObjectService().updateObjectProperty(ref, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendObjectHistoryEntry(TCSObjectReference<?> ref, ObjectHistory.Entry entry)
|
||||
throws ObjectUnknownException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(entry, "entry");
|
||||
|
||||
getObjectService().appendObjectHistoryEntry(ref, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retruns the {@link TCSObjectService} implementation being used.
|
||||
*
|
||||
* @return The {@link TCSObjectService} implementation being used.
|
||||
*/
|
||||
public TCSObjectService getObjectService() {
|
||||
return objectService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.components.kernel.Dispatcher;
|
||||
import org.opentcs.components.kernel.dipatching.TransportOrderAssignmentException;
|
||||
import org.opentcs.components.kernel.services.DispatcherService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.order.ReroutingType;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.kernel.workingset.TCSObjectRepository;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link DispatcherService} interface.
|
||||
*/
|
||||
public class StandardDispatcherService
|
||||
implements
|
||||
DispatcherService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The container of all course model and transport order objects.
|
||||
*/
|
||||
private final TCSObjectRepository objectRepo;
|
||||
/**
|
||||
* The dispatcher.
|
||||
*/
|
||||
private final Dispatcher dispatcher;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param objectRepo The object repo to be used.
|
||||
* @param dispatcher The dispatcher.
|
||||
*/
|
||||
@Inject
|
||||
public StandardDispatcherService(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
TCSObjectRepository objectRepo,
|
||||
Dispatcher dispatcher
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.objectRepo = requireNonNull(objectRepo, "objectRepo");
|
||||
this.dispatcher = requireNonNull(dispatcher, "dispatcher");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch() {
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void withdrawByVehicle(TCSObjectReference<Vehicle> ref, boolean immediateAbort)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.withdrawOrder(objectRepo.getObject(Vehicle.class, ref), immediateAbort);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void withdrawByTransportOrder(
|
||||
TCSObjectReference<TransportOrder> ref,
|
||||
boolean immediateAbort
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.withdrawOrder(
|
||||
objectRepo.getObject(TransportOrder.class, ref),
|
||||
immediateAbort
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reroute(TCSObjectReference<Vehicle> ref, ReroutingType reroutingType)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(reroutingType, "reroutingType");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.reroute(objectRepo.getObject(Vehicle.class, ref), reroutingType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rerouteAll(ReroutingType reroutingType) {
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.rerouteAll(reroutingType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignNow(TCSObjectReference<TransportOrder> ref)
|
||||
throws ObjectUnknownException,
|
||||
TransportOrderAssignmentException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.assignNow(objectRepo.getObject(TransportOrder.class, ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import org.opentcs.components.kernel.services.NotificationService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.notification.UserNotification;
|
||||
import org.opentcs.kernel.workingset.NotificationBuffer;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link NotificationService} interface.
|
||||
*/
|
||||
public class StandardNotificationService
|
||||
implements
|
||||
NotificationService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The buffer for all messages published.
|
||||
*/
|
||||
private final NotificationBuffer notificationBuffer;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param notificationBuffer The notification buffer to be used.
|
||||
*/
|
||||
@Inject
|
||||
public StandardNotificationService(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
NotificationBuffer notificationBuffer
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.notificationBuffer = requireNonNull(notificationBuffer, "notificationBuffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserNotification> fetchUserNotifications(Predicate<UserNotification> predicate) {
|
||||
synchronized (globalSyncObject) {
|
||||
return notificationBuffer.getNotifications(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishUserNotification(UserNotification notification) {
|
||||
requireNonNull(notification, "notification");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
notificationBuffer.addNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.access.KernelRuntimeException;
|
||||
import org.opentcs.components.kernel.PeripheralJobDispatcher;
|
||||
import org.opentcs.components.kernel.services.PeripheralDispatcherService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.data.peripherals.PeripheralJob;
|
||||
import org.opentcs.kernel.workingset.TCSObjectRepository;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link PeripheralDispatcherService} interface.
|
||||
*/
|
||||
public class StandardPeripheralDispatcherService
|
||||
implements
|
||||
PeripheralDispatcherService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The container of all course model and transport order objects.
|
||||
*/
|
||||
private final TCSObjectRepository objectRepo;
|
||||
/**
|
||||
* The peripheral job dispatcher.
|
||||
*/
|
||||
private final PeripheralJobDispatcher dispatcher;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param objectRepo The object repo to be used.
|
||||
* @param dispatcher The peripheral job dispatcher.
|
||||
*/
|
||||
@Inject
|
||||
public StandardPeripheralDispatcherService(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
TCSObjectRepository objectRepo,
|
||||
PeripheralJobDispatcher dispatcher
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.objectRepo = requireNonNull(objectRepo, "objectRepo");
|
||||
this.dispatcher = requireNonNull(dispatcher, "dispatcher");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch() {
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void withdrawByLocation(TCSResourceReference<Location> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.withdrawJob(objectRepo.getObject(Location.class, ref));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void withdrawByPeripheralJob(TCSObjectReference<PeripheralJob> ref)
|
||||
throws ObjectUnknownException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
dispatcher.withdrawJob(objectRepo.getObject(PeripheralJob.class, ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.access.KernelRuntimeException;
|
||||
import org.opentcs.access.to.peripherals.PeripheralJobCreationTO;
|
||||
import org.opentcs.components.kernel.services.InternalPeripheralJobService;
|
||||
import org.opentcs.components.kernel.services.PeripheralJobService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectExistsException;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.peripherals.PeripheralJob;
|
||||
import org.opentcs.kernel.workingset.PeripheralJobPoolManager;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link PeripheralJobService} interface.
|
||||
*/
|
||||
public class StandardPeripheralJobService
|
||||
extends
|
||||
AbstractTCSObjectService
|
||||
implements
|
||||
InternalPeripheralJobService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The job pool manager.
|
||||
*/
|
||||
private final PeripheralJobPoolManager jobPoolManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The tcs obejct service.
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param jobPoolManager The job pool manager to be used.
|
||||
*/
|
||||
@Inject
|
||||
public StandardPeripheralJobService(
|
||||
TCSObjectService objectService,
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
PeripheralJobPoolManager jobPoolManager
|
||||
) {
|
||||
super(objectService);
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.jobPoolManager = requireNonNull(jobPoolManager, "jobPoolManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePeripheralJobState(
|
||||
TCSObjectReference<PeripheralJob> ref,
|
||||
PeripheralJob.State state
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(state, "state");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
jobPoolManager.setPeripheralJobState(ref, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralJob createPeripheralJob(PeripheralJobCreationTO to)
|
||||
throws ObjectUnknownException,
|
||||
ObjectExistsException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(to, "to");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return jobPoolManager.createPeripheralJob(to);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.components.kernel.services.InternalPeripheralService;
|
||||
import org.opentcs.components.kernel.services.PeripheralService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
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.PeripheralAdapterCommand;
|
||||
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
|
||||
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
|
||||
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentInformation;
|
||||
import org.opentcs.kernel.peripherals.PeripheralAttachmentManager;
|
||||
import org.opentcs.kernel.peripherals.PeripheralEntry;
|
||||
import org.opentcs.kernel.peripherals.PeripheralEntryPool;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link PeripheralService} interface.
|
||||
*/
|
||||
public class StandardPeripheralService
|
||||
extends
|
||||
AbstractTCSObjectService
|
||||
implements
|
||||
InternalPeripheralService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The attachment manager.
|
||||
*/
|
||||
private final PeripheralAttachmentManager attachmentManager;
|
||||
/**
|
||||
* The pool of peripheral entries.
|
||||
*/
|
||||
private final PeripheralEntryPool peripheralEntryPool;
|
||||
/**
|
||||
* The plant model manager.
|
||||
*/
|
||||
private final PlantModelManager plantModelManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The tcs object service.
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param attachmentManager The attachment manager.
|
||||
* @param peripheralEntryPool The pool of peripheral entries.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
*/
|
||||
@Inject
|
||||
public StandardPeripheralService(
|
||||
TCSObjectService objectService,
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
PeripheralAttachmentManager attachmentManager,
|
||||
PeripheralEntryPool peripheralEntryPool,
|
||||
PlantModelManager plantModelManager
|
||||
) {
|
||||
super(objectService);
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.attachmentManager = requireNonNull(attachmentManager, "attachmentManager");
|
||||
this.peripheralEntryPool = requireNonNull(peripheralEntryPool, "peripheralEntryPool");
|
||||
this.plantModelManager = requireNonNull(plantModelManager, "plantModelManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachCommAdapter(
|
||||
TCSResourceReference<Location> ref,
|
||||
PeripheralCommAdapterDescription description
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(description, "description");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
attachmentManager.attachAdapterToLocation(ref, description);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCommAdapter(TCSResourceReference<Location> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
peripheralEntryPool.getEntryFor(ref).getCommAdapter().disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCommAdapter(TCSResourceReference<Location> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
peripheralEntryPool.getEntryFor(ref).getCommAdapter().enable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralAttachmentInformation fetchAttachmentInformation(
|
||||
TCSResourceReference<Location> ref
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return attachmentManager.getAttachmentInformation(ref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeripheralProcessModel fetchProcessModel(TCSResourceReference<Location> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return peripheralEntryPool.getEntryFor(ref).getCommAdapter().getProcessModel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommAdapterCommand(
|
||||
TCSResourceReference<Location> ref,
|
||||
PeripheralAdapterCommand command
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(command, "command");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
PeripheralEntry entry = peripheralEntryPool.getEntryFor(ref);
|
||||
synchronized (entry.getCommAdapter()) {
|
||||
entry.getCommAdapter().execute(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePeripheralProcState(
|
||||
TCSResourceReference<Location> ref,
|
||||
PeripheralInformation.ProcState state
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(state, "state");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setLocationProcState(ref, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePeripheralReservationToken(
|
||||
TCSResourceReference<Location> ref,
|
||||
String reservationToken
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setLocationReservationToken(ref, reservationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePeripheralState(
|
||||
TCSResourceReference<Location> ref,
|
||||
PeripheralInformation.State state
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(state, "state");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setLocationState(ref, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePeripheralJob(
|
||||
TCSResourceReference<Location> ref,
|
||||
TCSObjectReference<PeripheralJob> peripheralJob
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setLocationPeripheralJob(ref, peripheralJob);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.opentcs.access.Kernel;
|
||||
import org.opentcs.access.KernelRuntimeException;
|
||||
import org.opentcs.access.LocalKernel;
|
||||
import org.opentcs.access.ModelTransitionEvent;
|
||||
import org.opentcs.access.to.model.PlantModelCreationTO;
|
||||
import org.opentcs.components.kernel.services.InternalPlantModelService;
|
||||
import org.opentcs.components.kernel.services.NotificationService;
|
||||
import org.opentcs.components.kernel.services.PlantModelService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectExistsException;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Block;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.LocationType;
|
||||
import org.opentcs.data.model.Path;
|
||||
import org.opentcs.data.model.PlantModel;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.TCSResource;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.model.visualization.VisualLayout;
|
||||
import org.opentcs.data.notification.UserNotification;
|
||||
import org.opentcs.kernel.persistence.ModelPersister;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link PlantModelService} interface.
|
||||
*/
|
||||
public class StandardPlantModelService
|
||||
extends
|
||||
AbstractTCSObjectService
|
||||
implements
|
||||
InternalPlantModelService {
|
||||
|
||||
/**
|
||||
* The kernel.
|
||||
*/
|
||||
private final Kernel kernel;
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The plant model manager.
|
||||
*/
|
||||
private final PlantModelManager plantModelManager;
|
||||
/**
|
||||
* The persister loading and storing model data.
|
||||
*/
|
||||
private final ModelPersister modelPersister;
|
||||
/**
|
||||
* Where we send events to.
|
||||
*/
|
||||
private final EventHandler eventHandler;
|
||||
/**
|
||||
* The notification service.
|
||||
*/
|
||||
private final NotificationService notificationService;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param kernel The kernel.
|
||||
* @param objectService The tcs object service.
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param modelPersister The model persister to be used.
|
||||
* @param eventHandler Where this instance sends events to.
|
||||
* @param notificationService The notification service.
|
||||
*/
|
||||
@Inject
|
||||
public StandardPlantModelService(
|
||||
LocalKernel kernel,
|
||||
TCSObjectService objectService,
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
PlantModelManager plantModelManager,
|
||||
ModelPersister modelPersister,
|
||||
@ApplicationEventBus
|
||||
EventHandler eventHandler,
|
||||
NotificationService notificationService
|
||||
) {
|
||||
super(objectService);
|
||||
this.kernel = requireNonNull(kernel, "kernel");
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.plantModelManager = requireNonNull(plantModelManager, "plantModelManager");
|
||||
this.modelPersister = requireNonNull(modelPersister, "modelPersister");
|
||||
this.eventHandler = requireNonNull(eventHandler, "eventHandler");
|
||||
this.notificationService = requireNonNull(notificationService, "notificationService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TCSResource<?>> expandResources(Set<TCSResourceReference<?>> resources)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(resources, "resources");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return plantModelManager.expandResources(resources);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlantModel()
|
||||
throws IllegalStateException {
|
||||
synchronized (globalSyncObject) {
|
||||
if (!modelPersister.hasSavedModel()) {
|
||||
createPlantModel(new PlantModelCreationTO(Kernel.DEFAULT_MODEL_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
final String oldModelName = getModelName();
|
||||
// Load the new model
|
||||
PlantModelCreationTO modelCreationTO = modelPersister.readModel();
|
||||
final String newModelName = isNullOrEmpty(modelCreationTO.getName())
|
||||
? ""
|
||||
: modelCreationTO.getName();
|
||||
// Let listeners know we're in transition.
|
||||
emitModelEvent(oldModelName, newModelName, true, false);
|
||||
plantModelManager.createPlantModelObjects(modelCreationTO);
|
||||
// Let listeners know we're done with the transition.
|
||||
emitModelEvent(oldModelName, newModelName, true, true);
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
"Kernel loaded model " + newModelName,
|
||||
UserNotification.Level.INFORMATIONAL
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePlantModel()
|
||||
throws IllegalStateException {
|
||||
synchronized (globalSyncObject) {
|
||||
modelPersister.saveModel(plantModelManager.createPlantModelCreationTO());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlantModel getPlantModel() {
|
||||
synchronized (globalSyncObject) {
|
||||
return new PlantModel(plantModelManager.getName())
|
||||
.withProperties(getModelProperties())
|
||||
.withPoints(fetchObjects(Point.class))
|
||||
.withPaths(fetchObjects(Path.class))
|
||||
.withLocationTypes(fetchObjects(LocationType.class))
|
||||
.withLocations(fetchObjects(Location.class))
|
||||
.withBlocks(fetchObjects(Block.class))
|
||||
.withVehicles(fetchObjects(Vehicle.class))
|
||||
.withVisualLayout(fetchObjects(VisualLayout.class).stream().findFirst().get());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlantModel(PlantModelCreationTO to)
|
||||
throws ObjectUnknownException,
|
||||
ObjectExistsException,
|
||||
IllegalStateException {
|
||||
requireNonNull(to, "to");
|
||||
|
||||
boolean kernelInOperating = kernel.getState() == Kernel.State.OPERATING;
|
||||
// If we are in state operating, change the kernel state before creating the plant model
|
||||
if (kernelInOperating) {
|
||||
kernel.setState(Kernel.State.MODELLING);
|
||||
}
|
||||
|
||||
String oldModelName = getModelName();
|
||||
emitModelEvent(oldModelName, to.getName(), true, false);
|
||||
|
||||
// Create the plant model
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.createPlantModelObjects(to);
|
||||
}
|
||||
|
||||
savePlantModel();
|
||||
|
||||
// If we were in state operating before, change the kernel state back to operating
|
||||
if (kernelInOperating) {
|
||||
kernel.setState(Kernel.State.OPERATING);
|
||||
}
|
||||
|
||||
emitModelEvent(oldModelName, to.getName(), true, true);
|
||||
notificationService.publishUserNotification(
|
||||
new UserNotification(
|
||||
"Kernel created model " + to.getName(),
|
||||
UserNotification.Level.INFORMATIONAL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
synchronized (globalSyncObject) {
|
||||
return plantModelManager.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getModelProperties()
|
||||
throws KernelRuntimeException {
|
||||
synchronized (globalSyncObject) {
|
||||
return plantModelManager.getProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLocationLock(TCSObjectReference<Location> ref, boolean locked)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setLocationLocked(ref, locked);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePathLock(TCSObjectReference<Path> ref, boolean locked)
|
||||
throws ObjectUnknownException,
|
||||
KernelRuntimeException {
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setPathLocked(ref, locked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an event for a Model change.
|
||||
*
|
||||
* @param oldModelName The old model name.
|
||||
* @param newModelName The new model name.
|
||||
* @param modelContentChanged Whether the model's content actually changed.
|
||||
* @param transitionFinished Whether the transition is finished or not.
|
||||
*/
|
||||
private void emitModelEvent(
|
||||
String oldModelName,
|
||||
String newModelName,
|
||||
boolean modelContentChanged,
|
||||
boolean transitionFinished
|
||||
) {
|
||||
requireNonNull(newModelName, "newModelName");
|
||||
|
||||
eventHandler.onEvent(
|
||||
new ModelTransitionEvent(
|
||||
oldModelName,
|
||||
newModelName,
|
||||
modelContentChanged,
|
||||
transitionFinished
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.opentcs.components.kernel.Query;
|
||||
import org.opentcs.components.kernel.QueryResponder;
|
||||
import org.opentcs.components.kernel.services.InternalQueryService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
|
||||
/**
|
||||
* The default implementation of the {@link InternalQueryService} interface.
|
||||
*/
|
||||
public class StandardQueryService
|
||||
implements
|
||||
InternalQueryService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The responders, by query type.
|
||||
*/
|
||||
private final Map<Class<? extends Query<?>>, QueryResponder> respondersByQueryType
|
||||
= new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
*/
|
||||
@Inject
|
||||
public StandardQueryService(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T query(Query<T> query) {
|
||||
requireNonNull(query, "query");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
QueryResponder responder = respondersByQueryType.get(query.getClass());
|
||||
|
||||
checkArgument(responder != null, "Query class not taken: %s", query.getClass().getName());
|
||||
return responder.query(query);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerResponder(
|
||||
@Nonnull
|
||||
Class<? extends Query<?>> clazz,
|
||||
@Nonnull
|
||||
QueryResponder responder
|
||||
) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(responder, "responder");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
checkArgument(
|
||||
!respondersByQueryType.containsKey(clazz),
|
||||
"Query class already taken: %s",
|
||||
clazz.getName()
|
||||
);
|
||||
|
||||
respondersByQueryType.put(clazz, responder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterResponder(
|
||||
@Nonnull
|
||||
Class<? extends Query<?>> clazz
|
||||
) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
respondersByQueryType.remove(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.access.KernelRuntimeException;
|
||||
import org.opentcs.components.kernel.Router;
|
||||
import org.opentcs.components.kernel.services.RouterService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Path;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.order.Route;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link RouterService} interface.
|
||||
*/
|
||||
public class StandardRouterService
|
||||
implements
|
||||
RouterService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The router.
|
||||
*/
|
||||
private final Router router;
|
||||
/**
|
||||
* The plant model manager.
|
||||
*/
|
||||
private final PlantModelManager plantModelManager;
|
||||
/**
|
||||
* The object service.
|
||||
*/
|
||||
private final TCSObjectService objectService;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param router The scheduler.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
* @param objectService The object service.
|
||||
*/
|
||||
@Inject
|
||||
public StandardRouterService(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
Router router,
|
||||
PlantModelManager plantModelManager,
|
||||
TCSObjectService objectService
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.router = requireNonNull(router, "router");
|
||||
this.plantModelManager = requireNonNull(plantModelManager, "plantModelManager");
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRoutingTopology(Set<TCSObjectReference<Path>> refs)
|
||||
throws KernelRuntimeException {
|
||||
synchronized (globalSyncObject) {
|
||||
router.updateRoutingTopology(
|
||||
refs.stream()
|
||||
.map(ref -> plantModelManager.getObjectRepo().getObject(Path.class, ref))
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TCSObjectReference<Point>, Route> computeRoutes(
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
TCSObjectReference<Point> sourcePointRef,
|
||||
Set<TCSObjectReference<Point>> destinationPointRefs,
|
||||
Set<TCSResourceReference<?>> resourcesToAvoid
|
||||
) {
|
||||
requireNonNull(vehicleRef, "vehicleRef");
|
||||
requireNonNull(sourcePointRef, "sourcePointRef");
|
||||
requireNonNull(destinationPointRefs, "destinationPointRefs");
|
||||
requireNonNull(resourcesToAvoid, "resourcesToAvoid");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
Map<TCSObjectReference<Point>, Route> result = new HashMap<>();
|
||||
Vehicle vehicle = objectService.fetchObject(Vehicle.class, vehicleRef);
|
||||
if (vehicle == null) {
|
||||
throw new ObjectUnknownException("Unknown vehicle: " + vehicleRef.getName());
|
||||
}
|
||||
Point sourcePoint = objectService.fetchObject(Point.class, sourcePointRef);
|
||||
if (sourcePoint == null) {
|
||||
throw new ObjectUnknownException("Unknown source point: " + sourcePointRef.getName());
|
||||
}
|
||||
for (TCSObjectReference<Point> dest : destinationPointRefs) {
|
||||
Point destinationPoint = objectService.fetchObject(Point.class, dest);
|
||||
if (destinationPoint == null) {
|
||||
throw new ObjectUnknownException("Unknown destination point: " + dest.getName());
|
||||
}
|
||||
result.put(
|
||||
dest,
|
||||
router.getRoute(vehicle, sourcePoint, destinationPoint, resourcesToAvoid)
|
||||
.orElse(null)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectHistory;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObject;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.kernel.workingset.TCSObjectManager;
|
||||
import org.opentcs.kernel.workingset.TCSObjectRepository;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link TCSObjectService} interface.
|
||||
*/
|
||||
public class StandardTCSObjectService
|
||||
implements
|
||||
TCSObjectService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The object manager.
|
||||
*/
|
||||
private final TCSObjectManager objectManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param objectManager The object manager.
|
||||
*/
|
||||
@Inject
|
||||
public StandardTCSObjectService(
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
TCSObjectManager objectManager
|
||||
) {
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.objectManager = requireNonNull(objectManager, "objectManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> T fetchObject(Class<T> clazz, TCSObjectReference<T> ref) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
return getObjectRepo().getObjectOrNull(clazz, ref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> T fetchObject(Class<T> clazz, String name) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
return getObjectRepo().getObjectOrNull(clazz, name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> Set<T> fetchObjects(Class<T> clazz) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
Set<T> objects = getObjectRepo().getObjects(clazz);
|
||||
Set<T> copies = new HashSet<>();
|
||||
for (T object : objects) {
|
||||
copies.add(object);
|
||||
}
|
||||
return copies;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TCSObject<T>> Set<T> fetchObjects(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
Predicate<? super T> predicate
|
||||
) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(predicate, "predicate");
|
||||
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
return getObjectRepo().getObjects(clazz, predicate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjectProperty(
|
||||
TCSObjectReference<?> ref,
|
||||
String key,
|
||||
@Nullable
|
||||
String value
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(key, "key");
|
||||
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
objectManager.setObjectProperty(ref, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendObjectHistoryEntry(TCSObjectReference<?> ref, ObjectHistory.Entry entry)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(entry, "entry");
|
||||
|
||||
synchronized (getGlobalSyncObject()) {
|
||||
objectManager.appendObjectHistoryEntry(ref, entry);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object getGlobalSyncObject() {
|
||||
return globalSyncObject;
|
||||
}
|
||||
|
||||
protected TCSObjectRepository getObjectRepo() {
|
||||
return objectManager.getObjectRepo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import org.opentcs.access.to.order.OrderSequenceCreationTO;
|
||||
import org.opentcs.access.to.order.TransportOrderCreationTO;
|
||||
import org.opentcs.components.kernel.services.InternalTransportOrderService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.components.kernel.services.TransportOrderService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectExistsException;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
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.kernel.workingset.PlantModelManager;
|
||||
import org.opentcs.kernel.workingset.TCSObjectRepository;
|
||||
import org.opentcs.kernel.workingset.TransportOrderPoolManager;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link TransportOrderService} interface.
|
||||
*/
|
||||
public class StandardTransportOrderService
|
||||
extends
|
||||
AbstractTCSObjectService
|
||||
implements
|
||||
InternalTransportOrderService {
|
||||
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* The container of all course model and transport order objects.
|
||||
*/
|
||||
private final TCSObjectRepository globalObjectPool;
|
||||
/**
|
||||
* The order pool manager.
|
||||
*/
|
||||
private final TransportOrderPoolManager orderPoolManager;
|
||||
/**
|
||||
* The plant model manager.
|
||||
*/
|
||||
private final PlantModelManager plantModelManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The tcs obejct service.
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param globalObjectPool The object pool to be used.
|
||||
* @param orderPoolManager The order pool manager to be used.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
*/
|
||||
@Inject
|
||||
public StandardTransportOrderService(
|
||||
TCSObjectService objectService,
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
TCSObjectRepository globalObjectPool,
|
||||
TransportOrderPoolManager orderPoolManager,
|
||||
PlantModelManager plantModelManager
|
||||
) {
|
||||
super(objectService);
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.globalObjectPool = requireNonNull(globalObjectPool, "globalObjectPool");
|
||||
this.orderPoolManager = requireNonNull(orderPoolManager, "orderPoolManager");
|
||||
this.plantModelManager = requireNonNull(plantModelManager, "plantModelManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markOrderSequenceFinished(TCSObjectReference<OrderSequence> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
OrderSequence seq = globalObjectPool.getObject(OrderSequence.class, ref);
|
||||
// Make sure we don't execute this if the sequence is already marked as finished, as that
|
||||
// would make it possible to trigger disposition of a vehicle at any given moment.
|
||||
if (seq.isFinished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
orderPoolManager.setOrderSequenceFinished(ref);
|
||||
// If the sequence was being processed by a vehicle, clear its back reference to the sequence
|
||||
// to make it available again and dispatch it.
|
||||
if (seq.getProcessingVehicle() != null) {
|
||||
Vehicle vehicle = globalObjectPool.getObject(
|
||||
Vehicle.class,
|
||||
seq.getProcessingVehicle()
|
||||
);
|
||||
plantModelManager.setVehicleOrderSequence(vehicle.getReference(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderSequenceFinishedIndex(TCSObjectReference<OrderSequence> ref, int index)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setOrderSequenceFinishedIndex(ref, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrderSequenceProcessingVehicle(
|
||||
TCSObjectReference<OrderSequence> seqRef,
|
||||
TCSObjectReference<Vehicle> vehicleRef
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(seqRef, "seqRef");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setOrderSequenceProcessingVehicle(seqRef, vehicleRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportOrderProcessingVehicle(
|
||||
TCSObjectReference<TransportOrder> orderRef,
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
List<DriveOrder> driveOrders
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
requireNonNull(orderRef, "orderRef");
|
||||
requireNonNull(driveOrders, "driveOrders");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setTransportOrderProcessingVehicle(orderRef, vehicleRef, driveOrders);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportOrderDriveOrders(
|
||||
TCSObjectReference<TransportOrder> ref,
|
||||
List<DriveOrder> driveOrders
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(driveOrders, "driveOrders");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setTransportOrderDriveOrders(ref, driveOrders);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportOrderNextDriveOrder(TCSObjectReference<TransportOrder> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setTransportOrderNextDriveOrder(ref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportOrderCurrentRouteStepIndex(
|
||||
TCSObjectReference<TransportOrder> ref,
|
||||
int index
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setTransportOrderCurrentRouteStepIndex(ref, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportOrderState(
|
||||
TCSObjectReference<TransportOrder> ref,
|
||||
TransportOrder.State state
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(state, "state");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setTransportOrderState(ref, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderSequence createOrderSequence(OrderSequenceCreationTO to) {
|
||||
requireNonNull(to, "to");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return orderPoolManager.createOrderSequence(to);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrder createTransportOrder(TransportOrderCreationTO to)
|
||||
throws ObjectUnknownException,
|
||||
ObjectExistsException {
|
||||
requireNonNull(to, "to");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return orderPoolManager.createTransportOrder(to);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markOrderSequenceComplete(TCSObjectReference<OrderSequence> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
OrderSequence seq = globalObjectPool.getObject(OrderSequence.class, ref);
|
||||
// Make sure we don't execute this if the sequence is already marked as finished, as that
|
||||
// would make it possible to trigger disposition of a vehicle at any given moment.
|
||||
if (seq.isComplete()) {
|
||||
return;
|
||||
}
|
||||
orderPoolManager.setOrderSequenceComplete(ref);
|
||||
// If there aren't any transport orders left to be processed as part of the sequence, mark
|
||||
// it as finished, too.
|
||||
if (seq.getNextUnfinishedOrder() == null) {
|
||||
orderPoolManager.setOrderSequenceFinished(ref);
|
||||
// If the sequence was being processed by a vehicle, clear its back reference to the
|
||||
// sequence to make it available again and dispatch it.
|
||||
if (seq.getProcessingVehicle() != null) {
|
||||
Vehicle vehicle = globalObjectPool.getObject(
|
||||
Vehicle.class,
|
||||
seq.getProcessingVehicle()
|
||||
);
|
||||
plantModelManager.setVehicleOrderSequence(vehicle.getReference(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportOrderIntendedVehicle(
|
||||
TCSObjectReference<TransportOrder> orderRef,
|
||||
TCSObjectReference<Vehicle> vehicleRef
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
requireNonNull(orderRef, "orderRef");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
orderPoolManager.setTransportOrderIntendedVehicle(orderRef, vehicleRef);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.services;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.opentcs.access.KernelRuntimeException;
|
||||
import org.opentcs.components.kernel.services.InternalVehicleService;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.components.kernel.services.VehicleService;
|
||||
import org.opentcs.customizations.kernel.GlobalSyncObject;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.BoundingBox;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.Pose;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
import org.opentcs.data.model.Triple;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.model.Vehicle.EnergyLevelThresholdSet;
|
||||
import org.opentcs.data.order.OrderSequence;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.drivers.vehicle.AdapterCommand;
|
||||
import org.opentcs.drivers.vehicle.LoadHandlingDevice;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
|
||||
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
|
||||
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
|
||||
import org.opentcs.kernel.extensions.controlcenter.vehicles.AttachmentManager;
|
||||
import org.opentcs.kernel.extensions.controlcenter.vehicles.VehicleEntry;
|
||||
import org.opentcs.kernel.extensions.controlcenter.vehicles.VehicleEntryPool;
|
||||
import org.opentcs.kernel.vehicles.LocalVehicleControllerPool;
|
||||
import org.opentcs.kernel.vehicles.VehicleCommAdapterRegistry;
|
||||
import org.opentcs.kernel.workingset.PlantModelManager;
|
||||
import org.opentcs.util.annotations.ScheduledApiChange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class is the standard implementation of the {@link VehicleService} interface.
|
||||
*/
|
||||
public class StandardVehicleService
|
||||
extends
|
||||
AbstractTCSObjectService
|
||||
implements
|
||||
InternalVehicleService {
|
||||
|
||||
/**
|
||||
* This class' logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StandardVehicleService.class);
|
||||
/**
|
||||
* A global object to be used for synchronization within the kernel.
|
||||
*/
|
||||
private final Object globalSyncObject;
|
||||
/**
|
||||
* A pool of vehicle controllers.
|
||||
*/
|
||||
private final LocalVehicleControllerPool vehicleControllerPool;
|
||||
/**
|
||||
* A pool of vehicle entries.
|
||||
*/
|
||||
private final VehicleEntryPool vehicleEntryPool;
|
||||
/**
|
||||
* The attachment manager.
|
||||
*/
|
||||
private final AttachmentManager attachmentManager;
|
||||
/**
|
||||
* The registry for all communication adapters.
|
||||
*/
|
||||
private final VehicleCommAdapterRegistry commAdapterRegistry;
|
||||
/**
|
||||
* The plant model manager.
|
||||
*/
|
||||
private final PlantModelManager plantModelManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The tcs object service.
|
||||
* @param globalSyncObject The kernel threads' global synchronization object.
|
||||
* @param vehicleControllerPool The controller pool to be used.
|
||||
* @param vehicleEntryPool The pool of vehicle entries to be used.
|
||||
* @param attachmentManager The attachment manager.
|
||||
* @param commAdapterRegistry The registry for all communication adapters.
|
||||
* @param plantModelManager The plant model manager to be used.
|
||||
*/
|
||||
@Inject
|
||||
public StandardVehicleService(
|
||||
TCSObjectService objectService,
|
||||
@GlobalSyncObject
|
||||
Object globalSyncObject,
|
||||
LocalVehicleControllerPool vehicleControllerPool,
|
||||
VehicleEntryPool vehicleEntryPool,
|
||||
AttachmentManager attachmentManager,
|
||||
VehicleCommAdapterRegistry commAdapterRegistry,
|
||||
PlantModelManager plantModelManager
|
||||
) {
|
||||
super(objectService);
|
||||
this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
|
||||
this.vehicleControllerPool = requireNonNull(vehicleControllerPool, "vehicleControllerPool");
|
||||
this.vehicleEntryPool = requireNonNull(vehicleEntryPool, "vehicleEntryPool");
|
||||
this.attachmentManager = requireNonNull(attachmentManager, "attachmentManager");
|
||||
this.commAdapterRegistry = requireNonNull(commAdapterRegistry, "commAdapterRegistry");
|
||||
this.plantModelManager = requireNonNull(plantModelManager, "plantModelManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleEnergyLevel(TCSObjectReference<Vehicle> ref, int energyLevel)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleEnergyLevel(ref, energyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleLoadHandlingDevices(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
List<LoadHandlingDevice> devices
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(devices, "devices");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleLoadHandlingDevices(ref, devices);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleNextPosition(
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
TCSObjectReference<Point> pointRef
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(vehicleRef, "vehicleRef");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleNextPosition(vehicleRef, pointRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleOrderSequence(
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
TCSObjectReference<OrderSequence> sequenceRef
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(vehicleRef, "vehicleRef");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleOrderSequence(vehicleRef, sequenceRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void updateVehicleOrientationAngle(TCSObjectReference<Vehicle> ref, double angle)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
Vehicle previousState = plantModelManager.getObjectRepo().getObject(Vehicle.class, ref);
|
||||
plantModelManager.setVehiclePose(ref, previousState.getPose().withOrientationAngle(angle));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehiclePosition(
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
TCSObjectReference<Point> pointRef
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(vehicleRef, "vehicleRef");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
LOG.debug("Vehicle {} has reached point {}.", vehicleRef, pointRef);
|
||||
plantModelManager.setVehiclePosition(vehicleRef, pointRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void updateVehiclePrecisePosition(TCSObjectReference<Vehicle> ref, Triple position)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
Vehicle previousState = plantModelManager.getObjectRepo().getObject(Vehicle.class, ref);
|
||||
plantModelManager.setVehiclePose(ref, previousState.getPose().withPosition(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehiclePose(TCSObjectReference<Vehicle> ref, Pose pose)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(pose, "pose");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehiclePose(ref, pose);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleProcState(TCSObjectReference<Vehicle> ref, Vehicle.ProcState state)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(state, "state");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
LOG.debug("Updating procState of vehicle {} to {}...", ref.getName(), state);
|
||||
plantModelManager.setVehicleProcState(ref, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleRechargeOperation(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
String rechargeOperation
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(rechargeOperation, "rechargeOperation");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleRechargeOperation(ref, rechargeOperation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleClaimedResources(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
List<Set<TCSResourceReference<?>>> resources
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(resources, "resources");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleClaimedResources(ref, resources);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleAllocatedResources(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
List<Set<TCSResourceReference<?>>> resources
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(resources, "resources");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleAllocatedResources(ref, resources);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleState(TCSObjectReference<Vehicle> ref, Vehicle.State state)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(state, "state");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleState(ref, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void updateVehicleLength(TCSObjectReference<Vehicle> ref, int length)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleBoundingBox(
|
||||
ref,
|
||||
plantModelManager.getObjectRepo().getObject(Vehicle.class, ref)
|
||||
.getBoundingBox()
|
||||
.withLength(length)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleTransportOrder(
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
TCSObjectReference<TransportOrder> orderRef
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(vehicleRef, "vehicleRef");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleTransportOrder(vehicleRef, orderRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachCommAdapter(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
VehicleCommAdapterDescription description
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(description, "description");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
attachmentManager.attachAdapterToVehicle(
|
||||
ref.getName(),
|
||||
commAdapterRegistry.findFactoryFor(description)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCommAdapter(TCSObjectReference<Vehicle> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
VehicleEntry entry = vehicleEntryPool.getEntryFor(ref.getName());
|
||||
if (entry == null) {
|
||||
throw new IllegalArgumentException("No vehicle entry found for" + ref.getName());
|
||||
}
|
||||
|
||||
entry.getCommAdapter().disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCommAdapter(TCSObjectReference<Vehicle> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
VehicleEntry entry = vehicleEntryPool.getEntryFor(ref.getName());
|
||||
if (entry == null) {
|
||||
throw new IllegalArgumentException("No vehicle entry found for " + ref.getName());
|
||||
}
|
||||
|
||||
entry.getCommAdapter().enable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleAttachmentInformation fetchAttachmentInformation(TCSObjectReference<Vehicle> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
return attachmentManager.getAttachmentInformation(ref.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleProcessModelTO fetchProcessModel(TCSObjectReference<Vehicle> ref)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
VehicleEntry entry = vehicleEntryPool.getEntryFor(ref.getName());
|
||||
if (entry == null) {
|
||||
throw new IllegalArgumentException("No vehicle entry found for " + ref.getName());
|
||||
}
|
||||
|
||||
return entry.getCommAdapter().createTransferableProcessModel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommAdapterCommand(TCSObjectReference<Vehicle> ref, AdapterCommand command)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(command, "command");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
vehicleControllerPool
|
||||
.getVehicleController(ref.getName())
|
||||
.sendCommAdapterCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommAdapterMessage(TCSObjectReference<Vehicle> ref, Object message)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
vehicleControllerPool
|
||||
.getVehicleController(ref.getName())
|
||||
.sendCommAdapterMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleIntegrationLevel(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
Vehicle.IntegrationLevel integrationLevel
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(integrationLevel, "integrationLevel");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
Vehicle vehicle = fetchObject(Vehicle.class, ref);
|
||||
|
||||
if (vehicle.isProcessingOrder()
|
||||
&& (integrationLevel == Vehicle.IntegrationLevel.TO_BE_IGNORED
|
||||
|| integrationLevel == Vehicle.IntegrationLevel.TO_BE_NOTICED)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"%s: Cannot change integration level to %s while processing orders.",
|
||||
vehicle.getName(),
|
||||
integrationLevel.name()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
plantModelManager.setVehicleIntegrationLevel(ref, integrationLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehiclePaused(TCSObjectReference<Vehicle> ref, boolean paused)
|
||||
throws ObjectUnknownException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehiclePaused(ref, paused);
|
||||
|
||||
vehicleControllerPool.getVehicleController(ref.getName()).onVehiclePaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleEnergyLevelThresholdSet(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
EnergyLevelThresholdSet energyLevelThresholdSet
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(energyLevelThresholdSet, "energyLevelThresholdSet");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleEnergyLevelThresholdSet(ref, energyLevelThresholdSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleAllowedOrderTypes(
|
||||
TCSObjectReference<Vehicle> ref,
|
||||
Set<String> allowedOrderTypes
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(allowedOrderTypes, "allowedOrderTypes");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleAllowedOrderTypes(ref, allowedOrderTypes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ScheduledApiChange(when = "7.0", details = "Envelope key will become non-null.")
|
||||
public void updateVehicleEnvelopeKey(TCSObjectReference<Vehicle> ref, String envelopeKey)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
Vehicle vehicle = fetchObject(Vehicle.class, ref);
|
||||
if (vehicle.isProcessingOrder()
|
||||
|| !vehicle.getClaimedResources().isEmpty()
|
||||
|| !vehicle.getAllocatedResources().isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Updating a vehicle's envelope key while the vehicle is processing an order or "
|
||||
+ "claiming/allocating resources is currently not supported."
|
||||
);
|
||||
}
|
||||
|
||||
plantModelManager.setVehicleEnvelopeKey(ref, envelopeKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVehicleBoundingBox(TCSObjectReference<Vehicle> ref, BoundingBox boundingBox)
|
||||
throws ObjectUnknownException,
|
||||
KernelRuntimeException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(boundingBox, "boundingBox");
|
||||
|
||||
synchronized (globalSyncObject) {
|
||||
plantModelManager.setVehicleBoundingBox(ref, boundingBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,772 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
import static org.opentcs.util.Assertions.checkState;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.SequencedCollection;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.Path;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.TCSResource;
|
||||
import org.opentcs.drivers.vehicle.MovementCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Tracks processing of movement commands.
|
||||
* <p>
|
||||
* After the movement commands for a new or updated drive order have been passed to
|
||||
* {@link #driveOrderUpdated(SequencedCollection)}, movement commands and their corresponding
|
||||
* resources are expected to be processed in the following order:
|
||||
* <ol>
|
||||
* <li>{@link #allocationRequested(Set)}</li>
|
||||
* <li>{@link #allocationConfirmed(Set)}</li>
|
||||
* <li>{@link #commandSent(MovementCommand)}</li>
|
||||
* <li>{@link #commandExecuted(MovementCommand)}</li>
|
||||
* <li>{@link #allocationReleased(Set)}</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class CommandProcessingTracker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommandProcessingTracker.class);
|
||||
/**
|
||||
* The queue of commands that still need to be sent to the communication adapter.
|
||||
*/
|
||||
private final Deque<CommandResourcePair> futureCommands = new ArrayDeque<>();
|
||||
/**
|
||||
* A command (the next one for the current drive order) that has yet to be sent to the
|
||||
* communication adapter.
|
||||
*/
|
||||
private CommandResourcePair pendingCommand;
|
||||
/**
|
||||
* The state the pending command is currently in.
|
||||
*/
|
||||
private PendingCommandState pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
/**
|
||||
* The queue of commands that have been sent to the communication adapter.
|
||||
*/
|
||||
private final Deque<CommandResourcePair> sentCommands = new ArrayDeque<>();
|
||||
/**
|
||||
* The command that was executed last.
|
||||
*/
|
||||
private MovementCommand lastCommandExecuted;
|
||||
/**
|
||||
* The queue of resource sets that the vehicle has already passed.
|
||||
* <p>
|
||||
* For every movement command that is executed, two elements are added to this queue - one element
|
||||
* containing only the path (if any) associated with the respective movement command, and another
|
||||
* element containing the point and the location (if any) associated with the respective movement
|
||||
* command.
|
||||
* </p>
|
||||
*/
|
||||
private final Deque<Set<TCSResource<?>>> passedResources = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public CommandProcessingTracker() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears (i.e. resets) this instance.
|
||||
*/
|
||||
public void clear() {
|
||||
futureCommands.clear();
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
sentCommands.clear();
|
||||
lastCommandExecuted = null;
|
||||
passedResources.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a drive order was updated (either because a new one was assigned to a vehicle or
|
||||
* the one being currently processed was updated due to rerouting).
|
||||
*
|
||||
* @param movementCommands The collection of movement commands that belong to the updated drive
|
||||
* order.
|
||||
*/
|
||||
public void driveOrderUpdated(
|
||||
@Nonnull
|
||||
SequencedCollection<MovementCommand> movementCommands
|
||||
) {
|
||||
requireNonNull(movementCommands, "movementCommands");
|
||||
|
||||
if (isDriveOrderFinished()) {
|
||||
// The movement commands belong to a new drive order.
|
||||
futureCommands.addAll(toCommandResourcePairs(movementCommands));
|
||||
}
|
||||
else {
|
||||
// The movement commands belong to the same drive order we are currently processing.
|
||||
futureCommands.clear();
|
||||
if (pendingCommandState == PendingCommandState.ALLOCATION_PENDING) {
|
||||
// With drive order updates, any pending resource allocation is reset.
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
}
|
||||
|
||||
futureCommands.addAll(toCommandResourcePairs(movementCommands));
|
||||
|
||||
// The current drive order got updated but our queue of future commands now contains commands
|
||||
// that have already been processed, so discard these.
|
||||
discardProcessedFutureCommands();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a drive order was aborted.
|
||||
*
|
||||
* @param immediate Indicates whether the drive order was aborted immediately or regularly.
|
||||
*/
|
||||
public void driveOrderAborted(boolean immediate) {
|
||||
if (immediate) {
|
||||
futureCommands.clear();
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
sentCommands.clear();
|
||||
}
|
||||
else {
|
||||
futureCommands.clear();
|
||||
if (pendingCommandState != PendingCommandState.SENDING_PENDING) {
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any movement commands that are yet to be sent to the communication adapter.
|
||||
*
|
||||
* @return {@code true} if there are commands to be sent, otherwise {@code false}.
|
||||
*/
|
||||
public boolean hasCommandsToBeSent() {
|
||||
return !futureCommands.isEmpty()
|
||||
|| pendingCommandState == PendingCommandState.ALLOCATION_PENDING
|
||||
|| pendingCommandState == PendingCommandState.SENDING_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current drive order is considered finished.
|
||||
*
|
||||
* @return {@code true}, if there are no more commands that need to be sent to the communication
|
||||
* adapter and all commands already sent have been reported as executed.
|
||||
*/
|
||||
public boolean isDriveOrderFinished() {
|
||||
return futureCommands.isEmpty()
|
||||
&& pendingCommand == null
|
||||
&& sentCommands.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a vehicle's allocation was reset.
|
||||
*
|
||||
* @param resources The (only) set of resources that the vehicle now allocates.
|
||||
*/
|
||||
public void allocationReset(
|
||||
@Nonnull
|
||||
Set<TCSResource<?>> resources
|
||||
) {
|
||||
requireNonNull(resources, "resources");
|
||||
|
||||
// Clear resources that have been passed previously as they are no longer allocated.
|
||||
passedResources.clear();
|
||||
|
||||
if (!resources.isEmpty()) {
|
||||
// Now, the given resources are allocated and considered as the new passed resources.
|
||||
passedResources.add(resources);
|
||||
}
|
||||
|
||||
// Discard the pending command since pending allocations are reset and resources that have
|
||||
// already been allocated are freed when allocation is reset.
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
|
||||
// Clear sent commands since we don't expect a vehicle to report these commands as executed
|
||||
// after allocation has been reset.
|
||||
sentCommands.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a resource allocation was requested.
|
||||
*
|
||||
* @param resources The resources for which allocation was requested.
|
||||
*/
|
||||
public void allocationRequested(
|
||||
@Nonnull
|
||||
Set<TCSResource<?>> resources
|
||||
) {
|
||||
requireNonNull(resources, "resources");
|
||||
checkArgument(
|
||||
!futureCommands.isEmpty(),
|
||||
"Allocation requested, but there are no future commands: %s",
|
||||
resources
|
||||
);
|
||||
checkArgument(
|
||||
Objects.equals(futureCommands.peek().getResources(), resources),
|
||||
"Resource set is not head of future commands: %s (futureCommands=%s)",
|
||||
resources,
|
||||
futureCommands
|
||||
);
|
||||
checkArgument(
|
||||
pendingCommandState == PendingCommandState.UNDEFINED,
|
||||
"pendingCommandState is not '%s' but '%s'",
|
||||
PendingCommandState.UNDEFINED,
|
||||
pendingCommandState
|
||||
);
|
||||
|
||||
pendingCommand = futureCommands.remove();
|
||||
pendingCommandState = PendingCommandState.ALLOCATION_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a resource allocation was confirmed.
|
||||
*
|
||||
* @param resources The resources for which allocation was confirmed.
|
||||
*/
|
||||
public void allocationConfirmed(
|
||||
@Nonnull
|
||||
Set<TCSResource<?>> resources
|
||||
) {
|
||||
requireNonNull(resources, "resources");
|
||||
checkArgument(
|
||||
pendingCommandState == PendingCommandState.ALLOCATION_PENDING,
|
||||
"pendingCommandState is not '%s' but '%s'",
|
||||
PendingCommandState.ALLOCATION_PENDING,
|
||||
pendingCommandState
|
||||
);
|
||||
checkArgument(
|
||||
Objects.equals(pendingCommand.getResources(), resources),
|
||||
"Resource set does not belong to pending command: %s (pendingCommand=%s)",
|
||||
resources,
|
||||
pendingCommand
|
||||
);
|
||||
|
||||
pendingCommandState = PendingCommandState.SENDING_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a resource allocation was revoked.
|
||||
*
|
||||
* @param resources The resources for which allocation was revoked.
|
||||
*/
|
||||
public void allocationRevoked(
|
||||
@Nonnull
|
||||
Set<TCSResource<?>> resources
|
||||
) {
|
||||
requireNonNull(resources, "resources");
|
||||
checkArgument(
|
||||
pendingCommandState == PendingCommandState.SENDING_PENDING,
|
||||
"pendingCommandState is not '%s' but '%s'",
|
||||
PendingCommandState.SENDING_PENDING,
|
||||
pendingCommandState
|
||||
);
|
||||
checkArgument(
|
||||
Objects.equals(pendingCommand.getResources(), resources),
|
||||
"Resource set does not belong to pending command: %s (pendingCommand=%s)",
|
||||
resources,
|
||||
pendingCommand
|
||||
);
|
||||
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a movement command won't be sent to the communication adapter.
|
||||
*
|
||||
* @param movementCommand The movement command.
|
||||
*/
|
||||
public void commandSendingStopped(
|
||||
@Nonnull
|
||||
MovementCommand movementCommand
|
||||
) {
|
||||
requireNonNull(movementCommand, "movementCommand");
|
||||
checkArgument(
|
||||
pendingCommandState == PendingCommandState.SENDING_PENDING,
|
||||
"pendingCommandState is not '%s' but '%s'",
|
||||
PendingCommandState.SENDING_PENDING,
|
||||
pendingCommandState
|
||||
);
|
||||
checkArgument(
|
||||
Objects.equals(pendingCommand.getMovementCommand(), movementCommand),
|
||||
"Movement command does not belong to pending command: %s (pendingCommand=%s)",
|
||||
movementCommand,
|
||||
pendingCommand
|
||||
);
|
||||
|
||||
pendingCommandState = PendingCommandState.WONT_SEND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a movement command was sent to the communication adapter.
|
||||
*
|
||||
* @param movementCommand The movement command.
|
||||
*/
|
||||
public void commandSent(
|
||||
@Nonnull
|
||||
MovementCommand movementCommand
|
||||
) {
|
||||
requireNonNull(movementCommand, "movementCommand");
|
||||
checkArgument(
|
||||
pendingCommandState == PendingCommandState.SENDING_PENDING,
|
||||
"pendingCommandState is not '%s' but '%s'",
|
||||
PendingCommandState.SENDING_PENDING,
|
||||
pendingCommandState
|
||||
);
|
||||
checkArgument(
|
||||
Objects.equals(pendingCommand.getMovementCommand(), movementCommand),
|
||||
"Movement command does not belong to pending command: %s (pendingCommand=%s)",
|
||||
movementCommand,
|
||||
pendingCommand
|
||||
);
|
||||
|
||||
sentCommands.add(pendingCommand);
|
||||
pendingCommand = null;
|
||||
pendingCommandState = PendingCommandState.UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a movement command was reported as executed.
|
||||
*
|
||||
* @param movementCommand The movement command.
|
||||
*/
|
||||
public void commandExecuted(
|
||||
@Nonnull
|
||||
MovementCommand movementCommand
|
||||
) {
|
||||
requireNonNull(movementCommand, "movementCommand");
|
||||
checkArgument(
|
||||
!sentCommands.isEmpty(),
|
||||
"Movement command reported as executed, but no commands have been sent: %s",
|
||||
movementCommand
|
||||
);
|
||||
MovementCommand expectedCommand = sentCommands.peek().getMovementCommand();
|
||||
checkArgument(
|
||||
Objects.equals(expectedCommand, movementCommand),
|
||||
"%s: Unexpected movement command executed: %s != %s",
|
||||
movementCommand.getTransportOrder().getProcessingVehicle().getName(),
|
||||
movementCommand,
|
||||
expectedCommand
|
||||
);
|
||||
|
||||
CommandResourcePair executedCommand = sentCommands.remove();
|
||||
lastCommandExecuted = executedCommand.getMovementCommand();
|
||||
passedResources.add(extractPath(executedCommand.getResources()));
|
||||
passedResources.add(extractPointAndLocation(executedCommand.getResources()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a resource allocation was released.
|
||||
*
|
||||
* @param resources The resources for which allocation was released.
|
||||
*/
|
||||
public void allocationReleased(
|
||||
@Nonnull
|
||||
Set<TCSResource<?>> resources
|
||||
) {
|
||||
requireNonNull(resources, "resources");
|
||||
checkArgument(
|
||||
Objects.equals(passedResources.peek(), resources),
|
||||
"Resource set is not head of passed resources: %s (passedResources=%s)",
|
||||
resources,
|
||||
passedResources
|
||||
);
|
||||
|
||||
passedResources.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue of resources claimed by the vehicle.
|
||||
* <p>
|
||||
* The order of the elements in this queue corresponds to the order in which they will be
|
||||
* allocated, with the first element in the queue (i.e. its head) corresponding to the resources
|
||||
* that will be allocated next.
|
||||
* </p>
|
||||
*
|
||||
* @return The queue of resources claimed by the vehicle.
|
||||
*/
|
||||
@Nonnull
|
||||
public Deque<Set<TCSResource<?>>> getClaimedResources() {
|
||||
Deque<Set<TCSResource<?>>> claimedResources = new ArrayDeque<>();
|
||||
|
||||
if (pendingCommandState == PendingCommandState.ALLOCATION_PENDING) {
|
||||
claimedResources.add(pendingCommand.getResources());
|
||||
}
|
||||
|
||||
futureCommands.stream()
|
||||
.map(CommandResourcePair::getResources)
|
||||
.forEach(claimedResources::add);
|
||||
|
||||
return claimedResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue of resources allocated by the vehicle.
|
||||
* <p>
|
||||
* The order of the elements in this queue corresponds to the order in which they were allocated,
|
||||
* with the first element in the queue (i.e. its head) corresponding to the oldest resources.
|
||||
* </p>
|
||||
*
|
||||
* @return The queue of resources allocated by the vehicle.
|
||||
*/
|
||||
@Nonnull
|
||||
public Deque<Set<TCSResource<?>>> getAllocatedResources() {
|
||||
Deque<Set<TCSResource<?>>> allocatedResources = new ArrayDeque<>();
|
||||
|
||||
allocatedResources.addAll(passedResources);
|
||||
allocatedResources.addAll(getAllocatedResourcesAhead());
|
||||
|
||||
return allocatedResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue of resources allocated by the vehicle that lie in front of it.
|
||||
* <p>
|
||||
* The order of the elements in this queue corresponds to the order in which they were allocated,
|
||||
* with the first element in the queue (i.e. its head) corresponding to the resources right in
|
||||
* front of the vehicle.
|
||||
* </p>
|
||||
*
|
||||
* @return The queue of allocated resources in front of the vehicle.
|
||||
*/
|
||||
@Nonnull
|
||||
public Deque<Set<TCSResource<?>>> getAllocatedResourcesAhead() {
|
||||
Deque<Set<TCSResource<?>>> allocatedResourcesAhead = new ArrayDeque<>();
|
||||
|
||||
sentCommands.stream()
|
||||
.map(CommandResourcePair::getResources)
|
||||
.forEach(allocatedResourcesAhead::add);
|
||||
|
||||
if (pendingCommandState == PendingCommandState.SENDING_PENDING
|
||||
|| pendingCommandState == PendingCommandState.WONT_SEND) {
|
||||
allocatedResourcesAhead.add(pendingCommand.getResources());
|
||||
}
|
||||
|
||||
return allocatedResourcesAhead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the movement command for which resource allocation is currently pending.
|
||||
*
|
||||
* @return An optional containing the movement command for which resource allocation is currently
|
||||
* pending or {@link Optional#empty()} if there is no such command.
|
||||
* @see #getAllocationPendingResources()
|
||||
*/
|
||||
public Optional<MovementCommand> getAllocationPendingCommand() {
|
||||
if (pendingCommandState == PendingCommandState.ALLOCATION_PENDING) {
|
||||
return Optional.of(pendingCommand.getMovementCommand());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resources for which allocation is currently pending.
|
||||
*
|
||||
* @return An optional containing the resources for which allocation is currently pending or
|
||||
* {@link Optional#empty()} if there are no such resources.
|
||||
* @see #getAllocationPendingCommand()
|
||||
*/
|
||||
public Optional<Set<TCSResource<?>>> getAllocationPendingResources() {
|
||||
if (pendingCommandState == PendingCommandState.ALLOCATION_PENDING) {
|
||||
return Optional.of(pendingCommand.getResources());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the movement command for which resources have already been allocated but which is yet
|
||||
* to be sent to the communication adapter.
|
||||
*
|
||||
* @return An optional containing the movement command for which resources have already been
|
||||
* allocated but which is yet to be sent to the communication adapter or {@link Optional#empty()}
|
||||
* if there is no such command.
|
||||
*/
|
||||
public Optional<MovementCommand> getSendingPendingCommand() {
|
||||
if (pendingCommandState == PendingCommandState.SENDING_PENDING) {
|
||||
return Optional.of(pendingCommand.getMovementCommand());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue of movement commands that have been sent to the communication adapter but
|
||||
* have not yet been reported as executed.
|
||||
*
|
||||
* @return The queue of movement commands that have been sent to the communication adapter but
|
||||
* have not yet been reported as executed.
|
||||
*/
|
||||
public Deque<MovementCommand> getSentCommands() {
|
||||
return sentCommands.stream()
|
||||
.map(CommandResourcePair::getMovementCommand)
|
||||
.collect(Collectors.toCollection(ArrayDeque::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the movement command that was executed last.
|
||||
*
|
||||
* @return The movement command that was executed last.
|
||||
*/
|
||||
public Optional<MovementCommand> getLastCommandExecuted() {
|
||||
return Optional.ofNullable(lastCommandExecuted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the movement command for which resources are to be allocated next.
|
||||
*
|
||||
* @return The movement command for which resources are to be allocated next.
|
||||
* @see #getNextAllocationResources()
|
||||
*/
|
||||
public Optional<MovementCommand> getNextAllocationCommand() {
|
||||
return Optional.ofNullable(futureCommands.peek())
|
||||
.map(CommandResourcePair::getMovementCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resources that are to be allocated next.
|
||||
*
|
||||
* @return The resources that are to be allocated next.
|
||||
* @see #getNextAllocationCommand()
|
||||
*/
|
||||
public Optional<Set<TCSResource<?>>> getNextAllocationResources() {
|
||||
return Optional.ofNullable(futureCommands.peek())
|
||||
.map(CommandResourcePair::getResources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are resources for which allocation was requested but is yet to be confirmed.
|
||||
*
|
||||
* @return {@code true} if there are resources for which allocation was requested but is yet to
|
||||
* be confirmed, otherwise {@code false}.
|
||||
*/
|
||||
public boolean isWaitingForAllocation() {
|
||||
return pendingCommandState == PendingCommandState.ALLOCATION_PENDING;
|
||||
}
|
||||
|
||||
private SequencedCollection<CommandResourcePair> toCommandResourcePairs(
|
||||
SequencedCollection<MovementCommand> movementCommands
|
||||
) {
|
||||
return movementCommands.stream()
|
||||
.map(command -> new CommandResourcePair(command, getNeededResources(command)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void discardProcessedFutureCommands() {
|
||||
MovementCommand lastCommandProcessed = lastCommandProcessed();
|
||||
if (futureCommands.isEmpty()) {
|
||||
// There are no commands to be discarded.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fromSameDriveOrder(lastCommandProcessed, futureCommands.peek().getMovementCommand())) {
|
||||
// If the last processed command is from a different drive order, there is nothing to be
|
||||
// discarded. This is the case, for example, if the vehicle didn't yet process the very first
|
||||
// movement command of a new drive order.
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug(
|
||||
"{}: Discarding future commands up to '{}' (inclusively): {}",
|
||||
lastCommandProcessed.getTransportOrder().getProcessingVehicle().getName(),
|
||||
lastCommandProcessed,
|
||||
futureCommands
|
||||
);
|
||||
// Discard commands up to lastCommandProcessed...
|
||||
while (!futureCommands.isEmpty()
|
||||
&& !lastCommandProcessed.equalsInMovement(futureCommands.peek().getMovementCommand())) {
|
||||
futureCommands.remove();
|
||||
}
|
||||
|
||||
checkState(
|
||||
!futureCommands.isEmpty(),
|
||||
"%s: Future commands should not be empty.",
|
||||
lastCommandProcessed.getTransportOrder().getProcessingVehicle().getName(),
|
||||
lastCommandProcessed
|
||||
);
|
||||
checkState(
|
||||
lastCommandProcessed.equalsInMovement(futureCommands.peek().getMovementCommand()),
|
||||
"%s: Last command processed is not head of future commands: %s (futureCommands=%s)",
|
||||
lastCommandProcessed.getTransportOrder().getProcessingVehicle().getName(),
|
||||
lastCommandProcessed,
|
||||
futureCommands
|
||||
);
|
||||
|
||||
// ...and also discard lastCommandProcessed itself.
|
||||
futureCommands.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last movement command that has been processed in a way that is relevant in the
|
||||
* context of rerouting.
|
||||
* <p>
|
||||
* Generally, a movement command is processed in multiple stages. It is:
|
||||
* <ol>
|
||||
* <li>Added to the <code>futureCommands</code> queue (when a transport order for the vehicle
|
||||
* is set or updated).</li>
|
||||
* <li>Removed from the <code>futureCommands</code> queue and set as the
|
||||
* <code>pendingCommand</code> (when allocation for the resources needed for executing the next
|
||||
* command has been requested).</li>
|
||||
* <li>Unset as the <code>pendingCommand</code> and added to the
|
||||
* <code>commandsSent</code> queue (when the command has been handed over to the vehicle driver).
|
||||
* </li>
|
||||
* <li>Removed from the <code>commandsSent</code> queue and set as the
|
||||
* <code>lastCommandExecuted</code> (when the driver reports that the command has been executed)
|
||||
* </li>
|
||||
* </ol>
|
||||
* </p>
|
||||
* <p>
|
||||
* The earliest stage a movement command can be in that is relevant in the context of rerouting is
|
||||
* when it is set as the <code>pendingCommand</code> with the state
|
||||
* {@link PendingCommandState#SENDING_PENDING}. At this stage, the resources for the command have
|
||||
* already been (successfully) allocated, and it will either be handed over to the vehicle driver
|
||||
* or discarded. Rerouting should therefore take place from this command (or rather the respective
|
||||
* step) at the earliest.
|
||||
* </p>
|
||||
* <p>
|
||||
* <code>pendingCommand</code> with the state {@link PendingCommandState#ALLOCATION_PENDING}
|
||||
* (as well as everything prior to that) is not relevant here, as the allocation for corresponding
|
||||
* resources is still pending at this stage, and all pending allocations are cleared upon
|
||||
* rerouting.
|
||||
* </p>
|
||||
*
|
||||
* @return A movement command or {@code null} if there is no movement command that has been
|
||||
* processed by this vehicle controller in a way that is relevant in the context of rerouting.
|
||||
*/
|
||||
@Nullable
|
||||
private MovementCommand lastCommandProcessed() {
|
||||
return Optional.ofNullable(pendingCommand)
|
||||
.or(() -> Optional.ofNullable(sentCommands.peekLast()))
|
||||
.map(CommandResourcePair::getMovementCommand)
|
||||
.orElse(lastCommandExecuted);
|
||||
}
|
||||
|
||||
private boolean fromSameDriveOrder(
|
||||
@Nullable
|
||||
MovementCommand commandA,
|
||||
@Nullable
|
||||
MovementCommand commandB
|
||||
) {
|
||||
return commandA != null
|
||||
&& commandB != null
|
||||
&& Objects.equals(commandA.getDriveOrder(), commandB.getDriveOrder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of resources needed for executing the given command.
|
||||
*
|
||||
* @param cmd The command for which to return the needed resources.
|
||||
* @return A set of resources needed for executing the given command.
|
||||
*/
|
||||
@Nonnull
|
||||
private Set<TCSResource<?>> getNeededResources(MovementCommand cmd) {
|
||||
requireNonNull(cmd, "cmd");
|
||||
|
||||
Set<TCSResource<?>> result = new HashSet<>();
|
||||
result.add(cmd.getStep().getDestinationPoint());
|
||||
if (cmd.getStep().getPath() != null) {
|
||||
result.add(cmd.getStep().getPath());
|
||||
}
|
||||
if (cmd.getOpLocation() != null) {
|
||||
result.add(cmd.getOpLocation());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<TCSResource<?>> extractPointAndLocation(Set<TCSResource<?>> resources) {
|
||||
return resources.stream()
|
||||
.filter(resource -> resource instanceof Point || resource instanceof Location)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<TCSResource<?>> extractPath(Set<TCSResource<?>> resources) {
|
||||
return resources.stream()
|
||||
.filter(resource -> resource instanceof Path)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the states the pending command can be in.
|
||||
*/
|
||||
private enum PendingCommandState {
|
||||
/**
|
||||
* The state is undefined. This is the case when the pending command is {@code null}.
|
||||
*/
|
||||
UNDEFINED,
|
||||
/**
|
||||
* Allocation of the resources for the pending command was requested but is yet to be confirmed.
|
||||
*/
|
||||
ALLOCATION_PENDING,
|
||||
/**
|
||||
* Allocation of the resources for the pending command was confirmed but the command is yet to
|
||||
* be sent to the communication adapter.
|
||||
*/
|
||||
SENDING_PENDING,
|
||||
/**
|
||||
* The pending command won't be sent to the communication adapter but the corresponding
|
||||
* resources are still allocated.
|
||||
*/
|
||||
WONT_SEND;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a {@link MovementCommand} and the {@link TCSResource}s that are associated with
|
||||
* it.
|
||||
*/
|
||||
private static class CommandResourcePair {
|
||||
|
||||
private final MovementCommand movementCommand;
|
||||
private final Set<TCSResource<?>> resources;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param movementCommand The movement command.
|
||||
* @param resources The set of resources associated with the movement command.
|
||||
*/
|
||||
CommandResourcePair(MovementCommand movementCommand, Set<TCSResource<?>> resources) {
|
||||
this.movementCommand = requireNonNull(movementCommand, "movementCommand");
|
||||
this.resources = requireNonNull(resources, "resources");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the movement command.
|
||||
*
|
||||
* @return The movement command.
|
||||
*/
|
||||
public MovementCommand getMovementCommand() {
|
||||
return movementCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of resources associated with the movement command.
|
||||
*
|
||||
* @return The set of resources associated with the movement command.
|
||||
*/
|
||||
public Set<TCSResource<?>> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommandResourcePair{" +
|
||||
"movementCommand=" + movementCommand +
|
||||
", resources=" + resources +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,166 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.opentcs.components.kernel.services.InternalVehicleService;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
|
||||
import org.opentcs.drivers.vehicle.VehicleController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Maintains associations of {@link Vehicle}, {@link VehicleController} and
|
||||
* {@link VehicleCommAdapter}.
|
||||
*/
|
||||
public final class DefaultVehicleControllerPool
|
||||
implements
|
||||
LocalVehicleControllerPool {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultVehicleControllerPool.class);
|
||||
/**
|
||||
* The vehicle service.
|
||||
*/
|
||||
private final InternalVehicleService vehicleService;
|
||||
/**
|
||||
* A factory for vehicle managers.
|
||||
*/
|
||||
private final VehicleControllerFactory vehicleManagerFactory;
|
||||
/**
|
||||
* The currently existing/assigned managers, mapped by the names of the
|
||||
* corresponding vehicles.
|
||||
*/
|
||||
private final Map<String, PoolEntry> poolEntries = new HashMap<>();
|
||||
/**
|
||||
* Indicates whether this components is initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new StandardVehicleManagerPool.
|
||||
*
|
||||
* @param vehicleService The vehicle service.
|
||||
* @param vehicleManagerFactory A factory for vehicle managers.
|
||||
*/
|
||||
@Inject
|
||||
public DefaultVehicleControllerPool(
|
||||
InternalVehicleService vehicleService,
|
||||
VehicleControllerFactory vehicleManagerFactory
|
||||
) {
|
||||
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
|
||||
this.vehicleManagerFactory = requireNonNull(vehicleManagerFactory, "vehicleManagerFactory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (initialized) {
|
||||
LOG.debug("Already initialized, doing nothing.");
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!initialized) {
|
||||
LOG.debug("Not initialized, doing nothing.");
|
||||
return;
|
||||
}
|
||||
// Detach all vehicles and reset their positions.
|
||||
for (PoolEntry curEntry : poolEntries.values()) {
|
||||
curEntry.vehicleController.terminate();
|
||||
Vehicle vehicle = vehicleService.fetchObject(Vehicle.class, curEntry.vehicleName);
|
||||
vehicleService.updateVehiclePosition(vehicle.getReference(), null);
|
||||
}
|
||||
poolEntries.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void attachVehicleController(
|
||||
String vehicleName,
|
||||
VehicleCommAdapter commAdapter
|
||||
) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
requireNonNull(commAdapter, "commAdapter");
|
||||
|
||||
if (poolEntries.containsKey(vehicleName)) {
|
||||
LOG.warn("manager already attached, doing nothing");
|
||||
return;
|
||||
}
|
||||
|
||||
Vehicle vehicle = vehicleService.fetchObject(Vehicle.class, vehicleName);
|
||||
checkArgument(vehicle != null, "No such vehicle: %s", vehicleName);
|
||||
|
||||
VehicleController controller = vehicleManagerFactory.createVehicleController(
|
||||
vehicle,
|
||||
commAdapter
|
||||
);
|
||||
PoolEntry poolEntry = new PoolEntry(vehicleName, controller);
|
||||
poolEntries.put(vehicleName, poolEntry);
|
||||
controller.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void detachVehicleController(String vehicleName) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
|
||||
LOG.debug("Detaching controller for vehicle {}...", vehicleName);
|
||||
PoolEntry poolEntry = poolEntries.remove(vehicleName);
|
||||
if (poolEntry == null) {
|
||||
LOG.debug("A vehicle named '{}' is not attached to a controller.", vehicleName);
|
||||
return;
|
||||
}
|
||||
// Clean up - mark vehicle state and adapter state as unknown.
|
||||
poolEntry.vehicleController.terminate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleController getVehicleController(String vehicleName) {
|
||||
requireNonNull(vehicleName, "vehicleName");
|
||||
|
||||
PoolEntry poolEntry = poolEntries.get(vehicleName);
|
||||
return poolEntry == null ? new NullVehicleController(vehicleName) : poolEntry.vehicleController;
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry in this vehicle manager pool.
|
||||
*/
|
||||
private static final class PoolEntry {
|
||||
|
||||
/**
|
||||
* The name of the vehicle managed.
|
||||
*/
|
||||
private final String vehicleName;
|
||||
/**
|
||||
* The vehicle controller associated with the vehicle.
|
||||
*/
|
||||
private final VehicleController vehicleController;
|
||||
|
||||
/**
|
||||
* Creates a new pool entry.
|
||||
*
|
||||
* @param name The name of the vehicle managed.
|
||||
* @param manager The vehicle manager associated with the vehicle.
|
||||
* @param controller The VehicleController
|
||||
*/
|
||||
private PoolEntry(String name, VehicleController controller) {
|
||||
vehicleName = requireNonNull(name, "name");
|
||||
vehicleController = requireNonNull(controller, "controller");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
|
||||
import org.opentcs.drivers.vehicle.VehicleControllerPool;
|
||||
|
||||
/**
|
||||
* Manages the attachment of vehicle controllers to vehicles and comm adapters.
|
||||
*/
|
||||
public interface LocalVehicleControllerPool
|
||||
extends
|
||||
VehicleControllerPool,
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* Associates a vehicle controller with a named vehicle and a comm adapter.
|
||||
*
|
||||
* @param vehicleName The name of the vehicle.
|
||||
* @param commAdapter The communication adapter that is going to control the physical vehicle.
|
||||
*/
|
||||
void attachVehicleController(String vehicleName, VehicleCommAdapter commAdapter)
|
||||
throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Disassociates a vehicle control and a comm adapter from a vehicle.
|
||||
*
|
||||
* @param vehicleName The name of the vehicle from which to detach.
|
||||
*/
|
||||
void detachVehicleController(String vehicleName);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.opentcs.components.kernel.services.TCSObjectService;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.order.DriveOrder;
|
||||
import org.opentcs.data.order.Route;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.drivers.vehicle.MovementCommand;
|
||||
|
||||
/**
|
||||
* Provides methods for mapping {@link DriveOrder}s to a list of {@link MovementCommand}s.
|
||||
*/
|
||||
public class MovementCommandMapper {
|
||||
|
||||
private final TCSObjectService objectService;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectService The object service to use.
|
||||
*/
|
||||
@Inject
|
||||
public MovementCommandMapper(TCSObjectService objectService) {
|
||||
this.objectService = requireNonNull(objectService, "objectService");
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the given {@link DriveOrder} to a corresponding list of {@link MovementCommand}s.
|
||||
*
|
||||
* @param driveOrder The {@link DriveOrder} to map.
|
||||
* @param transportOrder The {@link TransportOrder} the drive order belongs to.
|
||||
* @return A list of {@link MovementCommand}s.
|
||||
*/
|
||||
public List<MovementCommand> toMovementCommands(
|
||||
DriveOrder driveOrder,
|
||||
TransportOrder transportOrder
|
||||
) {
|
||||
requireNonNull(driveOrder, "driveOrder");
|
||||
requireNonNull(transportOrder, "transportOrder");
|
||||
|
||||
// Gather information from the drive order.
|
||||
String op = driveOrder.getDestination().getOperation();
|
||||
Route orderRoute = driveOrder.getRoute();
|
||||
Point finalDestination = orderRoute.getFinalDestinationPoint();
|
||||
Location finalDestinationLocation
|
||||
= objectService.fetchObject(
|
||||
Location.class,
|
||||
driveOrder.getDestination().getDestination().getName()
|
||||
);
|
||||
Map<String, String> destProperties = driveOrder.getDestination().getProperties();
|
||||
|
||||
List<MovementCommand> result = new ArrayList<>(orderRoute.getSteps().size());
|
||||
// Create a movement command for every step in the drive order's route.
|
||||
Iterator<Route.Step> stepIter = orderRoute.getSteps().iterator();
|
||||
while (stepIter.hasNext()) {
|
||||
Route.Step curStep = stepIter.next();
|
||||
boolean isFinalMovement = !stepIter.hasNext();
|
||||
|
||||
String operation = isFinalMovement ? op : MovementCommand.NO_OPERATION;
|
||||
Location location = isFinalMovement ? finalDestinationLocation : null;
|
||||
|
||||
result.add(
|
||||
new MovementCommand(
|
||||
transportOrder,
|
||||
driveOrder,
|
||||
curStep,
|
||||
operation,
|
||||
location,
|
||||
isFinalMovement,
|
||||
finalDestinationLocation,
|
||||
finalDestination,
|
||||
op,
|
||||
mergeProperties(transportOrder.getProperties(), destProperties)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the properties of a transport order and those of a drive order.
|
||||
*
|
||||
* @param orderProps The properties of a transport order.
|
||||
* @param destProps The properties of a drive order destination.
|
||||
* @return The merged properties.
|
||||
*/
|
||||
private Map<String, String> mergeProperties(
|
||||
Map<String, String> orderProps,
|
||||
Map<String, String> destProps
|
||||
) {
|
||||
requireNonNull(orderProps, "orderProps");
|
||||
requireNonNull(destProps, "destProps");
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.putAll(orderProps);
|
||||
result.putAll(destProps);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import org.opentcs.data.order.Route.Step;
|
||||
import org.opentcs.drivers.vehicle.MovementCommand;
|
||||
|
||||
/**
|
||||
* Provides methods for comparing movements represented by {@link MovementCommand}s and
|
||||
* {@link Step}s.
|
||||
*/
|
||||
public class MovementComparisons {
|
||||
|
||||
private MovementComparisons() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the two given lists of steps, ignoring rerouting-related properties.
|
||||
*
|
||||
* @param stepsA The first list of steps.
|
||||
* @param stepsB The second list of steps.
|
||||
* @return {@code true}, if the given lists of steps are equal (ignoring rerouting-related
|
||||
* properties), otherwise {@code false}.
|
||||
*/
|
||||
public static boolean equalsInMovement(List<Step> stepsA, List<Step> stepsB) {
|
||||
requireNonNull(stepsA, "stepsA");
|
||||
requireNonNull(stepsB, "stepsB");
|
||||
|
||||
if (stepsA.size() != stepsB.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Iterator<Step> itStepsA = stepsA.iterator();
|
||||
Iterator<Step> itStepsB = stepsB.iterator();
|
||||
|
||||
while (itStepsA.hasNext()) {
|
||||
if (!itStepsA.next().equalsInMovement(itStepsB.next())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import org.opentcs.data.model.TCSResource;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.drivers.vehicle.AdapterCommand;
|
||||
import org.opentcs.drivers.vehicle.MovementCommand;
|
||||
import org.opentcs.drivers.vehicle.VehicleController;
|
||||
import org.opentcs.util.ExplainedBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Null-object implementation of {@link VehicleController}.
|
||||
*/
|
||||
public class NullVehicleController
|
||||
implements
|
||||
VehicleController {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NullVehicleController.class);
|
||||
/**
|
||||
* The associated vehicle's name.
|
||||
*/
|
||||
private final String vehicleName;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param vehicleName The associated vehicle's name.
|
||||
*/
|
||||
public NullVehicleController(
|
||||
@Nonnull
|
||||
String vehicleName
|
||||
) {
|
||||
this.vehicleName = requireNonNull(vehicleName, "vehicleName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransportOrder(TransportOrder newOrder) {
|
||||
LOG.warn("No comm adapter attached to vehicle {}", vehicleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abortTransportOrder(boolean immediate) {
|
||||
LOG.warn("No comm adapter attached to vehicle {}", vehicleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplainedBoolean canProcess(TransportOrder order) {
|
||||
return new ExplainedBoolean(false, "NullVehicleController");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommAdapterMessage(Object message) {
|
||||
LOG.warn("No comm adapter attached to vehicle {}", vehicleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommAdapterCommand(AdapterCommand command) {
|
||||
LOG.warn("No comm adapter attached to vehicle {}", vehicleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queue<MovementCommand> getCommandsSent() {
|
||||
LOG.warn("No comm adapter attached to vehicle {}", vehicleName);
|
||||
return new ArrayDeque<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVehiclePaused(boolean paused) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MovementCommand> getInteractionsPendingCommand() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mayAllocateNow(Set<TCSResource<?>> resources) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkState;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.access.to.peripherals.PeripheralJobCreationTO;
|
||||
import org.opentcs.access.to.peripherals.PeripheralOperationCreationTO;
|
||||
import org.opentcs.components.kernel.services.PeripheralJobService;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Describes an interaction with any number of peripheral devices.
|
||||
* <p>
|
||||
* An interaction is always associated with a {@link MovementCommand} and contains
|
||||
* {@link PeripheralOperation}s for which {@link PeripheralJob}s are created once the interaction
|
||||
* is started.
|
||||
* </p>
|
||||
* <p>
|
||||
* In case there are no operations with the completion required flag set, the interaction is marked
|
||||
* as finished immediately after it was started.
|
||||
* In case there are operations with the completion required flag set, the interaction is only
|
||||
* marked as finished after all corresponding jobs have been processed by the respective peripheral
|
||||
* device.
|
||||
* </p>
|
||||
* <p>
|
||||
* Once the interaction is finished, an interaction-specific callback is executed.
|
||||
* </p>
|
||||
*/
|
||||
public class PeripheralInteraction {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PeripheralInteraction.class);
|
||||
/**
|
||||
* The reference to the vehicle that's interacting with peripheral devices.
|
||||
*/
|
||||
private final TCSObjectReference<Vehicle> vehicleRef;
|
||||
/**
|
||||
* A reference to the transport order this interaction is related to.
|
||||
*/
|
||||
private final TCSObjectReference<TransportOrder> orderRef;
|
||||
/**
|
||||
* The movement command this interaction is associated with.
|
||||
*/
|
||||
private final MovementCommand movementCommand;
|
||||
/**
|
||||
* The operations that jobs have to be created for.
|
||||
*/
|
||||
private final List<PeripheralOperation> operations;
|
||||
/**
|
||||
* The jobs that are required to be finished in order for this interaction itself to be marked as
|
||||
* finished.
|
||||
*/
|
||||
private final List<PeripheralJob> pendingJobsWithCompletionRequired = new ArrayList<>();
|
||||
/**
|
||||
* The peripheral job service to use for creating jobs.
|
||||
*/
|
||||
private final PeripheralJobService peripheralJobService;
|
||||
/**
|
||||
* The reservation token to use for creating jobs.
|
||||
*/
|
||||
private final String reservationToken;
|
||||
/**
|
||||
* The callback that is to be executed if the interaction succeeds.
|
||||
*/
|
||||
private Runnable interactionSucceededCallback;
|
||||
/**
|
||||
* The callback that is executed if the interaction fails.
|
||||
*/
|
||||
private Runnable interactionFailedCallback;
|
||||
/**
|
||||
* The state of the interaction.
|
||||
*/
|
||||
private State state = State.PRISTINE;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param vehicleRef The reference to the vehicle that's interacting with peripheral devices.
|
||||
* @param orderRef A reference to the transport order that this interaction is related to.
|
||||
* @param movementCommand The movement command this interaction is associated with.
|
||||
* @param operations The operations that jobs have to be created for.
|
||||
* @param peripheralJobService The peripheral job service to use for creating jobs.
|
||||
* @param reservationToken The reservation token to use for creating jobs.
|
||||
*/
|
||||
public PeripheralInteraction(
|
||||
@Nonnull
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
@Nonnull
|
||||
TCSObjectReference<TransportOrder> orderRef,
|
||||
@Nonnull
|
||||
MovementCommand movementCommand,
|
||||
@Nonnull
|
||||
List<PeripheralOperation> operations,
|
||||
@Nonnull
|
||||
PeripheralJobService peripheralJobService,
|
||||
@Nonnull
|
||||
String reservationToken
|
||||
) {
|
||||
this.vehicleRef = requireNonNull(vehicleRef, "vehicleRef");
|
||||
this.orderRef = requireNonNull(orderRef, "orderRef");
|
||||
this.movementCommand = requireNonNull(movementCommand, "movementCommand");
|
||||
this.operations = requireNonNull(operations, "operations");
|
||||
this.peripheralJobService = requireNonNull(peripheralJobService, "peripheralJobService");
|
||||
this.reservationToken = requireNonNull(reservationToken, "reservationToken");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts this peripheral interaction.
|
||||
*
|
||||
* @param interactionSucceededCallback The callback that is to be executed if the interaction
|
||||
* succeeds.
|
||||
* @param interactionFailedCallback The callback that is to be executed if the interaction fails.
|
||||
*/
|
||||
public void start(
|
||||
@Nonnull
|
||||
Runnable interactionSucceededCallback,
|
||||
@Nonnull
|
||||
Runnable interactionFailedCallback
|
||||
) {
|
||||
this.interactionSucceededCallback = requireNonNull(
|
||||
interactionSucceededCallback,
|
||||
"interactionSucceededCallback"
|
||||
);
|
||||
this.interactionFailedCallback = requireNonNull(
|
||||
interactionFailedCallback,
|
||||
"interactionFailedCallback"
|
||||
);
|
||||
|
||||
LOG.debug(
|
||||
"{}: Starting peripheral interaction for movement to {}",
|
||||
vehicleRef.getName(),
|
||||
movementCommand.getStep().getDestinationPoint().getName()
|
||||
);
|
||||
for (PeripheralOperation operation : operations) {
|
||||
PeripheralJob job = createPeripheralJob(operation);
|
||||
if (operation.isCompletionRequired()) {
|
||||
pendingJobsWithCompletionRequired.add(job);
|
||||
}
|
||||
}
|
||||
|
||||
state = State.STARTED;
|
||||
|
||||
if (pendingJobsWithCompletionRequired.isEmpty()) {
|
||||
onInteractionFinished();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs this interaction that a peripheral job (that might be of interest for this interaction)
|
||||
* has been finished.
|
||||
*
|
||||
* @param job The peripheral job that has been finished.
|
||||
*/
|
||||
public void onPeripheralJobFinished(
|
||||
@Nonnull
|
||||
PeripheralJob job
|
||||
) {
|
||||
requireNonNull(job, "job");
|
||||
if (pendingJobsWithCompletionRequired.remove(job)
|
||||
&& pendingJobsWithCompletionRequired.isEmpty()) {
|
||||
// The last pending job has been finished.
|
||||
onInteractionFinished();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs this interaction that a peripheral job (that might be of interest for this interaction)
|
||||
* has failed.
|
||||
*
|
||||
* @param job The peripheral job that has failed.
|
||||
*/
|
||||
public void onPeripheralJobFailed(
|
||||
@Nonnull
|
||||
PeripheralJob job
|
||||
) {
|
||||
requireNonNull(job, "job");
|
||||
if (pendingJobsWithCompletionRequired.contains(job)) {
|
||||
// As soon as one of the jobs for this interaction fails, the entire interaction itself is
|
||||
// considered failed. At this point, we're no longer interested in any of the pending jobs and
|
||||
// don't expect any other job to be reported as finished or failed. Therefore, simply forget
|
||||
// all pending jobs and mark the interaction as failed. This also ensures that the callback
|
||||
// for the failed interaction is called only once.
|
||||
pendingJobsWithCompletionRequired.clear();
|
||||
onInteractionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the movement command this interaction is associated with.
|
||||
*
|
||||
* @return The movement command this interaction is associated with.
|
||||
*/
|
||||
public MovementCommand getMovementCommand() {
|
||||
return movementCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the interaction is finished.
|
||||
*
|
||||
* @return Whether the interaction is finished.
|
||||
*/
|
||||
public boolean isFinished() {
|
||||
return hasState(State.FINISHED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the interaction has failed.
|
||||
*
|
||||
* @return Whether the interaction has failed.
|
||||
*/
|
||||
public boolean isFailed() {
|
||||
return hasState(State.FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the interaction is in the given state.
|
||||
*
|
||||
* @param state The state.
|
||||
* @return Whether the interaction is in the given state.
|
||||
*/
|
||||
public boolean hasState(State state) {
|
||||
return this.state == state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this interaction has some operations that are required to be completed.
|
||||
*
|
||||
* @return Whether this interaction has some operations that are required to be completed.
|
||||
*/
|
||||
public boolean hasRequiredOperations() {
|
||||
return operations.stream()
|
||||
.anyMatch(PeripheralOperation::isCompletionRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of operations that are required to be completed and that haven't been
|
||||
* completed yet.
|
||||
*
|
||||
* @return A list of operations.
|
||||
*/
|
||||
public List<PeripheralOperation> getPendingRequiredOperations() {
|
||||
// If we're already done interacting with the peripheral device, there cannot be any pending
|
||||
// operations.
|
||||
if (hasState(State.FINISHED)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (!hasRequiredOperations()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (!pendingJobsWithCompletionRequired.isEmpty()) {
|
||||
// The interaction is still ongoing. Jobs are not yet finished or have even failed.
|
||||
return pendingJobsWithCompletionRequired.stream()
|
||||
.map(job -> job.getPeripheralOperation())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// The interaction is still ongoing but no jobs have been created (yet) for the required
|
||||
// operations.
|
||||
return operations.stream()
|
||||
.filter(PeripheralOperation::isCompletionRequired)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void onInteractionFinished() {
|
||||
checkState(interactionSucceededCallback != null, "The interaction hasn't been started yet.");
|
||||
checkState(!hasState(State.FAILED), "The interaction has already been marked as failed.");
|
||||
checkState(!hasState(State.FINISHED), "The interaction has already been marked as finished.");
|
||||
state = State.FINISHED;
|
||||
|
||||
LOG.debug(
|
||||
"{}: Peripheral interaction finished for movement to {}",
|
||||
vehicleRef.getName(),
|
||||
movementCommand.getStep().getDestinationPoint().getName()
|
||||
);
|
||||
interactionSucceededCallback.run();
|
||||
}
|
||||
|
||||
private PeripheralJob createPeripheralJob(PeripheralOperation operation) {
|
||||
return peripheralJobService.createPeripheralJob(
|
||||
new PeripheralJobCreationTO(
|
||||
"Job-",
|
||||
reservationToken,
|
||||
new PeripheralOperationCreationTO(
|
||||
operation.getOperation(),
|
||||
operation.getLocation().getName()
|
||||
)
|
||||
.withExecutionTrigger(operation.getExecutionTrigger())
|
||||
.withCompletionRequired(operation.isCompletionRequired())
|
||||
)
|
||||
.withIncompleteName(true)
|
||||
.withRelatedVehicleName(vehicleRef.getName())
|
||||
.withRelatedTransportOrderName(orderRef.getName())
|
||||
);
|
||||
}
|
||||
|
||||
private void onInteractionFailed() {
|
||||
checkState(interactionFailedCallback != null, "The interaction hasn't been started yet.");
|
||||
checkState(!hasState(State.FINISHED), "The interaction has already been marked as finished.");
|
||||
checkState(!hasState(State.FAILED), "The interaction has already been marked as failed.");
|
||||
state = State.FAILED;
|
||||
|
||||
LOG.debug(
|
||||
"{}: Peripheral interaction failed for movement to {}",
|
||||
vehicleRef.getName(),
|
||||
movementCommand.getStep().getDestinationPoint().getName()
|
||||
);
|
||||
interactionFailedCallback.run();
|
||||
}
|
||||
|
||||
public enum State {
|
||||
/**
|
||||
* The interaction is initialized and yet to be started.
|
||||
*/
|
||||
PRISTINE,
|
||||
/**
|
||||
* The interaction was started.
|
||||
*/
|
||||
STARTED,
|
||||
/**
|
||||
* The interaction was finished.
|
||||
* All the required operations (if any) have been finished successfully.
|
||||
*/
|
||||
FINISHED,
|
||||
/**
|
||||
* The interaction has failed.
|
||||
* At least one of the required operations failed.
|
||||
*/
|
||||
FAILED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
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<Vehicle> 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<MovementCommand, PeripheralInteraction> 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<MovementCommand, PeripheralInteraction> 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<Vehicle> 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<TransportOrder> orderRef,
|
||||
MovementCommand movementCommand
|
||||
) {
|
||||
Path path = movementCommand.getStep().getPath();
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<PeripheralOperation.ExecutionTrigger, List<PeripheralOperation>> 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<PeripheralOperation> preMovementOperations
|
||||
= operations.get(PeripheralOperation.ExecutionTrigger.AFTER_ALLOCATION);
|
||||
if (!preMovementOperations.isEmpty()) {
|
||||
preMovementInteractions.put(
|
||||
movementCommand,
|
||||
new PeripheralInteraction(
|
||||
vehicleRef,
|
||||
orderRef,
|
||||
movementCommand,
|
||||
preMovementOperations,
|
||||
peripheralJobService,
|
||||
reservationToken
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
List<PeripheralOperation> 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<String, List<PeripheralOperation>> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.opentcs.data.model.Path;
|
||||
import org.opentcs.data.model.TCSResource;
|
||||
|
||||
/**
|
||||
* Utility methods for resource-related computations.
|
||||
*/
|
||||
public class ResourceMath {
|
||||
|
||||
/**
|
||||
* Prevents instantiation.
|
||||
*/
|
||||
private ResourceMath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of resource sets that could be freed based on the given vehicle length.
|
||||
*
|
||||
* @param resourcesPassed A list of passed resource sets still allocated for a vehicle, from
|
||||
* oldest to youngest, including those for the vehicle's current position (= the last set in the
|
||||
* list).
|
||||
* @param vehicleLength The vehicle's length. Must be a positive value.
|
||||
* @return The number of resource sets from {@code resourcesPassed} that could be freed because
|
||||
* they are not covered by the vehicle's length any more.
|
||||
*/
|
||||
public static int freeableResourceSetCount(
|
||||
@Nonnull
|
||||
List<Set<TCSResource<?>>> resourcesPassed,
|
||||
long vehicleLength
|
||||
) {
|
||||
requireNonNull(resourcesPassed, "resourcesPassed");
|
||||
checkArgument(vehicleLength > 0, "vehicleLength <= 0");
|
||||
|
||||
// We want to iterate over the passed resources from youngest (= current position) to oldest, so
|
||||
// we reverse the list here.
|
||||
List<Set<TCSResource<?>>> reversedPassedResources = new ArrayList<>(resourcesPassed);
|
||||
Collections.reverse(reversedPassedResources);
|
||||
|
||||
long remainingRequiredLength = vehicleLength;
|
||||
int result = 0;
|
||||
for (Set<TCSResource<?>> curSet : reversedPassedResources) {
|
||||
if (remainingRequiredLength > 0) {
|
||||
remainingRequiredLength -= requiredLength(curSet);
|
||||
}
|
||||
else {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long requiredLength(Set<TCSResource<?>> resources) {
|
||||
return resources.stream()
|
||||
.filter(resource -> resource instanceof Path)
|
||||
.mapToLong(resource -> ((Path) resource).getLength())
|
||||
.sum();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.opentcs.data.model.TCSResource;
|
||||
|
||||
/**
|
||||
* A vehicle's resources, split into resources the vehicle has already passed (including the
|
||||
* resources for the vehicle's current position) and resources that still lay ahead of it.
|
||||
*/
|
||||
public class SplitResources {
|
||||
|
||||
private final List<Set<TCSResource<?>>> resourcesPassed;
|
||||
private final List<Set<TCSResource<?>>> resourcesAhead;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param resourcesPassed The passed resources, including the ones for the vehicle's current
|
||||
* position.
|
||||
* @param resourcesAhead The resources ahead of the vehicle.
|
||||
*/
|
||||
public SplitResources(
|
||||
@Nonnull
|
||||
List<Set<TCSResource<?>>> resourcesPassed,
|
||||
@Nonnull
|
||||
List<Set<TCSResource<?>>> resourcesAhead
|
||||
) {
|
||||
this.resourcesPassed = requireNonNull(resourcesPassed, "resourcesPassed");
|
||||
this.resourcesAhead = requireNonNull(resourcesAhead, "resourcesAhead");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resources the vehicle has already passed, from oldest to youngest, with the
|
||||
* youngest being the resources for the vehicle's current position.
|
||||
*
|
||||
* @return The resources the vehicle has already passed, from oldest to youngest, with the
|
||||
* youngest being the resources for the vehicle's current position.
|
||||
*/
|
||||
public List<Set<TCSResource<?>>> getResourcesPassed() {
|
||||
return resourcesPassed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resources ahead of the vehicle, from oldest to youngest.
|
||||
*
|
||||
* @return The resources ahead of the vehicle, from oldest to youngest.
|
||||
*/
|
||||
public List<Set<TCSResource<?>>> getResourcesAhead() {
|
||||
return resourcesAhead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance created from the given iterable of resources, split at the element that
|
||||
* contains the given delimiter (resources).
|
||||
*
|
||||
* @param resourceSets The iterable of resources to be split, from oldest to youngest.
|
||||
* @param delimiter The delimiter / resources for the vehicle's current position.
|
||||
* @return A new instance created from the given iterable of resources, split at the element that
|
||||
* contains the given delimiter (resources).
|
||||
*/
|
||||
public static SplitResources from(
|
||||
@Nonnull
|
||||
Iterable<Set<TCSResource<?>>> resourceSets,
|
||||
@Nonnull
|
||||
Set<TCSResource<?>> delimiter
|
||||
) {
|
||||
requireNonNull(resourceSets, "resourceSets");
|
||||
requireNonNull(delimiter, "delimiter");
|
||||
|
||||
List<Set<TCSResource<?>>> resourcesPassed = new ArrayList<>();
|
||||
List<Set<TCSResource<?>>> resourcesAhead = new ArrayList<>();
|
||||
List<Set<TCSResource<?>>> resourcesToPutIn = resourcesPassed;
|
||||
|
||||
for (Set<TCSResource<?>> curSet : resourceSets) {
|
||||
resourcesToPutIn.add(curSet);
|
||||
|
||||
if (!delimiter.isEmpty() && curSet.containsAll(delimiter)) {
|
||||
resourcesToPutIn = resourcesAhead;
|
||||
}
|
||||
}
|
||||
|
||||
return new SplitResources(resourcesPassed, resourcesAhead);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.access.LocalKernel;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapterFactory;
|
||||
import org.opentcs.virtualvehicle.LoopbackCommunicationAdapterDescription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A registry for all communication adapters in the system.
|
||||
*/
|
||||
public class VehicleCommAdapterRegistry
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VehicleCommAdapterRegistry.class);
|
||||
/**
|
||||
* The registered factories. Uses a comparator to sort the loopback driver to the end.
|
||||
*/
|
||||
private final Map<VehicleCommAdapterDescription, VehicleCommAdapterFactory> factories
|
||||
= new TreeMap<>((f1, f2) -> {
|
||||
if (f1 instanceof LoopbackCommunicationAdapterDescription
|
||||
&& f2 instanceof LoopbackCommunicationAdapterDescription) {
|
||||
return 0;
|
||||
}
|
||||
if (f1 instanceof LoopbackCommunicationAdapterDescription) {
|
||||
return 1;
|
||||
}
|
||||
else if (f2 instanceof LoopbackCommunicationAdapterDescription) {
|
||||
return -1;
|
||||
}
|
||||
return f1.getDescription().compareTo(f2.getDescription());
|
||||
});
|
||||
/**
|
||||
* Indicates whether this component is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new registry.
|
||||
*
|
||||
* @param kernel A reference to the local kernel.
|
||||
* @param factories The comm adapter factories.
|
||||
*/
|
||||
@Inject
|
||||
public VehicleCommAdapterRegistry(LocalKernel kernel, Set<VehicleCommAdapterFactory> factories) {
|
||||
requireNonNull(kernel, "kernel");
|
||||
|
||||
for (VehicleCommAdapterFactory factory : factories) {
|
||||
LOG.info("Setting up communication adapter factory: {}", factory.getClass().getName());
|
||||
this.factories.put(factory.getDescription(), factory);
|
||||
}
|
||||
|
||||
checkState(!factories.isEmpty(), "No adapter factories found.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (initialized) {
|
||||
LOG.debug("Already initialized.");
|
||||
return;
|
||||
}
|
||||
for (VehicleCommAdapterFactory factory : factories.values()) {
|
||||
factory.initialize();
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!initialized) {
|
||||
LOG.debug("Not initialized.");
|
||||
return;
|
||||
}
|
||||
for (VehicleCommAdapterFactory factory : factories.values()) {
|
||||
factory.terminate();
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered factories that can provide communication adapters.
|
||||
*
|
||||
* @return All registered factories that can provide communication adapters.
|
||||
*/
|
||||
public List<VehicleCommAdapterFactory> getFactories() {
|
||||
return new ArrayList<>(factories.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the factory for the given description.
|
||||
*
|
||||
* @param description The description to get the factory for.
|
||||
* @return The factory for the given description.
|
||||
*/
|
||||
public VehicleCommAdapterFactory findFactoryFor(VehicleCommAdapterDescription description) {
|
||||
requireNonNull(description, "description");
|
||||
checkArgument(
|
||||
factories.get(description) != null,
|
||||
"No factory for description %s",
|
||||
description
|
||||
);
|
||||
|
||||
return factories.get(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of factories that can provide communication adapters for the
|
||||
* given vehicle.
|
||||
*
|
||||
* @param vehicle The vehicle to find communication adapters/factories for.
|
||||
* @return A set of factories that can provide communication adapters for the
|
||||
* given vehicle.
|
||||
*/
|
||||
public List<VehicleCommAdapterFactory> findFactoriesFor(Vehicle vehicle) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return factories.values().stream()
|
||||
.filter(factory -> factory.providesAdapterFor(vehicle))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
|
||||
/**
|
||||
* A factory for various components related to a vehicle controller.
|
||||
*/
|
||||
public interface VehicleControllerComponentsFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link PeripheralInteractor} instance for the given vehicle.
|
||||
*
|
||||
* @param vehicleRef The vehicle.
|
||||
* @return A new peripheral interactor.
|
||||
*/
|
||||
PeripheralInteractor createPeripheralInteractor(TCSObjectReference<Vehicle> vehicleRef);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles;
|
||||
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleCommAdapter;
|
||||
|
||||
/**
|
||||
* A factory for <code>VehicleManager</code> instances.
|
||||
*/
|
||||
public interface VehicleControllerFactory {
|
||||
|
||||
/**
|
||||
* Creates a new vehicle controller for the given vehicle and communication adapter.
|
||||
*
|
||||
* @param vehicle The vehicle.
|
||||
* @param commAdapter The communication adapter.
|
||||
* @return A new vehicle controller.
|
||||
*/
|
||||
DefaultVehicleController createVehicleController(Vehicle vehicle, VehicleCommAdapter commAdapter);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles.transformers;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
import org.opentcs.data.model.Pose;
|
||||
import org.opentcs.data.model.Triple;
|
||||
import org.opentcs.drivers.vehicle.IncomingPoseTransformer;
|
||||
|
||||
/**
|
||||
* Transforms {@link Pose}s by subtracting offsets in a given
|
||||
* {@link CoordinateSystemTransformation}.
|
||||
*/
|
||||
public class CoordinateSystemIncomingPoseTransformer
|
||||
implements
|
||||
IncomingPoseTransformer {
|
||||
|
||||
private final CoordinateSystemTransformation transformation;
|
||||
|
||||
public CoordinateSystemIncomingPoseTransformer(
|
||||
@Nonnull
|
||||
CoordinateSystemTransformation transformation
|
||||
) {
|
||||
this.transformation = requireNonNull(transformation, "transformation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose apply(
|
||||
@Nonnull
|
||||
Pose pose
|
||||
) {
|
||||
requireNonNull(pose, "pose");
|
||||
|
||||
return pose
|
||||
.withPosition(transformTriple(pose.getPosition()))
|
||||
.withOrientationAngle(
|
||||
(pose.getOrientationAngle() - transformation.getOffsetOrientation()) % 360.0
|
||||
);
|
||||
}
|
||||
|
||||
private Triple transformTriple(Triple triple) {
|
||||
return Optional.ofNullable(triple)
|
||||
.map(
|
||||
originalTriple -> new Triple(
|
||||
originalTriple.getX() - transformation.getOffsetX(),
|
||||
originalTriple.getY() - transformation.getOffsetY(),
|
||||
originalTriple.getZ() - transformation.getOffsetZ()
|
||||
)
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles.transformers;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.Pose;
|
||||
import org.opentcs.data.model.Triple;
|
||||
import org.opentcs.data.order.DriveOrder;
|
||||
import org.opentcs.data.order.Route;
|
||||
import org.opentcs.data.order.Route.Step;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.drivers.vehicle.MovementCommand;
|
||||
import org.opentcs.drivers.vehicle.MovementCommandTransformer;
|
||||
|
||||
/**
|
||||
* Transforms coordinates in {@link MovementCommand}s by adding offsets in a given
|
||||
* {@link CoordinateSystemTransformation}.
|
||||
*/
|
||||
public class CoordinateSystemMovementCommandTransformer
|
||||
implements
|
||||
MovementCommandTransformer {
|
||||
|
||||
private final CoordinateSystemTransformation transformation;
|
||||
|
||||
public CoordinateSystemMovementCommandTransformer(
|
||||
@Nonnull
|
||||
CoordinateSystemTransformation transformation
|
||||
) {
|
||||
this.transformation = requireNonNull(transformation, "transformation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovementCommand apply(MovementCommand command) {
|
||||
return command
|
||||
.withTransportOrder(transformTransportOrder(command.getTransportOrder()))
|
||||
.withDriveOrder(transformDriveOrder(command.getDriveOrder()))
|
||||
.withFinalDestination(transformPoint(command.getFinalDestination()))
|
||||
.withFinalDestinationLocation(transformLocation(command.getFinalDestinationLocation()))
|
||||
.withOpLocation(transformLocation(command.getOpLocation()))
|
||||
.withStep(transformStep(command.getStep()));
|
||||
}
|
||||
|
||||
private TransportOrder transformTransportOrder(TransportOrder oldTransportOrder) {
|
||||
return oldTransportOrder.withDriveOrders(
|
||||
transformDriveOrders(oldTransportOrder.getAllDriveOrders())
|
||||
);
|
||||
}
|
||||
|
||||
private List<DriveOrder> transformDriveOrders(List<DriveOrder> oldDriveOrders) {
|
||||
return oldDriveOrders.stream()
|
||||
.map(this::transformDriveOrder)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private DriveOrder transformDriveOrder(DriveOrder oldOrder) {
|
||||
return oldOrder.withRoute(transformRoute(oldOrder.getRoute()));
|
||||
}
|
||||
|
||||
private Route transformRoute(Route route) {
|
||||
return Optional.ofNullable(route)
|
||||
.map(
|
||||
originalRoute -> new Route(
|
||||
originalRoute.getSteps().stream()
|
||||
.map(step -> transformStep(step))
|
||||
.collect(Collectors.toList()),
|
||||
originalRoute.getCosts()
|
||||
)
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Step transformStep(Step oldStep) {
|
||||
return new Step(
|
||||
oldStep.getPath(),
|
||||
transformPoint(oldStep.getSourcePoint()),
|
||||
transformPoint(oldStep.getDestinationPoint()),
|
||||
oldStep.getVehicleOrientation(),
|
||||
oldStep.getRouteIndex(),
|
||||
oldStep.isExecutionAllowed(),
|
||||
oldStep.getReroutingType()
|
||||
);
|
||||
}
|
||||
|
||||
private Point transformPoint(Point point) {
|
||||
return Optional.ofNullable(point)
|
||||
.map(
|
||||
originalPoint -> originalPoint.withPose(
|
||||
new Pose(
|
||||
transformTriple(originalPoint.getPose().getPosition()),
|
||||
(originalPoint.getPose().getOrientationAngle() + transformation
|
||||
.getOffsetOrientation()) % 360.0
|
||||
)
|
||||
)
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Location transformLocation(Location location) {
|
||||
return Optional.ofNullable(location)
|
||||
.map(
|
||||
originalLocation -> originalLocation.withPosition(
|
||||
transformTriple(originalLocation.getPosition())
|
||||
)
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Triple transformTriple(Triple triple) {
|
||||
return Optional.ofNullable(triple)
|
||||
.map(
|
||||
originalTriple -> new Triple(
|
||||
originalTriple.getX() + transformation.getOffsetX(),
|
||||
originalTriple.getY() + transformation.getOffsetY(),
|
||||
originalTriple.getZ() + transformation.getOffsetZ()
|
||||
)
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles.transformers;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The data used by coordinate system transformer classes.
|
||||
* <p>
|
||||
* The data consists of offset values that are ...
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>added to coordinate and orientation angle values sent to the vehicle driver.</li>
|
||||
* <li>subtracted from coordinate and orientation angle values reported by the vehicle driver.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CoordinateSystemTransformation {
|
||||
|
||||
/**
|
||||
* The key of the property for the x offset.
|
||||
*/
|
||||
public static final String PROPKEY_OFFSET_X = "tcs:offsetTransformer.x";
|
||||
/**
|
||||
* The key of the property for the y offset.
|
||||
*/
|
||||
public static final String PROPKEY_OFFSET_Y = "tcs:offsetTransformer.y";
|
||||
/**
|
||||
* The key of the property for the z offset.
|
||||
*/
|
||||
public static final String PROPKEY_OFFSET_Z = "tcs:offsetTransformer.z";
|
||||
/**
|
||||
* The key of the property for the orientation offset.
|
||||
*/
|
||||
public static final String PROPKEY_OFFSET_ORIENTATION = "tcs:offsetTransformer.orientation";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CoordinateSystemTransformation.class);
|
||||
private final int offsetX;
|
||||
private final int offsetY;
|
||||
private final int offsetZ;
|
||||
private final double offsetOrientation;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param offsetX The x offset.
|
||||
* @param offsetY The y offset.
|
||||
* @param offsetZ The z offset.
|
||||
* @param offsetOrientation The orientation offset.
|
||||
*/
|
||||
public CoordinateSystemTransformation(
|
||||
int offsetX, int offsetY, int offsetZ, double offsetOrientation
|
||||
) {
|
||||
this.offsetX = offsetX;
|
||||
this.offsetY = offsetY;
|
||||
this.offsetZ = offsetZ;
|
||||
this.offsetOrientation = offsetOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x offset.
|
||||
*
|
||||
* @return The x offset.
|
||||
*/
|
||||
public int getOffsetX() {
|
||||
return offsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y offset.
|
||||
*
|
||||
* @return The y offset.
|
||||
*/
|
||||
public int getOffsetY() {
|
||||
return offsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the z offset.
|
||||
*
|
||||
* @return The z offset.
|
||||
*/
|
||||
public int getOffsetZ() {
|
||||
return offsetZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the orientation offset.
|
||||
*
|
||||
* @return The orientation offset.
|
||||
*/
|
||||
public double getOffsetOrientation() {
|
||||
return offsetOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CoordinateSystemTransformation} based on offsets given via vehicle properties.
|
||||
* <p>
|
||||
* The property keys used are the values of the following constants:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>{@link #PROPKEY_OFFSET_X}</li>
|
||||
* <li>{@link #PROPKEY_OFFSET_Y}</li>
|
||||
* <li>{@link #PROPKEY_OFFSET_Z}</li>
|
||||
* <li>{@link #PROPKEY_OFFSET_ORIENTATION}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param vehicle The vehicle.
|
||||
* @return An {@link Optional} containing the {@link CoordinateSystemTransformation} or
|
||||
* {@link Optional#empty()}, if one of the corresponding property values could not be parsed.
|
||||
*/
|
||||
public static Optional<CoordinateSystemTransformation> fromVehicle(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
try {
|
||||
return Optional.of(
|
||||
new CoordinateSystemTransformation(
|
||||
getPropertyInteger(vehicle, PROPKEY_OFFSET_X, 0),
|
||||
getPropertyInteger(vehicle, PROPKEY_OFFSET_Y, 0),
|
||||
getPropertyInteger(vehicle, PROPKEY_OFFSET_Z, 0),
|
||||
getPropertyDouble(vehicle, PROPKEY_OFFSET_ORIENTATION, 0.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
LOG.warn(
|
||||
"Could not create coordinate system transformation for vehicle '{}'.",
|
||||
vehicle.getName(),
|
||||
e
|
||||
);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPropertyInteger(Vehicle vehicle, String propertyKey, int defaultValue)
|
||||
throws NumberFormatException {
|
||||
String property = vehicle.getProperty(propertyKey);
|
||||
if (property != null) {
|
||||
return Integer.parseInt(property);
|
||||
}
|
||||
else {
|
||||
LOG.debug(
|
||||
"{}: Property '{}' is not set. Using default value {}.",
|
||||
vehicle.getName(),
|
||||
propertyKey,
|
||||
defaultValue
|
||||
);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static double getPropertyDouble(Vehicle vehicle, String propertyKey, double defaultValue)
|
||||
throws NumberFormatException {
|
||||
String property = vehicle.getProperty(propertyKey);
|
||||
if (property != null) {
|
||||
return Double.parseDouble(property);
|
||||
}
|
||||
else {
|
||||
LOG.debug(
|
||||
"{}: Property '{}' is not set. Using default value {}.",
|
||||
vehicle.getName(),
|
||||
propertyKey,
|
||||
defaultValue
|
||||
);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles.transformers;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.IncomingPoseTransformer;
|
||||
import org.opentcs.drivers.vehicle.MovementCommandTransformer;
|
||||
import org.opentcs.drivers.vehicle.VehicleDataTransformerFactory;
|
||||
|
||||
/**
|
||||
* Provides instances of {@link CoordinateSystemMovementCommandTransformer} and
|
||||
* {@link CoordinateSystemIncomingPoseTransformer}.
|
||||
*/
|
||||
public class CoordinateSystemTransformerFactory
|
||||
implements
|
||||
VehicleDataTransformerFactory {
|
||||
|
||||
public CoordinateSystemTransformerFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return "OFFSET_TRANSFORMER";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public MovementCommandTransformer createMovementCommandTransformer(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle);
|
||||
|
||||
return new CoordinateSystemMovementCommandTransformer(
|
||||
CoordinateSystemTransformation.fromVehicle(vehicle)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException(
|
||||
"Cannot create transformer without transformation data."
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public IncomingPoseTransformer createIncomingPoseTransformer(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle);
|
||||
|
||||
return new CoordinateSystemIncomingPoseTransformer(
|
||||
CoordinateSystemTransformation.fromVehicle(vehicle)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException(
|
||||
"Cannot create transformer without transformation data."
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean providesTransformersFor(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return CoordinateSystemTransformation.fromVehicle(vehicle).isPresent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles.transformers;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.IncomingPoseTransformer;
|
||||
import org.opentcs.drivers.vehicle.MovementCommandTransformer;
|
||||
import org.opentcs.drivers.vehicle.VehicleDataTransformerFactory;
|
||||
|
||||
/**
|
||||
* Provides transformers that do not modify their inputs in any way.
|
||||
*/
|
||||
public class DefaultVehicleDataTransformerFactory
|
||||
implements
|
||||
VehicleDataTransformerFactory {
|
||||
|
||||
public DefaultVehicleDataTransformerFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return "DEFAULT_TRANSFORMER";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public MovementCommandTransformer createMovementCommandTransformer(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return command -> command;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public IncomingPoseTransformer createIncomingPoseTransformer(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return pose -> pose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean providesTransformersFor(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.vehicles.transformers;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkState;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.opentcs.components.Lifecycle;
|
||||
import org.opentcs.data.ObjectPropConstants;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.drivers.vehicle.VehicleDataTransformerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A registry for all vehicle data transformers in the system.
|
||||
*/
|
||||
public class VehicleDataTransformerRegistry
|
||||
implements
|
||||
Lifecycle {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VehicleDataTransformerRegistry.class);
|
||||
/**
|
||||
* The registered factories.
|
||||
*/
|
||||
private final Set<VehicleDataTransformerFactory> factories;
|
||||
/**
|
||||
* Indicates whether this component is initialized or not.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Creates a new registry.
|
||||
*
|
||||
* @param factories The data transformer factories.
|
||||
*/
|
||||
@Inject
|
||||
public VehicleDataTransformerRegistry(
|
||||
@Nonnull
|
||||
Set<VehicleDataTransformerFactory> factories
|
||||
) {
|
||||
this.factories = requireNonNull(factories, "factories");
|
||||
|
||||
checkState(!factories.isEmpty(), "No adapter factories found.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (isInitialized()) {
|
||||
LOG.debug("Already initialized.");
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
if (!isInitialized()) {
|
||||
LOG.debug("Not initialized.");
|
||||
return;
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a factory for data transformers for the given vehicle.
|
||||
*
|
||||
* @param vehicle The vehicle to find a data transformer factory for.
|
||||
* @return A factory for data transformers for the given vehicle.
|
||||
*/
|
||||
public VehicleDataTransformerFactory findFactoryFor(
|
||||
@Nonnull
|
||||
Vehicle vehicle
|
||||
) {
|
||||
requireNonNull(vehicle, "vehicle");
|
||||
|
||||
return Optional.ofNullable(vehicle.getProperty(ObjectPropConstants.VEHICLE_DATA_TRANSFORMER))
|
||||
.flatMap(
|
||||
name -> factories.stream()
|
||||
.filter(factory -> name.equals(factory.getName()))
|
||||
.filter(factory -> factory.providesTransformersFor(vehicle))
|
||||
.findAny()
|
||||
)
|
||||
.orElseGet(() -> {
|
||||
LOG.debug("Falling back to default transformer for vehicle '{}'", vehicle.getName());
|
||||
return new DefaultVehicleDataTransformerFactory();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.opentcs.components.kernel.OrderSequenceCleanupApproval;
|
||||
import org.opentcs.data.order.OrderSequence;
|
||||
|
||||
/**
|
||||
* A collection of {@link OrderSequenceCleanupApproval}s.
|
||||
*/
|
||||
public class CompositeOrderSequenceCleanupApproval
|
||||
implements
|
||||
OrderSequenceCleanupApproval {
|
||||
|
||||
private final Set<OrderSequenceCleanupApproval> sequenceCleanupApprovals;
|
||||
private final DefaultOrderSequenceCleanupApproval defaultOrderSequenceCleanupApproval;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param sequenceCleanupApprovals The {@link OrderSequenceCleanupApproval}s.
|
||||
* @param defaultOrderSequenceCleanupApproval The {@link OrderSequenceCleanupApproval}, which
|
||||
* should always be applied by default.
|
||||
*/
|
||||
@Inject
|
||||
public CompositeOrderSequenceCleanupApproval(
|
||||
Set<OrderSequenceCleanupApproval> sequenceCleanupApprovals,
|
||||
DefaultOrderSequenceCleanupApproval defaultOrderSequenceCleanupApproval
|
||||
) {
|
||||
this.sequenceCleanupApprovals = requireNonNull(
|
||||
sequenceCleanupApprovals,
|
||||
"sequenceCleanupApprovals"
|
||||
);
|
||||
this.defaultOrderSequenceCleanupApproval
|
||||
= requireNonNull(
|
||||
defaultOrderSequenceCleanupApproval,
|
||||
"defaultOrderSequenceCleanupApproval"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(OrderSequence seq) {
|
||||
if (!defaultOrderSequenceCleanupApproval.test(seq)) {
|
||||
return false;
|
||||
}
|
||||
for (OrderSequenceCleanupApproval approval : sequenceCleanupApprovals) {
|
||||
if (!approval.test(seq)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.opentcs.components.kernel.PeripheralJobCleanupApproval;
|
||||
import org.opentcs.data.peripherals.PeripheralJob;
|
||||
|
||||
/**
|
||||
* A collection of {@link PeripheralJobCleanupApproval}s.
|
||||
*/
|
||||
public class CompositePeripheralJobCleanupApproval
|
||||
implements
|
||||
PeripheralJobCleanupApproval {
|
||||
|
||||
private final Set<PeripheralJobCleanupApproval> peripheralJobCleanupApprovals;
|
||||
private final DefaultPeripheralJobCleanupApproval defaultPeripheralJobCleanupApproval;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param peripheralJobCleanupApprovals The {@link PeripheralJobCleanupApproval}s.
|
||||
* @param defaultPeripheralJobCleanupApproval The {@link PeripheralJobCleanupApproval}, which
|
||||
* should always be applied by default.
|
||||
*/
|
||||
@Inject
|
||||
public CompositePeripheralJobCleanupApproval(
|
||||
Set<PeripheralJobCleanupApproval> peripheralJobCleanupApprovals,
|
||||
DefaultPeripheralJobCleanupApproval defaultPeripheralJobCleanupApproval
|
||||
) {
|
||||
this.peripheralJobCleanupApprovals = requireNonNull(
|
||||
peripheralJobCleanupApprovals,
|
||||
"peripheralJobCleanupApprovals"
|
||||
);
|
||||
this.defaultPeripheralJobCleanupApproval
|
||||
= requireNonNull(
|
||||
defaultPeripheralJobCleanupApproval,
|
||||
"defaultPeripheralJobCleanupApproval"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(PeripheralJob job) {
|
||||
if (!defaultPeripheralJobCleanupApproval.test(job)) {
|
||||
return false;
|
||||
}
|
||||
for (PeripheralJobCleanupApproval approval : peripheralJobCleanupApprovals) {
|
||||
if (!approval.test(job)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.opentcs.components.kernel.TransportOrderCleanupApproval;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
|
||||
/**
|
||||
* A collection of {@link TransportOrderCleanupApproval}s.
|
||||
*/
|
||||
public class CompositeTransportOrderCleanupApproval
|
||||
implements
|
||||
TransportOrderCleanupApproval {
|
||||
|
||||
private final Set<TransportOrderCleanupApproval> orderCleanupApprovals;
|
||||
private final DefaultTransportOrderCleanupApproval defaultTransportOrderCleanupApproval;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param orderCleanupApprovals The {@link TransportOrderCleanupApproval}s.
|
||||
* @param defaultTransportOrderCleanupApproval The {@link TransportOrderCleanupApproval}, which
|
||||
* should always be applied by default.
|
||||
*/
|
||||
@Inject
|
||||
public CompositeTransportOrderCleanupApproval(
|
||||
Set<TransportOrderCleanupApproval> orderCleanupApprovals,
|
||||
DefaultTransportOrderCleanupApproval defaultTransportOrderCleanupApproval
|
||||
) {
|
||||
this.orderCleanupApprovals = requireNonNull(orderCleanupApprovals, "orderCleanupApprovals");
|
||||
this.defaultTransportOrderCleanupApproval
|
||||
= requireNonNull(
|
||||
defaultTransportOrderCleanupApproval,
|
||||
"defaultTransportOrderCleanupApproval"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(TransportOrder order) {
|
||||
if (!defaultTransportOrderCleanupApproval.test(order)) {
|
||||
return false;
|
||||
}
|
||||
for (TransportOrderCleanupApproval approval : orderCleanupApprovals) {
|
||||
if (!approval.test(order)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Keeps track of the time used to determine whether a working set item should be removed (according
|
||||
* to its creation time).
|
||||
*/
|
||||
public class CreationTimeThreshold {
|
||||
|
||||
private Instant currentThreshold;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
@Inject
|
||||
public CreationTimeThreshold() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current threshold by subtracting the given amount of milliseconds from the current
|
||||
* time.
|
||||
*
|
||||
* @param millis The amount of milliseconds to subtract from the current time.
|
||||
*/
|
||||
public void updateCurrentThreshold(long millis) {
|
||||
currentThreshold = Instant.now().minusMillis(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current threshold.
|
||||
* <p>
|
||||
* Working set items that are created before this point of time should be removed.
|
||||
*
|
||||
* @return The current threshold.
|
||||
*/
|
||||
public Instant getCurrentThreshold() {
|
||||
return currentThreshold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.components.kernel.OrderSequenceCleanupApproval;
|
||||
import org.opentcs.data.order.OrderSequence;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
|
||||
/**
|
||||
* Checks whether an order sequence may be removed.
|
||||
*/
|
||||
public class DefaultOrderSequenceCleanupApproval
|
||||
implements
|
||||
OrderSequenceCleanupApproval {
|
||||
|
||||
private final TransportOrderPoolManager orderPoolManager;
|
||||
private final DefaultTransportOrderCleanupApproval defaultTransportOrderCleanupApproval;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param orderPoolManager The order pool manager to be used.
|
||||
* @param defaultTransportOrderCleanupApproval Checks whether a transport order may be removed.
|
||||
*/
|
||||
@Inject
|
||||
public DefaultOrderSequenceCleanupApproval(
|
||||
TransportOrderPoolManager orderPoolManager,
|
||||
DefaultTransportOrderCleanupApproval defaultTransportOrderCleanupApproval
|
||||
) {
|
||||
this.orderPoolManager = requireNonNull(orderPoolManager, "orderPoolManager");
|
||||
this.defaultTransportOrderCleanupApproval
|
||||
= requireNonNull(
|
||||
defaultTransportOrderCleanupApproval,
|
||||
"defaultTransportOrderCleanupApproval"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(OrderSequence seq) {
|
||||
if (!seq.isFinished()) {
|
||||
return false;
|
||||
}
|
||||
if (hasUnapprovedOrder(seq)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasUnapprovedOrder(OrderSequence seq) {
|
||||
return !(seq.getOrders()
|
||||
.stream()
|
||||
.map(
|
||||
reference -> orderPoolManager.getObjectRepo()
|
||||
.getObject(TransportOrder.class, reference)
|
||||
)
|
||||
.allMatch(defaultTransportOrderCleanupApproval));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.components.kernel.PeripheralJobCleanupApproval;
|
||||
import org.opentcs.data.peripherals.PeripheralJob;
|
||||
|
||||
/**
|
||||
* Checks whether a peripheral job may be removed.
|
||||
*/
|
||||
public class DefaultPeripheralJobCleanupApproval
|
||||
implements
|
||||
PeripheralJobCleanupApproval {
|
||||
|
||||
private final CreationTimeThreshold creationTimeThreshold;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param creationTimeThreshold Keeps track of the time used to determine whether a peripheral
|
||||
* job should be removed (according to its creation time).
|
||||
*/
|
||||
@Inject
|
||||
public DefaultPeripheralJobCleanupApproval(CreationTimeThreshold creationTimeThreshold) {
|
||||
this.creationTimeThreshold = requireNonNull(creationTimeThreshold, "creationTimeThreshold");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(PeripheralJob job) {
|
||||
if (!job.getState().isFinalState()) {
|
||||
return false;
|
||||
}
|
||||
if (job.getCreationTime().isAfter(creationTimeThreshold.getCurrentThreshold())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Objects;
|
||||
import org.opentcs.components.kernel.TransportOrderCleanupApproval;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.data.peripherals.PeripheralJob;
|
||||
|
||||
/**
|
||||
* Checks whether a transport order may be removed.
|
||||
*/
|
||||
public class DefaultTransportOrderCleanupApproval
|
||||
implements
|
||||
TransportOrderCleanupApproval {
|
||||
|
||||
private final PeripheralJobPoolManager peripheralJobPoolManager;
|
||||
private final DefaultPeripheralJobCleanupApproval defaultPeripheralJobCleanupApproval;
|
||||
private final CreationTimeThreshold creationTimeThreshold;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param peripheralJobPoolManager The peripheral job pool manager to be used.
|
||||
* @param defaultPeripheralJobCleanupApproval Checks whether a peripheral job may be removed.
|
||||
* @param creationTimeThreshold Keeps track of the time used to determine whether a transport
|
||||
* order should be removed (according to its creation time).
|
||||
*/
|
||||
@Inject
|
||||
public DefaultTransportOrderCleanupApproval(
|
||||
PeripheralJobPoolManager peripheralJobPoolManager,
|
||||
DefaultPeripheralJobCleanupApproval defaultPeripheralJobCleanupApproval,
|
||||
CreationTimeThreshold creationTimeThreshold
|
||||
) {
|
||||
this.peripheralJobPoolManager = requireNonNull(
|
||||
peripheralJobPoolManager,
|
||||
"peripheralJobPoolManager"
|
||||
);
|
||||
this.defaultPeripheralJobCleanupApproval
|
||||
= requireNonNull(
|
||||
defaultPeripheralJobCleanupApproval,
|
||||
"defaultPeripheralJobCleanupApproval"
|
||||
);
|
||||
this.creationTimeThreshold = requireNonNull(creationTimeThreshold, "creationTimeThreshold");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(TransportOrder order) {
|
||||
if (!order.getState().isFinalState()) {
|
||||
return false;
|
||||
}
|
||||
if (isRelatedToJobWithNonFinalState(order)) {
|
||||
return false;
|
||||
}
|
||||
if (isRelatedToUnapprovedJob(order)) {
|
||||
return false;
|
||||
}
|
||||
if (order.getCreationTime().isAfter(creationTimeThreshold.getCurrentThreshold())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isRelatedToJobWithNonFinalState(TransportOrder order) {
|
||||
return peripheralJobPoolManager.getObjectRepo()
|
||||
.getObjects(
|
||||
PeripheralJob.class,
|
||||
job -> Objects.equals(job.getRelatedTransportOrder(), order.getReference())
|
||||
)
|
||||
.stream()
|
||||
.filter(job -> !job.getState().isFinalState())
|
||||
.findAny()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private boolean isRelatedToUnapprovedJob(TransportOrder order) {
|
||||
return !(peripheralJobPoolManager.getObjectRepo().getObjects(
|
||||
PeripheralJob.class,
|
||||
job -> Objects.equals(job.getRelatedTransportOrder(), order.getReference())
|
||||
)
|
||||
.stream()
|
||||
.allMatch(defaultPeripheralJobCleanupApproval));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkInRange;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.access.NotificationPublicationEvent;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.notification.UserNotification;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A buffer which can store a (configurable) limited number of
|
||||
* {@link UserNotification UserNotification} objects.
|
||||
* <p>
|
||||
* When a new message is added to the buffer and the number of messages in the buffer exceeds its
|
||||
* <code>capacity</code>, messages are removed from the buffer until it contains not more than
|
||||
* <code>capacity</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that no synchronization is done inside this class. Concurrent access of
|
||||
* instances of this class must be synchronized externally.
|
||||
* </p>
|
||||
*/
|
||||
public class NotificationBuffer {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NotificationBuffer.class);
|
||||
/**
|
||||
* The actual messages.
|
||||
*/
|
||||
private final Queue<UserNotification> notifications = new ArrayDeque<>();
|
||||
/**
|
||||
* The maximum number of messages that should be kept in this buffer.
|
||||
*/
|
||||
private int capacity = 500;
|
||||
/**
|
||||
* A listener for events concerning the stored messages.
|
||||
*/
|
||||
private final EventHandler messageEventListener;
|
||||
|
||||
/**
|
||||
* Creates a new instance that uses the given event listener.
|
||||
*
|
||||
* @param eventListener The event listener to be used.
|
||||
*/
|
||||
@Inject
|
||||
public NotificationBuffer(
|
||||
@ApplicationEventBus
|
||||
EventHandler eventListener
|
||||
) {
|
||||
messageEventListener = requireNonNull(eventListener, "eventListener");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this buffer's capacity.
|
||||
*
|
||||
* @return This buffer's capacity.
|
||||
*/
|
||||
public int getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts this buffer's <code>capacity</code>.
|
||||
* If the new capacity is less than the current number of messages in this
|
||||
* buffer, messages are removed until the number of messages equals the
|
||||
* buffer's <code>capacity</code>.
|
||||
*
|
||||
* @param capacity The buffer's new capacity. Must be at least 1.
|
||||
* @throws IllegalArgumentException If <code>newCapacity</code> is less than 1.
|
||||
*/
|
||||
public void setCapacity(int capacity) {
|
||||
this.capacity = checkInRange(capacity, 1, Integer.MAX_VALUE, "capacity");
|
||||
cutBackMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification to the buffer.
|
||||
*
|
||||
* @param notification The notification to be added.
|
||||
*/
|
||||
public void addNotification(UserNotification notification) {
|
||||
requireNonNull(notification, "notification");
|
||||
|
||||
notifications.add(notification);
|
||||
LOG.info("User notification added: {}", notification);
|
||||
|
||||
// Make sure we don't have too many messages now.
|
||||
cutBackMessages();
|
||||
// Emit an event for this message.
|
||||
emitMessageEvent(notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all notifications that are accepted by the given filter, or all notifications, if no
|
||||
* filter is given.
|
||||
*
|
||||
* @param predicate The predicate used to filter. May be <code>null</code> to return all
|
||||
* notifications.
|
||||
* @return A list of notifications accepted by the given filter.
|
||||
*/
|
||||
public List<UserNotification> getNotifications(
|
||||
@Nullable
|
||||
Predicate<UserNotification> predicate
|
||||
) {
|
||||
Predicate<UserNotification> filterPredicate
|
||||
= predicate == null
|
||||
? (notification) -> true
|
||||
: predicate;
|
||||
|
||||
return notifications.stream()
|
||||
.filter(filterPredicate)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all messages from this buffer.
|
||||
*/
|
||||
public void clear() {
|
||||
notifications.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event for the given message.
|
||||
*
|
||||
* @param message The message to emit an event for.
|
||||
*/
|
||||
public void emitMessageEvent(UserNotification message) {
|
||||
messageEventListener.onEvent(new NotificationPublicationEvent(message));
|
||||
}
|
||||
|
||||
private void cutBackMessages() {
|
||||
while (notifications.size() > capacity) {
|
||||
notifications.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.access.to.peripherals.PeripheralJobCreationTO;
|
||||
import org.opentcs.access.to.peripherals.PeripheralOperationCreationTO;
|
||||
import org.opentcs.components.kernel.ObjectNameProvider;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.ObjectExistsException;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObjectEvent;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.TCSResourceReference;
|
||||
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.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Keeps all {@code PeripheralJobs}s and provides methods to create and manipulate them.
|
||||
* <p>
|
||||
* Note that no synchronization is done inside this class. Concurrent access of instances of this
|
||||
* class must be synchronized externally.
|
||||
* </p>
|
||||
*/
|
||||
public class PeripheralJobPoolManager
|
||||
extends
|
||||
TCSObjectManager {
|
||||
|
||||
/**
|
||||
* This class's logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PeripheralJobPoolManager.class);
|
||||
/**
|
||||
* Provides names for peripheral jobs.
|
||||
*/
|
||||
private final ObjectNameProvider objectNameProvider;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectRepo The object repo.
|
||||
* @param eventHandler The event handler to publish events to.
|
||||
* @param orderNameProvider Provides names for peripheral jobs.
|
||||
*/
|
||||
@Inject
|
||||
public PeripheralJobPoolManager(
|
||||
@Nonnull
|
||||
TCSObjectRepository objectRepo,
|
||||
@Nonnull
|
||||
@ApplicationEventBus
|
||||
EventHandler eventHandler,
|
||||
@Nonnull
|
||||
ObjectNameProvider orderNameProvider
|
||||
) {
|
||||
super(objectRepo, eventHandler);
|
||||
this.objectNameProvider = requireNonNull(orderNameProvider, "orderNameProvider");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all peripheral jobs from this pool.
|
||||
*/
|
||||
public void clear() {
|
||||
for (PeripheralJob job : getObjectRepo().getObjects(PeripheralJob.class)) {
|
||||
getObjectRepo().removeObject(job.getReference());
|
||||
emitObjectEvent(
|
||||
null,
|
||||
job,
|
||||
TCSObjectEvent.Type.OBJECT_REMOVED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new peripheral job to the pool.
|
||||
*
|
||||
* @param to The transfer object from which to create the new peripheral job.
|
||||
* @return The newly created peripheral job.
|
||||
* @throws ObjectExistsException If an object with the new object's name already exists.
|
||||
* @throws ObjectUnknownException If any object referenced in the TO does not exist.
|
||||
* @throws IllegalArgumentException If the transfer object's combination of parameters is invalid.
|
||||
*/
|
||||
public PeripheralJob createPeripheralJob(PeripheralJobCreationTO to)
|
||||
throws ObjectUnknownException,
|
||||
ObjectExistsException,
|
||||
IllegalArgumentException {
|
||||
checkArgument(
|
||||
!hasCompletionRequiredAndExecutionTriggerImmediate(to),
|
||||
"Peripheral job's operation has executionTrigger 'immediate' and completionRequired set."
|
||||
);
|
||||
|
||||
PeripheralJob job = new PeripheralJob(
|
||||
nameFor(to),
|
||||
to.getReservationToken(),
|
||||
toPeripheralOperation(to.getPeripheralOperation())
|
||||
)
|
||||
.withRelatedVehicle(toVehicleReference(to.getRelatedVehicleName()))
|
||||
.withRelatedTransportOrder(toTransportOrderReference(to.getRelatedTransportOrderName()))
|
||||
.withProperties(to.getProperties());
|
||||
|
||||
LOG.info(
|
||||
"Peripheral job is being created: {} -- {}",
|
||||
job.getName(),
|
||||
job.getPeripheralOperation()
|
||||
);
|
||||
|
||||
getObjectRepo().addObject(job);
|
||||
emitObjectEvent(job, null, TCSObjectEvent.Type.OBJECT_CREATED);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a peripheral jobs's state.
|
||||
*
|
||||
* @param ref A reference to the peripheral job to be modified.
|
||||
* @param newState The peripheral job's new state.
|
||||
* @return The modified peripheral job.
|
||||
* @throws ObjectUnknownException If the referenced peripheral job is not in this pool.
|
||||
*/
|
||||
public PeripheralJob setPeripheralJobState(
|
||||
TCSObjectReference<PeripheralJob> ref,
|
||||
PeripheralJob.State newState
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
PeripheralJob previousState = getObjectRepo().getObject(PeripheralJob.class, ref);
|
||||
|
||||
checkArgument(
|
||||
!previousState.getState().isFinalState(),
|
||||
"Peripheral job %s already in a final state, not changing %s -> %s.",
|
||||
ref.getName(),
|
||||
previousState.getState(),
|
||||
newState
|
||||
);
|
||||
|
||||
LOG.info(
|
||||
"Peripheral job's state changes: {} -- {} -> {}",
|
||||
previousState.getName(),
|
||||
previousState.getState(),
|
||||
newState
|
||||
);
|
||||
|
||||
PeripheralJob job = previousState.withState(newState);
|
||||
getObjectRepo().replaceObject(job);
|
||||
emitObjectEvent(
|
||||
job,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the referenced peripheral job from the pool.
|
||||
*
|
||||
* @param ref A reference to the peripheral job to be removed.
|
||||
* @return The removed peripheral job.
|
||||
* @throws ObjectUnknownException If the referenced peripheral job is not in the pool.
|
||||
*/
|
||||
public PeripheralJob removePeripheralJob(TCSObjectReference<PeripheralJob> ref)
|
||||
throws ObjectUnknownException {
|
||||
PeripheralJob job = getObjectRepo().getObject(PeripheralJob.class, ref);
|
||||
// Make sure only jobs in a final state are removed.
|
||||
checkArgument(
|
||||
job.getState().isFinalState(),
|
||||
"Peripheral job %s is not in a final state.",
|
||||
job.getName()
|
||||
);
|
||||
getObjectRepo().removeObject(ref);
|
||||
emitObjectEvent(
|
||||
null,
|
||||
job,
|
||||
TCSObjectEvent.Type.OBJECT_REMOVED
|
||||
);
|
||||
return job;
|
||||
}
|
||||
|
||||
private PeripheralOperation toPeripheralOperation(PeripheralOperationCreationTO to)
|
||||
throws ObjectUnknownException {
|
||||
return new PeripheralOperation(
|
||||
toLocationReference(to.getLocationName()),
|
||||
to.getOperation(),
|
||||
to.getExecutionTrigger(),
|
||||
to.isCompletionRequired()
|
||||
);
|
||||
}
|
||||
|
||||
private TCSResourceReference<Location> toLocationReference(String locationName)
|
||||
throws ObjectUnknownException {
|
||||
Location location = getObjectRepo().getObject(Location.class, locationName);
|
||||
return location.getReference();
|
||||
}
|
||||
|
||||
private TCSObjectReference<Vehicle> toVehicleReference(String vehicleName)
|
||||
throws ObjectUnknownException {
|
||||
if (vehicleName == null) {
|
||||
return null;
|
||||
}
|
||||
Vehicle vehicle = getObjectRepo().getObject(Vehicle.class, vehicleName);
|
||||
return vehicle.getReference();
|
||||
}
|
||||
|
||||
private TCSObjectReference<TransportOrder> toTransportOrderReference(String transportOrderName)
|
||||
throws ObjectUnknownException {
|
||||
if (transportOrderName == null) {
|
||||
return null;
|
||||
}
|
||||
TransportOrder order = getObjectRepo().getObject(TransportOrder.class, transportOrderName);
|
||||
return order.getReference();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String nameFor(
|
||||
@Nonnull
|
||||
PeripheralJobCreationTO to
|
||||
) {
|
||||
if (to.hasIncompleteName()) {
|
||||
return objectNameProvider.apply(to);
|
||||
}
|
||||
else {
|
||||
return to.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCompletionRequiredAndExecutionTriggerImmediate(PeripheralJobCreationTO to) {
|
||||
PeripheralOperationCreationTO opTo = to.getPeripheralOperation();
|
||||
return opTo.isCompletionRequired()
|
||||
&& opTo.getExecutionTrigger() == PeripheralOperation.ExecutionTrigger.IMMEDIATE;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import de.huxhorn.sulky.ulid.ULID;
|
||||
import java.util.Optional;
|
||||
import org.opentcs.access.to.CreationTO;
|
||||
import org.opentcs.components.kernel.ObjectNameProvider;
|
||||
|
||||
/**
|
||||
* Provides names for objects based on ULIDs, prefixed with the name taken from a given
|
||||
* {@link CreationTO}.
|
||||
*/
|
||||
public class PrefixedUlidObjectNameProvider
|
||||
implements
|
||||
ObjectNameProvider {
|
||||
|
||||
/**
|
||||
* Generates ULIDs for us.
|
||||
*/
|
||||
private final ULID ulid = new ULID();
|
||||
/**
|
||||
* The previously generated ULID value.
|
||||
*/
|
||||
private ULID.Value previousUlid = ulid.nextValue();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public PrefixedUlidObjectNameProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(CreationTO to) {
|
||||
requireNonNull(to, "to");
|
||||
|
||||
Optional<ULID.Value> newValue = ulid.nextStrictlyMonotonicValue(previousUlid);
|
||||
while (newValue.isEmpty()) {
|
||||
newValue = ulid.nextStrictlyMonotonicValue(previousUlid);
|
||||
}
|
||||
previousUlid = newValue.get();
|
||||
return to.getName() + newValue.get().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.ObjectHistory;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObject;
|
||||
import org.opentcs.data.TCSObjectEvent;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles generic modifications of objects contained in a {@link TCSObjectRepository}.
|
||||
* <p>
|
||||
* Note that no synchronization is done inside this class. Concurrent access of instances of this
|
||||
* class must be synchronized externally.
|
||||
* </p>
|
||||
*/
|
||||
public class TCSObjectManager {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TCSObjectManager.class);
|
||||
/**
|
||||
* The object repo.
|
||||
*/
|
||||
private final TCSObjectRepository objectRepo;
|
||||
/**
|
||||
* A handler we should emit object events to.
|
||||
*/
|
||||
private final EventHandler eventHandler;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectRepo The object repo.
|
||||
* @param eventHandler The event handler to publish events to.
|
||||
*/
|
||||
@Inject
|
||||
public TCSObjectManager(
|
||||
@Nonnull
|
||||
TCSObjectRepository objectRepo,
|
||||
@Nonnull
|
||||
@ApplicationEventBus
|
||||
EventHandler eventHandler
|
||||
) {
|
||||
this.objectRepo = requireNonNull(objectRepo, "objectRepo");
|
||||
this.eventHandler = requireNonNull(eventHandler, "eventHandler");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying object repo.
|
||||
*
|
||||
* @return The underlying object repo.
|
||||
*/
|
||||
@Nonnull
|
||||
public TCSObjectRepository getObjectRepo() {
|
||||
return objectRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property for the referenced object.
|
||||
*
|
||||
* @param ref A reference to the object to be modified.
|
||||
* @param key The property's key/name.
|
||||
* @param value The property's value. If <code>null</code>, removes the
|
||||
* property from the object.
|
||||
* @throws ObjectUnknownException If the referenced object does not exist.
|
||||
*/
|
||||
public void setObjectProperty(
|
||||
@Nonnull
|
||||
TCSObjectReference<?> ref,
|
||||
@Nonnull
|
||||
String key,
|
||||
@Nullable
|
||||
String value
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(key, "key");
|
||||
|
||||
TCSObject<?> object = objectRepo.getObject(ref);
|
||||
TCSObject<?> previousState = object;
|
||||
LOG.debug(
|
||||
"Setting property on object named '{}': key='{}', value='{}'",
|
||||
ref.getName(),
|
||||
key,
|
||||
value
|
||||
);
|
||||
object = object.withProperty(key, value);
|
||||
objectRepo.replaceObject(object);
|
||||
emitObjectEvent(object, previousState, TCSObjectEvent.Type.OBJECT_MODIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a history entry to the referenced object.
|
||||
*
|
||||
* @param ref A reference to the object to be modified.
|
||||
* @param entry The history entry to be appended.
|
||||
* @throws ObjectUnknownException If the referenced object does not exist.
|
||||
*/
|
||||
public void appendObjectHistoryEntry(
|
||||
@Nonnull
|
||||
TCSObjectReference<?> ref,
|
||||
@Nonnull
|
||||
ObjectHistory.Entry entry
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
requireNonNull(entry, "entry");
|
||||
|
||||
TCSObject<?> object = objectRepo.getObject(ref);
|
||||
TCSObject<?> previousState = object;
|
||||
LOG.debug("Appending history entry to object named '{}': {}", ref.getName(), entry);
|
||||
object = object.withHistoryEntry(entry);
|
||||
objectRepo.replaceObject(object);
|
||||
emitObjectEvent(object, previousState, TCSObjectEvent.Type.OBJECT_MODIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event for the given object with the given type.
|
||||
*
|
||||
* @param currentObjectState The current state of the object to emit an event
|
||||
* for.
|
||||
* @param previousObjectState The previous state of the object to emit an
|
||||
* event for.
|
||||
* @param evtType The type of event to emit.
|
||||
*/
|
||||
public void emitObjectEvent(
|
||||
TCSObject<?> currentObjectState,
|
||||
TCSObject<?> previousObjectState,
|
||||
TCSObjectEvent.Type evtType
|
||||
) {
|
||||
eventHandler.onEvent(new TCSObjectEvent(currentObjectState, previousObjectState, evtType));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import org.opentcs.data.ObjectExistsException;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObject;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
|
||||
/**
|
||||
* A container for <code>TCSObject</code>s belonging together.
|
||||
* <p>
|
||||
* Provides access to a set of data objects and ensures they have unique names.
|
||||
* </p>
|
||||
*/
|
||||
public class TCSObjectRepository {
|
||||
|
||||
/**
|
||||
* The objects contained in this pool, mapped by their names, grouped by their classes.
|
||||
*/
|
||||
private final Map<Class<?>, Map<String, TCSObject<?>>> objects = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public TCSObjectRepository() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new object to the pool.
|
||||
*
|
||||
* @param newObject The object to be added to the pool.
|
||||
* @throws ObjectExistsException If an object with the same ID or the same
|
||||
* name as the new one already exists in this pool.
|
||||
*/
|
||||
public void addObject(
|
||||
@Nonnull
|
||||
TCSObject<?> newObject
|
||||
)
|
||||
throws ObjectExistsException {
|
||||
requireNonNull(newObject, "newObject");
|
||||
|
||||
if (containsName(newObject.getName())) {
|
||||
throw new ObjectExistsException("Object name already exists: " + newObject.getName());
|
||||
}
|
||||
|
||||
Map<String, TCSObject<?>> objectsByName = objects.get(newObject.getClass());
|
||||
if (objectsByName == null) {
|
||||
objectsByName = new HashMap<>();
|
||||
objects.put(newObject.getClass(), objectsByName);
|
||||
}
|
||||
objectsByName.put(newObject.getName(), newObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the given object to replace an object in the pool with same name.
|
||||
*
|
||||
* @param object The replacing object.
|
||||
* @throws IllegalArgumentException If an object with the same name as the given object does not
|
||||
* exist in this repository, yet, or if an object with the same name does exist but is an instance
|
||||
* of a different class.
|
||||
*/
|
||||
@Nonnull
|
||||
public void replaceObject(
|
||||
@Nonnull
|
||||
TCSObject<?> object
|
||||
)
|
||||
throws IllegalArgumentException {
|
||||
requireNonNull(object, "object");
|
||||
TCSObject<?> oldObject = getObjectOrNull(object.getName());
|
||||
checkArgument(
|
||||
oldObject != null,
|
||||
"Object named '%s' does not exist",
|
||||
object.getName()
|
||||
);
|
||||
checkArgument(
|
||||
object.getClass() == oldObject.getClass(),
|
||||
"Object named '%s' not an instance of the same class: '%s' != '%s'",
|
||||
object.getName(),
|
||||
object.getClass().getName(),
|
||||
oldObject.getClass().getName()
|
||||
);
|
||||
|
||||
objects.get(object.getClass()).put(object.getName(), object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param ref A reference to the object to return.
|
||||
* @return The referenced object, or <code>null</code>, if no such object exists in this pool.
|
||||
*/
|
||||
@Nullable
|
||||
public TCSObject<?> getObjectOrNull(
|
||||
@Nonnull
|
||||
TCSObjectReference<?> ref
|
||||
) {
|
||||
requireNonNull(ref);
|
||||
|
||||
return objects.getOrDefault(ref.getReferentClass(), Map.of()).get(ref.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param ref A reference to the object to return.
|
||||
* @return The referenced object.
|
||||
* @throws ObjectUnknownException If the referenced object does not exist.
|
||||
*/
|
||||
@Nonnull
|
||||
public TCSObject<?> getObject(
|
||||
@Nonnull
|
||||
TCSObjectReference<?> ref
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
TCSObject<?> result = getObjectOrNull(ref);
|
||||
if (result == null) {
|
||||
throw new ObjectUnknownException(ref);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param <T> The object's type.
|
||||
* @param clazz The class of the object to be returned.
|
||||
* @param ref A reference to the object to be returned.
|
||||
* @return The referenced object, or <code>null</code>, if no such object
|
||||
* exists in this pool or if an object exists but is not an instance of the
|
||||
* given class.
|
||||
*/
|
||||
@Nullable
|
||||
public <T extends TCSObject<T>> T getObjectOrNull(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
TCSObjectReference<T> ref
|
||||
) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
TCSObject<?> result = objects.getOrDefault(clazz, Map.of()).get(ref.getName());
|
||||
if (clazz.isInstance(result)) {
|
||||
return clazz.cast(result);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param <T> The object's type.
|
||||
* @param clazz The class of the object to be returned.
|
||||
* @param ref A reference to the object to be returned.
|
||||
* @return The referenced object.
|
||||
* @throws ObjectUnknownException If the referenced object does not exist, or if an object exists
|
||||
* but is not an instance of the given class.
|
||||
*/
|
||||
@Nonnull
|
||||
public <T extends TCSObject<T>> T getObject(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
TCSObjectReference<T> ref
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
T result = getObjectOrNull(clazz, ref);
|
||||
if (result == null) {
|
||||
throw new ObjectUnknownException(ref);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param name The name of the object to return.
|
||||
* @return The object with the given name, or <code>null</code>, if no such
|
||||
* object exists in this pool.
|
||||
*/
|
||||
@Nullable
|
||||
public TCSObject<?> getObjectOrNull(
|
||||
@Nonnull
|
||||
String name
|
||||
) {
|
||||
requireNonNull(name, "name");
|
||||
|
||||
return objects.values().stream()
|
||||
.map(objectsByName -> objectsByName.get(name))
|
||||
.filter(object -> object != null)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param name The name of the object to return.
|
||||
* @return The object with the given name.
|
||||
* @throws ObjectUnknownException If the referenced object does not exist.
|
||||
*/
|
||||
@Nonnull
|
||||
public TCSObject<?> getObject(
|
||||
@Nonnull
|
||||
String name
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
TCSObject<?> result = getObjectOrNull(name);
|
||||
if (result == null) {
|
||||
throw new ObjectUnknownException(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param <T> The object's type.
|
||||
* @param clazz The class of the object to be returned.
|
||||
* @param name The name of the object to be returned.
|
||||
* @return The named object, or <code>null</code>, if no such object
|
||||
* exists in this pool or if an object exists but is not an instance of the
|
||||
* given class.
|
||||
*/
|
||||
@Nullable
|
||||
public <T extends TCSObject<T>> T getObjectOrNull(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
String name
|
||||
) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(name, "name");
|
||||
|
||||
TCSObject<?> result = objects.getOrDefault(clazz, Map.of()).get(name);
|
||||
if (clazz.isInstance(result)) {
|
||||
return clazz.cast(result);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object from the pool.
|
||||
*
|
||||
* @param <T> The object's type.
|
||||
* @param clazz The class of the object to be returned.
|
||||
* @param name The name of the object to be returned.
|
||||
* @return The named object.
|
||||
* @throws ObjectUnknownException If no object with the given name exists in this pool or if an
|
||||
* object exists but is not an instance of the given class.
|
||||
*/
|
||||
@Nonnull
|
||||
public <T extends TCSObject<T>> T getObject(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
String name
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
T result = getObjectOrNull(clazz, name);
|
||||
if (result == null) {
|
||||
throw new ObjectUnknownException(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of objects belonging to the given class.
|
||||
*
|
||||
* @param <T> The objects' type.
|
||||
* @param clazz The class of the objects to be returned.
|
||||
* @return A set of objects belonging to the given class.
|
||||
*/
|
||||
@Nonnull
|
||||
public <T extends TCSObject<T>> Set<T> getObjects(
|
||||
@Nonnull
|
||||
Class<T> clazz
|
||||
) {
|
||||
return objects.getOrDefault(clazz, Map.of()).values().stream()
|
||||
.map(object -> clazz.cast(object))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of objects of the given class for which the given predicate is true.
|
||||
*
|
||||
* @param <T> The objects' type.
|
||||
* @param clazz The class of the objects to be returned.
|
||||
* @param predicate The predicate that must be true for returned objects.
|
||||
* @return A set of objects of the given class for which the given predicate is true. If no such
|
||||
* objects exist, the returned set is empty.
|
||||
*/
|
||||
@Nonnull
|
||||
public <T extends TCSObject<T>> Set<T> getObjects(
|
||||
@Nonnull
|
||||
Class<T> clazz,
|
||||
@Nonnull
|
||||
Predicate<? super T> predicate
|
||||
) {
|
||||
requireNonNull(clazz, "clazz");
|
||||
requireNonNull(predicate, "predicate");
|
||||
|
||||
return objects.getOrDefault(clazz, Map.of()).values().stream()
|
||||
.map(object -> clazz.cast(object))
|
||||
.filter(predicate)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a referenced object from this pool.
|
||||
*
|
||||
* @param ref A reference to the object to be removed.
|
||||
* @return The object that was removed from the pool.
|
||||
* @throws ObjectUnknownException If the referenced object does not exist.
|
||||
*/
|
||||
@Nonnull
|
||||
public TCSObject<?> removeObject(
|
||||
@Nonnull
|
||||
TCSObjectReference<?> ref
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
requireNonNull(ref, "ref");
|
||||
|
||||
Map<String, TCSObject<?>> map = objects.get(ref.getReferentClass());
|
||||
TCSObject<?> obj = (map == null) ? null : map.remove(ref.getName());
|
||||
if (obj == null) {
|
||||
throw new ObjectUnknownException(ref);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private boolean containsName(String name) {
|
||||
return objects.values().stream().anyMatch(objectsByName -> objectsByName.containsKey(name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,817 @@
|
||||
// SPDX-FileCopyrightText: The openTCS Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
package org.opentcs.kernel.workingset;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.opentcs.util.Assertions.checkArgument;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.opentcs.access.to.order.DestinationCreationTO;
|
||||
import org.opentcs.access.to.order.OrderSequenceCreationTO;
|
||||
import org.opentcs.access.to.order.TransportOrderCreationTO;
|
||||
import org.opentcs.components.kernel.ObjectNameProvider;
|
||||
import org.opentcs.customizations.ApplicationEventBus;
|
||||
import org.opentcs.data.ObjectExistsException;
|
||||
import org.opentcs.data.ObjectUnknownException;
|
||||
import org.opentcs.data.TCSObject;
|
||||
import org.opentcs.data.TCSObjectEvent;
|
||||
import org.opentcs.data.TCSObjectReference;
|
||||
import org.opentcs.data.model.Location;
|
||||
import org.opentcs.data.model.Location.Link;
|
||||
import org.opentcs.data.model.LocationType;
|
||||
import org.opentcs.data.model.Point;
|
||||
import org.opentcs.data.model.Vehicle;
|
||||
import org.opentcs.data.order.DriveOrder;
|
||||
import org.opentcs.data.order.DriveOrder.Destination;
|
||||
import org.opentcs.data.order.OrderSequence;
|
||||
import org.opentcs.data.order.TransportOrder;
|
||||
import org.opentcs.util.event.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Keeps all {@code TransportOrder}s and provides methods to create and manipulate them.
|
||||
* <p>
|
||||
* Note that no synchronization is done inside this class. Concurrent access of instances of this
|
||||
* class must be synchronized externally.
|
||||
* </p>
|
||||
*/
|
||||
public class TransportOrderPoolManager
|
||||
extends
|
||||
TCSObjectManager {
|
||||
|
||||
/**
|
||||
* This class's Logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TransportOrderPoolManager.class);
|
||||
/**
|
||||
* Provides names for transport orders and order sequences.
|
||||
*/
|
||||
private final ObjectNameProvider objectNameProvider;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param objectRepo The object repo.
|
||||
* @param eventHandler The event handler to publish events to.
|
||||
* @param orderNameProvider Provides names for transport orders.
|
||||
*/
|
||||
@Inject
|
||||
public TransportOrderPoolManager(
|
||||
@Nonnull
|
||||
TCSObjectRepository objectRepo,
|
||||
@Nonnull
|
||||
@ApplicationEventBus
|
||||
EventHandler eventHandler,
|
||||
@Nonnull
|
||||
ObjectNameProvider orderNameProvider
|
||||
) {
|
||||
super(objectRepo, eventHandler);
|
||||
this.objectNameProvider = requireNonNull(orderNameProvider, "orderNameProvider");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all transport orders from this pool.
|
||||
*/
|
||||
public void clear() {
|
||||
List<TCSObject<?>> objects = new ArrayList<>();
|
||||
objects.addAll(getObjectRepo().getObjects(OrderSequence.class));
|
||||
objects.addAll(getObjectRepo().getObjects(TransportOrder.class));
|
||||
|
||||
for (TCSObject<?> curObject : objects) {
|
||||
getObjectRepo().removeObject(curObject.getReference());
|
||||
emitObjectEvent(
|
||||
null,
|
||||
curObject,
|
||||
TCSObjectEvent.Type.OBJECT_REMOVED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new transport order to the pool.
|
||||
* This method implicitly adds the transport order to its wrapping sequence, if any.
|
||||
*
|
||||
* @param to The transfer object from which to create the new transport order.
|
||||
* @return The newly created transport order.
|
||||
* @throws ObjectExistsException If an object with the new object's name already exists.
|
||||
* @throws ObjectUnknownException If any object referenced in the TO does not exist.
|
||||
* @throws IllegalArgumentException One of:
|
||||
* <ol>
|
||||
* <li>The order is supposed to be part of an order sequence, but
|
||||
* the sequence is already complete.</li>
|
||||
* <li>The type of the transport order and the order sequence differ.</li>
|
||||
* <li>The intended vehicle of the transport order and the order sequence differ.</li>
|
||||
* <li>A destination operation is not a valid operation on the destination object.</li>
|
||||
* </ol>
|
||||
*/
|
||||
public TransportOrder createTransportOrder(TransportOrderCreationTO to)
|
||||
throws ObjectUnknownException,
|
||||
ObjectExistsException,
|
||||
IllegalArgumentException {
|
||||
TransportOrder newOrder = new TransportOrder(
|
||||
nameFor(to),
|
||||
toDriveOrders(to.getDestinations())
|
||||
)
|
||||
.withCreationTime(Instant.now())
|
||||
.withPeripheralReservationToken(to.getPeripheralReservationToken())
|
||||
.withIntendedVehicle(toVehicleReference(to.getIntendedVehicleName()))
|
||||
.withType(to.getType())
|
||||
.withDeadline(to.getDeadline())
|
||||
.withDispensable(to.isDispensable())
|
||||
.withWrappingSequence(getWrappingSequence(to))
|
||||
.withDependencies(getDependencies(to))
|
||||
.withProperties(to.getProperties());
|
||||
|
||||
LOG.info(
|
||||
"Transport order is being created: {} -- {}",
|
||||
newOrder.getName(),
|
||||
newOrder.getAllDriveOrders()
|
||||
);
|
||||
|
||||
getObjectRepo().addObject(newOrder);
|
||||
emitObjectEvent(newOrder, null, TCSObjectEvent.Type.OBJECT_CREATED);
|
||||
|
||||
if (newOrder.getWrappingSequence() != null) {
|
||||
OrderSequence sequence = getObjectRepo().getObject(
|
||||
OrderSequence.class,
|
||||
newOrder.getWrappingSequence()
|
||||
);
|
||||
OrderSequence prevSeq = sequence;
|
||||
sequence = sequence.withOrder(newOrder.getReference());
|
||||
getObjectRepo().replaceObject(sequence);
|
||||
emitObjectEvent(sequence, prevSeq, TCSObjectEvent.Type.OBJECT_MODIFIED);
|
||||
}
|
||||
|
||||
// Return the newly created transport order.
|
||||
return newOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a transport order's state.
|
||||
*
|
||||
* @param ref A reference to the transport order to be modified.
|
||||
* @param newState The transport order's new state.
|
||||
* @return The modified transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public TransportOrder setTransportOrderState(
|
||||
TCSObjectReference<TransportOrder> ref,
|
||||
TransportOrder.State newState
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
TransportOrder previousState = getObjectRepo().getObject(TransportOrder.class, ref);
|
||||
|
||||
checkArgument(
|
||||
!previousState.getState().isFinalState(),
|
||||
"Transport order %s already in a final state, not changing %s -> %s.",
|
||||
ref.getName(),
|
||||
previousState.getState(),
|
||||
newState
|
||||
);
|
||||
|
||||
LOG.info(
|
||||
"Transport order's state changes: {} -- {} -> {}",
|
||||
previousState.getName(),
|
||||
previousState.getState(),
|
||||
newState
|
||||
);
|
||||
|
||||
TransportOrder order = previousState.withState(newState);
|
||||
getObjectRepo().replaceObject(order);
|
||||
emitObjectEvent(
|
||||
order,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a transport order's processing vehicle.
|
||||
*
|
||||
* @param orderRef A reference to the transport order to be modified.
|
||||
* @param vehicleRef A reference to the vehicle processing the order.
|
||||
* @param driveOrders The drive orders containing the data to be copied into this transport
|
||||
* order's drive orders.
|
||||
* @return The modified transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
* @throws IllegalArgumentException If the destinations of the given drive
|
||||
* orders do not match the destinations of the drive orders in this transport
|
||||
* order.
|
||||
*/
|
||||
public TransportOrder setTransportOrderProcessingVehicle(
|
||||
TCSObjectReference<TransportOrder> orderRef,
|
||||
TCSObjectReference<Vehicle> vehicleRef,
|
||||
List<DriveOrder> driveOrders
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
TransportOrder order = getObjectRepo().getObject(TransportOrder.class, orderRef);
|
||||
|
||||
LOG.info(
|
||||
"Transport order's processing vehicle changes: {} -- {} -> {}",
|
||||
order.getName(),
|
||||
toObjectName(order.getProcessingVehicle()),
|
||||
toObjectName(vehicleRef)
|
||||
);
|
||||
|
||||
TransportOrder previousState = order;
|
||||
if (vehicleRef == null) {
|
||||
order = order.withProcessingVehicle(null);
|
||||
getObjectRepo().replaceObject(order);
|
||||
}
|
||||
else {
|
||||
Vehicle vehicle = getObjectRepo().getObject(Vehicle.class, vehicleRef);
|
||||
order = order.withProcessingVehicle(vehicle.getReference())
|
||||
.withDriveOrders(driveOrders)
|
||||
.withCurrentDriveOrderIndex(0);
|
||||
getObjectRepo().replaceObject(order);
|
||||
if (order.getCurrentDriveOrder() != null) {
|
||||
order = order.withCurrentDriveOrderState(DriveOrder.State.TRAVELLING);
|
||||
getObjectRepo().replaceObject(order);
|
||||
}
|
||||
}
|
||||
emitObjectEvent(
|
||||
order,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies drive order data from a list of drive orders to the given transport
|
||||
* order's future drive orders.
|
||||
*
|
||||
* @param orderRef A reference to the transport order to be modified.
|
||||
* @param newOrders The drive orders containing the data to be copied into
|
||||
* this transport order's drive orders.
|
||||
* @return The modified transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
* @throws IllegalArgumentException If the destinations of the given drive
|
||||
* orders do not match the destinations of the drive orders in this transport
|
||||
* order.
|
||||
*/
|
||||
public TransportOrder setTransportOrderDriveOrders(
|
||||
TCSObjectReference<TransportOrder> orderRef,
|
||||
List<DriveOrder> newOrders
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
TransportOrder previousState = getObjectRepo().getObject(TransportOrder.class, orderRef);
|
||||
|
||||
LOG.debug(
|
||||
"Transport order's drive orders change: {} -- {} -> {}",
|
||||
previousState.getName(),
|
||||
previousState.getAllDriveOrders(),
|
||||
newOrders
|
||||
);
|
||||
|
||||
TransportOrder order = previousState.withDriveOrders(newOrders);
|
||||
getObjectRepo().replaceObject(order);
|
||||
emitObjectEvent(
|
||||
order,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a transport order's current drive order.
|
||||
* Marks the current drive order as finished, adds it to the list of past
|
||||
* drive orders and sets the current drive order to the next one of the list
|
||||
* of future drive orders (or <code>null</code>, if that list is empty).
|
||||
* If the current drive order is <code>null</code> because all drive orders
|
||||
* have been finished already or none has been started, yet, nothing happens.
|
||||
*
|
||||
* @param ref A reference to the transport order to be modified.
|
||||
* @return The modified transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public TransportOrder setTransportOrderNextDriveOrder(TCSObjectReference<TransportOrder> ref)
|
||||
throws ObjectUnknownException {
|
||||
TransportOrder previousState = getObjectRepo().getObject(TransportOrder.class, ref);
|
||||
TransportOrder order = previousState;
|
||||
// First, mark the current drive order as FINISHED and send an event.
|
||||
// Then, shift drive orders and send a second event.
|
||||
// Then, mark the current drive order as TRAVELLING and send another event.
|
||||
if (order.getCurrentDriveOrder() != null) {
|
||||
LOG.info(
|
||||
"Transport order's drive order finished: {} -- {}",
|
||||
order.getName(),
|
||||
order.getCurrentDriveOrder().getDestination()
|
||||
);
|
||||
|
||||
order = order.withCurrentDriveOrderState(DriveOrder.State.FINISHED);
|
||||
getObjectRepo().replaceObject(order);
|
||||
TransportOrder newState = order;
|
||||
emitObjectEvent(
|
||||
newState,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
previousState = newState;
|
||||
order = order.withCurrentDriveOrderIndex(order.getCurrentDriveOrderIndex() + 1)
|
||||
.withCurrentRouteStepIndex(TransportOrder.ROUTE_STEP_INDEX_DEFAULT);
|
||||
getObjectRepo().replaceObject(order);
|
||||
newState = order;
|
||||
emitObjectEvent(
|
||||
newState,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
previousState = newState;
|
||||
if (order.getCurrentDriveOrder() != null) {
|
||||
order = order.withCurrentDriveOrderState(DriveOrder.State.TRAVELLING);
|
||||
getObjectRepo().replaceObject(order);
|
||||
newState = order;
|
||||
emitObjectEvent(
|
||||
newState,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
previousState = newState;
|
||||
}
|
||||
}
|
||||
emitObjectEvent(
|
||||
order,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a transport order's index of the last route step travelled for the currently processed
|
||||
* drive order.
|
||||
*
|
||||
* @param ref A reference to the transport order to be modified.
|
||||
* @param index The new index.
|
||||
* @return The modified transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order does not exist.
|
||||
*/
|
||||
public TransportOrder setTransportOrderCurrentRouteStepIndex(
|
||||
TCSObjectReference<TransportOrder> ref,
|
||||
int index
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
TransportOrder previousState = getObjectRepo().getObject(TransportOrder.class, ref);
|
||||
|
||||
LOG.debug(
|
||||
"Transport order's route step index changes: {} -- {} -> {}",
|
||||
previousState.getName(),
|
||||
previousState.getCurrentRouteStepIndex(),
|
||||
index
|
||||
);
|
||||
|
||||
TransportOrder order = previousState.withCurrentRouteStepIndex(index);
|
||||
getObjectRepo().replaceObject(order);
|
||||
emitObjectEvent(
|
||||
order,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a transport order's intended vehicle.
|
||||
*
|
||||
* @param orderRef A reference to the transport order to be modified.
|
||||
* @param vehicleRef A reference to the vehicle intended for the transport order.
|
||||
* @return The modified transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool or if the intended vehicle is not null and not in this this pool.
|
||||
* @throws IllegalArgumentException If the transport order is not in the dispatchable state.
|
||||
*/
|
||||
public TransportOrder setTransportOrderIntendedVehicle(
|
||||
TCSObjectReference<TransportOrder> orderRef,
|
||||
TCSObjectReference<Vehicle> vehicleRef
|
||||
)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
TransportOrder order = getObjectRepo().getObject(TransportOrder.class, orderRef);
|
||||
|
||||
if (!canSetIntendedVehicle(order)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Cannot set intended vehicle '%s' for transport order '%s' in state '%s'",
|
||||
toObjectName(vehicleRef),
|
||||
order.getName(),
|
||||
order.getState()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (vehicleRef != null && getObjectRepo().getObjectOrNull(Vehicle.class, vehicleRef) == null) {
|
||||
throw new ObjectUnknownException("Unknown vehicle: " + vehicleRef.getName());
|
||||
}
|
||||
|
||||
LOG.info(
|
||||
"Transport order's intended vehicle changes: {} -- {} -> {}",
|
||||
order.getName(),
|
||||
toObjectName(order.getIntendedVehicle()),
|
||||
toObjectName(vehicleRef)
|
||||
);
|
||||
|
||||
TransportOrder previousState = order;
|
||||
order = order.withIntendedVehicle(vehicleRef);
|
||||
getObjectRepo().replaceObject(order);
|
||||
emitObjectEvent(
|
||||
order,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the referenced transport order from this pool.
|
||||
*
|
||||
* @param ref A reference to the transport order to be removed.
|
||||
* @return The removed transport order.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public TransportOrder removeTransportOrder(TCSObjectReference<TransportOrder> ref)
|
||||
throws ObjectUnknownException {
|
||||
TransportOrder order = getObjectRepo().getObject(TransportOrder.class, ref);
|
||||
// Make sure only orders in a final state are removed.
|
||||
checkArgument(
|
||||
order.getState().isFinalState(),
|
||||
"Transport order %s is not in a final state.",
|
||||
order.getName()
|
||||
);
|
||||
getObjectRepo().removeObject(ref);
|
||||
emitObjectEvent(
|
||||
null,
|
||||
order,
|
||||
TCSObjectEvent.Type.OBJECT_REMOVED
|
||||
);
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new order sequence to the pool.
|
||||
*
|
||||
* @param to The transfer object from which to create the new order sequence.
|
||||
* @return The newly created order sequence.
|
||||
* @throws ObjectExistsException If an object with the new object's name already exists.
|
||||
* @throws ObjectUnknownException If any object referenced in the TO does not exist.
|
||||
*/
|
||||
public OrderSequence createOrderSequence(OrderSequenceCreationTO to)
|
||||
throws ObjectExistsException,
|
||||
ObjectUnknownException {
|
||||
OrderSequence newSequence = new OrderSequence(nameFor(to))
|
||||
.withType(to.getType())
|
||||
.withIntendedVehicle(toVehicleReference(to.getIntendedVehicleName()))
|
||||
.withFailureFatal(to.isFailureFatal())
|
||||
.withProperties(to.getProperties());
|
||||
|
||||
LOG.info("Order sequence is being created: {}", newSequence.getName());
|
||||
|
||||
getObjectRepo().addObject(newSequence);
|
||||
emitObjectEvent(
|
||||
newSequence,
|
||||
null,
|
||||
TCSObjectEvent.Type.OBJECT_CREATED
|
||||
);
|
||||
// Return the newly created transport order.
|
||||
return newSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an order sequence's finished index.
|
||||
*
|
||||
* @param seqRef A reference to the order sequence to be modified.
|
||||
* @param index The sequence's new finished index.
|
||||
* @return The modified order sequence.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public OrderSequence setOrderSequenceFinishedIndex(
|
||||
TCSObjectReference<OrderSequence> seqRef,
|
||||
int index
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
OrderSequence previousState = getObjectRepo().getObject(OrderSequence.class, seqRef);
|
||||
|
||||
LOG.debug(
|
||||
"Order sequence's finished index changes: {} -- {} -> {}",
|
||||
previousState.getName(),
|
||||
previousState.getFinishedIndex(),
|
||||
index
|
||||
);
|
||||
|
||||
OrderSequence sequence = previousState.withFinishedIndex(index);
|
||||
getObjectRepo().replaceObject(sequence);
|
||||
emitObjectEvent(
|
||||
sequence,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an order sequence's complete flag.
|
||||
*
|
||||
* @param seqRef A reference to the order sequence to be modified.
|
||||
* @return The modified order sequence.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public OrderSequence setOrderSequenceComplete(TCSObjectReference<OrderSequence> seqRef)
|
||||
throws ObjectUnknownException {
|
||||
OrderSequence previousState = getObjectRepo().getObject(OrderSequence.class, seqRef);
|
||||
|
||||
LOG.info("Order sequence being marked as complete: {}", previousState.getName());
|
||||
|
||||
OrderSequence sequence = previousState.withComplete(true);
|
||||
getObjectRepo().replaceObject(sequence);
|
||||
emitObjectEvent(
|
||||
sequence,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an order sequence's finished flag.
|
||||
*
|
||||
* @param seqRef A reference to the order sequence to be modified.
|
||||
* @return The modified order sequence.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public OrderSequence setOrderSequenceFinished(TCSObjectReference<OrderSequence> seqRef)
|
||||
throws ObjectUnknownException {
|
||||
OrderSequence previousState = getObjectRepo().getObject(OrderSequence.class, seqRef);
|
||||
|
||||
LOG.info("Order sequence being marked as finished: {}", previousState.getName());
|
||||
|
||||
OrderSequence sequence = previousState.withFinished(true);
|
||||
getObjectRepo().replaceObject(sequence);
|
||||
emitObjectEvent(
|
||||
sequence,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an order sequence's processing vehicle.
|
||||
*
|
||||
* @param seqRef A reference to the order sequence to be modified.
|
||||
* @param vehicleRef A reference to the vehicle processing the order sequence.
|
||||
* @return The modified order sequence.
|
||||
* @throws ObjectUnknownException If the referenced transport order is not
|
||||
* in this pool.
|
||||
*/
|
||||
public OrderSequence setOrderSequenceProcessingVehicle(
|
||||
TCSObjectReference<OrderSequence> seqRef,
|
||||
TCSObjectReference<Vehicle> vehicleRef
|
||||
)
|
||||
throws ObjectUnknownException {
|
||||
OrderSequence previousState = getObjectRepo().getObject(OrderSequence.class, seqRef);
|
||||
|
||||
LOG.info(
|
||||
"Order sequence's processing vehicle changes: {} -- {} -> {}",
|
||||
previousState.getName(),
|
||||
toObjectName(previousState.getProcessingVehicle()),
|
||||
toObjectName(vehicleRef)
|
||||
);
|
||||
|
||||
OrderSequence sequence = previousState;
|
||||
if (vehicleRef == null) {
|
||||
sequence = sequence.withProcessingVehicle(null);
|
||||
getObjectRepo().replaceObject(sequence);
|
||||
}
|
||||
else {
|
||||
Vehicle vehicle = getObjectRepo().getObject(Vehicle.class, vehicleRef);
|
||||
sequence = sequence.withProcessingVehicle(vehicle.getReference());
|
||||
getObjectRepo().replaceObject(sequence);
|
||||
}
|
||||
emitObjectEvent(
|
||||
sequence,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_MODIFIED
|
||||
);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the referenced order sequence from this pool.
|
||||
*
|
||||
* @param ref A reference to the order sequence to be removed.
|
||||
* @return The removed order sequence.
|
||||
* @throws ObjectUnknownException If the referenced order sequence is not in this pool.
|
||||
*/
|
||||
public OrderSequence removeOrderSequence(TCSObjectReference<OrderSequence> ref)
|
||||
throws ObjectUnknownException {
|
||||
OrderSequence previousState = getObjectRepo().getObject(OrderSequence.class, ref);
|
||||
OrderSequence sequence = previousState;
|
||||
// XXX Any sanity checks here?
|
||||
getObjectRepo().removeObject(ref);
|
||||
emitObjectEvent(
|
||||
null,
|
||||
previousState,
|
||||
TCSObjectEvent.Type.OBJECT_REMOVED
|
||||
);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a completed order sequence including its transport orders.
|
||||
*
|
||||
* @param ref A reference to the order sequence.
|
||||
* @throws ObjectUnknownException If the referenced order sequence is not in this pool.
|
||||
* @throws IllegalArgumentException If the order sequence is not finished, yet.
|
||||
*/
|
||||
public void removeFinishedOrderSequenceAndOrders(TCSObjectReference<OrderSequence> ref)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
OrderSequence previousState = getObjectRepo().getObject(OrderSequence.class, ref);
|
||||
checkArgument(
|
||||
previousState.isFinished(),
|
||||
"Order sequence %s is not finished",
|
||||
previousState.getName()
|
||||
);
|
||||
OrderSequence sequence = previousState;
|
||||
getObjectRepo().removeObject(ref);
|
||||
emitObjectEvent(null, previousState, TCSObjectEvent.Type.OBJECT_REMOVED);
|
||||
// Also remove all orders in the sequence.
|
||||
for (TCSObjectReference<TransportOrder> orderRef : sequence.getOrders()) {
|
||||
removeTransportOrder(orderRef);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<TCSObjectReference<TransportOrder>> getDependencies(TransportOrderCreationTO to)
|
||||
throws ObjectUnknownException {
|
||||
Set<TCSObjectReference<TransportOrder>> result = new HashSet<>();
|
||||
for (String dependencyName : to.getDependencyNames()) {
|
||||
result.add(getObjectRepo().getObject(TransportOrder.class, dependencyName).getReference());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private TCSObjectReference<OrderSequence> getWrappingSequence(TransportOrderCreationTO to)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
if (to.getWrappingSequence() == null) {
|
||||
return null;
|
||||
}
|
||||
OrderSequence sequence = getObjectRepo().getObject(
|
||||
OrderSequence.class,
|
||||
to.getWrappingSequence()
|
||||
);
|
||||
checkArgument(!sequence.isComplete(), "Order sequence %s is already complete", sequence);
|
||||
checkArgument(
|
||||
Objects.equals(to.getType(), sequence.getType()),
|
||||
"Order sequence %s has different type than order %s: %s != %s",
|
||||
sequence,
|
||||
to.getName(),
|
||||
sequence.getType(),
|
||||
to.getType()
|
||||
);
|
||||
checkArgument(
|
||||
Objects.equals(to.getIntendedVehicleName(), getIntendedVehicleName(sequence)),
|
||||
"Order sequence %s has different intended vehicle than order %s: %s != %s",
|
||||
sequence,
|
||||
to.getName(),
|
||||
sequence.getIntendedVehicle(),
|
||||
to.getIntendedVehicleName()
|
||||
);
|
||||
return sequence.getReference();
|
||||
}
|
||||
|
||||
private TCSObjectReference<Vehicle> toVehicleReference(String vehicleName)
|
||||
throws ObjectUnknownException {
|
||||
if (vehicleName == null) {
|
||||
return null;
|
||||
}
|
||||
Vehicle vehicle = getObjectRepo().getObject(Vehicle.class, vehicleName);
|
||||
return vehicle.getReference();
|
||||
}
|
||||
|
||||
private List<DriveOrder> toDriveOrders(List<DestinationCreationTO> dests)
|
||||
throws ObjectUnknownException,
|
||||
IllegalArgumentException {
|
||||
List<DriveOrder> result = new ArrayList<>(dests.size());
|
||||
for (DestinationCreationTO destTo : dests) {
|
||||
TCSObject<?> destObject = getObjectRepo().getObjectOrNull(destTo.getDestLocationName());
|
||||
|
||||
if (destObject instanceof Point) {
|
||||
if (!isValidOperationOnPoint(destTo.getDestOperation())) {
|
||||
throw new IllegalArgumentException(
|
||||
destTo.getDestOperation()
|
||||
+ " is not a valid operation for point destination " + destObject.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (destObject instanceof Location) {
|
||||
if (!isValidLocationDestination(destTo, (Location) destObject)) {
|
||||
throw new IllegalArgumentException(
|
||||
destTo.getDestOperation()
|
||||
+ " is not a valid operation for location destination " + destObject.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new ObjectUnknownException(destTo.getDestLocationName());
|
||||
}
|
||||
result.add(
|
||||
new DriveOrder(
|
||||
new DriveOrder.Destination(destObject.getReference())
|
||||
.withOperation(destTo.getDestOperation())
|
||||
.withProperties(destTo.getProperties())
|
||||
)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isValidOperationOnPoint(String operation) {
|
||||
return operation.equals(Destination.OP_MOVE)
|
||||
|| operation.equals(Destination.OP_PARK);
|
||||
}
|
||||
|
||||
private boolean isValidLocationDestination(DestinationCreationTO dest, Location location) {
|
||||
LocationType type = getObjectRepo()
|
||||
.getObjectOrNull(LocationType.class, location.getType().getName());
|
||||
|
||||
return type != null
|
||||
&& isValidOperationOnLocationType(dest.getDestOperation(), type)
|
||||
&& location.getAttachedLinks().stream()
|
||||
.anyMatch(link -> isValidOperationOnLink(dest.getDestOperation(), link));
|
||||
}
|
||||
|
||||
private boolean isValidOperationOnLink(String operation, Link link) {
|
||||
return link.getAllowedOperations().isEmpty()
|
||||
|| link.getAllowedOperations().contains(operation)
|
||||
|| operation.equals(Destination.OP_NOP);
|
||||
}
|
||||
|
||||
private boolean isValidOperationOnLocationType(String operation, LocationType type) {
|
||||
return type.isAllowedOperation(operation)
|
||||
|| operation.equals(Destination.OP_NOP);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getIntendedVehicleName(OrderSequence sequence) {
|
||||
return sequence.getIntendedVehicle() == null ? null : sequence.getIntendedVehicle().getName();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String nameFor(
|
||||
@Nonnull
|
||||
TransportOrderCreationTO to
|
||||
) {
|
||||
if (to.hasIncompleteName()) {
|
||||
return objectNameProvider.apply(to);
|
||||
}
|
||||
else {
|
||||
return to.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String nameFor(
|
||||
@Nonnull
|
||||
OrderSequenceCreationTO to
|
||||
) {
|
||||
if (to.hasIncompleteName()) {
|
||||
return objectNameProvider.apply(to);
|
||||
}
|
||||
else {
|
||||
return to.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String toObjectName(TCSObjectReference<?> ref) {
|
||||
return ref == null ? "null" : ref.getName();
|
||||
}
|
||||
|
||||
private boolean canSetIntendedVehicle(TransportOrder order) {
|
||||
return order.hasState(TransportOrder.State.RAW)
|
||||
|| order.hasState(TransportOrder.State.ACTIVE)
|
||||
|| order.hasState(TransportOrder.State.DISPATCHABLE);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user