package org.openhab.binding.insteon.internal;
import java.io.File;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.RemoteSceneButtonConfig;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.RemoteSwitchButtonConfig;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.VenstarSystemMode;
import org.openhab.core.OpenHAB;
import org.openhab.core.thing.ThingTypeUID;
public static final String FEATURE_RAMP_RATE = "rampRate";
public static final String FEATURE_SCENE_ON_OFF = "sceneOnOff";
public static final String FEATURE_STAY_AWAKE = "stayAwake";
- public static final String FEATURE_SYSTEM_MODE = "systemMode";
public static final String FEATURE_TEMPERATURE_SCALE = "temperatureScale";
public static final String FEATURE_TWO_GROUPS = "2Groups";
public static final String FEATURE_TYPE_KEYPAD_BUTTON_ON_MASK = "KeypadButtonOnMask";
public static final String FEATURE_TYPE_KEYPAD_BUTTON_TOGGLE_MODE = "KeypadButtonToggleMode";
public static final String FEATURE_TYPE_OUTLET_SWITCH = "OutletSwitch";
+ public static final String FEATURE_TYPE_REMOTE_SCENE_BUTTON_CONFIG = "RemoteSceneButtonConfig";
+ public static final String FEATURE_TYPE_REMOTE_SWITCH_BUTTON_CONFIG = "RemoteSwitchButtonConfig";
public static final String FEATURE_TYPE_THERMOSTAT_FAN_MODE = "ThermostatFanMode";
public static final String FEATURE_TYPE_THERMOSTAT_SYSTEM_MODE = "ThermostatSystemMode";
public static final String FEATURE_TYPE_THERMOSTAT_COOL_SETPOINT = "ThermostatCoolSetpoint";
public static final String FEATURE_TYPE_VENSTAR_COOL_SETPOINT = "VenstarCoolSetpoint";
public static final String FEATURE_TYPE_VENSTAR_HEAT_SETPOINT = "VenstarHeatSetpoint";
- // List of specific device types
- public static final String DEVICE_TYPE_CLIMATE_CONTROL_VENSTAR_THERMOSTAT = "ClimateControl_VenstarThermostat";
-
// Map of custom state description options
- public static final Map<String, String[]> CUSTOM_STATE_DESCRIPTION_OPTIONS = Map.ofEntries(
- // Venstar Thermostat System Mode
- Map.entry(DEVICE_TYPE_CLIMATE_CONTROL_VENSTAR_THERMOSTAT + ":" + FEATURE_SYSTEM_MODE,
- VenstarSystemMode.names().toArray(String[]::new)));
+ public static final Map<String, List<String>> CUSTOM_STATE_DESCRIPTION_OPTIONS = Map.ofEntries(
+ Map.entry(FEATURE_TYPE_REMOTE_SCENE_BUTTON_CONFIG, RemoteSceneButtonConfig.names()),
+ Map.entry(FEATURE_TYPE_REMOTE_SWITCH_BUTTON_CONFIG, RemoteSwitchButtonConfig.names()),
+ Map.entry(FEATURE_TYPE_VENSTAR_SYSTEM_MODE, VenstarSystemMode.names()));
}
import org.openhab.binding.insteon.internal.device.database.ModemDBChange;
import org.openhab.binding.insteon.internal.device.database.ModemDBEntry;
import org.openhab.binding.insteon.internal.device.database.ModemDBRecord;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.DeviceTypeRenamer;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonToggleMode;
import org.openhab.binding.insteon.internal.handler.InsteonDeviceHandler;
+import org.openhab.binding.insteon.internal.transport.message.FieldException;
import org.openhab.binding.insteon.internal.transport.message.GroupMessageStateMachine;
-import org.openhab.binding.insteon.internal.transport.message.GroupMessageStateMachine.GroupMessageType;
import org.openhab.binding.insteon.internal.transport.message.Msg;
import org.openhab.binding.insteon.internal.utils.BinaryUtils;
import org.openhab.core.library.types.DecimalType;
}
/**
- * Returns if a broadcast message is duplicate
+ * Returns if an incoming message is duplicate
*
- * @param cmd1 the cmd1 from the broadcast message received
- * @param timestamp the timestamp from the broadcast message received
- * @return true if the broadcast message is duplicate
- */
- public boolean isDuplicateBroadcastMsg(byte cmd1, long timestamp) {
- synchronized (lastBroadcastReceived) {
- long timelapse = timestamp - lastBroadcastReceived.getOrDefault(cmd1, timestamp);
- if (timelapse > 0 && timelapse < BCAST_STATE_TIMEOUT) {
- return true;
- } else {
- lastBroadcastReceived.put(cmd1, timestamp);
- return false;
- }
- }
- }
-
- /**
- * Returns if a group message is duplicate
- *
- * @param cmd1 cmd1 from the group message received
- * @param timestamp the timestamp from the broadcast message received
- * @param group the broadcast group
- * @param type the group message type that was received
- * @return true if the group message is duplicate
- */
- public boolean isDuplicateGroupMsg(byte cmd1, long timestamp, int group, GroupMessageType type) {
- synchronized (groupState) {
- GroupMessageStateMachine stateMachine = groupState.get(group);
- if (stateMachine == null) {
- stateMachine = new GroupMessageStateMachine();
- groupState.put(group, stateMachine);
- logger.trace("{} created group {} state", address, group);
- }
- if (stateMachine.getLastCommand() == cmd1 && stateMachine.getLastTimestamp() == timestamp) {
- logger.trace("{} using previous group {} state for {}", address, group, type);
- return stateMachine.isDuplicate();
- } else {
- logger.trace("{} updating group {} state to {}", address, group, type);
- return stateMachine.update(address, group, cmd1, timestamp, type);
+ * @param msg the message received
+ * @return true if group or broadcast message is duplicate
+ */
+ public boolean isDuplicateMsg(Msg msg) {
+ try {
+ if (msg.isAllLinkBroadcastOrCleanup()) {
+ synchronized (groupState) {
+ int group = msg.getGroup();
+ GroupMessageStateMachine stateMachine = groupState.computeIfAbsent(group,
+ k -> new GroupMessageStateMachine());
+ return stateMachine != null && stateMachine.isDuplicate(msg);
+ }
+ } else if (msg.isBroadcast()) {
+ synchronized (lastBroadcastReceived) {
+ byte cmd1 = msg.getByte("command1");
+ long timestamp = msg.getTimestamp();
+ Long lastTimestamp = lastBroadcastReceived.put(cmd1, timestamp);
+ return lastTimestamp != null && Math.abs(timestamp - lastTimestamp) <= BCAST_STATE_TIMEOUT;
+ }
}
+ } catch (FieldException e) {
+ logger.warn("error parsing msg: {}", msg, e);
}
+ return false;
}
/**
getFeatures().stream().filter(DeviceFeature::isStatusFeature)
.forEach(feature -> feature.handleMessage(msg));
}
+ // poll battery powered device while awake if non-duplicate all link or broadcast message
+ if ((msg.isAllLinkBroadcastOrCleanup() || msg.isBroadcast()) && isBatteryPowered() && isAwake()
+ && !isDuplicateMsg(msg)) {
+ // add poll delay for non-replayed all link broadcast allowing cleanup msg to be be processed beforehand
+ long delay = msg.isAllLinkBroadcast() && !msg.isAllLinkSuccessReport() && !msg.isReplayed() ? 1500L : 0L;
+ doPoll(delay);
+ }
// notify if responding state changed
if (isPrevResponding != isResponding()) {
statusChanged();
/**
* Updates this device type
*
- * @param newType the new device type to use
+ * @param renamer the device type renamer
*/
+ public void updateType(DeviceTypeRenamer renamer) {
+ Optional.ofNullable(getType()).map(DeviceType::getName).map(renamer::getNewDeviceType)
+ .map(name -> DeviceTypeRegistry.getInstance().getDeviceType(name)).ifPresent(this::updateType);
+ }
+ /**
+ * Updates this device type
+ *
+ * @param newType the new device type to use
+ */
public void updateType(DeviceType newType) {
ProductData productData = getProductData();
DeviceType currentType = getType();
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonConfig;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonToggleMode;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.MicroModuleOpMode;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.RemoteSceneButtonConfig;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.RemoteSwitchButtonConfig;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.SirenAlertType;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatFanMode;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatSystemMode;
protected int getOpFlagCommand(Command cmd) {
try {
String config = ((StringType) cmd).toString();
- return KeypadButtonConfig.valueOf(config).getValue();
+ return KeypadButtonConfig.valueOf(config).shouldSetFlag() ? getParameterAsInteger("on", -1)
+ : getParameterAsInteger("off", -1);
} catch (IllegalArgumentException e) {
logger.warn("{}: got unexpected button config command: {}, ignoring request", nm(), cmd);
return -1;
}
}
+ /**
+ * Remote scene button config command handler
+ */
+ public static class RemoteSceneButtonConfigCommandHandler extends MultiOpFlagsCommandHandler {
+ RemoteSceneButtonConfigCommandHandler(DeviceFeature feature) {
+ super(feature);
+ }
+
+ @Override
+ protected Map<Integer, String> getOpFlagCommands(Command cmd) {
+ Map<Integer, String> commands = new HashMap<>();
+ try {
+ String mode = ((StringType) cmd).toString();
+ switch (RemoteSceneButtonConfig.valueOf(mode)) {
+ case BUTTON_4:
+ commands.put(0x0F, "grouped ON");
+ commands.put(0x09, "toggle off ON");
+ break;
+ case BUTTON_8_ALWAYS_ON:
+ commands.put(0x0E, "grouped OFF");
+ commands.put(0x09, "toggle off ON");
+ break;
+ case BUTTON_8_TOGGLE:
+ commands.put(0x0E, "grouped OFF");
+ commands.put(0x08, "toggle off OFF");
+ break;
+ }
+ } catch (IllegalArgumentException e) {
+ logger.warn("{}: got unexpected button config command: {}, ignoring request", nm(), cmd);
+ }
+ return commands;
+ }
+ }
+
+ /**
+ * Remote switch button config command handler
+ */
+ public static class RemoteSwitchButtonConfigCommandHandler extends MultiOpFlagsCommandHandler {
+ RemoteSwitchButtonConfigCommandHandler(DeviceFeature feature) {
+ super(feature);
+ }
+
+ @Override
+ protected Map<Integer, String> getOpFlagCommands(Command cmd) {
+ Map<Integer, String> commands = new HashMap<>();
+ try {
+ String mode = ((StringType) cmd).toString();
+ switch (RemoteSwitchButtonConfig.valueOf(mode)) {
+ case BUTTON_1:
+ commands.put(0x0F, "grouped ON");
+ commands.put(0x09, "toggle off ON");
+ break;
+ case BUTTON_2_ALWAYS_ON:
+ commands.put(0x0E, "grouped OFF");
+ commands.put(0x09, "toggle off ON");
+ break;
+ case BUTTON_2_TOGGLE:
+ commands.put(0x0E, "grouped OFF");
+ commands.put(0x08, "toggle off OFF");
+ break;
+ }
+ } catch (IllegalArgumentException e) {
+ logger.warn("{}: got unexpected button config command: {}, ignoring request", nm(), cmd);
+ }
+ return commands;
+ }
+ }
+
/**
* Sprinkler valve on/off command handler
*/
import java.util.List;
import java.util.Map;
import java.util.function.Function;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
}
}
- public static enum KeypadButtonConfig {
- BUTTON_6(0x07, 6),
- BUTTON_8(0x06, 8);
+ public static enum KeypadButtonConfig implements DeviceTypeRenamer {
+ BUTTON_6(false, "KeypadButton6"),
+ BUTTON_8(true, "KeypadButton8");
- private int value;
- private int count;
+ private static final Pattern DEVICE_TYPE_NAME_PATTERN = Pattern.compile("KeypadButton[68]$");
- private KeypadButtonConfig(int value, int count) {
- this.value = value;
- this.count = count;
+ private boolean setFlag;
+ private String replacement;
+
+ private KeypadButtonConfig(boolean setFlag, String replacement) {
+ this.setFlag = setFlag;
+ this.replacement = replacement;
}
- public int getValue() {
- return value;
+ @Override
+ public String getNewDeviceType(String deviceType) {
+ return DEVICE_TYPE_NAME_PATTERN.matcher(deviceType).replaceAll(replacement);
}
- public int getCount() {
- return count;
+ public boolean shouldSetFlag() {
+ return setFlag;
}
public static KeypadButtonConfig from(boolean is8Button) {
}
}
+ public static enum RemoteSceneButtonConfig implements DeviceTypeRenamer {
+ BUTTON_4("MiniRemoteScene4"),
+ BUTTON_8_ALWAYS_ON("MiniRemoteScene8"),
+ BUTTON_8_TOGGLE("MiniRemoteScene8");
+
+ private static final Pattern DEVICE_TYPE_NAME_PATTERN = Pattern.compile("MiniRemoteScene[48]$");
+
+ private String replacement;
+
+ private RemoteSceneButtonConfig(String replacement) {
+ this.replacement = replacement;
+ }
+
+ @Override
+ public String getNewDeviceType(String deviceType) {
+ return DEVICE_TYPE_NAME_PATTERN.matcher(deviceType).replaceAll(replacement);
+ }
+
+ public static RemoteSceneButtonConfig valueOf(int value) {
+ if (BinaryUtils.isBitSet(value, 6)) {
+ // return button 4, when grouped op flag (6) is on
+ return RemoteSceneButtonConfig.BUTTON_4;
+ } else if (BinaryUtils.isBitSet(value, 4)) {
+ // return button 8 always on, when toggle off op flag (5) is on
+ return RemoteSceneButtonConfig.BUTTON_8_ALWAYS_ON;
+ } else {
+ // return button 8 toggle, otherwise
+ return RemoteSceneButtonConfig.BUTTON_8_TOGGLE;
+ }
+ }
+
+ public static List<String> names() {
+ return Arrays.stream(values()).map(String::valueOf).toList();
+ }
+ }
+
+ public static enum RemoteSwitchButtonConfig implements DeviceTypeRenamer {
+ BUTTON_1("MiniRemoteSwitch"),
+ BUTTON_2_ALWAYS_ON("MiniRemoteSwitch2"),
+ BUTTON_2_TOGGLE("MiniRemoteSwitch2");
+
+ private static final Pattern DEVICE_TYPE_NAME_PATTERN = Pattern.compile("MiniRemoteSwitch[2]?$");
+
+ private String replacement;
+
+ private RemoteSwitchButtonConfig(String replacement) {
+ this.replacement = replacement;
+ }
+
+ @Override
+ public String getNewDeviceType(String deviceType) {
+ return DEVICE_TYPE_NAME_PATTERN.matcher(deviceType).replaceAll(replacement);
+ }
+
+ public static RemoteSwitchButtonConfig valueOf(int value) {
+ if (BinaryUtils.isBitSet(value, 6)) {
+ // return button 1, when grouped op flag (6) is on
+ return RemoteSwitchButtonConfig.BUTTON_1;
+ } else if (BinaryUtils.isBitSet(value, 4)) {
+ // return button 2 always on, when toggle off op flag (5) is on
+ return RemoteSwitchButtonConfig.BUTTON_2_ALWAYS_ON;
+ } else {
+ // return button 2 toggle, otherwise
+ return RemoteSwitchButtonConfig.BUTTON_2_TOGGLE;
+ }
+ }
+
+ public static List<String> names() {
+ return Arrays.stream(values()).map(String::valueOf).toList();
+ }
+ }
+
public static enum SirenAlertType {
CHIME(0x00),
LOUD_SIREN(0x01);
return format;
}
}
+
+ public interface DeviceTypeRenamer {
+ String getNewDeviceType(String deviceType);
+ }
}
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.insteon.internal.device.DeviceFeature;
-import org.openhab.binding.insteon.internal.device.DeviceType;
-import org.openhab.binding.insteon.internal.device.DeviceTypeRegistry;
import org.openhab.binding.insteon.internal.device.InsteonEngine;
import org.openhab.binding.insteon.internal.device.RampRate;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ButtonEvent;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonConfig;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonToggleMode;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.MicroModuleOpMode;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.RemoteSceneButtonConfig;
+import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.RemoteSwitchButtonConfig;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.SirenAlertType;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatFanMode;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatSystemMode;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatTimeFormat;
import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.VenstarSystemMode;
import org.openhab.binding.insteon.internal.transport.message.FieldException;
-import org.openhab.binding.insteon.internal.transport.message.GroupMessageStateMachine.GroupMessageType;
import org.openhab.binding.insteon.internal.transport.message.Msg;
import org.openhab.binding.insteon.internal.utils.BinaryUtils;
import org.openhab.binding.insteon.internal.utils.HexUtils;
* Returns if an incoming message is a duplicate
*
* @param msg the received message
- * @return true if the broadcast message is a duplicate
+ * @return true if group or broadcast message is duplicate
*/
protected boolean isDuplicate(Msg msg) {
- try {
- if (msg.isAllLinkBroadcastOrCleanup()) {
- byte cmd1 = msg.getByte("command1");
- long timestamp = msg.getTimestamp();
- int group = msg.getGroup();
- GroupMessageType type = msg.isAllLinkBroadcast() ? GroupMessageType.BCAST : GroupMessageType.CLEAN;
- if (msg.isAllLinkSuccessReport()) {
- cmd1 = msg.getInsteonAddress("toAddress").getHighByte();
- type = GroupMessageType.SUCCESS;
- }
- return getInsteonDevice().isDuplicateGroupMsg(cmd1, timestamp, group, type);
- } else if (msg.isBroadcast()) {
- byte cmd1 = msg.getByte("command1");
- long timestamp = msg.getTimestamp();
- return getInsteonDevice().isDuplicateBroadcastMsg(cmd1, timestamp);
- }
- } catch (IllegalArgumentException e) {
- logger.warn("cannot parse msg: {}", msg, e);
- } catch (FieldException e) {
- logger.warn("cannot parse msg: {}", msg, e);
+ if (msg.isAllLinkBroadcastOrCleanup() || msg.isBroadcast()) {
+ return getInsteonDevice().isDuplicateMsg(msg);
}
return false;
}
* @throws FieldException if field not there
*/
private boolean matchesParameter(Msg msg, String field, String param) throws FieldException {
- int mp = getParameterAsInteger(param, -1);
+ int value = getParameterAsInteger(param, -1);
// parameter not filtered for, declare this a match!
- if (mp == -1) {
- return true;
- }
- byte value = msg.getByte(field);
- return value == mp;
+ return value == -1 || msg.getInt(field) == value;
}
/**
public void handleMessage(byte cmd1, Msg msg) {
// trigger poll if is my command reply message (0x20)
if (feature.getQueryCommand() == 0x20) {
- feature.triggerPoll(0L);
+ long delay = getPollDelay();
+ feature.triggerPoll(delay);
} else {
super.handleMessage(cmd1, msg);
}
}
+
+ protected long getPollDelay() {
+ return 0L;
+ }
}
/**
@Override
protected State getBitState(boolean is8Button) {
KeypadButtonConfig config = KeypadButtonConfig.from(is8Button);
- // update device type based on button count
- updateDeviceType(config.getCount());
+ // update device type based on button config
+ getInsteonDevice().updateType(config);
// return button config state
return new StringType(config.toString());
}
-
- private void updateDeviceType(int buttonCount) {
- DeviceType deviceType = getInsteonDevice().getType();
- if (deviceType == null) {
- logger.warn("{}: unknown device type for {}", nm(), getInsteonDevice().getAddress());
- } else {
- String name = deviceType.getName().replaceAll(".$", String.valueOf(buttonCount));
- DeviceType newType = DeviceTypeRegistry.getInstance().getDeviceType(name);
- if (newType == null) {
- logger.warn("{}: unknown device type {}", nm(), name);
- } else {
- getInsteonDevice().updateType(newType);
- }
- }
- }
}
/**
@Override
public void handleMessage(byte cmd1, Msg msg) {
super.handleMessage(cmd1, msg);
- // poll battery powered sensor device while awake
- if (getInsteonDevice().isBatteryPowered()) {
- // no delay for all link cleanup, all link success report or replayed messages
- // otherise, 1500ms for all link broadcast message allowing cleanup msg to be be processed beforehand
- long delay = msg.isAllLinkCleanup() || msg.isAllLinkSuccessReport() || msg.isReplayed() ? 0L : 1500L;
- getInsteonDevice().doPoll(delay);
- }
// poll related devices
feature.pollRelatedDevices(0L);
}
/**
* I/O linc relay mode reply message handler
*/
- public static class IOLincRelayModeReplyHandler extends CustomMsgHandler {
+ public static class IOLincRelayModeReplyHandler extends OpFlagsReplyHandler {
IOLincRelayModeReplyHandler(DeviceFeature feature) {
super(feature);
}
@Override
- public void handleMessage(byte cmd1, Msg msg) {
- // trigger poll if is my command reply message (0x20)
- if (feature.getQueryCommand() == 0x20) {
- feature.triggerPoll(5000L); // 5000ms delay to allow all op flag commands to be processed
- } else {
- super.handleMessage(cmd1, msg);
- }
+ protected long getPollDelay() {
+ return 5000L; // delay to allow all op flag commands to be processed
}
@Override
/**
* Micro module operation mode reply message handler
*/
- public static class MicroModuleOpModeReplyHandler extends CustomMsgHandler {
+ public static class MicroModuleOpModeReplyHandler extends OpFlagsReplyHandler {
MicroModuleOpModeReplyHandler(DeviceFeature feature) {
super(feature);
}
@Override
- public void handleMessage(byte cmd1, Msg msg) {
- // trigger poll if is my command reply message (0x20)
- if (feature.getQueryCommand() == 0x20) {
- feature.triggerPoll(2000L); // 2000ms delay to allow all op flag commands to be processed
- } else {
- super.handleMessage(cmd1, msg);
- }
+ protected long getPollDelay() {
+ return 2000L; // delay to allow all op flag commands to be processed
}
@Override
}
}
+ /**
+ * Remote scene button config reply message handler
+ */
+ public static class RemoteSceneButtonConfigReplyHandler extends OpFlagsReplyHandler {
+ RemoteSceneButtonConfigReplyHandler(DeviceFeature feature) {
+ super(feature);
+ }
+
+ @Override
+ protected long getPollDelay() {
+ return 2000L; // delay to allow all op flag commands to be processed
+ }
+
+ @Override
+ protected @Nullable State getState(byte cmd1, double value) {
+ RemoteSceneButtonConfig config = RemoteSceneButtonConfig.valueOf((int) value);
+ // update device type based on button config
+ getInsteonDevice().updateType(config);
+ // return button config state
+ return new StringType(config.toString());
+ }
+ }
+
+ /**
+ * Remote switch button config reply message handler
+ */
+ public static class RemoteSwitchButtonConfigReplyHandler extends OpFlagsReplyHandler {
+ RemoteSwitchButtonConfigReplyHandler(DeviceFeature feature) {
+ super(feature);
+ }
+
+ @Override
+ protected long getPollDelay() {
+ return 2000L; // delay to allow all op flag commands to be processed
+ }
+
+ @Override
+ protected @Nullable State getState(byte cmd1, double value) {
+ RemoteSwitchButtonConfig config = RemoteSwitchButtonConfig.valueOf((int) value);
+ // update device type based on button config
+ getInsteonDevice().updateType(config);
+ // return button config state
+ return new StringType(config.toString());
+ }
+ }
+
/**
* Siren request reply message handler
*/
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.insteon.internal.config.InsteonDeviceConfiguration;
import org.openhab.binding.insteon.internal.device.Device;
import org.openhab.binding.insteon.internal.device.DeviceCache;
-import org.openhab.binding.insteon.internal.device.DeviceType;
+import org.openhab.binding.insteon.internal.device.DeviceFeature;
import org.openhab.binding.insteon.internal.device.InsteonAddress;
import org.openhab.binding.insteon.internal.device.InsteonDevice;
import org.openhab.binding.insteon.internal.device.InsteonEngine;
@Override
protected void initializeChannels(Device device) {
- DeviceType deviceType = device.getType();
- if (deviceType == null) {
- return;
- }
-
super.initializeChannels(device);
- getThing().getChannels().forEach(channel -> setChannelCustomSettings(channel, deviceType.getName()));
+ getThing().getChannels().forEach(channel -> setChannelCustomSettings(channel, device));
}
- private void setChannelCustomSettings(Channel channel, String deviceTypeName) {
+ private void setChannelCustomSettings(Channel channel, Device device) {
ChannelUID channelUID = channel.getUID();
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null) {
return;
}
- String key = deviceTypeName + ":" + channelIdToFeatureName(channelTypeUID.getId());
- String[] stateDescriptionOptions = CUSTOM_STATE_DESCRIPTION_OPTIONS.get(key);
+ String featureName = channelIdToFeatureName(channelTypeUID.getId());
+ DeviceFeature feature = device.getFeature(featureName);
+ if (feature == null) {
+ return;
+ }
+
+ List<String> stateDescriptionOptions = CUSTOM_STATE_DESCRIPTION_OPTIONS.get(feature.getType());
if (stateDescriptionOptions == null) {
return;
}
- List<StateOption> options = Stream.of(stateDescriptionOptions).map(value -> new StateOption(value,
+ List<StateOption> options = stateDescriptionOptions.stream().map(value -> new StateOption(value,
StringUtils.capitalizeByWhitespace(value.replace("_", " ").toLowerCase()))).toList();
logger.trace("setting state options for {} to {}", channelUID, options);
package org.openhab.binding.insteon.internal.transport.message;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.insteon.internal.device.InsteonAddress;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Ideally, Insteon ALL LINK messages are received in this order, and
* IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:13.03.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x06|
* command2:0x00|
*/
- public static enum GroupMessageType {
+ private enum GroupMessageType {
BCAST,
CLEAN,
SUCCESS
* The state of the machine (i.e. what message we are expecting next).
* The usual state should be EXPECT_BCAST
*/
- private static enum State {
+ private enum State {
EXPECT_BCAST,
EXPECT_CLEAN,
EXPECT_SUCCESS
}
- private final Logger logger = LoggerFactory.getLogger(GroupMessageStateMachine.class);
-
private State state = State.EXPECT_BCAST;
private boolean duplicate = false;
private byte lastCmd1 = 0;
private long lastTimestamp = 0;
- public boolean isDuplicate() {
- return duplicate;
- }
+ /**
+ * Returns if group message is duplicate
+ *
+ * @param msg the group message
+ * @return true if the group message is duplicate
+ * @throws FieldException
+ */
+ public boolean isDuplicate(Msg msg) throws FieldException {
+ byte cmd1 = msg.isAllLinkSuccessReport() ? msg.getInsteonAddress("toAddress").getHighByte()
+ : msg.getByte("command1");
+ long timestamp = msg.getTimestamp();
- public byte getLastCommand() {
- return lastCmd1;
- }
+ if (cmd1 != lastCmd1 || timestamp != lastTimestamp) {
+ GroupMessageType type = msg.isAllLinkSuccessReport() ? GroupMessageType.SUCCESS
+ : msg.isAllLinkCleanup() ? GroupMessageType.CLEAN : GroupMessageType.BCAST;
- public long getLastTimestamp() {
- return lastTimestamp;
+ update(cmd1, timestamp, type);
+ }
+
+ return duplicate;
}
/**
- * Updates the state machine and determine if not duplicate
+ * Updates the state machine
*
- * @param address the address of the device that this state machine belongs to
- * @param group the group that this state machine belongs to
* @param cmd1 cmd1 from the message received
* @param timestamp timestamp from the message received
* @param type the group message type that was received
- * @return true if the group message is duplicate
*/
- public boolean update(InsteonAddress address, int group, byte cmd1, long timestamp, GroupMessageType type) {
- boolean isNewGroupMsg = cmd1 != lastCmd1 || timestamp > lastTimestamp + GROUP_STATE_TIMEOUT;
+ private void update(byte cmd1, long timestamp, GroupMessageType type) {
+ boolean isNewGroupMsg = cmd1 != lastCmd1 || Math.abs(timestamp - lastTimestamp) > GROUP_STATE_TIMEOUT;
- switch (state) {
- case EXPECT_BCAST:
- switch (type) {
- case BCAST:
+ switch (type) {
+ case BCAST:
+ switch (state) {
+ case EXPECT_BCAST:
+ case EXPECT_SUCCESS:
duplicate = false;
break;
- case CLEAN:
- case SUCCESS:
+ case EXPECT_CLEAN:
duplicate = !isNewGroupMsg;
break;
}
+ state = State.EXPECT_CLEAN;
break;
- case EXPECT_CLEAN:
- switch (type) {
- case BCAST:
+ case CLEAN:
+ switch (state) {
+ case EXPECT_BCAST:
duplicate = !isNewGroupMsg;
break;
- case CLEAN:
- case SUCCESS:
+ case EXPECT_CLEAN:
+ case EXPECT_SUCCESS:
duplicate = true;
break;
}
+ state = State.EXPECT_SUCCESS;
break;
- case EXPECT_SUCCESS:
- switch (type) {
- case BCAST:
- duplicate = false;
+ case SUCCESS:
+ switch (state) {
+ case EXPECT_BCAST:
+ duplicate = !isNewGroupMsg;
break;
- case CLEAN:
- case SUCCESS:
+ case EXPECT_CLEAN:
+ case EXPECT_SUCCESS:
duplicate = true;
break;
}
- break;
- }
-
- switch (type) {
- case BCAST:
- state = State.EXPECT_CLEAN;
- break;
- case CLEAN:
- state = State.EXPECT_SUCCESS;
- break;
- case SUCCESS:
state = State.EXPECT_BCAST;
break;
}
lastCmd1 = cmd1;
lastTimestamp = timestamp;
-
- logger.debug("{} group:{} type:{} state:{} duplicate:{}", address, group, type, state, duplicate);
-
- return duplicate;
}
}
channel-type.insteon.button-beep.description = Enable beep on button press.
channel-type.insteon.button-config.label = Button Config
channel-type.insteon.button-config.description = Configure the button/scene mode.
+channel-type.insteon.button-config.state.option.BUTTON_1 = 1-Button
+channel-type.insteon.button-config.state.option.BUTTON_2_ALWAYS_ON = 2-Button Always On
+channel-type.insteon.button-config.state.option.BUTTON_2_TOGGLE = 2-Button Toggle
+channel-type.insteon.button-config.state.option.BUTTON_4 = 4-Button
channel-type.insteon.button-config.state.option.BUTTON_6 = 6-Button
channel-type.insteon.button-config.state.option.BUTTON_8 = 8-Button
+channel-type.insteon.button-config.state.option.BUTTON_8_ALWAYS_ON = 8-Button Always On
+channel-type.insteon.button-config.state.option.BUTTON_8_TOGGLE = 8-Button Toggle
channel-type.insteon.button-lock.label = Button Lock
channel-type.insteon.button-lock.description = Disable the front button press.
channel-type.insteon.carbon-monoxide-alarm.label = Carbon Monoxide Alarm
channel-type.insteon.system-mode.state.option.COOL = Cool
channel-type.insteon.system-mode.state.option.AUTO = Auto
channel-type.insteon.system-mode.state.option.PROGRAM = Program
+channel-type.insteon.system-mode.state.option.PROGRAM_HEAT = Program Heat
+channel-type.insteon.system-mode.state.option.PROGRAM_COOL = Program Cool
+channel-type.insteon.system-mode.state.option.PROGRAM_AUTO = Program Heat
channel-type.insteon.system-state.label = System State
channel-type.insteon.system-state.state.option.OFF = Off
channel-type.insteon.system-state.state.option.COOLING = Cooling
<message-dispatcher>DefaultDispatcher</message-dispatcher>
<message-handler command="0x19">IOLincRelayModeReplyHandler</message-handler>
<message-handler default="true">NoOpMsgHandler</message-handler>
- <command-handler command="OnOffType">IOLincRelayModeCommandHandler</command-handler>
+ <command-handler command="StringType">IOLincRelayModeCommandHandler</command-handler>
<command-handler command="RefreshType">RefreshCommandHandler</command-handler>
<poll-handler>NoPollHandler</poll-handler> <!-- polled by OpFlagsGroup -->
</feature-type>
<poll-handler>NoPollHandler</poll-handler> <!-- polled by OutletStatusGroup -->
</feature-type>
+ <feature-type name="RemoteBatteryLevel">
+ <message-dispatcher>DefaultDispatcher</message-dispatcher>
+ <!-- battery level range 0xA0 => 0xB4 (undocumented) -->
+ <!-- message field data1 0x01 (documented); 0x00 (observed) -->
+ <message-handler command="0x2E" ext="1" cmd1="0x2E" cmd2="0x00" d1="0x00" d2="0x01" field="userData10"
+ min="0xA0" max="0xB4">CustomDimensionlessMsgHandler</message-handler>
+ <message-handler default="true">NoOpMsgHandler</message-handler>
+ <command-handler command="RefreshType">RefreshCommandHandler</command-handler>
+ <command-handler default="true">NoOpCommandHandler</command-handler>
+ <poll-handler>NoPollHandler</poll-handler> <!-- polled by ExtDataGroup -->
+ </feature-type>
+ <feature-type name="RemoteSceneButtonConfig">
+ <message-dispatcher>DefaultDispatcher</message-dispatcher>
+ <message-handler command="0x19">RemoteSceneButtonConfigReplyHandler</message-handler>
+ <message-handler default="true">NoOpMsgHandler</message-handler>
+ <command-handler command="StringType">RemoteSceneButtonConfigCommandHandler</command-handler>
+ <command-handler command="RefreshType">RefreshCommandHandler</command-handler>
+ <poll-handler>NoPollHandler</poll-handler> <!-- polled by OpFlagsGroup -->
+ </feature-type>
+ <feature-type name="RemoteSwitchButtonConfig">
+ <message-dispatcher>DefaultDispatcher</message-dispatcher>
+ <message-handler command="0x19">RemoteSwitchButtonConfigReplyHandler</message-handler>
+ <message-handler default="true">NoOpMsgHandler</message-handler>
+ <command-handler command="StringType">RemoteSwitchButtonConfigCommandHandler</command-handler>
+ <command-handler command="RefreshType">RefreshCommandHandler</command-handler>
+ <poll-handler>NoPollHandler</poll-handler> <!-- polled by OpFlagsGroup -->
+ </feature-type>
+
<feature-type name="PowerMeterDataGroup">
<message-dispatcher>PollGroupDispatcher</message-dispatcher>
<poll-handler ext="0" cmd1="0x82" cmd2="0x00">FlexPollHandler</poll-handler>
<feature name="eventButtonB" group="2">GenericButtonEvent</feature>
<feature name="eventButtonC" group="3">GenericButtonEvent</feature>
<feature name="eventButtonD" group="4">GenericButtonEvent</feature>
+ <feature-group name="extDataGroup" type="ExtDataGroup">
+ <feature name="batteryLevel">RemoteBatteryLevel</feature>
+ </feature-group>
<feature-group name="opFlagsGroup" type="OpFlagsGroup">
<feature name="programLock" bit="0" on="0x00" off="0x01">OpFlags</feature>
<feature name="ledOnOff" bit="1" on="0x02" off="0x03" inverted="true">OpFlags</feature>
<feature name="buttonBeep" bit="2" on="0x04" off="0x05">OpFlags</feature>
<feature name="stayAwake" bit="3" on="0x06" off="0x07">OpFlags</feature>
+ <feature name="buttonConfig">RemoteSceneButtonConfig</feature>
</feature-group>
<default-link name="buttonA" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
<default-link name="buttonB" type="controller" group="2" data1="0x03" data2="0x00" data3="0x00"/>
</device-type>
<device-type name="GeneralizedController_MiniRemoteScene8" batteryPowered="true">
- <feature name="eventButtonA" group="1">GenericButtonEvent</feature>
- <feature name="eventButtonB" group="2">GenericButtonEvent</feature>
- <feature name="eventButtonC" group="3">GenericButtonEvent</feature>
- <feature name="eventButtonD" group="4">GenericButtonEvent</feature>
- <feature name="eventButtonE" group="5">GenericButtonEvent</feature>
- <feature name="eventButtonF" group="6">GenericButtonEvent</feature>
- <feature name="eventButtonG" group="7">GenericButtonEvent</feature>
- <feature name="eventButtonH" group="8">GenericButtonEvent</feature>
+ <feature name="eventButtonA" group="2">GenericButtonEvent</feature>
+ <feature name="eventButtonB" group="1">GenericButtonEvent</feature>
+ <feature name="eventButtonC" group="4">GenericButtonEvent</feature>
+ <feature name="eventButtonD" group="3">GenericButtonEvent</feature>
+ <feature name="eventButtonE" group="6">GenericButtonEvent</feature>
+ <feature name="eventButtonF" group="5">GenericButtonEvent</feature>
+ <feature name="eventButtonG" group="8">GenericButtonEvent</feature>
+ <feature name="eventButtonH" group="7">GenericButtonEvent</feature>
+ <feature-group name="extDataGroup" type="ExtDataGroup">
+ <feature name="batteryLevel">RemoteBatteryLevel</feature>
+ </feature-group>
<feature-group name="opFlagsGroup" type="OpFlagsGroup">
<feature name="programLock" bit="0" on="0x00" off="0x01">OpFlags</feature>
<feature name="ledOnOff" bit="1" on="0x02" off="0x03" inverted="true">OpFlags</feature>
<feature name="buttonBeep" bit="2" on="0x04" off="0x05">OpFlags</feature>
<feature name="stayAwake" bit="3" on="0x06" off="0x07">OpFlags</feature>
- </feature-group>
- <default-link name="buttonA" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonB" type="controller" group="2" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonC" type="controller" group="3" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonD" type="controller" group="4" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonE" type="controller" group="5" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonF" type="controller" group="6" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonG" type="controller" group="7" data1="0x03" data2="0x00" data3="0x00"/>
- <default-link name="buttonH" type="controller" group="8" data1="0x03" data2="0x00" data3="0x00"/>
+ <feature name="buttonConfig">RemoteSceneButtonConfig</feature>
+ </feature-group>
+ <default-link name="buttonA" type="controller" group="2" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonB" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonC" type="controller" group="4" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonD" type="controller" group="3" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonE" type="controller" group="6" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonF" type="controller" group="5" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonG" type="controller" group="8" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonH" type="controller" group="7" data1="0x03" data2="0x00" data3="0x00"/>
</device-type>
<device-type name="GeneralizedController_MiniRemoteSwitch" batteryPowered="true">
<feature name="eventButton">GenericButtonEvent</feature>
+ <feature-group name="extDataGroup" type="ExtDataGroup">
+ <feature name="batteryLevel">RemoteBatteryLevel</feature>
+ </feature-group>
<feature-group name="opFlagsGroup" type="OpFlagsGroup">
<feature name="programLock" bit="0" on="0x00" off="0x01">OpFlags</feature>
<feature name="ledOnOff" bit="1" on="0x02" off="0x03" inverted="true">OpFlags</feature>
<feature name="buttonBeep" bit="2" on="0x04" off="0x05">OpFlags</feature>
<feature name="stayAwake" bit="3" on="0x06" off="0x07">OpFlags</feature>
+ <feature name="buttonConfig">RemoteSwitchButtonConfig</feature>
</feature-group>
<default-link name="button" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
</device-type>
+ <device-type name="GeneralizedController_MiniRemoteSwitch2" batteryPowered="true">
+ <feature name="eventButtonA" group="1">GenericButtonEvent</feature>
+ <feature name="eventButtonB" group="2">GenericButtonEvent</feature>
+ <feature-group name="extDataGroup" type="ExtDataGroup">
+ <feature name="batteryLevel">RemoteBatteryLevel</feature>
+ </feature-group>
+ <feature-group name="opFlagsGroup" type="OpFlagsGroup">
+ <feature name="programLock" bit="0" on="0x00" off="0x01">OpFlags</feature>
+ <feature name="ledOnOff" bit="1" on="0x02" off="0x03" inverted="true">OpFlags</feature>
+ <feature name="buttonBeep" bit="2" on="0x04" off="0x05">OpFlags</feature>
+ <feature name="stayAwake" bit="3" on="0x06" off="0x07">OpFlags</feature>
+ <feature name="buttonConfig">RemoteSwitchButtonConfig</feature>
+ </feature-group>
+ <default-link name="buttonA" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
+ <default-link name="buttonB" type="controller" group="2" data1="0x03" data2="0x00" data3="0x00"/>
+ </device-type>
+
<!-- Dimmable Lighting Control -->
<device-type name="DimmableLightingControl">