2 * Copyright (c) 2010-2021 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 public abstract class MessageDispatcher {
35 private static final Logger logger = LoggerFactory.getLogger(MessageDispatcher.class);
37 DeviceFeature feature;
39 Map<String, String> parameters = new HashMap<>();
44 * @param f DeviceFeature to which this MessageDispatcher belongs
46 MessageDispatcher(DeviceFeature f) {
50 public void setParameters(@Nullable Map<String, String> map) {
55 * Generic handling of incoming ALL LINK messages
57 * @param msg the message received
58 * @return true if the message was handled by this function
60 protected boolean handleAllLinkMessage(Msg msg) {
61 if (!msg.isAllLink()) {
65 InsteonAddress a = msg.getAddress("toAddress");
66 // ALL_LINK_BROADCAST and ALL_LINK_CLEANUP
67 // have a valid Command1 field
68 // but the CLEANUP_SUCCESS (of type ALL_LINK_BROADCAST!)
69 // message has cmd1 = 0x06 and the cmd as the
70 // high byte of the toAddress.
71 byte cmd1 = msg.getByte("command1");
72 if (!msg.isCleanup() && cmd1 == 0x06) {
73 cmd1 = a.getHighByte();
75 // For ALL_LINK_BROADCAST messages, the group is
76 // in the low byte of the toAddress. For direct
77 // ALL_LINK_CLEANUP, it is in Command2
79 int group = (msg.isCleanup() ? msg.getByte("command2") : a.getLowByte()) & 0xff;
80 MessageHandler h = feature.getMsgHandlers().get(cmd1 & 0xFF);
82 logger.debug("msg is not for this feature");
85 if (!h.isDuplicate(msg)) {
86 if (h.matchesGroup(group) && h.matches(msg)) {
87 logger.debug("{}:{}->{} cmd1:{} group {}/{}", feature.getDevice().getAddress(), feature.getName(),
88 h.getClass().getSimpleName(), Utils.getHexByte(cmd1), group, h.getGroup());
89 h.handleMessage(group, cmd1, msg, feature);
91 logger.debug("message ignored because matches group: {} matches filter: {}", h.matchesGroup(group),
95 logger.debug("message ignored as duplicate. Matches group: {} matches filter: {}",
96 h.matchesGroup(group), h.matches(msg));
98 } catch (FieldException e) {
99 logger.warn("couldn't parse ALL_LINK message: {}", msg, e);
105 * Checks if this message is in response to previous query by this feature
110 boolean isMyDirectAck(Msg msg) {
111 return msg.isAckOfDirect() && (feature.getQueryStatus() == DeviceFeature.QueryStatus.QUERY_PENDING)
112 && feature.getDevice().getFeatureQueried() == feature;
118 * @param msg Message to dispatch
119 * @return true if this message was found to be a reply to a direct message,
120 * and was claimed by one of the handlers
122 public abstract boolean dispatch(Msg msg);
126 // ------------ implementations of MessageDispatchers start here ------------------
130 public static class DefaultDispatcher extends MessageDispatcher {
131 DefaultDispatcher(DeviceFeature f) {
136 public boolean dispatch(Msg msg) {
139 boolean isConsumed = false;
142 cmd = msg.getByte("Cmd");
143 cmd1 = msg.getByte("command1");
144 } catch (FieldException e) {
145 logger.debug("no command found, dropping msg {}", msg);
148 if (msg.isAllLinkCleanupAckOrNack()) {
149 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
150 // in response to a direct status query message
153 if (handleAllLinkMessage(msg)) {
156 if (msg.isAckOfDirect()) {
157 // in the case of direct ack, the cmd1 code is useless.
158 // you have to know what message was sent before to
159 // interpret the reply message
160 if (isMyDirectAck(msg)) {
161 logger.debug("{}:{} DIRECT_ACK: q:{} cmd: {}", feature.getDevice().getAddress(), feature.getName(),
162 feature.getQueryStatus(), cmd);
165 // must be a reply to our message, tweak the cmd1 code!
166 logger.debug("changing key to 0x19 for msg {}", msg);
167 key = 0x19; // we have installed a handler under that command number
173 if (key != -1 || feature.isStatusFeature()) {
174 MessageHandler h = feature.getMsgHandlers().get(key);
176 h = feature.getDefaultMsgHandler();
178 if (h.matches(msg)) {
180 logger.debug("{}:{}->{} DIRECT", feature.getDevice().getAddress(), feature.getName(),
181 h.getClass().getSimpleName());
183 h.handleMessage(-1, cmd1, msg, feature);
187 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
188 logger.debug("defdisp: {}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
189 feature.getQueryStatus());
195 public static class DefaultGroupDispatcher extends MessageDispatcher {
196 DefaultGroupDispatcher(DeviceFeature f) {
201 public boolean dispatch(Msg msg) {
204 boolean isConsumed = false;
207 cmd = msg.getByte("Cmd");
208 cmd1 = msg.getByte("command1");
209 } catch (FieldException e) {
210 logger.debug("no command found, dropping msg {}", msg);
213 if (msg.isAllLinkCleanupAckOrNack()) {
214 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
215 // in response to a direct status query message
218 if (handleAllLinkMessage(msg)) {
221 if (msg.isAckOfDirect()) {
222 // in the case of direct ack, the cmd1 code is useless.
223 // you have to know what message was sent before to
224 // interpret the reply message
225 if (isMyDirectAck(msg)) {
226 logger.debug("{}:{} qs:{} cmd: {}", feature.getDevice().getAddress(), feature.getName(),
227 feature.getQueryStatus(), cmd);
230 // must be a reply to our message, tweak the cmd1 code!
231 logger.debug("changing key to 0x19 for msg {}", msg);
232 key = 0x19; // we have installed a handler under that command number
239 for (DeviceFeature f : feature.getConnectedFeatures()) {
240 MessageHandler h = f.getMsgHandlers().get(key);
242 h = f.getDefaultMsgHandler();
244 if (h.matches(msg)) {
246 logger.debug("{}:{}->{} DIRECT", f.getDevice().getAddress(), f.getName(),
247 h.getClass().getSimpleName());
249 h.handleMessage(-1, cmd1, msg, f);
255 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
256 logger.debug("{}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
257 feature.getQueryStatus());
263 public static class PollGroupDispatcher extends MessageDispatcher {
264 PollGroupDispatcher(DeviceFeature f) {
269 public boolean dispatch(Msg msg) {
270 if (msg.isAllLinkCleanupAckOrNack()) {
271 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
272 // in response to a direct status query message
275 if (handleAllLinkMessage(msg)) {
278 if (msg.isAckOfDirect()) {
279 boolean isMyAck = isMyDirectAck(msg);
281 logger.debug("{}:{} got poll ACK", feature.getDevice().getAddress(), feature.getName());
285 return (false); // not a direct ack, so we didn't consume it either
289 public static class SimpleDispatcher extends MessageDispatcher {
290 SimpleDispatcher(DeviceFeature f) {
295 public boolean dispatch(Msg msg) {
298 if (handleAllLinkMessage(msg)) {
301 if (msg.isAllLinkCleanupAckOrNack()) {
302 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
303 // in response to a direct status query message
306 cmd1 = msg.getByte("command1");
307 } catch (FieldException e) {
308 logger.debug("no cmd1 found, dropping msg {}", msg);
311 boolean isConsumed = isMyDirectAck(msg);
312 int key = (cmd1 & 0xFF);
313 MessageHandler h = feature.getMsgHandlers().get(key);
315 h = feature.getDefaultMsgHandler();
317 if (h.matches(msg)) {
318 logger.trace("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
319 h.getClass().getSimpleName(), msg);
320 h.handleMessage(-1, cmd1, msg, feature);
326 public static class X10Dispatcher extends MessageDispatcher {
327 X10Dispatcher(DeviceFeature f) {
332 public boolean dispatch(Msg msg) {
334 byte rawX10 = msg.getByte("rawX10");
335 int cmd = (rawX10 & 0x0f);
336 MessageHandler h = feature.getMsgHandlers().get(cmd);
338 h = feature.getDefaultMsgHandler();
340 logger.debug("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
341 h.getClass().getSimpleName(), msg);
342 if (h.matches(msg)) {
343 h.handleMessage(-1, (byte) cmd, msg, feature);
345 } catch (FieldException e) {
346 logger.warn("error parsing {}: ", msg, e);
352 public static class PassThroughDispatcher extends MessageDispatcher {
353 PassThroughDispatcher(DeviceFeature f) {
358 public boolean dispatch(Msg msg) {
359 MessageHandler h = feature.getDefaultMsgHandler();
360 if (h.matches(msg)) {
361 logger.trace("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
362 h.getClass().getSimpleName(), msg);
363 h.handleMessage(-1, (byte) 0x01, msg, feature);
370 * Drop all incoming messages silently
372 public static class NoOpDispatcher extends MessageDispatcher {
373 NoOpDispatcher(DeviceFeature f) {
378 public boolean dispatch(Msg msg) {
384 * Factory method for creating a dispatcher of a given name using java reflection
386 * @param name the name of the dispatcher to create
388 * @param f the feature for which to create the dispatcher
389 * @return the handler which was created
392 public static <T extends MessageDispatcher> T makeHandler(String name, @Nullable Map<String, String> params,
394 String cname = MessageDispatcher.class.getName() + "$" + name;
396 Class<?> c = Class.forName(cname);
397 @SuppressWarnings("unchecked")
398 Class<? extends T> dc = (Class<? extends T>) c;
400 T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f);
401 ch.setParameters(params);
403 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
404 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
405 logger.warn("error trying to create dispatcher: {}", name, e);