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.HashMap;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.insteon.internal.message.FieldException;
22 import org.openhab.binding.insteon.internal.message.Msg;
23 import org.openhab.binding.insteon.internal.utils.Utils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
28 * Does preprocessing of messages to decide which handler should be called.
30 * @author Bernd Pfrommer - Initial contribution
31 * @author Rob Nielsen - Port to openHAB 2 insteon binding
34 @SuppressWarnings("null")
35 public abstract class MessageDispatcher {
36 private static final Logger logger = LoggerFactory.getLogger(MessageDispatcher.class);
38 DeviceFeature feature;
40 Map<String, @Nullable String> parameters = new HashMap<>();
45 * @param f DeviceFeature to which this MessageDispatcher belongs
47 MessageDispatcher(DeviceFeature f) {
51 public void setParameters(@Nullable Map<String, @Nullable String> map) {
56 * Generic handling of incoming ALL LINK messages
58 * @param msg the message received
59 * @return true if the message was handled by this function
61 protected boolean handleAllLinkMessage(Msg msg) {
62 if (!msg.isAllLink()) {
66 InsteonAddress a = msg.getAddress("toAddress");
67 // ALL_LINK_BROADCAST and ALL_LINK_CLEANUP
68 // have a valid Command1 field
69 // but the CLEANUP_SUCCESS (of type ALL_LINK_BROADCAST!)
70 // message has cmd1 = 0x06 and the cmd as the
71 // high byte of the toAddress.
72 byte cmd1 = msg.getByte("command1");
73 if (!msg.isCleanup() && cmd1 == 0x06) {
74 cmd1 = a.getHighByte();
76 // For ALL_LINK_BROADCAST messages, the group is
77 // in the low byte of the toAddress. For direct
78 // ALL_LINK_CLEANUP, it is in Command2
80 int group = (msg.isCleanup() ? msg.getByte("command2") : a.getLowByte()) & 0xff;
81 MessageHandler h = feature.getMsgHandlers().get(cmd1 & 0xFF);
83 logger.debug("msg is not for this feature");
86 if (!h.isDuplicate(msg)) {
87 if (h.matchesGroup(group) && h.matches(msg)) {
88 logger.debug("{}:{}->{} cmd1:{} group {}/{}", feature.getDevice().getAddress(), feature.getName(),
89 h.getClass().getSimpleName(), Utils.getHexByte(cmd1), group, h.getGroup());
90 h.handleMessage(group, cmd1, msg, feature);
92 logger.debug("message ignored because matches group: {} matches filter: {}", h.matchesGroup(group),
96 logger.debug("message ignored as duplicate. Matches group: {} matches filter: {}",
97 h.matchesGroup(group), h.matches(msg));
99 } catch (FieldException e) {
100 logger.warn("couldn't parse ALL_LINK message: {}", msg, e);
106 * Checks if this message is in response to previous query by this feature
111 boolean isMyDirectAck(Msg msg) {
112 return msg.isAckOfDirect() && (feature.getQueryStatus() == DeviceFeature.QueryStatus.QUERY_PENDING)
113 && feature.getDevice().getFeatureQueried() == feature;
119 * @param msg Message to dispatch
120 * @return true if this message was found to be a reply to a direct message,
121 * and was claimed by one of the handlers
123 public abstract boolean dispatch(Msg msg);
127 // ------------ implementations of MessageDispatchers start here ------------------
132 public static class DefaultDispatcher extends MessageDispatcher {
133 DefaultDispatcher(DeviceFeature f) {
138 public boolean dispatch(Msg msg) {
141 boolean isConsumed = false;
144 cmd = msg.getByte("Cmd");
145 cmd1 = msg.getByte("command1");
146 } catch (FieldException e) {
147 logger.debug("no command found, dropping msg {}", msg);
150 if (msg.isAllLinkCleanupAckOrNack()) {
151 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
152 // in response to a direct status query message
155 if (handleAllLinkMessage(msg)) {
158 if (msg.isAckOfDirect()) {
159 // in the case of direct ack, the cmd1 code is useless.
160 // you have to know what message was sent before to
161 // interpret the reply message
162 if (isMyDirectAck(msg)) {
163 logger.debug("{}:{} DIRECT_ACK: q:{} cmd: {}", feature.getDevice().getAddress(), feature.getName(),
164 feature.getQueryStatus(), cmd);
167 // must be a reply to our message, tweak the cmd1 code!
168 logger.debug("changing key to 0x19 for msg {}", msg);
169 key = 0x19; // we have installed a handler under that command number
175 if (key != -1 || feature.isStatusFeature()) {
176 MessageHandler h = feature.getMsgHandlers().get(key);
178 h = feature.getDefaultMsgHandler();
180 if (h.matches(msg)) {
182 logger.debug("{}:{}->{} DIRECT", feature.getDevice().getAddress(), feature.getName(),
183 h.getClass().getSimpleName());
185 h.handleMessage(-1, cmd1, msg, feature);
189 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
190 logger.debug("defdisp: {}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
191 feature.getQueryStatus());
198 public static class DefaultGroupDispatcher extends MessageDispatcher {
199 DefaultGroupDispatcher(DeviceFeature f) {
204 public boolean dispatch(Msg msg) {
207 boolean isConsumed = false;
210 cmd = msg.getByte("Cmd");
211 cmd1 = msg.getByte("command1");
212 } catch (FieldException e) {
213 logger.debug("no command found, dropping msg {}", msg);
216 if (msg.isAllLinkCleanupAckOrNack()) {
217 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
218 // in response to a direct status query message
221 if (handleAllLinkMessage(msg)) {
224 if (msg.isAckOfDirect()) {
225 // in the case of direct ack, the cmd1 code is useless.
226 // you have to know what message was sent before to
227 // interpret the reply message
228 if (isMyDirectAck(msg)) {
229 logger.debug("{}:{} qs:{} cmd: {}", feature.getDevice().getAddress(), feature.getName(),
230 feature.getQueryStatus(), cmd);
233 // must be a reply to our message, tweak the cmd1 code!
234 logger.debug("changing key to 0x19 for msg {}", msg);
235 key = 0x19; // we have installed a handler under that command number
242 for (DeviceFeature f : feature.getConnectedFeatures()) {
243 MessageHandler h = f.getMsgHandlers().get(key);
245 h = f.getDefaultMsgHandler();
247 if (h.matches(msg)) {
249 logger.debug("{}:{}->{} DIRECT", f.getDevice().getAddress(), f.getName(),
250 h.getClass().getSimpleName());
252 h.handleMessage(-1, cmd1, msg, f);
258 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
259 logger.debug("{}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
260 feature.getQueryStatus());
267 public static class PollGroupDispatcher extends MessageDispatcher {
268 PollGroupDispatcher(DeviceFeature f) {
273 public boolean dispatch(Msg msg) {
274 if (msg.isAllLinkCleanupAckOrNack()) {
275 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
276 // in response to a direct status query message
279 if (handleAllLinkMessage(msg)) {
282 if (msg.isAckOfDirect()) {
283 boolean isMyAck = isMyDirectAck(msg);
285 logger.debug("{}:{} got poll ACK", feature.getDevice().getAddress(), feature.getName());
289 return (false); // not a direct ack, so we didn't consume it either
294 public static class SimpleDispatcher extends MessageDispatcher {
295 SimpleDispatcher(DeviceFeature f) {
300 public boolean dispatch(Msg msg) {
303 if (handleAllLinkMessage(msg)) {
306 if (msg.isAllLinkCleanupAckOrNack()) {
307 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
308 // in response to a direct status query message
311 cmd1 = msg.getByte("command1");
312 } catch (FieldException e) {
313 logger.debug("no cmd1 found, dropping msg {}", msg);
316 boolean isConsumed = isMyDirectAck(msg);
317 int key = (cmd1 & 0xFF);
318 MessageHandler h = feature.getMsgHandlers().get(key);
320 h = feature.getDefaultMsgHandler();
322 if (h.matches(msg)) {
323 logger.trace("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
324 h.getClass().getSimpleName(), msg);
325 h.handleMessage(-1, cmd1, msg, feature);
332 public static class X10Dispatcher extends MessageDispatcher {
333 X10Dispatcher(DeviceFeature f) {
338 public boolean dispatch(Msg msg) {
340 byte rawX10 = msg.getByte("rawX10");
341 int cmd = (rawX10 & 0x0f);
342 MessageHandler h = feature.getMsgHandlers().get(cmd);
344 h = feature.getDefaultMsgHandler();
346 logger.debug("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
347 h.getClass().getSimpleName(), msg);
348 if (h.matches(msg)) {
349 h.handleMessage(-1, (byte) cmd, msg, feature);
351 } catch (FieldException e) {
352 logger.warn("error parsing {}: ", msg, e);
359 public static class PassThroughDispatcher extends MessageDispatcher {
360 PassThroughDispatcher(DeviceFeature f) {
365 public boolean dispatch(Msg msg) {
366 MessageHandler h = feature.getDefaultMsgHandler();
367 if (h.matches(msg)) {
368 logger.trace("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
369 h.getClass().getSimpleName(), msg);
370 h.handleMessage(-1, (byte) 0x01, msg, feature);
377 * Drop all incoming messages silently
380 public static class NoOpDispatcher extends MessageDispatcher {
381 NoOpDispatcher(DeviceFeature f) {
386 public boolean dispatch(Msg msg) {
392 * Factory method for creating a dispatcher of a given name using java reflection
394 * @param name the name of the dispatcher to create
396 * @param f the feature for which to create the dispatcher
397 * @return the handler which was created
400 public static <T extends MessageDispatcher> T makeHandler(String name,
401 @Nullable Map<String, @Nullable String> params, DeviceFeature f) {
402 String cname = MessageDispatcher.class.getName() + "$" + name;
404 Class<?> c = Class.forName(cname);
405 @SuppressWarnings("unchecked")
406 Class<? extends T> dc = (Class<? extends T>) c;
407 T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f);
408 ch.setParameters(params);
410 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
411 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
412 logger.warn("error trying to create dispatcher: {}", name, e);