/** * 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.util.HashMap; 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.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Does preprocessing of messages to decide which handler should be called. * * @author Bernd Pfrommer * @since 1.5.0 */ public abstract class MessageDispatcher { private static final Logger logger = LoggerFactory.getLogger(MessageDispatcher.class); DeviceFeature m_feature = null; HashMap<String, String> m_parameters = new HashMap<String, String>(); /** * Constructor * * @param f DeviceFeature to which this MessageDispatcher belongs */ MessageDispatcher(DeviceFeature f) { m_feature = f; } public void setParameters(HashMap<String, String> hm) { m_parameters = hm; } /** * Generic handling of incoming ALL LINK messages * * @param msg the message received * @param port the port on which the message was received * @return true if the message was handled by this function */ protected boolean handleAllLinkMessage(Msg msg, String port) { if (!msg.isAllLink()) { return false; } try { InsteonAddress a = msg.getAddress("toAddress"); // ALL_LINK_BROADCAST and ALL_LINK_CLEANUP // have a valid Command1 field // but the CLEANUP_SUCCESS (of type ALL_LINK_BROADCAST!) // message has cmd1 = 0x06 and the cmd as the // high byte of the toAddress. byte cmd1 = msg.getByte("command1"); if (!msg.isCleanup() && cmd1 == 0x06) { cmd1 = a.getHighByte(); } // For ALL_LINK_BROADCAST messages, the group is // in the low byte of the toAddress. For direct // ALL_LINK_CLEANUP, it is in Command2 int group = (msg.isCleanup() ? msg.getByte("command2") : a.getLowByte()) & 0xff; MessageHandler h = m_feature.getMsgHandlers().get(cmd1 & 0xFF); if (h == null) { logger.debug("msg is not for this feature"); return true; } if (!h.isDuplicate(msg)) { if (h.matchesGroup(group) && h.matches(msg)) { logger.debug("{}:{}->{} cmd1:{} group {}/{}", m_feature.getDevice().getAddress(), m_feature.getName(), h.getClass().getSimpleName(), Utils.getHexByte(cmd1), group, h.getGroup()); h.handleMessage(group, cmd1, msg, m_feature, port); } else { logger.debug("message ignored because matches group: {} matches filter: {}", h.matchesGroup(group), h.matches(msg)); } } else { logger.debug("message ignored as duplicate. Matches group: {} matches filter: {}", h.matchesGroup(group), h.matches(msg)); } } catch (FieldException e) { logger.error("couldn't parse ALL_LINK message: {}", msg, e); } return true; } /** * Checks if this message is in response to previous query by this feature * * @param msg * @return true; */ boolean isMyDirectAck(Msg msg) { return msg.isAckOfDirect() && (m_feature.getQueryStatus() == DeviceFeature.QueryStatus.QUERY_PENDING) && m_feature.getDevice().getFeatureQueried() == m_feature; } /** * Dispatches message * * @param msg Message to dispatch * @param port Insteon device ('/dev/usb') from which the message came * @return true if this message was found to be a reply to a direct message, * and was claimed by one of the handlers */ public abstract boolean dispatch(Msg msg, String port); // // // ------------ implementations of MessageDispatchers start here ------------------ // // public static class DefaultDispatcher extends MessageDispatcher { DefaultDispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { byte cmd = 0x00; byte cmd1 = 0x00; boolean isConsumed = false; int key = -1; try { cmd = msg.getByte("Cmd"); cmd1 = msg.getByte("command1"); } catch (FieldException e) { logger.debug("no command found, dropping msg {}", msg); return false; } if (msg.isAllLinkCleanupAckOrNack()) { // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK // in response to a direct status query message return false; } if (handleAllLinkMessage(msg, port)) { return false; } if (msg.isAckOfDirect()) { // in the case of direct ack, the cmd1 code is useless. // you have to know what message was sent before to // interpret the reply message if (isMyDirectAck(msg)) { logger.debug("{}:{} DIRECT_ACK: q:{} cmd: {}", m_feature.getDevice().getAddress(), m_feature.getName(), m_feature.getQueryStatus(), cmd); isConsumed = true; if (cmd == 0x50) { // must be a reply to our message, tweak the cmd1 code! logger.debug("changing key to 0x19 for msg {}", msg); key = 0x19; // we have installed a handler under that command number } } } else { key = (cmd1 & 0xFF); } if (key != -1 || m_feature.isStatusFeature()) { MessageHandler h = m_feature.getMsgHandlers().get(key); if (h == null) { h = m_feature.getDefaultMsgHandler(); } if (h.matches(msg)) { if (!isConsumed) { logger.debug("{}:{}->{} DIRECT", m_feature.getDevice().getAddress(), m_feature.getName(), h.getClass().getSimpleName()); } h.handleMessage(-1, cmd1, msg, m_feature, port); } } if (isConsumed) { m_feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED); logger.debug("defdisp: {}:{} set status to: {}", m_feature.getDevice().getAddress(), m_feature.getName(), m_feature.getQueryStatus()); } return isConsumed; } } public static class DefaultGroupDispatcher extends MessageDispatcher { DefaultGroupDispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { byte cmd = 0x00; byte cmd1 = 0x00; boolean isConsumed = false; int key = -1; try { cmd = msg.getByte("Cmd"); cmd1 = msg.getByte("command1"); } catch (FieldException e) { logger.debug("no command found, dropping msg {}", msg); return false; } if (msg.isAllLinkCleanupAckOrNack()) { // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK // in response to a direct status query message return false; } if (handleAllLinkMessage(msg, port)) { return false; } if (msg.isAckOfDirect()) { // in the case of direct ack, the cmd1 code is useless. // you have to know what message was sent before to // interpret the reply message if (isMyDirectAck(msg)) { logger.debug("{}:{} qs:{} cmd: {}", m_feature.getDevice().getAddress(), m_feature.getName(), m_feature.getQueryStatus(), cmd); isConsumed = true; if (cmd == 0x50) { // must be a reply to our message, tweak the cmd1 code! logger.debug("changing key to 0x19 for msg {}", msg); key = 0x19; // we have installed a handler under that command number } } } else { key = (cmd1 & 0xFF); } if (key != -1) { for (DeviceFeature f : m_feature.getConnectedFeatures()) { MessageHandler h = f.getMsgHandlers().get(key); if (h == null) { h = f.getDefaultMsgHandler(); } if (h.matches(msg)) { if (!isConsumed) { logger.debug("{}:{}->{} DIRECT", f.getDevice().getAddress(), f.getName(), h.getClass().getSimpleName()); } h.handleMessage(-1, cmd1, msg, f, port); } } } if (isConsumed) { m_feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED); logger.debug("{}:{} set status to: {}", m_feature.getDevice().getAddress(), m_feature.getName(), m_feature.getQueryStatus()); } return isConsumed; } } public static class PollGroupDispatcher extends MessageDispatcher { PollGroupDispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { if (msg.isAllLinkCleanupAckOrNack()) { // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK // in response to a direct status query message return false; } if (handleAllLinkMessage(msg, port)) { return false; } if (msg.isAckOfDirect()) { boolean isMyAck = isMyDirectAck(msg); if (isMyAck) { logger.debug("{}:{} got poll ACK", m_feature.getDevice().getAddress(), m_feature.getName()); } return (isMyAck); } return (false); // not a direct ack, so we didn't consume it either } } public static class SimpleDispatcher extends MessageDispatcher { SimpleDispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { byte cmd1 = 0x00; try { if (handleAllLinkMessage(msg, port)) { return false; } if (msg.isAllLinkCleanupAckOrNack()) { // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK // in response to a direct status query message return false; } cmd1 = msg.getByte("command1"); } catch (FieldException e) { logger.debug("no cmd1 found, dropping msg {}", msg); return false; } boolean isConsumed = isMyDirectAck(msg); int key = (cmd1 & 0xFF); MessageHandler h = m_feature.getMsgHandlers().get(key); if (h == null) { h = m_feature.getDefaultMsgHandler(); } if (h.matches(msg)) { logger.trace("{}:{}->{} {}", m_feature.getDevice().getAddress(), m_feature.getName(), h.getClass().getSimpleName(), msg); h.handleMessage(-1, cmd1, msg, m_feature, port); } return isConsumed; } } public static class X10Dispatcher extends MessageDispatcher { X10Dispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { try { byte rawX10 = msg.getByte("rawX10"); int cmd = (rawX10 & 0x0f); MessageHandler h = m_feature.getMsgHandlers().get(cmd); if (h == null) { h = m_feature.getDefaultMsgHandler(); } logger.debug("{}:{}->{} {}", m_feature.getDevice().getAddress(), m_feature.getName(), h.getClass().getSimpleName(), msg); if (h.matches(msg)) { h.handleMessage(-1, (byte) cmd, msg, m_feature, port); } } catch (FieldException e) { logger.error("error parsing {}: ", msg, e); } return false; } } public static class PassThroughDispatcher extends MessageDispatcher { PassThroughDispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { MessageHandler h = m_feature.getDefaultMsgHandler(); if (h.matches(msg)) { logger.trace("{}:{}->{} {}", m_feature.getDevice().getAddress(), m_feature.getName(), h.getClass().getSimpleName(), msg); h.handleMessage(-1, (byte) 0x01, msg, m_feature, port); } return false; } } /** * Drop all incoming messages silently */ public static class NoOpDispatcher extends MessageDispatcher { NoOpDispatcher(DeviceFeature f) { super(f); } @Override public boolean dispatch(Msg msg, String port) { return false; } } /** * Factory method for creating a dispatcher of a given name using java reflection * * @param name the name of the dispatcher to create * @param params * @param f the feature for which to create the dispatcher * @return the handler which was created */ public static <T extends MessageDispatcher> T s_makeHandler(String name, HashMap<String, String> params, DeviceFeature f) { String cname = MessageDispatcher.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 dispatcher: {}", name, e); } return null; } }