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;
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 an 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 public abstract class CommandHandler {
48 private static final Logger logger = LoggerFactory.getLogger(CommandHandler.class);
49 DeviceFeature feature; // related DeviceFeature
50 Map<String, String> parameters = new HashMap<>();
55 * @param feature The DeviceFeature for which this command was intended.
56 * The openHAB commands are issued on an openhab item. The .items files bind
57 * an openHAB item to a DeviceFeature.
59 CommandHandler(DeviceFeature feature) {
60 this.feature = feature;
64 * Implements what to do when an openHAB command is received
66 * @param conf the configuration for the item that generated the command
67 * @param cmd the openhab command issued
68 * @param device the Insteon device to which this command applies
70 public abstract void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice device);
73 * Returns parameter as integer
75 * @param key key of parameter
77 * @return value of parameter
79 protected int getIntParameter(String key, int def) {
80 String val = parameters.get(key);
82 return (def); // param not found
86 ret = Utils.strToInt(val);
87 } catch (NumberFormatException e) {
88 logger.warn("malformed int parameter in command handler: {}", key);
94 * Returns parameter as String
96 * @param key key of parameter
98 * @return value of parameter
100 protected @Nullable String getStringParameter(String key, String def) {
101 return (parameters.get(key) == null ? def : parameters.get(key));
105 * Shorthand to return class name for logging purposes
107 * @return name of the class
109 protected String nm() {
110 return (this.getClass().getSimpleName());
113 protected int getMaxLightLevel(InsteonChannelConfiguration conf, int defaultLevel) {
114 Map<String, String> params = conf.getParameters();
115 if (conf.getFeature().contains("dimmer")) {
116 String dimmerMax = params.get("dimmermax");
117 if (dimmerMax != null) {
118 String item = conf.getChannelName();
120 int i = Integer.parseInt(dimmerMax);
121 if (i > 1 && i <= 99) {
122 int level = (int) Math.ceil((i * 255.0) / 100); // round up
123 if (level < defaultLevel) {
124 logger.debug("item {}: using dimmermax value of {}", item, dimmerMax);
128 logger.warn("item {}: dimmermax must be between 1-99 inclusive: {}", item, dimmerMax);
130 } catch (NumberFormatException e) {
131 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());
160 public static class WarnCommandHandler extends CommandHandler {
161 WarnCommandHandler(DeviceFeature f) {
166 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
167 logger.warn("{}: command {} is not implemented yet!", nm(), cmd);
171 public static class NoOpCommandHandler extends CommandHandler {
172 NoOpCommandHandler(DeviceFeature f) {
177 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
178 // do nothing, not even log
182 public static class LightOnOffCommandHandler extends CommandHandler {
183 LightOnOffCommandHandler(DeviceFeature f) {
188 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
190 int ext = getIntParameter("ext", 0);
194 if (cmd == OnOffType.ON) {
195 level = getMaxLightLevel(conf, 0xff);
197 logger.debug("{}: sent msg to switch {} to {}", nm(), dev.getAddress(),
198 level == 0xff ? "on" : level);
199 } else if (cmd == OnOffType.OFF) {
201 logger.debug("{}: sent msg to switch {} off", nm(), dev.getAddress());
203 if (ext == 1 || ext == 2) {
204 byte[] data = new byte[] { (byte) getIntParameter("d1", 0), (byte) getIntParameter("d2", 0),
205 (byte) getIntParameter("d3", 0) };
206 m = dev.makeExtendedMessage((byte) 0x0f, (byte) direc, (byte) level, data);
207 logger.debug("{}: was an extended message for device {}", nm(), dev.getAddress());
210 } else if (ext == 2) {
214 m = dev.makeStandardMessage((byte) 0x0f, (byte) direc, (byte) level, getGroup(conf));
216 logger.debug("Sending message to {}", dev.getAddress());
217 dev.enqueueMessage(m, feature);
218 // expect to get a direct ack after this!
219 } catch (InvalidMessageTypeException e) {
220 logger.warn("{}: invalid message: ", nm(), e);
221 } catch (FieldException e) {
222 logger.warn("{}: command send message creation error ", nm(), e);
227 public static class FastOnOffCommandHandler extends CommandHandler {
228 FastOnOffCommandHandler(DeviceFeature f) {
233 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
235 if (cmd == OnOffType.ON) {
236 int level = getMaxLightLevel(conf, 0xff);
237 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x12, (byte) level, getGroup(conf));
238 dev.enqueueMessage(m, feature);
239 logger.debug("{}: sent fast on to switch {} level {}", nm(), dev.getAddress(),
240 level == 0xff ? "on" : level);
241 } else if (cmd == OnOffType.OFF) {
242 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x14, (byte) 0x00, getGroup(conf));
243 dev.enqueueMessage(m, feature);
244 logger.debug("{}: sent fast off to switch {}", nm(), dev.getAddress());
246 // expect to get a direct ack after this!
247 } catch (InvalidMessageTypeException e) {
248 logger.warn("{}: invalid message: ", nm(), e);
249 } catch (FieldException e) {
250 logger.warn("{}: command send message creation error ", nm(), e);
255 public static class RampOnOffCommandHandler extends RampCommandHandler {
256 RampOnOffCommandHandler(DeviceFeature f) {
261 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
263 if (cmd == OnOffType.ON) {
264 double ramptime = getRampTime(conf, 0);
265 int ramplevel = getRampLevel(conf, 100);
266 byte cmd2 = encode(ramptime, ramplevel);
267 Msg m = dev.makeStandardMessage((byte) 0x0f, getOnCmd(), cmd2, getGroup(conf));
268 dev.enqueueMessage(m, feature);
269 logger.debug("{}: sent ramp on to switch {} time {} level {} cmd1 {}", nm(), dev.getAddress(),
270 ramptime, ramplevel, getOnCmd());
271 } else if (cmd == OnOffType.OFF) {
272 double ramptime = getRampTime(conf, 0);
273 int ramplevel = getRampLevel(conf, 0 /* ignored */);
274 byte cmd2 = encode(ramptime, ramplevel);
275 Msg m = dev.makeStandardMessage((byte) 0x0f, getOffCmd(), cmd2, getGroup(conf));
276 dev.enqueueMessage(m, feature);
277 logger.debug("{}: sent ramp off to switch {} time {} cmd1 {}", nm(), dev.getAddress(), ramptime,
280 // expect to get a direct ack after this!
281 } catch (InvalidMessageTypeException e) {
282 logger.warn("{}: invalid message: ", nm(), e);
283 } catch (FieldException e) {
284 logger.warn("{}: command send message creation error ", nm(), e);
288 private int getRampLevel(InsteonChannelConfiguration conf, int defaultValue) {
289 String str = conf.getParameters().get("ramplevel");
290 return str != null ? Integer.parseInt(str) : defaultValue;
294 public static class ManualChangeCommandHandler extends CommandHandler {
295 ManualChangeCommandHandler(DeviceFeature f) {
300 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
302 if (cmd instanceof DecimalType decimalCommand) {
303 int v = decimalCommand.intValue();
304 int cmd1 = (v != 1) ? 0x17 : 0x18; // start or stop
305 int cmd2 = (v == 2) ? 0x01 : 0; // up or down
306 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2, getGroup(conf));
307 dev.enqueueMessage(m, feature);
308 logger.debug("{}: cmd {} sent manual change {} {} to {}", nm(), v,
309 (cmd1 == 0x17) ? "START" : "STOP", (cmd2 == 0x01) ? "UP" : "DOWN", dev.getAddress());
311 logger.warn("{}: invalid command type: {}", nm(), cmd);
313 } catch (InvalidMessageTypeException e) {
314 logger.warn("{}: invalid message: ", nm(), e);
315 } catch (FieldException e) {
316 logger.warn("{}: command send message creation error ", nm(), e);
322 * Sends ALLLink broadcast commands to group
324 public static class GroupBroadcastCommandHandler extends CommandHandler {
325 GroupBroadcastCommandHandler(DeviceFeature f) {
330 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
332 if (cmd == OnOffType.ON || cmd == OnOffType.OFF) {
333 byte cmd1 = (byte) ((cmd == OnOffType.ON) ? 0x11 : 0x13);
334 byte value = (byte) ((cmd == OnOffType.ON) ? 0xFF : 0x00);
335 int group = getGroup(conf);
337 logger.warn("no group=xx specified in item {}", conf.getChannelName());
340 logger.debug("{}: sending {} broadcast to group {}", nm(), (cmd1 == 0x11) ? "ON" : "OFF",
342 Msg m = dev.makeStandardMessage((byte) 0x0f, cmd1, value, group);
343 dev.enqueueMessage(m, feature);
344 feature.pollRelatedDevices();
346 } catch (InvalidMessageTypeException e) {
347 logger.warn("{}: invalid message: ", nm(), e);
348 } catch (FieldException e) {
349 logger.warn("{}: command send message creation error ", nm(), e);
354 public static class LEDOnOffCommandHandler extends CommandHandler {
355 LEDOnOffCommandHandler(DeviceFeature f) {
360 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
362 if (cmd == OnOffType.ON) {
363 Msg m = dev.makeExtendedMessage((byte) 0x1f, (byte) 0x20, (byte) 0x09,
364 new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00 });
365 dev.enqueueMessage(m, feature);
366 logger.debug("{}: sent msg to switch {} on", nm(), dev.getAddress());
367 } else if (cmd == OnOffType.OFF) {
368 Msg m = dev.makeExtendedMessage((byte) 0x1f, (byte) 0x20, (byte) 0x08,
369 new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00 });
370 dev.enqueueMessage(m, feature);
371 logger.debug("{}: sent msg to switch {} off", nm(), dev.getAddress());
373 } catch (InvalidMessageTypeException e) {
374 logger.warn("{}: invalid message: ", nm(), e);
375 } catch (FieldException e) {
376 logger.warn("{}: command send message creation error ", nm(), e);
381 public static class X10OnOffCommandHandler extends CommandHandler {
382 X10OnOffCommandHandler(DeviceFeature f) {
387 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
389 byte houseCode = dev.getX10HouseCode();
390 byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode());
391 if (cmd == OnOffType.ON || cmd == OnOffType.OFF) {
392 byte houseCommandCode = (byte) (houseCode << 4
393 | (cmd == OnOffType.ON ? X10.Command.ON.code() : X10.Command.OFF.code()));
394 Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code
395 dev.enqueueMessage(munit, feature);
396 Msg mcmd = dev.makeX10Message(houseCommandCode, (byte) 0x80); // send command code
397 dev.enqueueMessage(mcmd, feature);
398 String onOff = cmd == OnOffType.ON ? "ON" : "OFF";
399 logger.debug("{}: sent msg to switch {} {}", nm(), dev.getAddress(), onOff);
401 } catch (InvalidMessageTypeException e) {
402 logger.warn("{}: invalid message: ", nm(), e);
403 } catch (FieldException e) {
404 logger.warn("{}: command send message creation error ", nm(), e);
409 public static class X10PercentCommandHandler extends CommandHandler {
410 X10PercentCommandHandler(DeviceFeature f) {
415 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
418 // I did not have hardware that would respond to the PRESET_DIM codes.
419 // This code path needs testing.
421 byte houseCode = dev.getX10HouseCode();
422 byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode());
423 Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code
424 dev.enqueueMessage(munit, feature);
425 PercentType pc = (PercentType) cmd;
426 logger.debug("{}: changing level of {} to {}", nm(), dev.getAddress(), pc.intValue());
427 int level = (pc.intValue() * 32) / 100;
428 byte cmdCode = (level >= 16) ? X10.Command.PRESET_DIM_2.code() : X10.Command.PRESET_DIM_1.code();
433 houseCode = (byte) x10CodeForLevel[level];
434 cmdCode |= (houseCode << 4);
435 Msg mcmd = dev.makeX10Message(cmdCode, (byte) 0x80); // send command code
436 dev.enqueueMessage(mcmd, feature);
437 } catch (InvalidMessageTypeException e) {
438 logger.warn("{}: invalid message: ", nm(), e);
439 } catch (FieldException e) {
440 logger.warn("{}: command send message creation error ", nm(), e);
444 private final int[] x10CodeForLevel = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
447 public static class X10IncreaseDecreaseCommandHandler extends CommandHandler {
448 X10IncreaseDecreaseCommandHandler(DeviceFeature f) {
453 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
455 byte houseCode = dev.getX10HouseCode();
456 byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode());
457 if (cmd == IncreaseDecreaseType.INCREASE || cmd == IncreaseDecreaseType.DECREASE) {
458 byte houseCommandCode = (byte) (houseCode << 4
459 | (cmd == IncreaseDecreaseType.INCREASE ? X10.Command.BRIGHT.code()
460 : X10.Command.DIM.code()));
461 Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code
462 dev.enqueueMessage(munit, feature);
463 Msg mcmd = dev.makeX10Message(houseCommandCode, (byte) 0x80); // send command code
464 dev.enqueueMessage(mcmd, feature);
465 String bd = cmd == IncreaseDecreaseType.INCREASE ? "BRIGHTEN" : "DIM";
466 logger.debug("{}: sent msg to switch {} {}", nm(), dev.getAddress(), bd);
468 } catch (InvalidMessageTypeException e) {
469 logger.warn("{}: invalid message: ", nm(), e);
470 } catch (FieldException e) {
471 logger.warn("{}: command send message creation error ", nm(), e);
476 public static class IOLincOnOffCommandHandler extends CommandHandler {
477 IOLincOnOffCommandHandler(DeviceFeature f) {
482 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
484 if (cmd == OnOffType.ON) {
485 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x11, (byte) 0xff);
486 dev.enqueueMessage(m, feature);
487 logger.debug("{}: sent msg to switch {} on", nm(), dev.getAddress());
488 } else if (cmd == OnOffType.OFF) {
489 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x13, (byte) 0x00);
490 dev.enqueueMessage(m, feature);
491 logger.debug("{}: sent msg to switch {} off", nm(), dev.getAddress());
493 // This used to be configurable, but was made static to make
494 // the architecture of the binding cleaner.
496 delay = Math.max(1000, delay);
497 delay = Math.min(10000, delay);
498 Timer timer = new Timer();
499 timer.schedule(new TimerTask() {
502 Msg m = feature.makePollMsg();
503 InsteonDevice dev = feature.getDevice();
505 dev.enqueueMessage(m, feature);
509 } catch (InvalidMessageTypeException e) {
510 logger.warn("{}: invalid message: ", nm(), e);
511 } catch (FieldException e) {
512 logger.warn("{}: command send message creation error: ", nm(), e);
517 public static class IncreaseDecreaseCommandHandler extends CommandHandler {
518 IncreaseDecreaseCommandHandler(DeviceFeature f) {
523 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
525 if (cmd == IncreaseDecreaseType.INCREASE) {
526 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x15, (byte) 0x00);
527 dev.enqueueMessage(m, feature);
528 logger.debug("{}: sent msg to brighten {}", nm(), dev.getAddress());
529 } else if (cmd == IncreaseDecreaseType.DECREASE) {
530 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x16, (byte) 0x00);
531 dev.enqueueMessage(m, feature);
532 logger.debug("{}: sent msg to dimm {}", nm(), dev.getAddress());
534 } catch (InvalidMessageTypeException e) {
535 logger.warn("{}: invalid message: ", nm(), e);
536 } catch (FieldException e) {
537 logger.warn("{}: command send message creation error ", nm(), e);
542 public static class PercentHandler extends CommandHandler {
543 PercentHandler(DeviceFeature f) {
548 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
550 PercentType pc = (PercentType) cmd;
551 logger.debug("changing level of {} to {}", dev.getAddress(), pc.intValue());
552 int level = (int) Math.ceil((pc.intValue() * 255.0) / 100); // round up
553 if (level > 0) { // make light on message with given level
554 level = getMaxLightLevel(conf, level);
555 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x11, (byte) level);
556 dev.enqueueMessage(m, feature);
557 logger.debug("{}: sent msg to set {} to {}", nm(), dev.getAddress(), level);
558 } else { // switch off
559 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x13, (byte) 0x00);
560 dev.enqueueMessage(m, feature);
561 logger.debug("{}: sent msg to set {} to zero by switching off", nm(), dev.getAddress());
563 } catch (InvalidMessageTypeException e) {
564 logger.warn("{}: invalid message: ", nm(), e);
565 } catch (FieldException e) {
566 logger.warn("{}: command send message creation error ", nm(), e);
571 private abstract static class RampCommandHandler extends CommandHandler {
572 private static double[] halfRateRampTimes = new double[] { 0.1, 0.3, 2, 6.5, 19, 23.5, 28, 32, 38.5, 47, 90,
573 150, 210, 270, 360, 480 };
578 RampCommandHandler(DeviceFeature f) {
580 // Can't process parameters here because they are set after constructor is invoked.
581 // Unfortunately, this means we can't declare the onCmd, offCmd to be final.
585 void setParameters(Map<String, String> params) {
586 super.setParameters(params);
587 onCmd = (byte) getIntParameter("on", 0x2E);
588 offCmd = (byte) getIntParameter("off", 0x2F);
591 protected final byte getOnCmd() {
595 protected final byte getOffCmd() {
599 protected byte encode(double ramptimeSeconds, int ramplevel) throws FieldException {
600 if (ramplevel < 0 || ramplevel > 100) {
601 throw new FieldException("ramplevel must be in the range 0-100 (inclusive)");
604 if (ramptimeSeconds < 0) {
605 throw new FieldException("ramptime must be greater than 0");
609 int insertionPoint = Arrays.binarySearch(halfRateRampTimes, ramptimeSeconds);
610 if (insertionPoint > 0) {
611 ramptime = 15 - insertionPoint;
613 insertionPoint = -insertionPoint - 1;
614 if (insertionPoint == 0) {
617 double d1 = Math.abs(halfRateRampTimes[insertionPoint - 1] - ramptimeSeconds);
618 double d2 = Math.abs(halfRateRampTimes[insertionPoint] - ramptimeSeconds);
619 ramptime = 15 - (d1 > d2 ? insertionPoint : insertionPoint - 1);
620 logger.debug("ramp encoding: time {} insert {} d1 {} d2 {} ramp {}", ramptimeSeconds,
621 insertionPoint, d1, d2, ramptime);
625 int r = (int) Math.round(ramplevel / (100.0 / 15.0));
626 return (byte) (((r & 0x0f) << 4) | (ramptime & 0xf));
629 protected double getRampTime(InsteonChannelConfiguration conf, double defaultValue) {
630 String str = conf.getParameters().get("ramptime");
631 return str != null ? Double.parseDouble(str) : defaultValue;
635 public static class RampPercentHandler extends RampCommandHandler {
637 RampPercentHandler(DeviceFeature f) {
642 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
644 PercentType pc = (PercentType) cmd;
645 double ramptime = getRampTime(conf, 0);
646 int level = pc.intValue();
647 if (level > 0) { // make light on message with given level
648 level = getMaxLightLevel(conf, level);
649 byte cmd2 = encode(ramptime, level);
650 Msg m = dev.makeStandardMessage((byte) 0x0f, getOnCmd(), cmd2);
651 dev.enqueueMessage(m, feature);
652 logger.debug("{}: sent msg to set {} to {} with {} second ramp time.", nm(), dev.getAddress(),
654 } else { // switch off
655 Msg m = dev.makeStandardMessage((byte) 0x0f, getOffCmd(), (byte) 0x00);
656 dev.enqueueMessage(m, feature);
657 logger.debug("{}: sent msg to set {} to zero by switching off with {} ramp time.", nm(),
658 dev.getAddress(), ramptime);
660 } catch (InvalidMessageTypeException e) {
661 logger.warn("{}: invalid message: ", nm(), e);
662 } catch (FieldException e) {
663 logger.warn("{}: command send message creation error ", nm(), e);
668 public static class PowerMeterCommandHandler extends CommandHandler {
669 PowerMeterCommandHandler(DeviceFeature f) {
674 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
675 String cmdParam = conf.getParameters().get(InsteonDeviceHandler.CMD);
676 if (cmdParam == null) {
677 logger.warn("{} ignoring cmd {} because no cmd= is configured!", nm(), cmd);
681 if (cmd == OnOffType.ON) {
682 if (cmdParam.equals(InsteonDeviceHandler.CMD_RESET)) {
683 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x80, (byte) 0x00);
684 dev.enqueueMessage(m, feature);
685 logger.debug("{}: sent reset msg to power meter {}", nm(), dev.getAddress());
686 feature.publish(OnOffType.OFF, StateChangeType.ALWAYS, InsteonDeviceHandler.CMD,
687 InsteonDeviceHandler.CMD_RESET);
688 } else if (cmdParam.equals(InsteonDeviceHandler.CMD_UPDATE)) {
689 Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x82, (byte) 0x00);
690 dev.enqueueMessage(m, feature);
691 logger.debug("{}: sent update msg to power meter {}", nm(), dev.getAddress());
692 feature.publish(OnOffType.OFF, StateChangeType.ALWAYS, InsteonDeviceHandler.CMD,
693 InsteonDeviceHandler.CMD_UPDATE);
695 logger.warn("{}: ignoring unknown cmd {} for power meter {}", nm(), cmdParam, dev.getAddress());
697 } else if (cmd == OnOffType.OFF) {
698 logger.debug("{}: ignoring off request for power meter {}", nm(), dev.getAddress());
700 } catch (InvalidMessageTypeException e) {
701 logger.warn("{}: invalid message: ", nm(), e);
702 } catch (FieldException e) {
703 logger.warn("{}: command send message creation error ", nm(), e);
709 * Command handler that sends a command with a numerical value to a device.
710 * The handler is very parameterizable so it can be reused for different devices.
711 * First used for setting thermostat parameters.
714 public static class NumberCommandHandler extends CommandHandler {
715 NumberCommandHandler(DeviceFeature f) {
719 public int transform(int cmd) {
724 public void handleCommand(InsteonChannelConfiguration conf, Command cmd, InsteonDevice dev) {
726 int dc = transform(((DecimalType) cmd).intValue());
727 int intFactor = getIntParameter("factor", 1);
729 // determine what level should be, and what field it should be in
731 int ilevel = dc * intFactor;
732 byte level = (byte) (ilevel > 255 ? 0xFF : ((ilevel < 0) ? 0 : ilevel));
733 String vfield = getStringParameter("value", "");
734 if (vfield == null || vfield.isEmpty()) {
735 logger.warn("{} has no value field specified", nm());
738 // figure out what cmd1, cmd2, d1, d2, d3 are supposed to be
739 // to form a proper message
741 int cmd1 = getIntParameter("cmd1", -1);
743 logger.warn("{} has no cmd1 specified!", nm());
746 int cmd2 = getIntParameter("cmd2", 0);
747 int ext = getIntParameter("ext", 0);
749 if (ext == 1 || ext == 2) {
750 byte[] data = new byte[] { (byte) getIntParameter("d1", 0), (byte) getIntParameter("d2", 0),
751 (byte) getIntParameter("d3", 0) };
752 m = dev.makeExtendedMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2, data);
753 m.setByte(vfield, level);
756 } else if (ext == 2) {
760 m = dev.makeStandardMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2);
761 m.setByte(vfield, level);
763 dev.enqueueMessage(m, feature);
764 logger.debug("{}: sent msg to change level to {}", nm(), ((DecimalType) cmd).intValue());
766 } catch (InvalidMessageTypeException e) {
767 logger.warn("{}: invalid message: ", nm(), e);
768 } catch (FieldException e) {
769 logger.warn("{}: command send message creation error ", nm(), e);
775 * Handler to set the thermostat system mode
777 public static class ThermostatSystemModeCommandHandler extends NumberCommandHandler {
778 ThermostatSystemModeCommandHandler(DeviceFeature f) {
783 public int transform(int cmd) {
786 return (0x09); // off
788 return (0x04); // heat
790 return (0x05); // cool
792 return (0x06); // auto (aka manual auto)
794 return (0x0A); // program (aka auto)
798 return (0x0A); // when in doubt go to program
803 * Handler to set the thermostat fan mode
805 public static class ThermostatFanModeCommandHandler extends NumberCommandHandler {
806 ThermostatFanModeCommandHandler(DeviceFeature f) {
811 public int transform(int cmd) {
814 return (0x08); // fan mode auto
816 return (0x07); // fan always on
820 return (0x08); // when in doubt go auto mode
825 * Handler to set the fanlinc fan mode
827 public static class FanLincFanCommandHandler extends NumberCommandHandler {
828 FanLincFanCommandHandler(DeviceFeature f) {
833 public int transform(int cmd) {
836 return (0x00); // fan off
838 return (0x55); // fan low
840 return (0xAA); // fan medium
842 return (0xFF); // fan high
846 return (0x00); // all other modes are "off"
851 * Factory method for creating handlers of a given name using java reflection
853 * @param name the name of the handler to create
855 * @param f the feature for which to create the handler
856 * @return the handler which was created
859 public static <T extends CommandHandler> T makeHandler(String name, Map<String, String> params, DeviceFeature f) {
860 String cname = CommandHandler.class.getName() + "$" + name;
862 Class<?> c = Class.forName(cname);
863 @SuppressWarnings("unchecked")
864 Class<? extends T> dc = (Class<? extends T>) c;
866 T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f);
867 ch.setParameters(params);
869 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
870 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
871 logger.warn("error trying to create message handler: {}", name, e);