mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[enocean] Add generic state machine infrastructure (#20218)
* [enocean] Add generic state machine infrastructure Signed-off-by: Sven Schad <svnsssd@gmail.com>
This commit is contained in:
+4
@@ -102,6 +102,7 @@ public class EnOceanBindingConstants {
|
||||
|
||||
public static final String CHANNEL_DIMMER = "dimmer";
|
||||
public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
|
||||
public static final String CHANNEL_STATEMACHINESTATE = "state-machine";
|
||||
public static final String CHANNEL_ANGLE = "angle";
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
public static final String CHANNEL_HUMIDITY = "humidity";
|
||||
@@ -243,6 +244,9 @@ public class EnOceanBindingConstants {
|
||||
Map.entry(CHANNEL_ROLLERSHUTTER,
|
||||
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_ROLLERSHUTTER),
|
||||
CoreItemFactory.ROLLERSHUTTER)),
|
||||
Map.entry(CHANNEL_STATEMACHINESTATE,
|
||||
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATEMACHINESTATE),
|
||||
CoreItemFactory.STRING)),
|
||||
Map.entry(CHANNEL_ANGLE,
|
||||
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_ANGLE),
|
||||
CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR + Angle.class.getSimpleName())),
|
||||
|
||||
+8
-4
@@ -29,6 +29,7 @@ import org.openhab.binding.enocean.internal.handler.EnOceanClassicDeviceHandler;
|
||||
import org.openhab.binding.enocean.internal.handler.EnOceanDatagramInjectorHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingManager;
|
||||
@@ -63,15 +64,18 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final ThingManager thingManager;
|
||||
private final ItemChannelLinkRegistry itemChannelLinkRegistry;
|
||||
private final StorageService storageService;
|
||||
|
||||
@Activate
|
||||
public EnOceanHandlerFactory(final @Reference SerialPortManager serialPortManager,
|
||||
final @Reference ThingManager thingManager,
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry) {
|
||||
// Obtain references to thes service using an OSGi reference
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
|
||||
final @Reference StorageService storageService) {
|
||||
// Obtain references to these services using an OSGi reference
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.thingManager = thingManager;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.storageService = storageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,11 +91,11 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
|
||||
registerDeviceDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (EnOceanBaseActuatorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new EnOceanBaseActuatorHandler(thing, itemChannelLinkRegistry);
|
||||
return new EnOceanBaseActuatorHandler(thing, itemChannelLinkRegistry, storageService);
|
||||
} else if (EnOceanBaseSensorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new EnOceanBaseSensorHandler(thing, itemChannelLinkRegistry);
|
||||
} else if (EnOceanClassicDeviceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new EnOceanClassicDeviceHandler(thing, itemChannelLinkRegistry);
|
||||
return new EnOceanClassicDeviceHandler(thing, itemChannelLinkRegistry, storageService);
|
||||
} else if (EnOceanDatagramInjectorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new EnOceanDatagramInjectorHandler(thing, itemChannelLinkRegistry);
|
||||
}
|
||||
|
||||
+71
-7
@@ -23,12 +23,17 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.enocean.internal.Helper;
|
||||
import org.openhab.binding.enocean.internal.messages.ERP1Message;
|
||||
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
|
||||
import org.openhab.binding.enocean.internal.statemachine.STMStateMachine;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
@@ -72,8 +77,22 @@ public abstract class EEP {
|
||||
setOptionalData(packet.getOptionalPayload());
|
||||
}
|
||||
|
||||
public EEP convertFromCommand(String channelId, String channelTypeId, Command command,
|
||||
Function<String, State> getCurrentStateFunc, @Nullable Configuration config) {
|
||||
public EEP convertFromCommand(Thing thing, ChannelUID channelUID, Command command,
|
||||
Function<String, State> getCurrentStateFunc, @Nullable STMStateMachine<?, ?> stm) {
|
||||
|
||||
String channelId = channelUID.getId();
|
||||
Channel channel = thing.getChannel(channelUID);
|
||||
// Virtual channels (e.g. sendCommand) are not registered as real Thing channels.
|
||||
// Allow those through; for any other unknown channel, log and bail out.
|
||||
if (channel == null && !VIRTUALCHANNEL_SEND_COMMAND.equals(channelId)) {
|
||||
logger.debug("Channel {} not found on thing {}; ignoring command {}", channelUID, thing.getUID(), command);
|
||||
return this;
|
||||
}
|
||||
|
||||
ChannelTypeUID channelTypeUID = (channel != null) ? channel.getChannelTypeUID() : null;
|
||||
String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
|
||||
Configuration config = (channel != null) ? channel.getConfiguration() : new Configuration();
|
||||
|
||||
if (!getEEPType().isChannelSupported(channelId, channelTypeId)) {
|
||||
throw new IllegalArgumentException(String.format("Command %s of channel %s(%s) is not supported",
|
||||
command.toString(), channelId, channelTypeId));
|
||||
@@ -82,13 +101,27 @@ public abstract class EEP {
|
||||
if (channelTypeId.equals(CHANNEL_TEACHINCMD) && command == OnOffType.ON) {
|
||||
teachInQueryImpl(config);
|
||||
} else {
|
||||
convertFromCommandImpl(channelId, channelTypeId, command, getCurrentStateFunc, config);
|
||||
convertFromCommandImpl(thing, channelUID, command, getCurrentStateFunc, stm);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public EEP convertFromCommand(String channelId, String channelTypeId, Command command,
|
||||
Function<String, State> getCurrentStateFunc, @Nullable Configuration channelConfig) {
|
||||
if (!getEEPType().isChannelSupported(channelId, channelTypeId)) {
|
||||
throw new IllegalArgumentException(String.format("Command %s of channel %s(%s) is not supported",
|
||||
command.toString(), channelId, channelTypeId));
|
||||
}
|
||||
if (channelTypeId.equals(CHANNEL_TEACHINCMD) && command == OnOffType.ON) {
|
||||
teachInQueryImpl(channelConfig);
|
||||
} else {
|
||||
convertFromCommandImpl(channelId, channelTypeId, command, getCurrentStateFunc, channelConfig);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public State convertToState(String channelId, String channelTypeId, Configuration config,
|
||||
Function<String, @Nullable State> getCurrentStateFunc) {
|
||||
Function<String, @Nullable State> getCurrentStateFunc, @Nullable STMStateMachine<?, ?> stm) {
|
||||
if (!getEEPType().isChannelSupported(channelId, channelTypeId)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Channel %s(%s) is not supported", channelId, channelTypeId));
|
||||
@@ -111,17 +144,17 @@ public abstract class EEP {
|
||||
return new DateTimeType();
|
||||
}
|
||||
|
||||
return convertToStateImpl(channelId, channelTypeId, getCurrentStateFunc, config);
|
||||
return convertToStateImpl(channelId, channelTypeId, getCurrentStateFunc, config, stm);
|
||||
}
|
||||
|
||||
public @Nullable String convertToEvent(String channelId, String channelTypeId, @Nullable String lastEvent,
|
||||
Configuration config) {
|
||||
Configuration config, @Nullable STMStateMachine<?, ?> stm) {
|
||||
if (!getEEPType().isChannelSupported(channelId, channelTypeId)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Channel %s(%s) is not supported", channelId, channelTypeId));
|
||||
}
|
||||
|
||||
return convertToEventImpl(channelId, channelTypeId, lastEvent, config);
|
||||
return convertToEventImpl(channelId, channelTypeId, lastEvent, config, stm);
|
||||
}
|
||||
|
||||
public EEP setRORG(RORG newRORG) {
|
||||
@@ -215,19 +248,50 @@ public abstract class EEP {
|
||||
|
||||
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
|
||||
Function<String, State> getCurrentStateFunc, @Nullable Configuration config) {
|
||||
// legacy interface
|
||||
logger.warn("No implementation for sending data from channel {}/{} for this EEP!", channelId, channelTypeId);
|
||||
}
|
||||
|
||||
protected void convertFromCommandImpl(Thing thing, ChannelUID channelUID, Command command,
|
||||
Function<String, State> getCurrentStateFunc, @Nullable STMStateMachine<?, ?> stm) {
|
||||
|
||||
// interface for EEPs using the state machine
|
||||
Channel channel = thing.getChannel(channelUID);
|
||||
if (channel != null) {
|
||||
Configuration channelConfig = channel.getConfiguration();
|
||||
String channelId = channelUID.getId();
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
|
||||
|
||||
convertFromCommandImpl(channelId, channelTypeId, command, getCurrentStateFunc, channelConfig);
|
||||
}
|
||||
}
|
||||
|
||||
protected State convertToStateImpl(String channelId, String channelTypeId,
|
||||
Function<String, @Nullable State> getCurrentStateFunc, Configuration config) {
|
||||
// legacy interface
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
protected State convertToStateImpl(String channelId, String channelTypeId,
|
||||
Function<String, @Nullable State> getCurrentStateFunc, Configuration config,
|
||||
@Nullable STMStateMachine<?, ?> stm) {
|
||||
// interface for EEPs using the state machine
|
||||
return convertToStateImpl(channelId, channelTypeId, getCurrentStateFunc, config);
|
||||
}
|
||||
|
||||
protected @Nullable String convertToEventImpl(String channelId, String channelTypeId, @Nullable String lastEvent,
|
||||
Configuration config) {
|
||||
// legacy interface
|
||||
return null;
|
||||
}
|
||||
|
||||
protected @Nullable String convertToEventImpl(String channelId, String channelTypeId, @Nullable String lastEvent,
|
||||
Configuration config, @Nullable STMStateMachine<?, ?> stm) {
|
||||
// interface for EEPs using the state machine
|
||||
return convertToEventImpl(channelId, channelTypeId, lastEvent, config);
|
||||
}
|
||||
|
||||
protected void teachInQueryImpl(@Nullable Configuration config) {
|
||||
logger.warn("No implementation for sending a response for this teach in!");
|
||||
}
|
||||
|
||||
+1
@@ -425,6 +425,7 @@ public enum EEPType {
|
||||
|
||||
{
|
||||
put(CHANNEL_ROLLERSHUTTER, new Configuration());
|
||||
put(CHANNEL_STATEMACHINESTATE, new Configuration());
|
||||
put(CHANNEL_TEACHINCMD, new Configuration() {
|
||||
{
|
||||
put(PARAMETER_CHANNEL_TEACHINMSG, "fff80d80");
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.eep;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.enocean.internal.statemachine.STMTransitionConfiguration;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* Interface for EEPs that provide a state machine for operation.
|
||||
* <p>
|
||||
* EEPs implementing this interface provide their state machine configuration
|
||||
* and channel initialization logic, allowing the handler to remain generic.
|
||||
*
|
||||
* @param <A> the action type (enum)
|
||||
* @param <S> the state type (enum)
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StateMachineProvider<A extends Enum<A>, S extends Enum<S>> {
|
||||
|
||||
/**
|
||||
* Gets the transition configuration for the state machine.
|
||||
* The implementation may read device-specific configuration from the Thing.
|
||||
*
|
||||
* @param thing the Thing to read configuration from
|
||||
* @return the transition configuration, or null if STM should not be used (e.g., legacy mode)
|
||||
*/
|
||||
@Nullable
|
||||
STMTransitionConfiguration<A, S> getTransitionConfiguration(Thing thing);
|
||||
|
||||
/**
|
||||
* Gets the initial state for the state machine.
|
||||
*
|
||||
* @return the initial state
|
||||
*/
|
||||
S getInitialState();
|
||||
|
||||
/**
|
||||
* Gets the actions that require callback registration.
|
||||
* These actions will trigger processStoredCommand when completed.
|
||||
*
|
||||
* @param thing the Thing to read configuration from
|
||||
* @return set of actions requiring callbacks
|
||||
*/
|
||||
Set<A> getRequiredCallbackActions(Thing thing);
|
||||
|
||||
/**
|
||||
* Gets the channel IDs that should be removed based on the device configuration.
|
||||
*
|
||||
* @param thing the Thing to read configuration from
|
||||
* @return set of channel IDs to remove (empty if none should be removed)
|
||||
*/
|
||||
Set<String> getChannelsToRemove(Thing thing);
|
||||
|
||||
/**
|
||||
* Returns the state to use when the state machine is initialized after a (re)start.
|
||||
* <p>
|
||||
* The handler reads the previously persisted state (if any) from storage and passes it here.
|
||||
* The implementation decides how to handle startup: it may restore the persisted state,
|
||||
* replace unsafe transient states (e.g. an interrupted movement) with a safe fallback,
|
||||
* or ignore the persisted state entirely.
|
||||
*
|
||||
* @param persistedState the state loaded from storage, or null if no state was persisted
|
||||
* @return the state to restore the STM to, or null to leave the STM at its initial state
|
||||
*/
|
||||
@Nullable
|
||||
S getStateOnStartup(@Nullable S persistedState);
|
||||
}
|
||||
+98
-10
@@ -28,9 +28,14 @@ import org.openhab.binding.enocean.internal.config.EnOceanActuatorConfig;
|
||||
import org.openhab.binding.enocean.internal.eep.EEP;
|
||||
import org.openhab.binding.enocean.internal.eep.EEPFactory;
|
||||
import org.openhab.binding.enocean.internal.eep.EEPType;
|
||||
import org.openhab.binding.enocean.internal.eep.StateMachineProvider;
|
||||
import org.openhab.binding.enocean.internal.messages.BasePacket;
|
||||
import org.openhab.binding.enocean.internal.statemachine.STMStateMachine;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@@ -45,6 +50,7 @@ import org.openhab.core.util.HexUtils;
|
||||
/**
|
||||
*
|
||||
* @author Daniel Weber - Initial contribution
|
||||
* @author Sven Schad - added state machine for blinds/rollershutter
|
||||
* This class defines base functionality for sending eep messages. This class extends EnOceanBaseSensorHandler
|
||||
* class as most actuator things send status or response messages, too.
|
||||
*/
|
||||
@@ -64,8 +70,14 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob; // used for polling current status of thing
|
||||
|
||||
public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
|
||||
private final Storage<String> storage;
|
||||
|
||||
private static final String STORAGE_KEY_STM_STATE = "stmstate";
|
||||
|
||||
public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry,
|
||||
StorageService storageService) {
|
||||
super(thing, itemChannelLinkRegistry);
|
||||
this.storage = storageService.getStorage(thing.getUID().toString(), String.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,7 +212,7 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
|
||||
|
||||
// Generic things are treated as actuator things, however to support also generic sensors one can omit
|
||||
// senderIdOffset
|
||||
// TODO: seperate generic actuators from generic sensors?
|
||||
// TODO: separate generic actuators from generic sensors?
|
||||
Integer senderOffset = getConfiguration().senderIdOffset;
|
||||
|
||||
if ((senderOffset == null && THING_TYPE_GENERICTHING.equals(this.getThing().getThingTypeUID()))) {
|
||||
@@ -239,19 +251,19 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
|
||||
|
||||
@Override
|
||||
protected void sendRequestResponse() {
|
||||
sendMessage(VIRTUALCHANNEL_SEND_COMMAND, VIRTUALCHANNEL_SEND_COMMAND, OnOffType.ON, null);
|
||||
sendMessage(new ChannelUID(thing.getUID(), VIRTUALCHANNEL_SEND_COMMAND), OnOffType.ON);
|
||||
}
|
||||
|
||||
protected void sendMessage(String channelId, String channelTypeId, Command command,
|
||||
@Nullable Configuration channelConfig) {
|
||||
protected void sendMessage(ChannelUID channelUID, Command command) {
|
||||
|
||||
EEPType sendType = sendingEEPType;
|
||||
if (sendType == null) {
|
||||
logger.warn("cannot send a message with an empty EEPType");
|
||||
return;
|
||||
}
|
||||
EEP eep = EEPFactory.createEEP(sendType);
|
||||
if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
|
||||
.hasData()) {
|
||||
|
||||
if (eep.convertFromCommand(thing, channelUID, command, id -> getCurrentState(id), stm).hasData()) {
|
||||
BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
|
||||
.setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
|
||||
if (msg == null) {
|
||||
@@ -274,7 +286,6 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
|
||||
}
|
||||
|
||||
// check if the channel is linked otherwise do nothing
|
||||
String channelId = channelUID.getId();
|
||||
Channel channel = getThing().getChannel(channelUID);
|
||||
if (channel == null || !isLinked(channelUID)) {
|
||||
return;
|
||||
@@ -299,8 +310,7 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
Configuration channelConfig = channel.getConfiguration();
|
||||
sendMessage(channelId, channelTypeId, command, channelConfig);
|
||||
sendMessage(channelUID, command);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Exception while sending telegram!", e);
|
||||
}
|
||||
@@ -331,4 +341,82 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
|
||||
this.refreshJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when the state machine changes state.
|
||||
* Updates the state channel for UI visibility and persists the new state
|
||||
* using the openHAB {@link StorageService} (JSON file in userdata).
|
||||
* No item linking or persistence service configuration is required.
|
||||
*
|
||||
* @param newState the new state
|
||||
*/
|
||||
private void onStateChanged(Enum<?> newState) {
|
||||
updateState(CHANNEL_STATEMACHINESTATE, new StringType(newState.name()));
|
||||
storage.put(STORAGE_KEY_STM_STATE, newState.name());
|
||||
logger.debug("STM state changed to {}", newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the state machine state from the StorageService after a restart.
|
||||
* The state is automatically persisted to a JSON file in the openHAB userdata
|
||||
* directory whenever the state machine transitions. No item linking or
|
||||
* persistence service configuration is required.
|
||||
* <p>
|
||||
* The provider's {@link StateMachineProvider#getStateOnStartup} method is responsible
|
||||
* for deciding which state to restore (e.g. replacing unsafe transient states).
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void restoreStateMachineState(StateMachineProvider<?, ?> stmEEP) {
|
||||
STMStateMachine<?, ?> localStm = stm;
|
||||
if (localStm == null) {
|
||||
return;
|
||||
}
|
||||
@Nullable
|
||||
Enum<?> persistedState = null;
|
||||
@Nullable
|
||||
String lastState = storage.get(STORAGE_KEY_STM_STATE);
|
||||
if (lastState != null) {
|
||||
try {
|
||||
Class<? extends Enum> stateClass = localStm.getState().getDeclaringClass();
|
||||
persistedState = Enum.valueOf(stateClass, lastState);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("Could not parse persisted STM state '{}', passing null to provider", lastState);
|
||||
}
|
||||
}
|
||||
@Nullable
|
||||
Enum<?> startupState = ((StateMachineProvider) stmEEP).getStateOnStartup(persistedState);
|
||||
if (startupState != null) {
|
||||
((STMStateMachine) localStm).restoreState(startupState);
|
||||
logger.debug("STM startup state: {}", startupState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a stored command after a state transition.
|
||||
* This is called via callback when calibration or positioning completes.
|
||||
*/
|
||||
private void processStoredCommand() {
|
||||
STMStateMachine<?, ?> stateMachine = stm;
|
||||
if (stateMachine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String channel = stateMachine.getStoredChannel();
|
||||
@Nullable
|
||||
Command command = stateMachine.getStoredCommand();
|
||||
|
||||
if (channel != null && command != null) {
|
||||
logger.debug("Processing stored command {} for channel {}", command, channel);
|
||||
stateMachine.clearStoredCommand();
|
||||
|
||||
// Schedule the command processing with a short delay
|
||||
stateMachine.scheduleDelayed(() -> {
|
||||
Channel ch = getThing().getChannel(channel);
|
||||
if (ch != null) {
|
||||
handleCommand(ch.getUID(), command);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -33,6 +33,7 @@ import org.openhab.binding.enocean.internal.eep.EEPType;
|
||||
import org.openhab.binding.enocean.internal.messages.BasePacket;
|
||||
import org.openhab.binding.enocean.internal.messages.ERP1Message;
|
||||
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
|
||||
import org.openhab.binding.enocean.internal.statemachine.STMStateMachine;
|
||||
import org.openhab.binding.enocean.internal.transceiver.PacketListener;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
@@ -66,6 +67,8 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
|
||||
|
||||
protected @Nullable ScheduledFuture<?> responseFuture = null;
|
||||
|
||||
protected @Nullable STMStateMachine<?, ?> stm = null;
|
||||
|
||||
public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
|
||||
super(thing, itemChannelLinkRegistry);
|
||||
}
|
||||
@@ -181,7 +184,7 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
|
||||
switch (channel.getKind()) {
|
||||
case STATE:
|
||||
State result = eep.convertToState(channelId, channelTypeId, channelConfig,
|
||||
this::getCurrentState);
|
||||
this::getCurrentState, stm);
|
||||
|
||||
// if message can be interpreted (result != UnDefType.UNDEF) => update item state
|
||||
if (result != UnDefType.UNDEF) {
|
||||
@@ -190,7 +193,8 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
|
||||
break;
|
||||
case TRIGGER:
|
||||
String lastEvent = lastEvents.get(channelId);
|
||||
String event = eep.convertToEvent(channelId, channelTypeId, lastEvent, channelConfig);
|
||||
String event = eep.convertToEvent(channelId, channelTypeId, lastEvent, channelConfig,
|
||||
stm);
|
||||
if (event != null) {
|
||||
triggerChannel(channel.getUID(), event);
|
||||
lastEvents.put(channelId, event);
|
||||
|
||||
+5
-1
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.handler;
|
||||
|
||||
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
|
||||
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.CHANNEL_STATUS_REQUEST_EVENT;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Collection;
|
||||
@@ -198,6 +198,10 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
protected void setState(ChannelUID channelUID, State state) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
|
||||
protected State getCurrentState(String channelId) {
|
||||
return getCurrentState(getThing().getChannel(channelId));
|
||||
}
|
||||
|
||||
+8
-8
@@ -33,6 +33,7 @@ import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.CommonTriggerEvents;
|
||||
@@ -62,8 +63,9 @@ public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
|
||||
@Nullable
|
||||
ScheduledFuture<?> releaseFuture = null;
|
||||
|
||||
public EnOceanClassicDeviceHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
|
||||
super(thing, itemChannelLinkRegistry);
|
||||
public EnOceanClassicDeviceHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry,
|
||||
StorageService storageService) {
|
||||
super(thing, itemChannelLinkRegistry, storageService);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,7 +224,6 @@ public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
String channelId = channelUID.getId();
|
||||
Channel channel = getThing().getChannel(channelUID);
|
||||
if (channel == null) {
|
||||
return;
|
||||
@@ -244,8 +245,8 @@ public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
|
||||
EEPType localSendType = sendingEEPType;
|
||||
if (localSendType != null) {
|
||||
EEP eep = EEPFactory.createEEP(localSendType);
|
||||
if (eep.setSenderId(senderId).setDestinationId(destinationId).convertFromCommand(channelId,
|
||||
channelTypeId, result, id -> this.getCurrentState(id), channel.getConfiguration()).hasData()) {
|
||||
if (eep.setSenderId(senderId).setDestinationId(destinationId)
|
||||
.convertFromCommand(thing, channelUID, command, id -> getCurrentState(id), stm).hasData()) {
|
||||
BasePacket press = eep.setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
|
||||
if (press != null) {
|
||||
EnOceanBridgeHandler handler = getBridgeHandler();
|
||||
@@ -256,9 +257,8 @@ public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
|
||||
|
||||
if (channelConfig.duration > 0) {
|
||||
releaseFuture = scheduler.schedule(() -> {
|
||||
if (eep.convertFromCommand(channelId, channelTypeId,
|
||||
convertToReleasedCommand(lastTriggerEvent), id -> this.getCurrentState(id),
|
||||
channel.getConfiguration()).hasData()) {
|
||||
if (eep.convertFromCommand(thing, channelUID, convertToReleasedCommand(lastTriggerEvent),
|
||||
id -> getCurrentState(id), stm).hasData()) {
|
||||
BasePacket release = eep.getERP1Message();
|
||||
if (release != null) {
|
||||
EnOceanBridgeHandler handler = getBridgeHandler();
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.statemachine;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Example actions for demonstrating state machine infrastructure.
|
||||
* This is a minimal example showing how to define custom actions for a state machine.
|
||||
*
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ExampleAction {
|
||||
START, // Begin operation
|
||||
READY, // Warmup complete
|
||||
FINISH // Work done
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.statemachine;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Example states for demonstrating state machine infrastructure.
|
||||
* This is a minimal example showing how to define custom states for a state machine.
|
||||
*
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ExampleState {
|
||||
IDLE, // Ready to start
|
||||
WARMUP, // Preparing
|
||||
ACTIVE // Working
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.statemachine;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Example transition configuration demonstrating state machine infrastructure usage.
|
||||
* <p>
|
||||
* This minimal example shows how to:
|
||||
* <ul>
|
||||
* <li>Define state transitions between custom states</li>
|
||||
* <li>Trigger actions that cause state changes</li>
|
||||
* <li>Create a simple three-state workflow: IDLE → WARMUP → ACTIVE → IDLE</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Usage example:
|
||||
*
|
||||
* <pre>
|
||||
* STMStateMachine<ExampleAction, ExampleState> stm = STMStateMachine.build(ExampleTransitions.SIMPLE,
|
||||
* ExampleState.IDLE, scheduler, this::onStateChanged);
|
||||
*
|
||||
* stm.apply(ExampleAction.START); // IDLE → WARMUP
|
||||
* stm.apply(ExampleAction.READY); // WARMUP → ACTIVE
|
||||
* stm.apply(ExampleAction.FINISH); // ACTIVE → IDLE
|
||||
* </pre>
|
||||
*
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ExampleTransitions {
|
||||
|
||||
/**
|
||||
* Simple three-state workflow demonstrating basic state machine functionality.
|
||||
*/
|
||||
public static final STMTransitionConfiguration<ExampleAction, ExampleState> SIMPLE = new STMTransitionConfiguration<>(
|
||||
Arrays.<STMTransition<ExampleAction, ExampleState>> asList(
|
||||
// IDLE → WARMUP when START action occurs
|
||||
new STMTransition<>(ExampleState.IDLE, ExampleAction.START, ExampleState.WARMUP),
|
||||
// WARMUP → ACTIVE when READY action occurs
|
||||
new STMTransition<>(ExampleState.WARMUP, ExampleAction.READY, ExampleState.ACTIVE),
|
||||
// ACTIVE → IDLE when FINISH action occurs
|
||||
new STMTransition<>(ExampleState.ACTIVE, ExampleAction.FINISH, ExampleState.IDLE)));
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.statemachine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Generic state machine for managing multi-stage device movements.
|
||||
* <p>
|
||||
* This state machine is decoupled from specific device handlers and uses
|
||||
* a callback mechanism to notify state changes. The caller is responsible
|
||||
* for persisting state and updating channels.
|
||||
*
|
||||
* @param <A> the action type (enum)
|
||||
* @param <S> the state type (enum)
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class STMStateMachine<A extends Enum<A>, S extends Enum<S>> {
|
||||
|
||||
private final List<STMTransition<A, S>> transitions;
|
||||
private final HashMap<A, Runnable> callbackActions;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final @Nullable Consumer<S> stateChangeCallback;
|
||||
|
||||
private S state;
|
||||
private S prevState;
|
||||
private @Nullable String storedChannel;
|
||||
private @Nullable Command storedCommand;
|
||||
|
||||
protected @Nullable ScheduledFuture<?> responseFuture = null;
|
||||
|
||||
protected Logger logger = LoggerFactory.getLogger(STMStateMachine.class);
|
||||
|
||||
/**
|
||||
* Creates a new state machine instance.
|
||||
*
|
||||
* @param config the transition configuration to use
|
||||
* @param initState the initial state
|
||||
* @param scheduler the scheduler for delayed operations
|
||||
* @param stateChangeCallback optional callback invoked on state changes
|
||||
* @return new state machine instance
|
||||
*/
|
||||
public static <A extends Enum<A>, S extends Enum<S>> STMStateMachine<A, S> build(
|
||||
STMTransitionConfiguration<A, S> config, S initState, ScheduledExecutorService scheduler,
|
||||
@Nullable Consumer<S> stateChangeCallback) {
|
||||
return new STMStateMachine<>(config, initState, scheduler, stateChangeCallback);
|
||||
}
|
||||
|
||||
private STMStateMachine(STMTransitionConfiguration<A, S> config, S initState, ScheduledExecutorService scheduler,
|
||||
@Nullable Consumer<S> stateChangeCallback) {
|
||||
this.callbackActions = new HashMap<>();
|
||||
this.state = initState;
|
||||
this.prevState = initState;
|
||||
this.transitions = config.getTransitions();
|
||||
this.scheduler = scheduler;
|
||||
this.stateChangeCallback = stateChangeCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be executed when a specific action is applied.
|
||||
*
|
||||
* @param action the action that triggers the callback
|
||||
* @param callback the callback to execute
|
||||
* @return this state machine for fluent configuration
|
||||
*/
|
||||
public synchronized STMStateMachine<A, S> register(A action, Runnable callback) {
|
||||
this.callbackActions.put(action, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current state.
|
||||
*
|
||||
* @return current state
|
||||
*/
|
||||
public synchronized S getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous state before the last transition.
|
||||
*
|
||||
* @return previous state
|
||||
*/
|
||||
public synchronized S getPrevState() {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a previously persisted state.
|
||||
* <p>
|
||||
* This method is intended to be called during handler initialization
|
||||
* to restore state from Thing properties. No callback is triggered.
|
||||
*
|
||||
* @param restoredState the state to restore
|
||||
*/
|
||||
public synchronized void restoreState(S restoredState) {
|
||||
this.prevState = this.state;
|
||||
this.state = restoredState;
|
||||
logger.debug("STM: State restored to {}", restoredState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an action to trigger a state transition.
|
||||
* <p>
|
||||
* If a matching transition is found, the state changes and registered
|
||||
* callbacks are invoked.
|
||||
*
|
||||
* @param action the action to apply
|
||||
* @return this state machine for fluent usage
|
||||
*/
|
||||
public synchronized STMStateMachine<A, S> apply(A action) {
|
||||
for (STMTransition<A, S> transition : transitions) {
|
||||
boolean currentStateMatches = transition.getFrom().equals(state);
|
||||
boolean conditionsMatch = transition.getAction().equals(action);
|
||||
|
||||
if (currentStateMatches && conditionsMatch) {
|
||||
logger.debug("STM: State change from {} to {} by action {}, prevState {}", state, transition.getTo(),
|
||||
action, prevState);
|
||||
prevState = state;
|
||||
state = transition.getTo();
|
||||
|
||||
// Execute action-specific callback if registered
|
||||
Runnable actionCallback = callbackActions.get(action);
|
||||
if (actionCallback != null) {
|
||||
actionCallback.run();
|
||||
}
|
||||
|
||||
// Notify state change via callback
|
||||
Consumer<S> callback = stateChangeCallback;
|
||||
if (callback != null) {
|
||||
callback.accept(state);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a runnable to be executed after a delay.
|
||||
* <p>
|
||||
* Useful for scheduling follow-up commands after movement completion.
|
||||
*
|
||||
* @param runnable the task to execute
|
||||
* @param delayMs delay in milliseconds
|
||||
*/
|
||||
public synchronized void scheduleDelayed(Runnable runnable, long delayMs) {
|
||||
ScheduledFuture<?> future = responseFuture;
|
||||
if (future == null || future.isDone()) {
|
||||
this.responseFuture = scheduler.schedule(runnable, delayMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a command for later processing.
|
||||
* <p>
|
||||
* This is used when a command cannot be executed immediately (e.g., during calibration)
|
||||
* and needs to be processed after a state transition completes.
|
||||
*
|
||||
* @param channel the channel ID for the command
|
||||
* @param command the command to store
|
||||
*/
|
||||
public synchronized void storeCommand(String channel, Command command) {
|
||||
this.storedChannel = channel;
|
||||
this.storedCommand = command;
|
||||
logger.debug("STM: Stored command {} for channel {}", command, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stored channel ID.
|
||||
*
|
||||
* @return the stored channel ID, or null if none stored
|
||||
*/
|
||||
public synchronized @Nullable String getStoredChannel() {
|
||||
return storedChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stored command.
|
||||
*
|
||||
* @return the stored command, or null if none stored
|
||||
*/
|
||||
public synchronized @Nullable Command getStoredCommand() {
|
||||
return storedCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored command.
|
||||
*/
|
||||
public synchronized void clearStoredCommand() {
|
||||
this.storedChannel = null;
|
||||
this.storedCommand = null;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.statemachine;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents a single transition in the state machine.
|
||||
*
|
||||
* @param <A> the action type (enum)
|
||||
* @param <S> the state type (enum)
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class STMTransition<A extends Enum<A>, S extends Enum<S>> {
|
||||
private final S from;
|
||||
private final A action;
|
||||
private final S to;
|
||||
|
||||
public STMTransition(S from, A action, S to) {
|
||||
this.from = from;
|
||||
this.action = action;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public S getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public A getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public S getTo() {
|
||||
return to;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.enocean.internal.statemachine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Transition configuration for state machines.
|
||||
* <p>
|
||||
* Contains the list of valid transitions for a specific device type.
|
||||
*
|
||||
* @param <A> the action type (enum)
|
||||
* @param <S> the state type (enum)
|
||||
* @author Sven Schad - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class STMTransitionConfiguration<A extends Enum<A>, S extends Enum<S>> {
|
||||
|
||||
private final List<STMTransition<A, S>> transitions;
|
||||
|
||||
public STMTransitionConfiguration(List<STMTransition<A, S>> transitions) {
|
||||
this.transitions = new ArrayList<>(transitions);
|
||||
}
|
||||
|
||||
public List<STMTransition<A, S>> getTransitions() {
|
||||
return Collections.unmodifiableList(transitions);
|
||||
}
|
||||
}
|
||||
@@ -591,6 +591,8 @@ channel-type.enocean.setPoint.label = Set Point
|
||||
channel-type.enocean.setPoint.description = linear set point
|
||||
channel-type.enocean.smokeDetection.label = Smoke Detected
|
||||
channel-type.enocean.smokeDetection.description = Smoke detection sensor state.
|
||||
channel-type.enocean.state-machine.label = State Machine State
|
||||
channel-type.enocean.state-machine.description = Current state of the internal state machine
|
||||
channel-type.enocean.statusRequestEvent.label = Status Request Event
|
||||
channel-type.enocean.statusRequestEvent.description = Is triggered when the actuator wakes up from sleep and asks for the current status.
|
||||
channel-type.enocean.supplyAirFanAirFlowRate.label = Supply Air Fan Air Flow Rate
|
||||
|
||||
@@ -78,6 +78,14 @@
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
<!-- State Machine State Channel -->
|
||||
<channel-type id="state-machine" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>State Machine State</label>
|
||||
<description>Current state of the internal state machine</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Dimmer Channel -->
|
||||
<channel-type id="dimmer">
|
||||
<item-type>Dimmer</item-type>
|
||||
|
||||
Reference in New Issue
Block a user