/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.insteonplm.internal.device; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Timer; import java.util.TimerTask; import org.openhab.binding.insteonplm.InsteonPLMBindingConfig; import org.openhab.binding.insteonplm.internal.device.DeviceFeatureListener.StateChangeType; import org.openhab.binding.insteonplm.internal.message.FieldException; import org.openhab.binding.insteonplm.internal.message.Msg; import org.openhab.binding.insteonplm.internal.utils.Utils; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A command handler translates an openHAB command into a insteon message * * @author Daniel Pfrommer * @author Bernd Pfrommer */ public abstract class CommandHandler { private static final Logger logger = LoggerFactory.getLogger(CommandHandler.class); DeviceFeature m_feature = null; // related DeviceFeature HashMap<String, String> m_parameters = new HashMap<String, String>(); /** * Constructor * * @param feature The DeviceFeature for which this command was intended. * The openHAB commands are issued on an openhab item. The .items files bind * an openHAB item to a DeviceFeature. */ CommandHandler(DeviceFeature feature) { m_feature = feature; } /** * Implements what to do when an openHAB command is received * * @param config the configuration for the item that generated the command * @param cmd the openhab command issued * @param device the Insteon device to which this command applies */ public abstract void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice device); /** * Returns parameter as integer * * @param key key of parameter * @param def default * @return value of parameter */ protected int getIntParameter(String key, int def) { String val = m_parameters.get(key); if (val == null) { return (def); // param not found } int ret = def; try { ret = Utils.strToInt(val); } catch (NumberFormatException e) { logger.error("malformed int parameter in command handler: {}", key); } return ret; } /** * Returns parameter as String * * @param key key of parameter * @param def default * @return value of parameter */ protected String getStringParameter(String key, String def) { return (m_parameters.get(key) == null ? def : m_parameters.get(key)); } /** * Shorthand to return class name for logging purposes * * @return name of the class */ protected String nm() { return (this.getClass().getSimpleName()); } protected int getMaxLightLevel(InsteonPLMBindingConfig conf, int defaultLevel) { HashMap<String, String> params = conf.getParameters(); if (conf.getFeature().contains("dimmer") && params.containsKey("dimmermax")) { String item = conf.getItemName(); String dimmerMax = params.get("dimmermax"); try { int i = Integer.parseInt(dimmerMax); if (i > 1 && i <= 99) { int level = (int) Math.ceil((i * 255.0) / 100); // round up if (level < defaultLevel) { logger.info("item {}: using dimmermax value of {}", item, dimmerMax); return level; } } else { logger.error("item {}: dimmermax must be between 1-99 inclusive: {}", item, dimmerMax); } } catch (NumberFormatException e) { logger.error("item {}: invalid int value for dimmermax: {}", item, dimmerMax); } } return defaultLevel; } void setParameters(HashMap<String, String> hm) { m_parameters = hm; } /** * Helper function to extract the group parameter from the binding config, * * @param c the binding configuration to test * @return the value of the "group" parameter, or -1 if none */ protected static int s_getGroup(InsteonPLMBindingConfig c) { String v = c.getParameters().get("group"); int iv = -1; try { iv = (v == null) ? -1 : Utils.strToInt(v); } catch (NumberFormatException e) { logger.error("malformed int parameter in for item {}", c.getItemName()); } return iv; } public static class WarnCommandHandler extends CommandHandler { WarnCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { logger.warn("{}: command {} is not implemented yet!", nm(), cmd); } } public static class NoOpCommandHandler extends CommandHandler { NoOpCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { // do nothing, not even log } } public static class LightOnOffCommandHandler extends CommandHandler { LightOnOffCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { int ext = getIntParameter("ext", 0); int direc = 0x00; int level = 0x00; Msg m = null; if (cmd == OnOffType.ON) { level = getMaxLightLevel(conf, 0xff); direc = 0x11; logger.info("{}: sent msg to switch {} to {}", nm(), dev.getAddress(), level == 0xff ? "on" : level); } else if (cmd == OnOffType.OFF) { direc = 0x13; logger.info("{}: sent msg to switch {} off", nm(), dev.getAddress()); } if (ext == 1 || ext == 2) { byte[] data = new byte[] { (byte) getIntParameter("d1", 0), (byte) getIntParameter("d2", 0), (byte) getIntParameter("d3", 0) }; m = dev.makeExtendedMessage((byte) 0x0f, (byte) direc, (byte) level, data); logger.info("{}: was an extended message for device {}", nm(), dev.getAddress()); if (ext == 1) { m.setCRC(); } else if (ext == 2) { m.setCRC2(); } } else { m = dev.makeStandardMessage((byte) 0x0f, (byte) direc, (byte) level, s_getGroup(conf)); } logger.info("Sending message to {}", dev.getAddress()); dev.enqueueMessage(m, m_feature); // expect to get a direct ack after this! } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class FastOnOffCommandHandler extends CommandHandler { FastOnOffCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { if (cmd == OnOffType.ON) { int level = getMaxLightLevel(conf, 0xff); Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x12, (byte) level, s_getGroup(conf)); dev.enqueueMessage(m, m_feature); logger.info("{}: sent fast on to switch {} level {}", nm(), dev.getAddress(), level == 0xff ? "on" : level); } else if (cmd == OnOffType.OFF) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x14, (byte) 0x00, s_getGroup(conf)); dev.enqueueMessage(m, m_feature); logger.info("{}: sent fast off to switch {}", nm(), dev.getAddress()); } // expect to get a direct ack after this! } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class RampOnOffCommandHandler extends RampCommandHandler { RampOnOffCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { if (cmd == OnOffType.ON) { double ramptime = getRampTime(conf, 0); int ramplevel = getRampLevel(conf, 100); byte cmd2 = encode(ramptime, ramplevel); Msg m = dev.makeStandardMessage((byte) 0x0f, getOnCmd(), cmd2, s_getGroup(conf)); dev.enqueueMessage(m, m_feature); logger.info("{}: sent ramp on to switch {} time {} level {} cmd1 {}", nm(), dev.getAddress(), ramptime, ramplevel, getOnCmd()); } else if (cmd == OnOffType.OFF) { double ramptime = getRampTime(conf, 0); int ramplevel = getRampLevel(conf, 0 /* ignored */); byte cmd2 = encode(ramptime, ramplevel); Msg m = dev.makeStandardMessage((byte) 0x0f, getOffCmd(), cmd2, s_getGroup(conf)); dev.enqueueMessage(m, m_feature); logger.info("{}: sent ramp off to switch {} time {} cmd1 {}", nm(), dev.getAddress(), ramptime, getOffCmd()); } // expect to get a direct ack after this! } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } private int getRampLevel(InsteonPLMBindingConfig conf, int defaultValue) { HashMap<String, String> params = conf.getParameters(); return params.containsKey("ramplevel") ? Integer.parseInt(params.get("ramplevel")) : defaultValue; } } public static class ManualChangeCommandHandler extends CommandHandler { ManualChangeCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { if (cmd instanceof DecimalType) { int v = ((DecimalType) cmd).intValue(); int cmd1 = (v != 1) ? 0x17 : 0x18; // start or stop int cmd2 = (v == 2) ? 0x01 : 0; // up or down Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2, s_getGroup(conf)); dev.enqueueMessage(m, m_feature); logger.info("{}: cmd {} sent manual change {} {} to {}", nm(), v, (cmd1 == 0x17) ? "START" : "STOP", (cmd2 == 0x01) ? "UP" : "DOWN", dev.getAddress()); } else { logger.error("{}: invalid command type: {}", nm(), cmd); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } /** * Sends ALLLink broadcast commands to group */ public static class GroupBroadcastCommandHandler extends CommandHandler { GroupBroadcastCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { if (cmd == OnOffType.ON || cmd == OnOffType.OFF) { byte cmd1 = (byte) ((cmd == OnOffType.ON) ? 0x11 : 0x13); byte value = (byte) ((cmd == OnOffType.ON) ? 0xFF : 0x00); int group = s_getGroup(conf); if (group == -1) { logger.error("no group=xx specified in item {}", conf.getItemName()); return; } logger.info("{}: sending {} broadcast to group {}", nm(), (cmd1 == 0x11) ? "ON" : "OFF", s_getGroup(conf)); Msg m = dev.makeStandardMessage((byte) 0x0f, cmd1, value, group); dev.enqueueMessage(m, m_feature); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } /** * This Handler was supposed to set the LEDs of the 2487S, but it doesn't work. * The parameters were modeled after the 2486D, it may work for that one, * leaving it in for now. * * From the HouseLinc PLM traffic log, the following commands (in the D2 data field) * of the 2486D are supported: * * 0x02: LED follow mask may work or not * 0x03: LED OFF mask * 0x04: X10 addr setting * 0x05: ramp rate * 0x06: on Level for button * 0x07: global LED brightness (could not see any effect during testing) * 0x0B: set nontoggle on/off command * * crucially, the 0x09 command does not work (NACK from device) * * @author Bernd Pfrommer */ public static class LEDOnOffCommandHandler extends CommandHandler { LEDOnOffCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { int button = this.getIntParameter("button", -1); if (cmd == OnOffType.ON) { Msg m = dev.makeExtendedMessage((byte) 0x1f, (byte) 0x2e, (byte) 0x00, new byte[] { (byte) button, (byte) 0x09, (byte) 0x01 }); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to switch {} on", nm(), dev.getAddress()); } else if (cmd == OnOffType.OFF) { Msg m = dev.makeExtendedMessage((byte) 0x1f, (byte) 0x2e, (byte) 0x00, new byte[] { (byte) button, (byte) 0x09, (byte) 0x00 }); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to switch {} off", nm(), dev.getAddress()); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class X10OnOffCommandHandler extends CommandHandler { X10OnOffCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { byte houseCode = dev.getX10HouseCode(); byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode()); if (cmd == OnOffType.ON || cmd == OnOffType.OFF) { byte houseCommandCode = (byte) (houseCode << 4 | (cmd == OnOffType.ON ? X10.Command.ON.code() : X10.Command.OFF.code())); Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code dev.enqueueMessage(munit, m_feature); Msg mcmd = dev.makeX10Message(houseCommandCode, (byte) 0x80); // send command code dev.enqueueMessage(mcmd, m_feature); String onOff = cmd == OnOffType.ON ? "ON" : "OFF"; logger.info("{}: sent msg to switch {} {}", nm(), dev.getAddress(), onOff); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class X10PercentCommandHandler extends CommandHandler { X10PercentCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { // // I did not have hardware that would respond to the PRESET_DIM codes. // This code path needs testing. // byte houseCode = dev.getX10HouseCode(); byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode()); Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code dev.enqueueMessage(munit, m_feature); PercentType pc = (PercentType) cmd; logger.debug("{}: changing level of {} to {}", nm(), dev.getAddress(), pc.intValue()); int level = (pc.intValue() * 32) / 100; byte cmdCode = (level >= 16) ? X10.Command.PRESET_DIM_2.code() : X10.Command.PRESET_DIM_1.code(); level = level % 16; if (level <= 0) { level = 0; } houseCode = (byte) s_X10CodeForLevel[level]; cmdCode |= (houseCode << 4); Msg mcmd = dev.makeX10Message(cmdCode, (byte) 0x80); // send command code dev.enqueueMessage(mcmd, m_feature); } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } static private final int[] s_X10CodeForLevel = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; } public static class X10IncreaseDecreaseCommandHandler extends CommandHandler { X10IncreaseDecreaseCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { byte houseCode = dev.getX10HouseCode(); byte houseUnitCode = (byte) (houseCode << 4 | dev.getX10UnitCode()); if (cmd == IncreaseDecreaseType.INCREASE || cmd == IncreaseDecreaseType.DECREASE) { byte houseCommandCode = (byte) (houseCode << 4 | (cmd == IncreaseDecreaseType.INCREASE ? X10.Command.BRIGHT.code() : X10.Command.DIM.code())); Msg munit = dev.makeX10Message(houseUnitCode, (byte) 0x00); // send unit code dev.enqueueMessage(munit, m_feature); Msg mcmd = dev.makeX10Message(houseCommandCode, (byte) 0x80); // send command code dev.enqueueMessage(mcmd, m_feature); String bd = cmd == IncreaseDecreaseType.INCREASE ? "BRIGHTEN" : "DIM"; logger.info("{}: sent msg to switch {} {}", nm(), dev.getAddress(), bd); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class IOLincOnOffCommandHandler extends CommandHandler { IOLincOnOffCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { if (cmd == OnOffType.ON) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x11, (byte) 0xff); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to switch {} on", nm(), dev.getAddress()); } else if (cmd == OnOffType.OFF) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x13, (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to switch {} off", nm(), dev.getAddress()); } // This used to be configurable, but was made static to make // the architecture of the binding cleaner. int delay = 2000; delay = Math.max(1000, delay); delay = Math.min(10000, delay); Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { Msg m = m_feature.makePollMsg(); InsteonDevice dev = m_feature.getDevice(); if (m != null) { dev.enqueueMessage(m, m_feature); } } }, delay); } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error: ", nm(), e); } } } public static class IncreaseDecreaseCommandHandler extends CommandHandler { IncreaseDecreaseCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { if (cmd == IncreaseDecreaseType.INCREASE) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x15, (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to brighten {}", nm(), dev.getAddress()); } else if (cmd == IncreaseDecreaseType.DECREASE) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x16, (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to dimm {}", nm(), dev.getAddress()); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class PercentHandler extends CommandHandler { PercentHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { PercentType pc = (PercentType) cmd; logger.debug("changing level of {} to {}", dev.getAddress(), pc.intValue()); int level = (int) Math.ceil((pc.intValue() * 255.0) / 100); // round up if (level > 0) { // make light on message with given level level = getMaxLightLevel(conf, level); Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x11, (byte) level); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to set {} to {}", nm(), dev.getAddress(), level); } else { // switch off Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x13, (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to set {} to zero by switching off", nm(), dev.getAddress()); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } private static abstract class RampCommandHandler extends CommandHandler { private static double[] halfRateRampTimes = new double[] { 0.1, 0.3, 2, 6.5, 19, 23.5, 28, 32, 38.5, 47, 90, 150, 210, 270, 360, 480 }; private byte onCmd; private byte offCmd; RampCommandHandler(DeviceFeature f) { super(f); // Can't process parameters here because they are set after constructor is invoked. // Unfortunately, this means we can't declare the onCmd, offCmd to be final. } @Override void setParameters(HashMap<String, String> params) { super.setParameters(params); onCmd = (byte) getIntParameter("on", 0x2E); offCmd = (byte) getIntParameter("off", 0x2F); } protected final byte getOnCmd() { return onCmd; } protected final byte getOffCmd() { return offCmd; } protected byte encode(double ramptimeSeconds, int ramplevel) throws FieldException { if (ramplevel < 0 || ramplevel > 100) { throw new FieldException("ramplevel must be in the range 0-100 (inclusive)"); } ramplevel = (int) Math.round(ramplevel / (100.0 / 15.0)); if (ramptimeSeconds < 0) { throw new FieldException("ramptime must be greater than 0"); } int ramptime; int insertionPoint = Arrays.binarySearch(halfRateRampTimes, ramptimeSeconds); if (insertionPoint > 0) { ramptime = 15 - insertionPoint; } else { insertionPoint = -insertionPoint - 1; if (insertionPoint == 0) { ramptime = 15; } else { double d1 = Math.abs(halfRateRampTimes[insertionPoint - 1] - ramptimeSeconds); double d2 = Math.abs(halfRateRampTimes[insertionPoint] - ramptimeSeconds); ramptime = 15 - (d1 > d2 ? insertionPoint : insertionPoint - 1); logger.debug("ramp encoding: time {} insert {} d1 {} d2 {} ramp {}", ramptimeSeconds, insertionPoint, d1, d2, ramptime); } } return (byte) (((ramplevel & 0x0f) << 4) | (ramptime & 0xf)); } protected double getRampTime(InsteonPLMBindingConfig conf, double defaultValue) { HashMap<String, String> params = conf.getParameters(); return params.containsKey("ramptime") ? Double.parseDouble(params.get("ramptime")) : defaultValue; } } public static class RampPercentHandler extends RampCommandHandler { RampPercentHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { PercentType pc = (PercentType) cmd; double ramptime = getRampTime(conf, 0); int level = pc.intValue(); if (level > 0) { // make light on message with given level level = getMaxLightLevel(conf, level); byte cmd2 = encode(ramptime, level); Msg m = dev.makeStandardMessage((byte) 0x0f, getOnCmd(), cmd2); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to set {} to {} with {} second ramp time.", nm(), dev.getAddress(), level, ramptime); } else { // switch off Msg m = dev.makeStandardMessage((byte) 0x0f, getOffCmd(), (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to set {} to zero by switching off with {} ramp time.", nm(), dev.getAddress(), ramptime); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } public static class PowerMeterCommandHandler extends CommandHandler { PowerMeterCommandHandler(DeviceFeature f) { super(f); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { String cmdParam = conf.getParameter("cmd"); if (cmdParam == null) { logger.error("{} ignoring cmd {} because no cmd= is configured!", nm(), cmd); return; } try { if (cmd == OnOffType.ON) { if (cmdParam.equals("reset")) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x80, (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent reset msg to power meter {}", nm(), dev.getAddress()); m_feature.publish(OnOffType.OFF, StateChangeType.ALWAYS, "cmd", "reset"); } else if (cmdParam.equals("update")) { Msg m = dev.makeStandardMessage((byte) 0x0f, (byte) 0x82, (byte) 0x00); dev.enqueueMessage(m, m_feature); logger.info("{}: sent update msg to power meter {}", nm(), dev.getAddress()); m_feature.publish(OnOffType.OFF, StateChangeType.ALWAYS, "cmd", "update"); } else { logger.error("{}: ignoring unknown cmd {} for power meter {}", nm(), cmdParam, dev.getAddress()); } } else if (cmd == OnOffType.OFF) { logger.info("{}: ignoring off request for power meter {}", nm(), dev.getAddress()); } } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } /** * Command handler that sends a command with a numerical value to a device. * The handler is very parameterizable so it can be reused for different devices. * First used for setting thermostat parameters. */ public static class NumberCommandHandler extends CommandHandler { NumberCommandHandler(DeviceFeature f) { super(f); } public int transform(int cmd) { return (cmd); } @Override public void handleCommand(InsteonPLMBindingConfig conf, Command cmd, InsteonDevice dev) { try { int dc = transform(((DecimalType) cmd).intValue()); int intFactor = getIntParameter("factor", 1); // // determine what level should be, and what field it should be in // int ilevel = dc * intFactor; byte level = (byte) (ilevel > 255 ? 0xFF : ((ilevel < 0) ? 0 : ilevel)); String vfield = getStringParameter("value", ""); if (vfield == "") { logger.error("{} has no value field specified", nm()); } // // figure out what cmd1, cmd2, d1, d2, d3 are supposed to be // to form a proper message // int cmd1 = getIntParameter("cmd1", -1); if (cmd1 < 0) { logger.error("{} has no cmd1 specified!", nm()); return; } int cmd2 = getIntParameter("cmd2", 0); int ext = getIntParameter("ext", 0); Msg m = null; if (ext == 1 || ext == 2) { byte[] data = new byte[] { (byte) getIntParameter("d1", 0), (byte) getIntParameter("d2", 0), (byte) getIntParameter("d3", 0) }; m = dev.makeExtendedMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2, data); m.setByte(vfield, level); if (ext == 1) { m.setCRC(); } else if (ext == 2) { m.setCRC2(); } } else { m = dev.makeStandardMessage((byte) 0x0f, (byte) cmd1, (byte) cmd2); m.setByte(vfield, level); } dev.enqueueMessage(m, m_feature); logger.info("{}: sent msg to change level to {}", nm(), ((DecimalType) cmd).intValue()); m = null; } catch (IOException e) { logger.error("{}: command send i/o error: ", nm(), e); } catch (FieldException e) { logger.error("{}: command send message creation error ", nm(), e); } } } /** * Handler to set the thermostat system mode */ public static class ThermostatSystemModeCommandHandler extends NumberCommandHandler { ThermostatSystemModeCommandHandler(DeviceFeature f) { super(f); } @Override public int transform(int cmd) { switch (cmd) { case 0: return (0x09); // off case 1: return (0x04); // heat case 2: return (0x05); // cool case 3: return (0x06); // auto (aka manual auto) case 4: return (0x0A); // program (aka auto) default: break; } return (0x0A); // when in doubt go to program } } /** * Handler to set the thermostat fan mode */ public static class ThermostatFanModeCommandHandler extends NumberCommandHandler { ThermostatFanModeCommandHandler(DeviceFeature f) { super(f); } @Override public int transform(int cmd) { switch (cmd) { case 0: return (0x08); // fan mode auto case 1: return (0x07); // fan always on default: break; } return (0x08); // when in doubt go auto mode } } /** * Handler to set the fanlinc fan mode */ public static class FanLincFanCommandHandler extends NumberCommandHandler { FanLincFanCommandHandler(DeviceFeature f) { super(f); } @Override public int transform(int cmd) { switch (cmd) { case 0: return (0x00); // fan off case 1: return (0x55); // fan low case 2: return (0xAA); // fan medium case 3: return (0xFF); // fan high default: break; } return (0x00); // all other modes are "off" } } /** * Factory method for creating handlers of a given name using java reflection * * @param name the name of the handler to create * @param params * @param f the feature for which to create the handler * @return the handler which was created */ public static <T extends CommandHandler> T s_makeHandler(String name, HashMap<String, String> params, DeviceFeature f) { String cname = CommandHandler.class.getName() + "$" + name; try { Class<?> c = Class.forName(cname); @SuppressWarnings("unchecked") Class<? extends T> dc = (Class<? extends T>) c; T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f); ch.setParameters(params); return ch; } catch (Exception e) { logger.error("error trying to create message handler: {}", name, e); } return null; } }