615 lines
18 KiB
Java
615 lines
18 KiB
Java
package li.strolch.plc.core;
|
|
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
import static li.strolch.plc.model.PlcConstants.PARAM_VALUE;
|
|
import static li.strolch.plc.model.PlcConstants.TYPE_PLC_ADDRESS;
|
|
import static li.strolch.runtime.StrolchConstants.DEFAULT_REALM;
|
|
import static li.strolch.utils.helper.ExceptionHelper.getCallerMethod;
|
|
|
|
import java.util.Map;
|
|
import java.util.ResourceBundle;
|
|
import java.util.concurrent.*;
|
|
|
|
import li.strolch.agent.api.ComponentContainer;
|
|
import li.strolch.model.Locator;
|
|
import li.strolch.model.Resource;
|
|
import li.strolch.model.log.LogMessage;
|
|
import li.strolch.model.log.LogMessageState;
|
|
import li.strolch.model.log.LogSeverity;
|
|
import li.strolch.model.parameter.Parameter;
|
|
import li.strolch.persistence.api.StrolchTransaction;
|
|
import li.strolch.plc.core.hw.PlcListener;
|
|
import li.strolch.plc.model.PlcAddress;
|
|
import li.strolch.plc.model.PlcAddressKey;
|
|
import li.strolch.plc.model.PlcServiceState;
|
|
import li.strolch.privilege.model.PrivilegeContext;
|
|
import li.strolch.runtime.privilege.PrivilegedRunnable;
|
|
import li.strolch.runtime.privilege.PrivilegedRunnableWithResult;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/**
|
|
* <p>This is an interface to implement short use cases in a plc.</p>
|
|
*
|
|
* <p>Each instance of a {@link PlcService} should correspond with one resource on a {@link PlcAddress#resource}.</p>
|
|
*
|
|
* <p>The service registers for changes on the hardware, e.g. button presses, light barriers, etc. and then performs a
|
|
* given action, e.g. turning a motor on or off.</p>
|
|
*
|
|
* <p>The PlcService has the following life cycle:</p>
|
|
* <ul>
|
|
* <li>{@link #register()}</li>
|
|
* <li>{@link #start(StrolchTransaction)}</li>
|
|
* <li>{@link #stop()}</li>
|
|
* <li>{@link #unregister()}</li>
|
|
* </ul>
|
|
*/
|
|
public abstract class PlcService implements PlcListener {
|
|
|
|
protected static final Logger logger = LoggerFactory.getLogger(PlcService.class);
|
|
protected final ComponentContainer container;
|
|
protected final PlcHandler plcHandler;
|
|
|
|
protected final Map<PlcAddress, Future<?>> debounceMap;
|
|
|
|
private PlcServiceState state;
|
|
|
|
public PlcService(PlcHandler plcHandler) {
|
|
this.container = plcHandler.getContainer();
|
|
this.plcHandler = plcHandler;
|
|
this.state = PlcServiceState.Unregistered;
|
|
this.debounceMap = new ConcurrentHashMap<>();
|
|
}
|
|
|
|
public PlcServiceState getState() {
|
|
return this.state;
|
|
}
|
|
|
|
@Override
|
|
public void handleNotification(PlcAddress address, Object value) {
|
|
// no-op
|
|
}
|
|
|
|
/**
|
|
* Called to initialize this service, here one would read the model state of a given address using
|
|
* {@link #getAddressState(StrolchTransaction, String, String)}
|
|
*
|
|
* @param tx
|
|
* the transaction giving access to the model
|
|
*/
|
|
public void start(StrolchTransaction tx) {
|
|
this.state = PlcServiceState.Started;
|
|
}
|
|
|
|
/**
|
|
* Called to stop the plc service. Here you would cancel any scheduled tasks.
|
|
*/
|
|
public void stop() {
|
|
this.state = PlcServiceState.Stopped;
|
|
}
|
|
|
|
/**
|
|
* Called to register this service for all relevant {@link PlcAddress}
|
|
*/
|
|
public void register() {
|
|
this.state = PlcServiceState.Registered;
|
|
}
|
|
|
|
/**
|
|
* Called to unregister this service from previously registered addresses
|
|
*/
|
|
public void unregister() {
|
|
this.state = PlcServiceState.Unregistered;
|
|
}
|
|
|
|
/**
|
|
* Register this service with the given resource and action
|
|
*
|
|
* @param resource
|
|
* the resource ID
|
|
* @param action
|
|
* the action
|
|
*/
|
|
public void register(String resource, String action) {
|
|
this.plcHandler.register(resource, action, this);
|
|
}
|
|
|
|
/**
|
|
* Unregister this service with the given resource and action
|
|
*
|
|
* @param resource
|
|
* the resource ID
|
|
* @param action
|
|
* the action
|
|
*/
|
|
public void unregister(String resource, String action) {
|
|
this.plcHandler.unregister(resource, action, this);
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link Resource} of type #TYPE_PLC_ADDRESS for the given resource and action
|
|
*
|
|
* @param tx
|
|
* the current TX
|
|
* @param resource
|
|
* the resource
|
|
* @param action
|
|
* the action
|
|
*
|
|
* @return the {@link Resource}
|
|
*/
|
|
protected Resource getPlcAddress(StrolchTransaction tx, String resource, String action) {
|
|
String plcAddressId = this.plcHandler.getPlcAddressId(resource, action);
|
|
return tx.getResourceBy(TYPE_PLC_ADDRESS, plcAddressId, true);
|
|
}
|
|
|
|
/**
|
|
* Returns the value of a plc address by calling {@link #getPlcAddress(StrolchTransaction, String, String)} for the
|
|
* given resource and action
|
|
*
|
|
* @param tx
|
|
* the current TX
|
|
* @param resource
|
|
* the resource
|
|
* @param action
|
|
* the action
|
|
* @param <T>
|
|
* the type of value to return
|
|
*
|
|
* @return the value of the given address
|
|
*/
|
|
protected <T> T getAddressState(StrolchTransaction tx, String resource, String action) {
|
|
Parameter<T> addressParam = getPlcAddress(tx, resource, action).getParameter(PARAM_VALUE, true);
|
|
return addressParam.getValue();
|
|
}
|
|
|
|
/**
|
|
* Enables an operations log message to be seen by a user
|
|
*
|
|
* @param addressKey
|
|
* the address for which the message is enabled
|
|
* @param bundle
|
|
* the resource bundle containing the message
|
|
* @param severity
|
|
* the severity of the message
|
|
*/
|
|
protected void enableMsg(PlcAddressKey addressKey, ResourceBundle bundle, LogSeverity severity) {
|
|
sendMsg(logMessageFor(addressKey, bundle, severity, LogMessageState.Active));
|
|
}
|
|
|
|
/**
|
|
* Disables an operations log message which was previously enabled
|
|
*
|
|
* @param addressKey
|
|
* the address for which the message was enabled
|
|
*/
|
|
protected void disableMsg(PlcAddressKey addressKey) {
|
|
disableMsg(Locator.valueOf("Plc", this.plcHandler.getPlcId(), addressKey.resource, addressKey.action));
|
|
}
|
|
|
|
/**
|
|
* Enables an operations log message to be seen by a user
|
|
*
|
|
* @param i18nKey
|
|
* the key of the message in the resource bundle
|
|
* @param bundle
|
|
* the resource bundle containing the message
|
|
* @param severity
|
|
* the severity of the message
|
|
*/
|
|
protected void enableMsg(String i18nKey, ResourceBundle bundle, LogSeverity severity) {
|
|
sendMsg(logMessageFor(i18nKey, bundle, severity, LogMessageState.Active));
|
|
}
|
|
|
|
/**
|
|
* Disables an operations log message which was previously enabled
|
|
*
|
|
* @param i18nKey
|
|
* the key of the message in the resource bundle for which the message was enabled
|
|
* @param bundle
|
|
* the resource bundle containing the message
|
|
*/
|
|
protected void disableMsg(String i18nKey, ResourceBundle bundle) {
|
|
disableMsg(Locator.valueOf("Plc", this.plcHandler.getPlcId(), bundle.getBaseBundleName(), i18nKey));
|
|
}
|
|
|
|
/**
|
|
* Sends a message created for the given properties to a remote listener
|
|
*
|
|
* @param i18nKey
|
|
* the key of the message
|
|
* @param bundle
|
|
* the bundle containing the key
|
|
* @param severity
|
|
* the severity of the message
|
|
*/
|
|
protected void sendMsg(String i18nKey, ResourceBundle bundle, LogSeverity severity) {
|
|
sendMsg(logMessageFor(i18nKey, bundle, severity));
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link LogMessage} for the given fields
|
|
*
|
|
* @param addressKey
|
|
* the address for the key
|
|
* @param bundle
|
|
* the bundle containing the message
|
|
* @param severity
|
|
* the severity of the message
|
|
*
|
|
* @return the {@link LogMessage} instance
|
|
*/
|
|
protected LogMessage logMessageFor(PlcAddressKey addressKey, ResourceBundle bundle, LogSeverity severity) {
|
|
return logMessageFor(addressKey, bundle, severity, LogMessageState.Information);
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link LogMessage} for the given fields
|
|
*
|
|
* @param addressKey
|
|
* the address for the key
|
|
* @param bundle
|
|
* the bundle containing the message
|
|
* @param severity
|
|
* the severity of the message
|
|
* @param state
|
|
* the state of the message
|
|
*
|
|
* @return the {@link LogMessage} instance
|
|
*/
|
|
protected LogMessage logMessageFor(PlcAddressKey addressKey, ResourceBundle bundle, LogSeverity severity,
|
|
LogMessageState state) {
|
|
return new LogMessage(DEFAULT_REALM, this.plcHandler.getPlcId(),
|
|
Locator.valueOf("Plc", this.plcHandler.getPlcId(), addressKey.resource, addressKey.action), severity,
|
|
state, bundle, addressKey.toKey());
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link LogMessage} for the given fields
|
|
*
|
|
* @param i18nKey
|
|
* the key of the message
|
|
* @param bundle
|
|
* the bundle containing the message
|
|
* @param severity
|
|
* the severity of the message
|
|
*
|
|
* @return the {@link LogMessage} instance
|
|
*/
|
|
protected LogMessage logMessageFor(String i18nKey, ResourceBundle bundle, LogSeverity severity) {
|
|
return logMessageFor(i18nKey, bundle, severity, LogMessageState.Information);
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link LogMessage} for the given fields
|
|
*
|
|
* @param i18nKey
|
|
* the key of the message
|
|
* @param bundle
|
|
* the bundle containing the message
|
|
* @param severity
|
|
* the severity of the message
|
|
* @param state
|
|
* the state of the message
|
|
*
|
|
* @return the {@link LogMessage} instance
|
|
*/
|
|
protected LogMessage logMessageFor(String i18nKey, ResourceBundle bundle, LogSeverity severity,
|
|
LogMessageState state) {
|
|
return new LogMessage(DEFAULT_REALM, this.plcHandler.getPlcId(),
|
|
Locator.valueOf("Plc", this.plcHandler.getPlcId(), bundle.getBaseBundleName(), i18nKey), severity,
|
|
state, bundle, i18nKey);
|
|
}
|
|
|
|
/**
|
|
* Sends the given {@link LogMessage} to the remote listener
|
|
*
|
|
* @param logMessage
|
|
* the message to send
|
|
*/
|
|
protected void sendMsg(LogMessage logMessage) {
|
|
switch (logMessage.getSeverity()) {
|
|
case Info, Notification -> logger.info(logMessage.toString());
|
|
case Warning -> logger.warn(logMessage.toString());
|
|
case Error, Exception -> logger.error(logMessage.toString());
|
|
}
|
|
this.plcHandler.sendMsg(logMessage);
|
|
}
|
|
|
|
/**
|
|
* Disables a message with the given {@link Locator}
|
|
*
|
|
* @param locator
|
|
* the locator of the message
|
|
*/
|
|
protected void disableMsg(Locator locator) {
|
|
logger.info("Disabling message for locator " + locator);
|
|
this.plcHandler.disableMsg(locator);
|
|
}
|
|
|
|
/**
|
|
* Causes the {@link PlcAddress} for the given resource and action to be sent as a telegram with its default value
|
|
*
|
|
* @param resource
|
|
* the resource
|
|
* @param action
|
|
* the action
|
|
*/
|
|
protected void send(String resource, String action) {
|
|
this.plcHandler.send(resource, action);
|
|
}
|
|
|
|
/**
|
|
* Causes the {@link PlcAddress} for the given resource and action to be sent as a telegram with the given value
|
|
*
|
|
* @param resource
|
|
* the resource
|
|
* @param action
|
|
* the action
|
|
* @param value
|
|
* the value to send with the {@link PlcAddress}
|
|
*/
|
|
protected void send(String resource, String action, Object value) {
|
|
this.plcHandler.send(resource, action, value);
|
|
}
|
|
|
|
/**
|
|
* Notifies listeners on the {@link PlcAddress} for the given resource and action, of the new value
|
|
*
|
|
* @param resource
|
|
* the resource
|
|
* @param action
|
|
* the action
|
|
* @param value
|
|
* the value to notify the listeners with
|
|
*/
|
|
protected void notify(String resource, String action, Object value) {
|
|
this.plcHandler.notify(resource, action, value);
|
|
}
|
|
|
|
/**
|
|
* Runs the given {@link PrivilegedRunnable} as the agent user
|
|
*
|
|
* @param runnable
|
|
* the runnable to run
|
|
*/
|
|
protected void run(PrivilegedRunnable runnable) throws Exception {
|
|
this.container.getPrivilegeHandler().runAsAgent(runnable);
|
|
}
|
|
|
|
/**
|
|
* Runs the given {@link PrivilegedRunnableWithResult} as the agent user, returning a value as the result
|
|
*
|
|
* @param runnable
|
|
* the runnable to run
|
|
* @param <T>
|
|
* the type of object being returned in the runnable
|
|
*
|
|
* @return the result of the runnable
|
|
*/
|
|
protected <T> T runWithResult(PrivilegedRunnableWithResult<T> runnable) throws Exception {
|
|
return this.container.getPrivilegeHandler().runAsAgentWithResult(runnable);
|
|
}
|
|
|
|
/**
|
|
* Opens a new {@link StrolchTransaction} with the given {@link PrivilegeContext}
|
|
*
|
|
* @param ctx
|
|
* the {@link PrivilegeContext}
|
|
* @param readOnly
|
|
* true for the TX to be read only
|
|
*
|
|
* @return the new TX to be used in a try-with-resource block
|
|
*/
|
|
protected StrolchTransaction openTx(PrivilegeContext ctx, boolean readOnly) {
|
|
return this.container.getRealm(ctx.getCertificate()).openTx(ctx.getCertificate(), getCallerMethod(2), readOnly);
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link ExecutorService} for this class
|
|
*
|
|
* @return the executor
|
|
*/
|
|
private ExecutorService getExecutor() {
|
|
return this.container.getAgent().getExecutor(getClass().getSimpleName());
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link ScheduledExecutorService} for this class
|
|
*
|
|
* @return the scheduled executor
|
|
*/
|
|
private ScheduledExecutorService getScheduledExecutor() {
|
|
return this.container.getAgent().getScheduledExecutor(getClass().getSimpleName());
|
|
}
|
|
|
|
/**
|
|
* <p>Delays the execution of the given runnable by the given delay in milliseconds. Subsequent calls with the
|
|
* given
|
|
* {@link PlcAddress} will cancel any previous calls with the same address, delaying the execution again by the
|
|
* given amount of time.</p>
|
|
*
|
|
* <p>This methods is used to handle hardware where the bits change often, before resting at a new state. E.g. a
|
|
* light barrier where it might toggle between true and false a few times, before staying true when the light
|
|
* barrier detects an object.</p>
|
|
*/
|
|
protected void debounce(PlcAddress address, Runnable runnable, int delay) {
|
|
Future<?> previousTask = this.debounceMap.put(address, schedule(() -> {
|
|
this.debounceMap.remove(address);
|
|
runnable.run();
|
|
}, delay, MILLISECONDS));
|
|
if (previousTask != null)
|
|
previousTask.cancel(true);
|
|
}
|
|
|
|
/**
|
|
* Submits the given runnable for asynchronous execution
|
|
*
|
|
* @param runnable
|
|
* the runnable to execute asynchronously
|
|
*/
|
|
protected void async(Runnable runnable) {
|
|
getExecutor().submit(() -> {
|
|
try {
|
|
runnable.run();
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delay the execution of the given {@link Runnable} by the given delay
|
|
*
|
|
* @param runnable
|
|
* the runnable to delay
|
|
* @param delay
|
|
* the time to delay
|
|
* @param delayUnit
|
|
* the unit of the time to delay
|
|
*
|
|
* @return a future to cancel the executor before execution
|
|
*/
|
|
protected ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit delayUnit) {
|
|
return getScheduledExecutor().schedule(() -> {
|
|
try {
|
|
runnable.run();
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
}, delay, delayUnit);
|
|
}
|
|
|
|
/**
|
|
* Delay the execution of the given {@link PrivilegedRunnable} by the given delay
|
|
*
|
|
* @param runnable
|
|
* the runnable to delay
|
|
* @param delay
|
|
* the time to delay
|
|
* @param delayUnit
|
|
* the unit of the time to delay
|
|
*
|
|
* @return a future to cancel the executor before execution
|
|
*/
|
|
protected ScheduledFuture<?> schedule(PrivilegedRunnable runnable, long delay, TimeUnit delayUnit) {
|
|
return getScheduledExecutor().schedule(() -> {
|
|
try {
|
|
this.container.getPrivilegeHandler().runAsAgent(runnable);
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
}, delay, delayUnit);
|
|
}
|
|
|
|
/**
|
|
* Submit the given {@link Runnable} for repeated execution
|
|
*
|
|
* @param runnable
|
|
* the runnable to delay
|
|
* @param initialDelay
|
|
* the initial delay
|
|
* @param period
|
|
* the delay between subsequent executions
|
|
* @param delayUnit
|
|
* the unit of the time to delay
|
|
*
|
|
* @return a future to cancel the executor before execution
|
|
*/
|
|
protected ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long initialDelay, long period,
|
|
TimeUnit delayUnit) {
|
|
return getScheduledExecutor().scheduleAtFixedRate(() -> {
|
|
try {
|
|
runnable.run();
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
}, initialDelay, period, delayUnit);
|
|
}
|
|
|
|
/**
|
|
* Submit the given {@link PrivilegedRunnable} for repeated execution
|
|
*
|
|
* @param runnable
|
|
* the runnable to delay
|
|
* @param initialDelay
|
|
* the initial delay
|
|
* @param period
|
|
* the delay between subsequent executions
|
|
* @param delayUnit
|
|
* the unit of the time to delay
|
|
*
|
|
* @return a future to cancel the executor before execution
|
|
*/
|
|
protected ScheduledFuture<?> scheduleAtFixedRate(PrivilegedRunnable runnable, long initialDelay, long period,
|
|
TimeUnit delayUnit) {
|
|
return getScheduledExecutor().scheduleAtFixedRate(() -> {
|
|
try {
|
|
this.container.getPrivilegeHandler().runAsAgent(runnable);
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
}, initialDelay, period, delayUnit);
|
|
}
|
|
|
|
/**
|
|
* Submit the given {@link Runnable} for repeated execution
|
|
*
|
|
* @param runnable
|
|
* the runnable to delay
|
|
* @param initialDelay
|
|
* the initial delay
|
|
* @param period
|
|
* the delay between subsequent executions
|
|
* @param delayUnit
|
|
* the unit of the time to delay
|
|
*
|
|
* @return a future to cancel the executor before execution
|
|
*/
|
|
protected ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, long initialDelay, long period,
|
|
TimeUnit delayUnit) {
|
|
return getScheduledExecutor().scheduleWithFixedDelay(() -> {
|
|
try {
|
|
runnable.run();
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
}, initialDelay, period, delayUnit);
|
|
}
|
|
|
|
/**
|
|
* Submit the given {@link PrivilegedRunnable} for repeated execution
|
|
*
|
|
* @param runnable
|
|
* the runnable to delay
|
|
* @param initialDelay
|
|
* the initial delay
|
|
* @param period
|
|
* the delay between subsequent executions
|
|
* @param delayUnit
|
|
* the unit of the time to delay
|
|
*
|
|
* @return a future to cancel the executor before execution
|
|
*/
|
|
protected ScheduledFuture<?> scheduleWithFixedDelay(PrivilegedRunnable runnable, long initialDelay, long period,
|
|
TimeUnit delayUnit) {
|
|
return getScheduledExecutor().scheduleWithFixedDelay(() -> {
|
|
try {
|
|
this.container.getPrivilegeHandler().runAsAgent(runnable);
|
|
} catch (Exception e) {
|
|
handleFailedAsync(e);
|
|
}
|
|
}, initialDelay, period, delayUnit);
|
|
}
|
|
|
|
/**
|
|
* Notifies the caller of one of the async, or schedule methods that the execution of a runnable failed
|
|
*
|
|
* @param e
|
|
* the exception which occurred
|
|
*/
|
|
protected void handleFailedAsync(Exception e) {
|
|
logger.error("Failed to execute " + getClass().getSimpleName(), e);
|
|
}
|
|
}
|