2 * Copyright (c) 2010-2020 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;
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.Arrays;
17 import java.util.HashMap;
19 import java.util.Timer;
20 import java.util.TimerTask;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.insteon.internal.config.InsteonChannelConfiguration;
25 import org.openhab.binding.insteon.internal.device.DeviceFeatureListener.StateChangeType;
26 import org.openhab.binding.insteon.internal.handler.InsteonDeviceHandler;
27 import org.openhab.binding.insteon.internal.message.FieldException;
28 import org.openhab.binding.insteon.internal.message.InvalidMessageTypeException;
29 import org.openhab.binding.insteon.internal.message.Msg;
30 import org.openhab.binding.insteon.internal.utils.Utils;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.IncreaseDecreaseType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.PercentType;
35 import org.openhab.core.types.Command;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * A command handler translates an openHAB command into a insteon message
42 * @author Daniel Pfrommer - Initial contribution
43 * @author Bernd Pfrommer - openHAB 1 insteonplm binding
44 * @author Rob Nielsen - Port to openHAB 2 insteon binding
47 @SuppressWarnings("null")
48 public abstract class CommandHandler {
49 private static final Logger logger = LoggerFactory.getLogger(CommandHandler.class);
50 DeviceFeature feature; // related DeviceFeature
52 Map<String, String> parameters = new HashMap<>();
57 * @param feature The DeviceFeature for which this command was intended.
58 * The openHAB commands are issued on an openhab item. The .items files bind
59 * an openHAB item to a DeviceFeature.
61 CommandHandler(DeviceFeature feature) {
62 this.feature = feature;
66 * Implements what to do when an openHAB command is received
68 * @param config the configuration for the item that generated the command
69 * @param cmd the openhab command issued
70 * @param device the Insteon device to which this command applies
72 public abstract void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice device);
75 * Returns parameter as integer
77 * @param key key of parameter
79 * @return value of parameter
81 protected int getIntParameter(String key, int def) {
82 String val = parameters.get(key);
84 return (def); // param not found
88 ret = Utils.strToInt(val);
89 } catch (NumberFormatException e) {
90 logger.warn("malformed int parameter in command handler: {}", key);
96 * Returns parameter as String
98 * @param key key of parameter
100 * @return value of parameter
102 protected @Nullable String getStringParameter(String key, String def) {
103 return (parameters.get(key) == null ? def : parameters.get(key));
107 * Shorthand to return class name for logging purposes
109 * @return name of the class
111 protected String nm() {
112 return (this.getClass().getSimpleName());
115 protected int getMaxLightLevel(InsteonChannelConfiguration conf, int defaultLevel) {
116 Map<String, String> params = conf.getParameters();
117 if (conf.getFeature().contains("dimmer") && params.containsKey("dimmermax")) {
118 String item = conf.getChannelName();
119 String dimmerMax = params.get("dimmermax");
121 int i = Integer.parseInt(dimmerMax);
122 if (i > 1 && i <= 99) {
123 int level = (int) Math.ceil((i * 255.0) / 100); // round up
124 if (level < defaultLevel) {
125 logger.debug("item {}: using dimmermax value of {}", item, dimmerMax);
129 logger.warn("item {}: dimmermax must be between 1-99 inclusive: {}", item, dimmerMax);
131 } catch (NumberFormatException e) {
132 logger.warn("item {}: invalid int value for dimmermax: {}", item, dimmerMax);
139 void setParameters(Map<String, String> map) {
144 * Helper function to extract the group parameter from the binding config,
146 * @param c the binding configuration to test
147 * @return the value of the "group" parameter, or -1 if none
149 protected static int getGroup(InsteonChannelConfiguration c) {
150 String v = c.getParameters().get("group");
153 iv = (v == null) ? -1 : Utils.strToInt(v);
154 } catch (NumberFormatException e) {
155 logger.warn("malformed int parameter in for item {}", c.getChannelName());
161 public static class WarnCommandHandler extends CommandHandler {
162 WarnCommandHandler(DeviceFeature f) {
167 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
168 logger.warn("{}: command {} is not implemented yet!", nm(), cmd);
173 public static class NoOpCommandHandler extends CommandHandler {
174 NoOpCommandHandler(DeviceFeature f) {
179 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
180 // do nothing, not even log
185 public static class LightOnOffCommandHandler extends CommandHandler {
186 LightOnOffCommandHandler(DeviceFeature f) {
191 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
193 int ext = getIntParameter("ext", 0);
197 if (cmd == OnOffType.ON) {
198 level = getMaxLightLevel(conf, 0xff);
200 logger.debug("{}: sent msg to switch {} to {}", nm(), dev.getAddress(),
201 level == 0xff ? "on" : level);
202 } else if (cmd == OnOffType.OFF) {
204 logger.debug("{}: sent msg to switch {} off", nm(), dev.getAddress());
206 if (ext == 1 || ext == 2) {
207 byte[] data = new byte[] { (byte) getIntParameter("d1", 0), (byte) getIntParameter("d2", 0),
208 (byte) getIntParameter("d3", 0) };
209 m = dev.makeExtendedMessage((byte) 0x0f, (byte) direc, (byte) level, data);
210 logger.debug("{}: was an extended message for device {}", nm(), dev.getAddress());
213 } else if (ext == 2) {
217 m = dev.makeStandardMessage((byte) 0x0f, (byte) direc, (byte) level, getGroup(conf));
219 logger.debug("Sending message to {}", dev.getAddress());
220 dev.enqueueMessage(m, feature);
221 // expect to get a direct ack after this!
222 } catch (InvalidMessageTypeException e) {
223 logger.warn("{}: invalid message: ", nm(), e);
224 } catch (FieldException e) {
225 logger.warn("{}: command send message creation error ", nm(), e);
231 public static class FastOnOffCommandHandler extends CommandHandler {
232 FastOnOffCommandHandler(DeviceFeature f) {
237 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
239 if (cmd == OnOffType.ON) {
240 int level = getMaxLightLevel(conf, 0xff);
241 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x12, (byte) level, getGroup(conf));
242 dev.enqueueMessage(m, feature);
243 logger.debug("{}: sent fast on to switch {} level {}", nm(), dev.getAddress(),
244 level == 0xff ? "on" : level);
245 } else if (cmd == OnOffType.OFF) {
246 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x14, (byte) 0x00, getGroup(conf));
247 dev.enqueueMessage(m, feature);
248 logger.debug("{}: sent fast off to switch {}", nm(), dev.getAddress());
250 // expect to get a direct ack after this!
251 } catch (InvalidMessageTypeException e) {
252 logger.warn("{}: invalid message: ", nm(), e);
253 } catch (FieldException e) {
254 logger.warn("{}: command send message creation error ", nm(), e);
260 public static class RampOnOffCommandHandler extends RampCommandHandler {
261 RampOnOffCommandHandler(DeviceFeature f) {
266 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
268 if (cmd == OnOffType.ON) {
269 double ramptime = getRampTime(conf, 0);
270 int ramplevel = getRampLevel(conf, 100);
271 byte cmd2 = encode(ramptime, ramplevel);
272 Msg m = dev.makeStandardMessage((byte) 0x0f, getOnCmd(), cmd2, getGroup(conf));
273 dev.enqueueMessage(m, feature);
274 logger.debug("{}: sent ramp on to switch {} time {} level {} cmd1 {}", nm(), dev.getAddress(),
275 ramptime, ramplevel, getOnCmd());
276 } else if (cmd == OnOffType.OFF) {
277 double ramptime = getRampTime(conf, 0);
278 int ramplevel = getRampLevel(conf, 0 /* ignored */);
279 byte cmd2 = encode(ramptime, ramplevel);
280 Msg m = dev.makeStandardMessage((byte) 0x0f, getOffCmd(), cmd2, getGroup(conf));
281 dev.enqueueMessage(m, feature);
282 logger.debug("{}: sent ramp off to switch {} time {} cmd1 {}", nm(), dev.getAddress(), ramptime,
285 // expect to get a direct ack after this!
286 } catch (InvalidMessageTypeException e) {
287 logger.warn("{}: invalid message: ", nm(), e);
288 } catch (FieldException e) {
289 logger.warn("{}: command send message creation error ", nm(), e);
293 private int getRampLevel(InsteonChannelConfiguration conf, int defaultValue) {
294 String str = conf.getParameters().get("ramplevel");
295 return str != null ? Integer.parseInt(str) : defaultValue;
300 public static class ManualChangeCommandHandler extends CommandHandler {
301 ManualChangeCommandHandler(DeviceFeature f) {
306 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
308 if (cmd instanceof DecimalType) {
309 int v = ((DecimalType) cmd).intValue();
310 int cmd1 = (v != 1) ? 0x17 : 0x18; // start or stop
311 int cmd2 = (v == 2) ? 0x01 : 0; // up or down
312 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2, getGroup(conf));
313 dev.enqueueMessage(m, feature);
314 logger.debug("{}: cmd {} sent manual change {} {} to {}", nm(), v,
315 (cmd1 == 0x17) ? "START" : "STOP", (cmd2 == 0x01) ? "UP" : "DOWN", dev.getAddress());
317 logger.warn("{}: invalid command type: {}", nm(), cmd);
319 } catch (InvalidMessageTypeException e) {
320 logger.warn("{}: invalid message: ", nm(), e);
321 } catch (FieldException e) {
322 logger.warn("{}: command send message creation error ", nm(), e);
328 * Sends ALLLink broadcast commands to group
331 public static class GroupBroadcastCommandHandler extends CommandHandler {
332 GroupBroadcastCommandHandler(DeviceFeature f) {
337 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
339 if (cmd == OnOffType.ON || cmd == OnOffType.OFF) {
340 byte cmd1 = (byte) ((cmd == OnOffType.ON) ? 0x11 : 0x13);
341 byte value = (byte) ((cmd == OnOffType.ON) ? 0xFF : 0x00);
342 int group = getGroup(conf);
344 logger.warn("no group=xx specified in item {}", conf.getChannelName());
347 logger.debug("{}: sending {} broadcast to group {}", nm(), (cmd1 == 0x11) ? "ON" : "OFF",
349 Msg m = dev.makeStandardMessage((byte) 0x0f, cmd1, value, group);
350 dev.enqueueMessage(m, feature);
351 feature.pollRelatedDevices();
353 } catch (InvalidMessageTypeException e) {
354 logger.warn("{}: invalid message: ", nm(), e);
355 } catch (FieldException e) {
356 logger.warn("{}: command send message creation error ", nm(), e);
362 public static class LEDOnOffCommandHandler extends CommandHandler {
363 LEDOnOffCommandHandler(DeviceFeature f) {
368 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
370 if (cmd == OnOffType.ON) {
371 Msg m = dev.makeExtendedMessage((byte) 0x1f, (byte) 0x20, (byte) 0x09,
372 new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00 });
373 dev.enqueueMessage(m, feature);
374 logger.debug("{}: sent msg to switch {} on", nm(), dev.getAddress());
375 } else if (cmd == OnOffType.OFF) {
376 Msg m = dev.makeExtendedMessage((byte) 0x1f, (byte) 0x20, (byte) 0x08,
377 new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00 });
378 dev.enqueueMessage(m, feature);
379 logger.debug("{}: sent msg to switch {} off", nm(), dev.getAddress());
381 } catch (InvalidMessageTypeException e) {
382 logger.warn("{}: invalid message: ", nm(), e);
383 } catch (FieldException e) {
384 logger.warn("{}: command send message creation error ", nm(), e);
390 public static class X10OnOffCommandHandler extends CommandHandler {
391 X10OnOffCommandHandler(DeviceFeature f) {
396 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
398 byte houseCode = dev.getX10HouseCode();
399 byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode());
400 if (cmd == OnOffType.ON || cmd == OnOffType.OFF) {
401 byte houseCommandCode = (byte) (houseCode << 4
402 | (cmd == OnOffType.ON ? X10.Command.ON.code() : X10.Command.OFF.code()));
403 Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code
404 dev.enqueueMessage(munit, feature);
405 Msg mcmd = dev.makeX10Message(houseCommandCode, (byte) 0x80); // send command code
406 dev.enqueueMessage(mcmd, feature);
407 String onOff = cmd == OnOffType.ON ? "ON" : "OFF";
408 logger.debug("{}: sent msg to switch {} {}", nm(), dev.getAddress(), onOff);
410 } catch (InvalidMessageTypeException e) {
411 logger.warn("{}: invalid message: ", nm(), e);
412 } catch (FieldException e) {
413 logger.warn("{}: command send message creation error ", nm(), e);
419 public static class X10PercentCommandHandler extends CommandHandler {
420 X10PercentCommandHandler(DeviceFeature f) {
425 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
428 // I did not have hardware that would respond to the PRESET_DIM codes.
429 // This code path needs testing.
431 byte houseCode = dev.getX10HouseCode();
432 byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode());
433 Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code
434 dev.enqueueMessage(munit, feature);
435 PercentType pc = (PercentType) cmd;
436 logger.debug("{}: changing level of {} to {}", nm(), dev.getAddress(), pc.intValue());
437 int level = (pc.intValue() * 32) / 100;
438 byte cmdCode = (level >= 16) ? X10.Command.PRESET_DIM_2.code() : X10.Command.PRESET_DIM_1.code();
443 houseCode = (byte) x10CodeForLevel[level];
444 cmdCode |= (houseCode << 4);
445 Msg mcmd = dev.makeX10Message(cmdCode, (byte) 0x80); // send command code
446 dev.enqueueMessage(mcmd, feature);
447 } catch (InvalidMessageTypeException e) {
448 logger.warn("{}: invalid message: ", nm(), e);
449 } catch (FieldException e) {
450 logger.warn("{}: command send message creation error ", nm(), e);
454 private final int[] x10CodeForLevel = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
458 public static class X10IncreaseDecreaseCommandHandler extends CommandHandler {
459 X10IncreaseDecreaseCommandHandler(DeviceFeature f) {
464 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
466 byte houseCode = dev.getX10HouseCode();
467 byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode());
468 if (cmd == IncreaseDecreaseType.INCREASE || cmd == IncreaseDecreaseType.DECREASE) {
469 byte houseCommandCode = (byte) (houseCode << 4
470 | (cmd == IncreaseDecreaseType.INCREASE ? X10.Command.BRIGHT.code()
471 : X10.Command.DIM.code()));
472 Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code
473 dev.enqueueMessage(munit, feature);
474 Msg mcmd = dev.makeX10Message(houseCommandCode, (byte) 0x80); // send command code
475 dev.enqueueMessage(mcmd, feature);
476 String bd = cmd == IncreaseDecreaseType.INCREASE ? "BRIGHTEN" : "DIM";
477 logger.debug("{}: sent msg to switch {} {}", nm(), dev.getAddress(), bd);
479 } catch (InvalidMessageTypeException e) {
480 logger.warn("{}: invalid message: ", nm(), e);
481 } catch (FieldException e) {
482 logger.warn("{}: command send message creation error ", nm(), e);
488 public static class IOLincOnOffCommandHandler extends CommandHandler {
489 IOLincOnOffCommandHandler(DeviceFeature f) {
494 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
496 if (cmd == OnOffType.ON) {
497 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x11, (byte) 0xff);
498 dev.enqueueMessage(m, feature);
499 logger.debug("{}: sent msg to switch {} on", nm(), dev.getAddress());
500 } else if (cmd == OnOffType.OFF) {
501 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x13, (byte) 0x00);
502 dev.enqueueMessage(m, feature);
503 logger.debug("{}: sent msg to switch {} off", nm(), dev.getAddress());
505 // This used to be configurable, but was made static to make
506 // the architecture of the binding cleaner.
508 delay = Math.max(1000, delay);
509 delay = Math.min(10000, delay);
510 Timer timer = new Timer();
511 timer.schedule(new TimerTask() {
514 Msg m = feature.makePollMsg();
515 InsteonDevice dev = feature.getDevice();
517 dev.enqueueMessage(m, feature);
521 } catch (InvalidMessageTypeException e) {
522 logger.warn("{}: invalid message: ", nm(), e);
523 } catch (FieldException e) {
524 logger.warn("{}: command send message creation error: ", nm(), e);
530 public static class IncreaseDecreaseCommandHandler extends CommandHandler {
531 IncreaseDecreaseCommandHandler(DeviceFeature f) {
536 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
538 if (cmd == IncreaseDecreaseType.INCREASE) {
539 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x15, (byte) 0x00);
540 dev.enqueueMessage(m, feature);
541 logger.debug("{}: sent msg to brighten {}", nm(), dev.getAddress());
542 } else if (cmd == IncreaseDecreaseType.DECREASE) {
543 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x16, (byte) 0x00);
544 dev.enqueueMessage(m, feature);
545 logger.debug("{}: sent msg to dimm {}", nm(), dev.getAddress());
547 } catch (InvalidMessageTypeException e) {
548 logger.warn("{}: invalid message: ", nm(), e);
549 } catch (FieldException e) {
550 logger.warn("{}: command send message creation error ", nm(), e);
556 public static class PercentHandler extends CommandHandler {
557 PercentHandler(DeviceFeature f) {
562 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
564 PercentType pc = (PercentType) cmd;
565 logger.debug("changing level of {} to {}", dev.getAddress(), pc.intValue());
566 int level = (int) Math.ceil((pc.intValue() * 255.0) / 100); // round up
567 if (level > 0) { // make light on message with given level
568 level = getMaxLightLevel(conf, level);
569 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x11, (byte) level);
570 dev.enqueueMessage(m, feature);
571 logger.debug("{}: sent msg to set {} to {}", nm(), dev.getAddress(), level);
572 } else { // switch off
573 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x13, (byte) 0x00);
574 dev.enqueueMessage(m, feature);
575 logger.debug("{}: sent msg to set {} to zero by switching off", nm(), dev.getAddress());
577 } catch (InvalidMessageTypeException e) {
578 logger.warn("{}: invalid message: ", nm(), e);
579 } catch (FieldException e) {
580 logger.warn("{}: command send message creation error ", nm(), e);
586 private abstract static class RampCommandHandler extends CommandHandler {
587 private static double[] halfRateRampTimes = new double[] { 0.1, 0.3, 2, 6.5, 19, 23.5, 28, 32, 38.5, 47, 90,
588 150, 210, 270, 360, 480 };
593 RampCommandHandler(DeviceFeature f) {
595 // Can't process parameters here because they are set after constructor is invoked.
596 // Unfortunately, this means we can't declare the onCmd, offCmd to be final.
600 void setParameters(Map<String, String> params) {
601 super.setParameters(params);
602 onCmd = (byte) getIntParameter("on", 0x2E);
603 offCmd = (byte) getIntParameter("off", 0x2F);
606 protected final byte getOnCmd() {
610 protected final byte getOffCmd() {
614 protected byte encode(double ramptimeSeconds, int ramplevel) throws FieldException {
615 if (ramplevel < 0 || ramplevel > 100) {
616 throw new FieldException("ramplevel must be in the range 0-100 (inclusive)");
619 if (ramptimeSeconds < 0) {
620 throw new FieldException("ramptime must be greater than 0");
624 int insertionPoint = Arrays.binarySearch(halfRateRampTimes, ramptimeSeconds);
625 if (insertionPoint > 0) {
626 ramptime = 15 - insertionPoint;
628 insertionPoint = -insertionPoint - 1;
629 if (insertionPoint == 0) {
632 double d1 = Math.abs(halfRateRampTimes[insertionPoint - 1] - ramptimeSeconds);
633 double d2 = Math.abs(halfRateRampTimes[insertionPoint] - ramptimeSeconds);
634 ramptime = 15 - (d1 > d2 ? insertionPoint : insertionPoint - 1);
635 logger.debug("ramp encoding: time {} insert {} d1 {} d2 {} ramp {}", ramptimeSeconds,
636 insertionPoint, d1, d2, ramptime);
640 int r = (int) Math.round(ramplevel / (100.0 / 15.0));
641 return (byte) (((r & 0x0f) << 4) | (ramptime & 0xf));
644 protected double getRampTime(InsteonChannelConfiguration conf, double defaultValue) {
645 String str = conf.getParameters().get("ramptime");
646 return str != null ? Double.parseDouble(str) : defaultValue;
651 public static class RampPercentHandler extends RampCommandHandler {
653 RampPercentHandler(DeviceFeature f) {
658 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
660 PercentType pc = (PercentType) cmd;
661 double ramptime = getRampTime(conf, 0);
662 int level = pc.intValue();
663 if (level > 0) { // make light on message with given level
664 level = getMaxLightLevel(conf, level);
665 byte cmd2 = encode(ramptime, level);
666 Msg m = dev.makeStandardMessage((byte) 0x0f, getOnCmd(), cmd2);
667 dev.enqueueMessage(m, feature);
668 logger.debug("{}: sent msg to set {} to {} with {} second ramp time.", nm(), dev.getAddress(),
670 } else { // switch off
671 Msg m = dev.makeStandardMessage((byte) 0x0f, getOffCmd(), (byte) 0x00);
672 dev.enqueueMessage(m, feature);
673 logger.debug("{}: sent msg to set {} to zero by switching off with {} ramp time.", nm(),
674 dev.getAddress(), ramptime);
676 } catch (InvalidMessageTypeException e) {
677 logger.warn("{}: invalid message: ", nm(), e);
678 } catch (FieldException e) {
679 logger.warn("{}: command send message creation error ", nm(), e);
685 public static class PowerMeterCommandHandler extends CommandHandler {
686 PowerMeterCommandHandler(DeviceFeature f) {
691 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
692 String cmdParam = conf.getParameters().get(InsteonDeviceHandler.CMD);
693 if (cmdParam == null) {
694 logger.warn("{} ignoring cmd {} because no cmd= is configured!", nm(), cmd);
698 if (cmd == OnOffType.ON) {
699 if (cmdParam.equals(InsteonDeviceHandler.CMD_RESET)) {
700 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x80, (byte) 0x00);
701 dev.enqueueMessage(m, feature);
702 logger.debug("{}: sent reset msg to power meter {}", nm(), dev.getAddress());
703 feature.publish(OnOffType.OFF, StateChangeType.ALWAYS, InsteonDeviceHandler.CMD,
704 InsteonDeviceHandler.CMD_RESET);
705 } else if (cmdParam.equals(InsteonDeviceHandler.CMD_UPDATE)) {
706 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x82, (byte) 0x00);
707 dev.enqueueMessage(m, feature);
708 logger.debug("{}: sent update msg to power meter {}", nm(), dev.getAddress());
709 feature.publish(OnOffType.OFF, StateChangeType.ALWAYS, InsteonDeviceHandler.CMD,
710 InsteonDeviceHandler.CMD_UPDATE);
712 logger.warn("{}: ignoring unknown cmd {} for power meter {}", nm(), cmdParam, dev.getAddress());
714 } else if (cmd == OnOffType.OFF) {
715 logger.debug("{}: ignoring off request for power meter {}", nm(), dev.getAddress());
717 } catch (InvalidMessageTypeException e) {
718 logger.warn("{}: invalid message: ", nm(), e);
719 } catch (FieldException e) {
720 logger.warn("{}: command send message creation error ", nm(), e);
726 * Command handler that sends a command with a numerical value to a device.
727 * The handler is very parameterizable so it can be reused for different devices.
728 * First used for setting thermostat parameters.
732 public static class NumberCommandHandler extends CommandHandler {
733 NumberCommandHandler(DeviceFeature f) {
737 public int transform(int cmd) {
742 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
744 int dc = transform(((DecimalType) cmd).intValue());
745 int intFactor = getIntParameter("factor", 1);
747 // determine what level should be, and what field it should be in
749 int ilevel = dc * intFactor;
750 byte level = (byte) (ilevel > 255 ? 0xFF : ((ilevel < 0) ? 0 : ilevel));
751 String vfield = getStringParameter("value", "");
753 logger.warn("{} has no value field specified", nm());
756 // figure out what cmd1, cmd2, d1, d2, d3 are supposed to be
757 // to form a proper message
759 int cmd1 = getIntParameter("cmd1", -1);
761 logger.warn("{} has no cmd1 specified!", nm());
764 int cmd2 = getIntParameter("cmd2", 0);
765 int ext = getIntParameter("ext", 0);
767 if (ext == 1 || ext == 2) {
768 byte[] data = new byte[] { (byte) getIntParameter("d1", 0), (byte) getIntParameter("d2", 0),
769 (byte) getIntParameter("d3", 0) };
770 m = dev.makeExtendedMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2, data);
771 m.setByte(vfield, level);
774 } else if (ext == 2) {
778 m = dev.makeStandardMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2);
779 m.setByte(vfield, level);
781 dev.enqueueMessage(m, feature);
782 logger.debug("{}: sent msg to change level to {}", nm(), ((DecimalType) cmd).intValue());
784 } catch (InvalidMessageTypeException e) {
785 logger.warn("{}: invalid message: ", nm(), e);
786 } catch (FieldException e) {
787 logger.warn("{}: command send message creation error ", nm(), e);
793 * Handler to set the thermostat system mode
796 public static class ThermostatSystemModeCommandHandler extends NumberCommandHandler {
797 ThermostatSystemModeCommandHandler(DeviceFeature f) {
802 public int transform(int cmd) {
805 return (0x09); // off
807 return (0x04); // heat
809 return (0x05); // cool
811 return (0x06); // auto (aka manual auto)
813 return (0x0A); // program (aka auto)
817 return (0x0A); // when in doubt go to program
822 * Handler to set the thermostat fan mode
825 public static class ThermostatFanModeCommandHandler extends NumberCommandHandler {
826 ThermostatFanModeCommandHandler(DeviceFeature f) {
831 public int transform(int cmd) {
834 return (0x08); // fan mode auto
836 return (0x07); // fan always on
840 return (0x08); // when in doubt go auto mode
845 * Handler to set the fanlinc fan mode
848 public static class FanLincFanCommandHandler extends NumberCommandHandler {
849 FanLincFanCommandHandler(DeviceFeature f) {
854 public int transform(int cmd) {
857 return (0x00); // fan off
859 return (0x55); // fan low
861 return (0xAA); // fan medium
863 return (0xFF); // fan high
867 return (0x00); // all other modes are "off"
872 * Factory method for creating handlers of a given name using java reflection
874 * @param name the name of the handler to create
876 * @param f the feature for which to create the handler
877 * @return the handler which was created
880 public static <T extends CommandHandler> T makeHandler(String name, Map<String, String> params, DeviceFeature f) {
881 String cname = CommandHandler.class.getName() + "$" + name;
883 Class<?> c = Class.forName(cname);
884 @SuppressWarnings("unchecked")
885 Class<? extends T> dc = (Class<? extends T>) c;
886 T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f);
887 ch.setParameters(params);
889 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
890 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
891 logger.warn("error trying to create message handler: {}", name, e);