2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.insteon.internal.device.feature;
15 import static org.openhab.binding.insteon.internal.InsteonBindingConstants.*;
17 import java.lang.reflect.InvocationTargetException;
18 import java.math.BigDecimal;
19 import java.math.RoundingMode;
20 import java.time.Instant;
21 import java.time.ZoneId;
22 import java.time.ZonedDateTime;
26 import javax.measure.Unit;
27 import javax.measure.quantity.Dimensionless;
28 import javax.measure.quantity.Energy;
29 import javax.measure.quantity.Power;
30 import javax.measure.quantity.Temperature;
31 import javax.measure.quantity.Time;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.insteon.internal.device.DeviceFeature;
36 import org.openhab.binding.insteon.internal.device.DeviceType;
37 import org.openhab.binding.insteon.internal.device.DeviceTypeRegistry;
38 import org.openhab.binding.insteon.internal.device.InsteonEngine;
39 import org.openhab.binding.insteon.internal.device.RampRate;
40 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ButtonEvent;
41 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.FanLincFanSpeed;
42 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.IMButtonEvent;
43 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.IOLincRelayMode;
44 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonConfig;
45 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.KeypadButtonToggleMode;
46 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.MicroModuleOpMode;
47 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.SirenAlertType;
48 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatFanMode;
49 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatSystemMode;
50 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatSystemState;
51 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatTemperatureScale;
52 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.ThermostatTimeFormat;
53 import org.openhab.binding.insteon.internal.device.feature.FeatureEnums.VenstarSystemMode;
54 import org.openhab.binding.insteon.internal.transport.message.FieldException;
55 import org.openhab.binding.insteon.internal.transport.message.GroupMessageStateMachine.GroupMessageType;
56 import org.openhab.binding.insteon.internal.transport.message.Msg;
57 import org.openhab.binding.insteon.internal.utils.BinaryUtils;
58 import org.openhab.binding.insteon.internal.utils.HexUtils;
59 import org.openhab.binding.insteon.internal.utils.ParameterParser;
60 import org.openhab.core.library.types.DateTimeType;
61 import org.openhab.core.library.types.DecimalType;
62 import org.openhab.core.library.types.OnOffType;
63 import org.openhab.core.library.types.OpenClosedType;
64 import org.openhab.core.library.types.PercentType;
65 import org.openhab.core.library.types.QuantityType;
66 import org.openhab.core.library.types.StringType;
67 import org.openhab.core.library.unit.ImperialUnits;
68 import org.openhab.core.library.unit.SIUnits;
69 import org.openhab.core.library.unit.Units;
70 import org.openhab.core.types.State;
71 import org.openhab.core.types.UnDefType;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
76 * A message handler processes incoming Insteon messages
78 * @author Daniel Pfrommer - Initial contribution
79 * @author Bernd Pfrommer - openHAB 1 insteonplm binding
80 * @author Rob Nielsen - Port to openHAB 2 insteon binding
81 * @author Jeremy Setton - Rewrite insteon binding
84 public abstract class MessageHandler extends BaseFeatureHandler {
85 private static final Set<Integer> SUPPORTED_GROUP_COMMANDS = Set.of(0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
88 protected final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
90 public MessageHandler(DeviceFeature feature) {
97 * @return handler id based on command and group parameters
99 public String getId() {
100 int command = getParameterAsInteger("command", -1);
101 int group = getGroup();
102 return MessageHandler.generateId(command, group);
106 * Returns handler group
108 * @return handler group based on feature or handler group parameter, if supports group, otherwise -1
110 public int getGroup() {
111 int command = getParameterAsInteger("command", -1);
112 // return -1 if handler doesn't support groups
113 if (!MessageHandler.supportsGroup(command)) {
116 int group = ParameterParser.getParameterAsOrDefault(parameters.get("group"), Integer.class, -1);
117 // return handler group parameter if non-standard
121 // return feature group parameter if defined, otherwise handler group parameter
122 return feature.getParameterAsInteger("group", group);
126 * Returns if can handle a given message
128 * @param msg the message to be handled
129 * @return true if handler not duplicate, valid and matches filter parameters
131 public boolean canHandle(Msg msg) {
132 if (isDuplicate(msg)) {
133 logger.trace("{}:{} ignoring msg as duplicate", getDevice().getAddress(), feature.getName());
135 } else if (!isValid(msg)) {
136 logger.trace("{}:{} ignoring msg as not valid", getDevice().getAddress(), feature.getName());
138 } else if (!matchesFilters(msg)) {
139 logger.trace("{}:{} ignoring msg as unmatch filters", getDevice().getAddress(), feature.getName());
146 * Returns if an incoming message is a duplicate
148 * @param msg the received message
149 * @return true if the broadcast message is a duplicate
151 protected boolean isDuplicate(Msg msg) {
153 if (msg.isAllLinkBroadcastOrCleanup()) {
154 byte cmd1 = msg.getByte("command1");
155 long timestamp = msg.getTimestamp();
156 int group = msg.getGroup();
157 GroupMessageType type = msg.isAllLinkBroadcast() ? GroupMessageType.BCAST : GroupMessageType.CLEAN;
158 if (msg.isAllLinkSuccessReport()) {
159 cmd1 = msg.getInsteonAddress("toAddress").getHighByte();
160 type = GroupMessageType.SUCCESS;
162 return getInsteonDevice().isDuplicateGroupMsg(cmd1, timestamp, group, type);
163 } else if (msg.isBroadcast()) {
164 byte cmd1 = msg.getByte("command1");
165 long timestamp = msg.getTimestamp();
166 return getInsteonDevice().isDuplicateBroadcastMsg(cmd1, timestamp);
168 } catch (IllegalArgumentException e) {
169 logger.warn("cannot parse msg: {}", msg, e);
170 } catch (FieldException e) {
171 logger.warn("cannot parse msg: {}", msg, e);
177 * Returns if an incoming DIRECT message is valid
179 * @param msg the received DIRECT message
180 * @return true if this message is valid
182 protected boolean isValid(Msg msg) {
183 if (msg.isDirect()) {
184 int ext = getParameterAsInteger("ext", -1);
185 // extended message crc is only included in incoming message when using the newer 2-byte method
187 return msg.hasValidCRC2();
194 * Returns if message matches the filter parameters
196 * @param msg message to check
197 * @return true if message matches
199 protected boolean matchesFilters(Msg msg) {
201 int ext = getParameterAsInteger("ext", -1);
203 if ((!msg.isExtended() && ext != 0) || (msg.isExtended() && ext != 1 && ext != 2)) {
206 if (!matchesParameter(msg, "command1", "cmd1")) {
210 if (!matchesParameter(msg, "command2", "cmd2")) {
213 if (!matchesParameter(msg, "userData1", "d1")) {
216 if (!matchesParameter(msg, "userData2", "d2")) {
219 if (!matchesParameter(msg, "userData3", "d3")) {
222 } catch (FieldException e) {
223 logger.warn("error matching message: {}", msg, e);
230 * Returns if parameter matches value
232 * @param msg message to check
233 * @param field field name to match
234 * @param param name of parameter to match
235 * @return true if parameter matches
236 * @throws FieldException if field not there
238 private boolean matchesParameter(Msg msg, String field, String param) throws FieldException {
239 int mp = getParameterAsInteger(param, -1);
240 // parameter not filtered for, declare this a match!
244 byte value = msg.getByte(field);
249 * Handles incoming message. The cmd1 parameter
250 * has been extracted earlier already (to make a decision which message handler to call),
251 * and is passed in as an argument so cmd1 does not have to be extracted from the message again.
253 * @param cmd1 the insteon cmd1 field
254 * @param msg the received insteon message
256 public abstract void handleMessage(byte cmd1, Msg msg);
259 * Default message handler
261 public static class DefaultMsgHandler extends MessageHandler {
262 DefaultMsgHandler(DeviceFeature feature) {
267 public void handleMessage(byte cmd1, Msg msg) {
268 if (logger.isDebugEnabled()) {
269 logger.debug("{}: ignoring unimpl message with cmd1 {}", nm(), HexUtils.getHexString(cmd1));
275 * No-op message handler
277 public static class NoOpMsgHandler extends MessageHandler {
278 NoOpMsgHandler(DeviceFeature feature) {
283 public void handleMessage(byte cmd1, Msg msg) {
284 if (logger.isTraceEnabled()) {
285 logger.trace("{}: ignoring message with cmd1 {}", nm(), HexUtils.getHexString(cmd1));
291 * Trigger poll message handler
293 public static class TriggerPollMsgHandler extends MessageHandler {
294 TriggerPollMsgHandler(DeviceFeature feature) {
299 public void handleMessage(byte cmd1, Msg msg) {
300 // trigger poll with delay based on parameter, defaulting to 0 ms
301 long delay = getParameterAsLong("delay", 0L);
302 feature.triggerPoll(delay);
307 * Custom state abstract message handler based of parameters
309 public abstract static class CustomMsgHandler extends MessageHandler {
310 CustomMsgHandler(DeviceFeature feature) {
315 public void handleMessage(byte cmd1, Msg msg) {
317 // extract raw value from message
318 int raw = getRawValue(msg);
319 // apply mask and right shift bit manipulation
320 int cooked = (raw & getParameterAsInteger("mask", 0xFF)) >> getParameterAsInteger("rshift", 0);
321 // multiply with factor and add offset
322 double value = cooked * getParameterAsDouble("factor", 1.0) + getParameterAsDouble("offset", 0.0);
323 // get state to update
324 State state = getState(cmd1, value);
325 // store extracted cooked message value
326 feature.setLastMsgValue(value);
327 // update state if defined
329 logger.debug("{}: device {} {} is {}", nm(), getInsteonDevice().getAddress(), feature.getName(),
331 feature.updateState(state);
333 } catch (FieldException e) {
334 logger.warn("{}: error parsing msg {}", nm(), msg, e);
338 private int getRawValue(Msg msg) throws FieldException {
339 // determine data field name based on parameter, default to cmd2 if is standard message
340 String field = getParameterAsString("field", !msg.isExtended() ? "command2" : "");
341 if (field.isEmpty()) {
342 throw new FieldException("handler misconfigured, no field parameter specified!");
344 if (field.startsWith("address") && !msg.isBroadcast() && !msg.isAllLinkBroadcast()) {
345 throw new FieldException("not broadcast msg, cannot use address bytes!");
347 // return raw value based on field name
350 return msg.getGroup();
351 case "addressHighByte":
352 // return broadcast address high byte value
353 return msg.getInsteonAddress("toAddress").getHighByte() & 0xFF;
354 case "addressMiddleByte":
355 // return broadcast address middle byte value
356 return msg.getInsteonAddress("toAddress").getMiddleByte() & 0xFF;
357 case "addressLowByte":
358 // return broadcast address low byte value
359 return msg.getInsteonAddress("toAddress").getLowByte() & 0xFF;
361 // return integer value starting from field name up to 4-bytes in size based on parameter
362 return msg.getInt(field, getParameterAsInteger("num_bytes", 1));
366 protected abstract @Nullable State getState(byte cmd1, double value);
370 * Custom bitmask message handler based of parameters
372 public static class CustomBitmaskMsgHandler extends CustomMsgHandler {
373 CustomBitmaskMsgHandler(DeviceFeature feature) {
378 protected @Nullable State getState(byte cmd1, double value) {
380 // get bit number based on parameter
381 int bit = getBitNumber();
382 // get bit state from bitmask value, if bit defined
384 boolean isSet = BinaryUtils.isBitSet((int) value, bit);
385 state = getBitState(isSet);
387 logger.debug("{}: invalid bit number defined for {}", nm(), feature.getName());
392 protected int getBitNumber() {
393 int bit = getParameterAsInteger("bit", -1);
394 // return bit if valid (0-7), otherwise -1
395 return bit >= 0 && bit <= 7 ? bit : -1;
398 protected State getBitState(boolean isSet) {
399 return OnOffType.from(isSet ^ getParameterAsBoolean("inverted", false));
404 * Custom cache message handler based of parameters
406 public static class CustomCacheMsgHandler extends CustomMsgHandler {
407 CustomCacheMsgHandler(DeviceFeature feature) {
412 protected @Nullable State getState(byte cmd1, double value) {
413 // only cache extracted message value
414 // mostly used for hidden features which are used by others
420 * Custom decimal type message handler based of parameters
422 public static class CustomDecimalMsgHandler extends CustomMsgHandler {
423 CustomDecimalMsgHandler(DeviceFeature feature) {
428 protected @Nullable State getState(byte cmd1, double value) {
429 return new DecimalType(value);
434 * Custom on/off type message handler based of parameters
436 public static class CustomOnOffMsgHandler extends CustomMsgHandler {
437 CustomOnOffMsgHandler(DeviceFeature feature) {
442 protected @Nullable State getState(byte cmd1, double value) {
443 int onLevel = getParameterAsInteger("on", 0xFF);
444 int offLevel = getParameterAsInteger("off", 0x00);
445 return value == onLevel ? OnOffType.ON : value == offLevel ? OnOffType.OFF : null;
450 * Custom percent type message handler based of parameters
452 public static class CustomPercentMsgHandler extends CustomMsgHandler {
453 CustomPercentMsgHandler(DeviceFeature feature) {
458 protected @Nullable State getState(byte cmd1, double value) {
459 int minValue = getParameterAsInteger("min", 0x00);
460 int maxValue = getParameterAsInteger("max", 0xFF);
461 double clampValue = Math.max(minValue, Math.min(maxValue, value));
462 int level = (int) Math.round((clampValue - minValue) / (maxValue - minValue) * 100);
463 return new PercentType(level);
468 * Custom dimensionless quantity type message handler based of parameters
470 public static class CustomDimensionlessMsgHandler extends CustomMsgHandler {
471 CustomDimensionlessMsgHandler(DeviceFeature feature) {
476 protected @Nullable State getState(byte cmd1, double value) {
477 int minValue = getParameterAsInteger("min", 0);
478 int maxValue = getParameterAsInteger("max", 100);
479 double clampValue = Math.max(minValue, Math.min(maxValue, value));
480 int level = (int) Math.round((clampValue - minValue) * 100 / (maxValue - minValue));
481 return new QuantityType<Dimensionless>(level, Units.PERCENT);
486 * Custom temperature quantity type message handler based of parameters
488 public static class CustomTemperatureMsgHandler extends CustomMsgHandler {
489 CustomTemperatureMsgHandler(DeviceFeature feature) {
494 protected @Nullable State getState(byte cmd1, double value) {
495 Unit<Temperature> unit = getTemperatureUnit();
496 return new QuantityType<Temperature>(value, unit);
499 protected Unit<Temperature> getTemperatureUnit() {
500 String scale = getParameterAsString("scale", "");
503 return SIUnits.CELSIUS;
505 return ImperialUnits.FAHRENHEIT;
507 logger.debug("{}: no valid temperature scale parameter found, defaulting to: CELSIUS", nm());
508 return SIUnits.CELSIUS;
514 * Custom time quantity type message handler based of parameters
516 public static class CustomTimeMsgHandler extends CustomMsgHandler {
517 CustomTimeMsgHandler(DeviceFeature feature) {
522 protected @Nullable State getState(byte cmd1, double value) {
523 Unit<Time> unit = getTimeUnit();
524 return new QuantityType<Time>(value, unit);
527 protected Unit<Time> getTimeUnit() {
528 String scale = getParameterAsString("scale", "");
537 logger.debug("{}: no valid time scale parameter found, defaulting to: SECONDS", nm());
544 * Database delta reply message handler
546 public static class DatabaseDeltaReplyHandler extends MessageHandler {
547 DatabaseDeltaReplyHandler(DeviceFeature feature) {
552 public void handleMessage(byte cmd1, Msg msg) {
554 int delta = msg.getInt("command2");
555 // update link db delta
556 getInsteonDevice().getLinkDB().updateDatabaseDelta(delta);
557 } catch (FieldException e) {
558 logger.warn("{}: error parsing msg: {}", nm(), msg, e);
564 * Insteon engine reply message handler
566 public static class InsteonEngineReplyHandler extends MessageHandler {
567 InsteonEngineReplyHandler(DeviceFeature feature) {
572 public void handleMessage(byte cmd1, Msg msg) {
574 int version = msg.getInt("command2");
575 InsteonEngine engine = InsteonEngine.valueOf(version);
576 // set device insteon engine
577 getInsteonDevice().setInsteonEngine(engine);
578 // continue device polling
579 getInsteonDevice().doPoll(0L);
580 } catch (FieldException e) {
581 logger.warn("{}: error parsing msg: {}", nm(), msg, e);
587 * Ping reply message handler
589 public static class PingReplyHandler extends MessageHandler {
590 PingReplyHandler(DeviceFeature feature) {
595 public void handleMessage(byte cmd1, Msg msg) {
596 logger.debug("{}: successfully pinged device {}", nm(), getInsteonDevice().getAddress());
601 * Heartbeat monitor message handler
603 public static class HeartbeatMonitorMsgHandler extends MessageHandler {
604 HeartbeatMonitorMsgHandler(DeviceFeature feature) {
609 public void handleMessage(byte cmd1, Msg msg) {
610 // reset device heartbeat monitor on all link broadcast or cleanup message not replayed
611 if (msg.isAllLinkBroadcastOrCleanup() && !msg.isReplayed()) {
612 getInsteonDevice().resetHeartbeatMonitor();
618 * Last time message handler
620 public static class LastTimeMsgHandler extends MessageHandler {
621 LastTimeMsgHandler(DeviceFeature feature) {
626 public void handleMessage(byte cmd1, Msg msg) {
627 Instant instant = Instant.ofEpochMilli(msg.getTimestamp());
628 ZonedDateTime timestamp = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
629 ZonedDateTime lastTimestamp = getLastTimestamp();
630 // set last time if not defined yet or message timestamp is greater than last value
631 if (lastTimestamp == null || timestamp.compareTo(lastTimestamp) > 0) {
632 feature.updateState(new DateTimeType(timestamp));
636 private @Nullable ZonedDateTime getLastTimestamp() {
637 State state = feature.getState();
638 return state instanceof DateTimeType datetime ? datetime.getZonedDateTime() : null;
643 * Button event message handler
645 public static class ButtonEventMsgHandler extends MessageHandler {
646 ButtonEventMsgHandler(DeviceFeature feature) {
651 protected boolean isDuplicate(Msg msg) {
652 // Disable duplicate elimination based on parameter because
653 // some button events such as hold or release have no cleanup or success messages.
654 return getParameterAsBoolean("duplicate", super.isDuplicate(msg));
658 public void handleMessage(byte cmd1, Msg msg) {
660 byte cmd2 = msg.getByte("command2");
661 ButtonEvent event = ButtonEvent.valueOf(cmd1, cmd2);
662 logger.debug("{}: device {} {} received event {}", nm(), getInsteonDevice().getAddress(),
663 feature.getName(), event);
664 feature.triggerEvent(event.toString());
665 feature.pollRelatedDevices(0L);
666 } catch (FieldException e) {
667 logger.warn("{}: error parsing msg: {}", nm(), msg, e);
668 } catch (IllegalArgumentException e) {
669 logger.warn("{}: got unexpected button event: {}", nm(), HexUtils.getHexString(cmd1));
675 * Status request reply message handler
677 public static class StatusRequestReplyHandler extends CustomMsgHandler {
678 StatusRequestReplyHandler(DeviceFeature feature) {
683 public void handleMessage(byte cmd1, Msg msg) {
684 // update link db delta if is my request status reply message (0x19)
685 if (feature.getQueryCommand() == 0x19) {
686 getInsteonDevice().getLinkDB().updateDatabaseDelta(cmd1 & 0xFF);
688 super.handleMessage(cmd1, msg);
692 protected @Nullable State getState(byte cmd1, double value) {
698 * On/Off abstract message handler
700 public abstract static class OnOffMsgHandler extends MessageHandler {
701 OnOffMsgHandler(DeviceFeature feature) {
706 public void handleMessage(byte cmd1, Msg msg) {
707 String mode = getParameterAsString("mode", "REGULAR");
708 State state = getState(mode);
710 logger.debug("{}: device {} is {} ({})", nm(), getInsteonDevice().getAddress(), state, mode);
711 feature.updateState(state);
715 protected abstract @Nullable State getState(String mode);
719 * Dimmer on message handler
721 public static class DimmerOnMsgHandler extends OnOffMsgHandler {
722 DimmerOnMsgHandler(DeviceFeature feature) {
727 protected @Nullable State getState(String mode) {
730 // set to 100% for fast on change
731 return PercentType.HUNDRED;
733 // set to device on level if the current state not at that level already, defaulting to 100%
734 // this is due to subsequent dimmer on button press cycling between on level and 100%
735 State onLevel = getInsteonDevice().getFeatureState(FEATURE_ON_LEVEL);
736 State state = feature.getState();
737 return onLevel instanceof PercentType && !state.equals(onLevel) ? onLevel : PercentType.HUNDRED;
743 * Dimmer off message handler
745 public static class DimmerOffMsgHandler extends OnOffMsgHandler {
746 DimmerOffMsgHandler(DeviceFeature feature) {
751 protected @Nullable State getState(String mode) {
752 return PercentType.ZERO;
757 * Dimmer request reply message handler
759 public static class DimmerRequestReplyHandler extends StatusRequestReplyHandler {
760 DimmerRequestReplyHandler(DeviceFeature feature) {
765 public void handleMessage(byte cmd1, Msg msg) {
766 int queryCmd = feature.getQueryCommand();
767 // 1) trigger poll if is my bright/dim or manual change stop command reply
768 // 2) handle fast on/off message if is my fast on/off command reply
769 // 3) handle ramp dimmer message if is my ramp rate on/off command reply
770 // 4) handle my standard/instant on/off command reply ignoring manual change start messages
771 if (queryCmd == 0x15 || queryCmd == 0x16 || queryCmd == 0x18) {
772 feature.triggerPoll(0L);
773 } else if (queryCmd == 0x12 || queryCmd == 0x14) {
774 handleFastOnOffMessage(cmd1, msg);
775 } else if (queryCmd == 0x2E || queryCmd == 0x2F || queryCmd == 0x34 || queryCmd == 0x35) {
776 handleRampDimmerMessage(cmd1, msg);
777 } else if (queryCmd != 0x17) {
778 super.handleMessage(cmd1, msg);
783 protected @Nullable State getState(byte cmd1, double value) {
784 int level = (int) Math.round(value * 100 / 255.0);
785 return new PercentType(level);
788 private void handleFastOnOffMessage(byte cmd1, Msg msg) {
789 FastOnOffMsgHandler handler = new FastOnOffMsgHandler(feature);
790 handler.setParameters(parameters);
791 handler.handleMessage(cmd1, msg);
794 private void handleRampDimmerMessage(byte cmd1, Msg msg) {
795 RampDimmerMsgHandler handler = new RampDimmerMsgHandler(feature);
796 handler.setParameters(parameters);
797 handler.handleMessage(cmd1, msg);
802 * Fast on/off message handler
804 public static class FastOnOffMsgHandler extends CustomMsgHandler {
805 FastOnOffMsgHandler(DeviceFeature feature) {
810 protected @Nullable State getState(byte cmd1, double value) {
813 return PercentType.ZERO;
815 return PercentType.HUNDRED;
817 logger.warn("{}: got unexpected command value: {}", nm(), HexUtils.getHexString(cmd1));
824 * Ramp dimmer message handler
826 public static class RampDimmerMsgHandler extends CustomMsgHandler {
827 RampDimmerMsgHandler(DeviceFeature feature) {
832 protected @Nullable State getState(byte cmd1, double value) {
836 return PercentType.ZERO;
839 int highByte = ((int) value) >> 4;
840 int level = (int) Math.round((highByte * 16 + 0x0F) * 100 / 255.0);
841 return new PercentType(level);
843 logger.warn("{}: got unexpected command value: {}", nm(), HexUtils.getHexString(cmd1));
850 * Switch on message handler
852 public static class SwitchOnMsgHandler extends OnOffMsgHandler {
853 SwitchOnMsgHandler(DeviceFeature feature) {
858 protected @Nullable State getState(String mode) {
864 * Switch off message handler
866 public static class SwitchOffMsgHandler extends OnOffMsgHandler {
867 SwitchOffMsgHandler(DeviceFeature feature) {
872 protected @Nullable State getState(String mode) {
873 return OnOffType.OFF;
878 * Switch request reply message handler
880 public static class SwitchRequestReplyHandler extends StatusRequestReplyHandler {
881 SwitchRequestReplyHandler(DeviceFeature feature) {
886 protected @Nullable State getState(byte cmd1, double value) {
887 int level = (int) value;
889 if (level == 0x00 || level == 0xFF) {
890 state = OnOffType.from(level == 0xFF);
892 logger.warn("{}: ignoring unexpected level received {}", nm(), HexUtils.getHexString(level));
899 * Keypad button on message handler
901 public static class KeypadButtonOnMsgHandler extends SwitchOnMsgHandler {
902 KeypadButtonOnMsgHandler(DeviceFeature feature) {
907 public void handleMessage(byte cmd1, Msg msg) {
908 super.handleMessage(cmd1, msg);
909 // trigger poll to account for button group changes
910 feature.triggerPoll(0L);
915 * Keypad button off message handler
917 public static class KeypadButtonOffMsgHandler extends SwitchOffMsgHandler {
918 KeypadButtonOffMsgHandler(DeviceFeature feature) {
923 public void handleMessage(byte cmd1, Msg msg) {
924 super.handleMessage(cmd1, msg);
925 // trigger poll to account for button group changes
926 feature.triggerPoll(0L);
931 * Keypad button reply message handler
933 public static class KeypadButtonReplyHandler extends CustomBitmaskMsgHandler {
934 KeypadButtonReplyHandler(DeviceFeature feature) {
939 public void handleMessage(byte cmd1, Msg msg) {
940 // trigger poll if is my command reply message (0x2E)
941 if (feature.getQueryCommand() == 0x2E) {
942 feature.triggerPoll(0L);
944 super.handleMessage(cmd1, msg);
949 protected int getBitNumber() {
950 int bit = feature.getGroup() - 1;
951 // return bit if representing keypad button 2-8, otherwise -1
952 return bit >= 1 && bit <= 7 ? bit : -1;
957 * Keypad button toggle mode message handler
959 public static class KeypadButtonToggleModeMsgHandler extends MessageHandler {
960 KeypadButtonToggleModeMsgHandler(DeviceFeature feature) {
965 public void handleMessage(byte cmd1, Msg msg) {
967 int bit = feature.getGroup() - 1;
968 if (bit < 0 || bit > 7) {
969 logger.debug("{}: invalid bit number defined for {}", nm(), feature.getName());
971 int value = msg.getByte("userData10") << 8 | msg.getByte("userData13");
972 KeypadButtonToggleMode mode = KeypadButtonToggleMode.valueOf(value, bit);
973 logger.debug("{}: device {} {} is {}", nm(), getInsteonDevice().getAddress(), feature.getName(),
975 feature.setLastMsgValue(value);
976 feature.updateState(new StringType(mode.toString()));
978 } catch (FieldException e) {
979 logger.warn("{}: error parsing msg: {}", nm(), msg, e);
985 * Operating flags reply message handler
987 public static class OpFlagsReplyHandler extends CustomBitmaskMsgHandler {
988 OpFlagsReplyHandler(DeviceFeature feature) {
993 public void handleMessage(byte cmd1, Msg msg) {
994 // trigger poll if is my command reply message (0x20)
995 if (feature.getQueryCommand() == 0x20) {
996 feature.triggerPoll(0L);
998 super.handleMessage(cmd1, msg);
1004 * Link operating flags reply message handler
1006 public static class LinkOpFlagsReplyHandler extends OpFlagsReplyHandler {
1007 LinkOpFlagsReplyHandler(DeviceFeature feature) {
1012 public void handleMessage(byte cmd1, Msg msg) {
1013 super.handleMessage(cmd1, msg);
1014 // update default links
1015 getInsteonDevice().updateDefaultLinks();
1020 * Heartbeat on/off operating flag reply message handler
1022 public static class HeartbeatOnOffReplyHandler extends OpFlagsReplyHandler {
1023 HeartbeatOnOffReplyHandler(DeviceFeature feature) {
1028 public void handleMessage(byte cmd1, Msg msg) {
1029 super.handleMessage(cmd1, msg);
1030 // reset device heartbeat monitor
1031 getInsteonDevice().resetHeartbeatMonitor();
1036 * Keypad button config operating flag reply message handler
1038 public static class KeypadButtonConfigReplyHandler extends OpFlagsReplyHandler {
1039 KeypadButtonConfigReplyHandler(DeviceFeature feature) {
1044 protected State getBitState(boolean is8Button) {
1045 KeypadButtonConfig config = KeypadButtonConfig.from(is8Button);
1046 // update device type based on button count
1047 updateDeviceType(config.getCount());
1048 // return button config state
1049 return new StringType(config.toString());
1052 private void updateDeviceType(int buttonCount) {
1053 DeviceType deviceType = getInsteonDevice().getType();
1054 if (deviceType == null) {
1055 logger.warn("{}: unknown device type for {}", nm(), getInsteonDevice().getAddress());
1057 String name = deviceType.getName().replaceAll(".$", String.valueOf(buttonCount));
1058 DeviceType newType = DeviceTypeRegistry.getInstance().getDeviceType(name);
1059 if (newType == null) {
1060 logger.warn("{}: unknown device type {}", nm(), name);
1062 getInsteonDevice().updateType(newType);
1069 * LED brightness message handler
1071 public static class LEDBrightnessMsgHandler extends CustomMsgHandler {
1072 LEDBrightnessMsgHandler(DeviceFeature feature) {
1077 protected @Nullable State getState(byte cmd1, double value) {
1078 int level = (int) Math.round(value * 100 / 127.0);
1079 State state = getInsteonDevice().getFeatureState(FEATURE_LED_ON_OFF);
1080 return OnOffType.OFF.equals(state) ? PercentType.ZERO : new PercentType(level);
1085 * Ramp rate message handler
1087 public static class RampRateMsgHandler extends CustomMsgHandler {
1088 RampRateMsgHandler(DeviceFeature feature) {
1093 protected @Nullable State getState(byte cmd1, double value) {
1094 RampRate rampRate = RampRate.valueOf((int) value);
1095 return new QuantityType<Time>(rampRate.getTimeInSeconds(), Units.SECOND);
1100 * Sensor abstract message handler
1102 public abstract static class SensorMsgHandler extends CustomMsgHandler {
1103 SensorMsgHandler(DeviceFeature feature) {
1108 public void handleMessage(byte cmd1, Msg msg) {
1109 super.handleMessage(cmd1, msg);
1110 // poll battery powered sensor device while awake
1111 if (getInsteonDevice().isBatteryPowered()) {
1112 // no delay for all link cleanup, all link success report or replayed messages
1113 // otherise, 1500ms for all link broadcast message allowing cleanup msg to be be processed beforehand
1114 long delay = msg.isAllLinkCleanup() || msg.isAllLinkSuccessReport() || msg.isReplayed() ? 0L : 1500L;
1115 getInsteonDevice().doPoll(delay);
1117 // poll related devices
1118 feature.pollRelatedDevices(0L);
1123 * Contact open message handler
1125 public static class ContactOpenMsgHandler extends SensorMsgHandler {
1126 ContactOpenMsgHandler(DeviceFeature feature) {
1131 protected @Nullable State getState(byte cmd1, double value) {
1132 return OpenClosedType.OPEN;
1137 * Contact closed message handler
1139 public static class ContactClosedMsgHandler extends SensorMsgHandler {
1140 ContactClosedMsgHandler(DeviceFeature feature) {
1145 protected @Nullable State getState(byte cmd1, double value) {
1146 return OpenClosedType.CLOSED;
1151 * Contact request reply message handler
1153 public static class ContactRequestReplyHandler extends StatusRequestReplyHandler {
1154 ContactRequestReplyHandler(DeviceFeature feature) {
1159 protected @Nullable State getState(byte cmd1, double value) {
1160 return value == 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
1165 * Wireless sensor open message handler
1167 public static class WirelessSensorOpenMsgHandler extends SensorMsgHandler {
1168 WirelessSensorOpenMsgHandler(DeviceFeature feature) {
1173 protected @Nullable State getState(byte cmd1, double value) {
1174 return OpenClosedType.OPEN;
1179 * Wireless sensor closed message handler
1181 public static class WirelessSensorClosedMsgHandler extends SensorMsgHandler {
1182 WirelessSensorClosedMsgHandler(DeviceFeature feature) {
1187 protected @Nullable State getState(byte cmd1, double value) {
1188 return OpenClosedType.CLOSED;
1193 * Wireless sensor on message handler
1195 public static class WirelessSensorOnMsgHandler extends SensorMsgHandler {
1196 WirelessSensorOnMsgHandler(DeviceFeature feature) {
1201 protected @Nullable State getState(byte cmd1, double value) {
1202 return OnOffType.ON;
1207 * Wireless sensor off message handler
1209 public static class WirelessSensorOffMsgHandler extends SensorMsgHandler {
1210 WirelessSensorOffMsgHandler(DeviceFeature feature) {
1215 protected @Nullable State getState(byte cmd1, double value) {
1216 return OnOffType.OFF;
1221 * Motion sensor 2 battery powered reply message handler
1223 public static class MotionSensor2BatteryPoweredReplyHandler extends CustomMsgHandler {
1224 MotionSensor2BatteryPoweredReplyHandler(DeviceFeature feature) {
1229 protected @Nullable State getState(byte cmd1, double value) {
1230 // stage flag bit 1 = USB Powered
1231 boolean isBatteryPowered = !BinaryUtils.isBitSet((int) value, 1);
1232 // update device based on battery powered flag
1233 updateDeviceFlag(isBatteryPowered);
1234 // return battery powered state
1235 return OnOffType.from(isBatteryPowered);
1238 private void updateDeviceFlag(boolean isBatteryPowered) {
1239 // update device batteryPowered flag
1240 getInsteonDevice().setFlag("batteryPowered", isBatteryPowered);
1241 // stop device polling if battery powered, otherwise start it
1242 if (isBatteryPowered) {
1243 getInsteonDevice().stopPolling();
1245 getInsteonDevice().startPolling();
1251 * Motion sensor 2 temperature message handler
1253 public static class MotionSensor2TemperatureMsgHandler extends CustomMsgHandler {
1254 MotionSensor2TemperatureMsgHandler(DeviceFeature feature) {
1259 protected @Nullable State getState(byte cmd1, double value) {
1260 boolean isBatteryPowered = getInsteonDevice().isBatteryPowered();
1261 // temperature (°F) = 0.73 * value - 20.53 (battery powered); 0.72 * value - 24.61 (usb powered)
1262 double temperature = isBatteryPowered ? 0.73 * value - 20.53 : 0.72 * value - 24.61;
1263 return new QuantityType<Temperature>(temperature, ImperialUnits.FAHRENHEIT);
1268 * Heartbeat interval message handler
1270 public static class HeartbeatIntervalMsgHandler extends CustomMsgHandler {
1271 HeartbeatIntervalMsgHandler(DeviceFeature feature) {
1276 public void handleMessage(byte cmd1, Msg msg) {
1277 super.handleMessage(cmd1, msg);
1278 // reset device heartbeat monitor
1279 getInsteonDevice().resetHeartbeatMonitor();
1283 protected @Nullable State getState(byte cmd1, double value) {
1284 int interval = getInterval((int) value);
1285 return interval > 0 ? new QuantityType<Time>(interval, Units.MINUTE) : null;
1288 private int getInterval(int value) {
1289 int preset = getParameterAsInteger("preset", 0);
1290 int increment = getParameterAsInteger("increment", 0);
1291 return value == 0x00 ? preset : value * increment;
1296 * FanLinc fan mode reply message handler
1298 public static class FanLincFanReplyHandler extends CustomMsgHandler {
1299 FanLincFanReplyHandler(DeviceFeature feature) {
1304 protected @Nullable State getState(byte cmd1, double value) {
1306 FanLincFanSpeed speed = FanLincFanSpeed.valueOf((int) value);
1307 return new StringType(speed.toString());
1308 } catch (IllegalArgumentException e) {
1309 logger.warn("{}: got unexpected fan speed reply value: {}", nm(), HexUtils.getHexString((int) value));
1310 return UnDefType.UNDEF;
1316 * I/O linc momentary duration message handler
1318 public static class IOLincMomentaryDurationMsgHandler extends CustomMsgHandler {
1319 IOLincMomentaryDurationMsgHandler(DeviceFeature feature) {
1324 protected @Nullable State getState(byte cmd1, double value) {
1325 int duration = getDuration((int) value);
1326 return new QuantityType<Time>(duration, Units.SECOND);
1329 private int getDuration(int value) {
1330 int prescaler = value >> 8; // high byte
1331 int delay = value & 0xFF; // low byte
1335 return delay * prescaler / 10;
1340 * I/O linc relay mode reply message handler
1342 public static class IOLincRelayModeReplyHandler extends CustomMsgHandler {
1343 IOLincRelayModeReplyHandler(DeviceFeature feature) {
1348 public void handleMessage(byte cmd1, Msg msg) {
1349 // trigger poll if is my command reply message (0x20)
1350 if (feature.getQueryCommand() == 0x20) {
1351 feature.triggerPoll(5000L); // 5000ms delay to allow all op flag commands to be processed
1353 super.handleMessage(cmd1, msg);
1358 protected @Nullable State getState(byte cmd1, double value) {
1359 IOLincRelayMode mode = IOLincRelayMode.valueOf((int) value);
1360 return new StringType(mode.toString());
1365 * Micro module operation mode reply message handler
1367 public static class MicroModuleOpModeReplyHandler extends CustomMsgHandler {
1368 MicroModuleOpModeReplyHandler(DeviceFeature feature) {
1373 public void handleMessage(byte cmd1, Msg msg) {
1374 // trigger poll if is my command reply message (0x20)
1375 if (feature.getQueryCommand() == 0x20) {
1376 feature.triggerPoll(2000L); // 2000ms delay to allow all op flag commands to be processed
1378 super.handleMessage(cmd1, msg);
1383 protected @Nullable State getState(byte cmd1, double value) {
1384 MicroModuleOpMode mode = MicroModuleOpMode.valueOf((int) value);
1385 return new StringType(mode.toString());
1390 * Outlet switch reply message handler
1392 * 0x00 = Both Outlets Off
1393 * 0x01 = Only Top Outlet On
1394 * 0x02 = Only Bottom Outlet On
1395 * 0x03 = Both Outlets On
1397 public static class OutletSwitchReplyHandler extends CustomMsgHandler {
1398 OutletSwitchReplyHandler(DeviceFeature feature) {
1403 protected @Nullable State getState(byte cmd1, double value) {
1404 return OnOffType.from(value == feature.getGroup() || value == 0x03);
1409 * Power meter energy message handler
1411 public static class PowerMeterEnergyMsgHandler extends CustomMsgHandler {
1412 PowerMeterEnergyMsgHandler(DeviceFeature feature) {
1417 protected @Nullable State getState(byte cmd1, double value) {
1418 BigDecimal energy = getEnergy((int) value);
1419 return new QuantityType<Energy>(energy, Units.KILOWATT_HOUR);
1422 private BigDecimal getEnergy(int value) {
1423 return (value >> 24) < 254
1424 ? new BigDecimal(value * 65535.0 / (1000 * 60 * 60 * 60)).setScale(4, RoundingMode.HALF_UP)
1430 * Power meter power message handler
1432 public static class PowerMeterPowerMsgHandler extends CustomMsgHandler {
1433 PowerMeterPowerMsgHandler(DeviceFeature feature) {
1438 protected @Nullable State getState(byte cmd1, double value) {
1439 int power = getPower((int) value);
1440 return new QuantityType<Power>(power, Units.WATT);
1443 private int getPower(int power) {
1444 return power > 32767 ? power - 65535 : power;
1449 * Siren request reply message handler
1451 public static class SirenRequesteplyHandler extends StatusRequestReplyHandler {
1452 SirenRequesteplyHandler(DeviceFeature feature) {
1457 protected @Nullable State getState(byte cmd1, double value) {
1458 int level = (int) value;
1459 return OnOffType.from(level != 0x00);
1464 * Siren armed reply message handler
1466 public static class SirenArmedReplyHandler extends CustomMsgHandler {
1467 SirenArmedReplyHandler(DeviceFeature feature) {
1472 protected @Nullable State getState(byte cmd1, double value) {
1473 boolean isArmed = BinaryUtils.isBitSet((int) value, 6) || BinaryUtils.isBitSet((int) value, 7);
1474 return OnOffType.from(isArmed);
1479 * Siren alert type message handler
1481 public static class SirenAlertTypeMsgHandler extends CustomMsgHandler {
1482 SirenAlertTypeMsgHandler(DeviceFeature feature) {
1487 protected @Nullable State getState(byte cmd1, double value) {
1489 SirenAlertType type = SirenAlertType.valueOf((int) value);
1490 return new StringType(type.toString());
1491 } catch (IllegalArgumentException e) {
1492 logger.warn("{}: got unexpected alert type value: {}", nm(), (int) value);
1493 return UnDefType.UNDEF;
1499 * Sprinkler valve message handler
1501 public static class SprinklerValveMsgHandler extends CustomMsgHandler {
1502 SprinklerValveMsgHandler(DeviceFeature feature) {
1507 protected @Nullable State getState(byte cmd1, double value) {
1508 int valve = getParameterAsInteger("valve", -1);
1509 if (valve < 0 || valve > 8) {
1510 logger.debug("{}: invalid valve number defined for {}", nm(), feature.getName());
1511 return UnDefType.UNDEF;
1513 boolean isValveOn = BinaryUtils.isBitSet((int) value, 7) && (((int) value) & 0x07) == valve
1514 || BinaryUtils.isBitSet((int) value, 6) && valve == 7;
1515 return OnOffType.from(isValveOn);
1520 * Sprinkler program message handler
1522 public static class SprinklerProgramMsgHandler extends CustomMsgHandler {
1523 SprinklerProgramMsgHandler(DeviceFeature feature) {
1528 protected @Nullable State getState(byte cmd1, double value) {
1529 int program = getParameterAsInteger("program", -1);
1530 if (program < 0 || program > 4) {
1531 logger.debug("{}: invalid program number defined for {}", nm(), feature.getName());
1532 return UnDefType.UNDEF;
1534 boolean isProgramOn = BinaryUtils.isBitSet((int) value, 5) && (((int) value) & 0x18) >> 3 == program;
1535 return OnOffType.from(isProgramOn);
1540 * Thermostat fan mode message handler
1542 public static class ThermostatFanModeMsgHandler extends CustomMsgHandler {
1543 ThermostatFanModeMsgHandler(DeviceFeature feature) {
1548 protected @Nullable State getState(byte cmd1, double value) {
1550 ThermostatFanMode mode = ThermostatFanMode.fromStatus((int) value);
1551 return new StringType(mode.toString());
1552 } catch (IllegalArgumentException e) {
1553 logger.warn("{}: got unexpected fan mode status: {}", nm(), HexUtils.getHexString((int) value));
1554 return UnDefType.UNDEF;
1560 * Thermostat fan mode reply message handler
1562 public static class ThermostatFanModeReplyHandler extends CustomMsgHandler {
1563 ThermostatFanModeReplyHandler(DeviceFeature feature) {
1568 protected @Nullable State getState(byte cmd1, double value) {
1570 ThermostatFanMode mode = ThermostatFanMode.valueOf((int) value);
1571 return new StringType(mode.toString());
1572 } catch (IllegalArgumentException e) {
1573 logger.warn("{}: got unexpected fan mode reply: {}", nm(), HexUtils.getHexString((int) value));
1574 return UnDefType.UNDEF;
1580 * Thermostat humidifier dehumidifying message handler
1582 public static class ThermostatHumidifierDehumidifyingMsgHandler extends CustomMsgHandler {
1583 ThermostatHumidifierDehumidifyingMsgHandler(DeviceFeature feature) {
1588 protected @Nullable State getState(byte cmd1, double value) {
1589 return new StringType(ThermostatSystemState.DEHUMIDIFYING.toString());
1594 * Thermostat humidifier humidifying message handler
1596 public static class ThermostatHumidifierHumidifyingMsgHandler extends CustomMsgHandler {
1597 ThermostatHumidifierHumidifyingMsgHandler(DeviceFeature feature) {
1602 protected @Nullable State getState(byte cmd1, double value) {
1603 return new StringType(ThermostatSystemState.HUMIDIFYING.toString());
1608 * Thermostat humidifier off message handler
1610 public static class ThermostatHumidifierOffMsgHandler extends CustomMsgHandler {
1611 ThermostatHumidifierOffMsgHandler(DeviceFeature feature) {
1616 protected @Nullable State getState(byte cmd1, double value) {
1617 return new StringType(ThermostatSystemState.OFF.toString());
1622 * Termostat system mode message handler
1624 public static class ThermostatSystemModeMsgHandler extends CustomMsgHandler {
1625 ThermostatSystemModeMsgHandler(DeviceFeature feature) {
1630 protected @Nullable State getState(byte cmd1, double value) {
1632 ThermostatSystemMode mode = ThermostatSystemMode.fromStatus((int) value);
1633 return new StringType(mode.toString());
1634 } catch (IllegalArgumentException e) {
1635 logger.warn("{}: got unexpected system mode status: {}", nm(), HexUtils.getHexString((int) value));
1636 return UnDefType.UNDEF;
1642 * Thermostat system mode reply message handler
1644 public static class ThermostatSystemModeReplyHandler extends CustomMsgHandler {
1645 ThermostatSystemModeReplyHandler(DeviceFeature feature) {
1650 protected @Nullable State getState(byte cmd1, double value) {
1652 ThermostatSystemMode mode = ThermostatSystemMode.valueOf((int) value);
1653 return new StringType(mode.toString());
1654 } catch (IllegalArgumentException e) {
1655 logger.warn("{}: got unexpected system mode reply: {}", nm(), HexUtils.getHexString((int) value));
1656 return UnDefType.UNDEF;
1662 * Thermostat system cooling message handler
1664 public static class ThermostatSystemCoolingMsgHandler extends CustomMsgHandler {
1665 ThermostatSystemCoolingMsgHandler(DeviceFeature feature) {
1670 protected @Nullable State getState(byte cmd1, double value) {
1671 return new StringType(ThermostatSystemState.COOLING.toString());
1676 * Thermostat system heating message handler
1678 public static class ThermostatSystemHeatingMsgHandler extends CustomMsgHandler {
1679 ThermostatSystemHeatingMsgHandler(DeviceFeature feature) {
1684 protected @Nullable State getState(byte cmd1, double value) {
1685 return new StringType(ThermostatSystemState.HEATING.toString());
1690 * Thermostat system off message handler
1692 public static class ThermostatSystemOffMsgHandler extends CustomMsgHandler {
1693 ThermostatSystemOffMsgHandler(DeviceFeature feature) {
1698 protected @Nullable State getState(byte cmd1, double value) {
1699 return new StringType(ThermostatSystemState.OFF.toString());
1704 * Thermostat temperature scale message handler
1706 public static class ThermostatTemperatureScaleMsgHandler extends CustomBitmaskMsgHandler {
1707 ThermostatTemperatureScaleMsgHandler(DeviceFeature feature) {
1712 protected State getBitState(boolean isCelsius) {
1713 ThermostatTemperatureScale format = ThermostatTemperatureScale.from(isCelsius);
1714 return new StringType(format.toString());
1719 * Thermostat time format message handler
1721 public static class ThermostatTimeFormatMsgHandler extends CustomBitmaskMsgHandler {
1722 ThermostatTimeFormatMsgHandler(DeviceFeature feature) {
1727 protected State getBitState(boolean is24Hr) {
1728 ThermostatTimeFormat format = ThermostatTimeFormat.from(is24Hr);
1729 return new StringType(format.toString());
1734 * Venstar thermostat system mode message handler
1736 public static class VenstarSystemModeMsgHandler extends CustomMsgHandler {
1737 VenstarSystemModeMsgHandler(DeviceFeature feature) {
1742 protected @Nullable State getState(byte cmd1, double value) {
1744 VenstarSystemMode mode = VenstarSystemMode.fromStatus((int) value);
1745 return new StringType(mode.toString());
1746 } catch (IllegalArgumentException e) {
1747 logger.warn("{}: got unexpected system mode status: {}", nm(), HexUtils.getHexString((int) value));
1748 return UnDefType.UNDEF;
1754 * Venstar thermostat system mode message handler
1756 public static class VenstarSystemModeReplyHandler extends CustomMsgHandler {
1757 VenstarSystemModeReplyHandler(DeviceFeature feature) {
1762 protected @Nullable State getState(byte cmd1, double value) {
1764 VenstarSystemMode mode = VenstarSystemMode.valueOf((int) value);
1765 return new StringType(mode.toString());
1766 } catch (IllegalArgumentException e) {
1767 logger.warn("{}: got unexpected system mode reply: {}", nm(), HexUtils.getHexString((int) value));
1768 return UnDefType.UNDEF;
1774 * Venstar thermostat temperature message handler
1776 public static class VenstarTemperatureMsgHandler extends CustomTemperatureMsgHandler {
1777 VenstarTemperatureMsgHandler(DeviceFeature feature) {
1782 protected Unit<Temperature> getTemperatureUnit() {
1784 // use temperature scale current state to determine temperature unit, defaulting to fahrenheit
1785 State state = getInsteonDevice().getFeatureState(FEATURE_TEMPERATURE_SCALE);
1787 && ThermostatTemperatureScale.valueOf(state.toString()) == ThermostatTemperatureScale.CELSIUS) {
1788 return SIUnits.CELSIUS;
1790 } catch (IllegalArgumentException e) {
1791 logger.debug("{}: unable to determine temperature unit, defaulting to: FAHRENHEIT", nm());
1793 return ImperialUnits.FAHRENHEIT;
1798 * IM button event message handler
1800 public static class IMButtonEventMsgHandler extends MessageHandler {
1801 IMButtonEventMsgHandler(DeviceFeature feature) {
1806 public void handleMessage(byte cmd1, Msg msg) {
1808 int cmd = msg.getInt("buttonEvent");
1809 int button = getParameterAsInteger("button", 1);
1810 int mask = (button - 1) << 4;
1811 IMButtonEvent event = IMButtonEvent.valueOf(cmd ^ mask);
1812 logger.debug("{}: IM {} received event {}", nm(), feature.getName(), event);
1813 feature.triggerEvent(event.toString());
1814 } catch (FieldException e) {
1815 logger.warn("{}: error parsing msg {}", nm(), msg, e);
1816 } catch (IllegalArgumentException e) {
1817 logger.warn("{}: got unexpected button event", nm(), e);
1823 * IM config message handler
1825 public static class IMConfigMsgHandler extends MessageHandler {
1826 IMConfigMsgHandler(DeviceFeature feature) {
1831 public void handleMessage(byte cmd1, Msg msg) {
1833 int flags = msg.getInt("IMConfigurationFlags");
1834 int bit = getParameterAsInteger("bit", -1);
1835 if (bit < 3 || bit > 7) {
1836 logger.debug("{}: invalid bit number defined for {}", nm(), feature.getName());
1839 boolean isSet = BinaryUtils.isBitSet(flags, bit);
1840 State state = OnOffType.from(isSet ^ getParameterAsBoolean("inverted", false));
1841 logger.debug("{}: IM {} is {}", nm(), feature.getName(), state);
1842 feature.setLastMsgValue(flags);
1843 feature.updateState(state);
1844 } catch (FieldException e) {
1845 logger.warn("{}: error parsing msg {}", nm(), msg, e);
1851 * Process X10 messages that are generated when another controller
1852 * changes the state of an X10 device.
1854 public static class X10OnHandler extends MessageHandler {
1855 X10OnHandler(DeviceFeature feature) {
1860 public void handleMessage(byte cmd1, Msg msg) {
1861 logger.debug("{}: device {} is ON", nm(), getX10Device().getAddress());
1862 feature.updateState(OnOffType.ON);
1866 public static class X10OffHandler extends MessageHandler {
1867 X10OffHandler(DeviceFeature feature) {
1872 public void handleMessage(byte cmd1, Msg msg) {
1873 logger.debug("{}: device {} is OFF", nm(), getX10Device().getAddress());
1874 feature.updateState(OnOffType.OFF);
1878 public static class X10BrightHandler extends MessageHandler {
1879 X10BrightHandler(DeviceFeature feature) {
1884 public void handleMessage(byte cmd1, Msg msg) {
1885 logger.debug("{}: ignoring brighten message for device {}", nm(), getX10Device().getAddress());
1889 public static class X10DimHandler extends MessageHandler {
1890 X10DimHandler(DeviceFeature feature) {
1895 public void handleMessage(byte cmd1, Msg msg) {
1896 logger.debug("{}: ignoring dim message for device {}", nm(), getX10Device().getAddress());
1900 public static class X10OpenHandler extends MessageHandler {
1901 X10OpenHandler(DeviceFeature feature) {
1906 public void handleMessage(byte cmd1, Msg msg) {
1907 logger.debug("{}: device {} is OPEN", nm(), getX10Device().getAddress());
1908 feature.updateState(OpenClosedType.OPEN);
1912 public static class X10ClosedHandler extends MessageHandler {
1913 X10ClosedHandler(DeviceFeature feature) {
1918 public void handleMessage(byte cmd1, Msg msg) {
1919 logger.debug("{}: device {} is CLOSED", nm(), getX10Device().getAddress());
1920 feature.updateState(OpenClosedType.CLOSED);
1925 * Factory method for dermining if a message handler command supports group
1927 * @param command the handler command
1928 * @return true if handler supports group, otherwise false
1930 public static boolean supportsGroup(int command) {
1931 return SUPPORTED_GROUP_COMMANDS.contains(command);
1935 * Factory method for generating a message handler id
1937 * @param command the handler command
1938 * @param group the handler group
1939 * @return the generated handler id
1941 public static String generateId(int command, int group) {
1942 if (command == -1) {
1945 String id = HexUtils.getHexString(command);
1953 * Factory method for creating a default message handler
1955 * @param feature the feature for which to create the handler
1956 * @return the default message handler which was created
1958 public static DefaultMsgHandler makeDefaultHandler(DeviceFeature feature) {
1959 return new DefaultMsgHandler(feature);
1963 * Factory method for creating a message handler for a given name using java reflection
1965 * @param name the name of the handler to create
1966 * @param parameters the parameters of the handler to create
1967 * @param feature the feature for which to create the handler
1968 * @return the handler which was created
1970 public static @Nullable <T extends MessageHandler> T makeHandler(String name, Map<String, String> parameters,
1971 DeviceFeature feature) {
1973 String className = MessageHandler.class.getName() + "$" + name;
1974 @SuppressWarnings("unchecked")
1975 Class<? extends T> classRef = (Class<? extends T>) Class.forName(className);
1977 T handler = classRef.getDeclaredConstructor(DeviceFeature.class).newInstance(feature);
1978 handler.setParameters(parameters);
1980 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
1981 | InvocationTargetException | NoSuchMethodException | SecurityException e) {