]> git.basschouten.com Git - openhab-addons.git/blob
b83dd75b574cd3c6b64874aac193b9d546cac3cf
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.insteon.internal.device;
14
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.HashMap;
17 import java.util.Map;
18
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;
26
27 /**
28  * Does preprocessing of messages to decide which handler should be called.
29  *
30  * @author Bernd Pfrommer - Initial contribution
31  * @author Rob Nielsen - Port to openHAB 2 insteon binding
32  */
33 @NonNullByDefault
34 @SuppressWarnings("null")
35 public abstract class MessageDispatcher {
36     private static final Logger logger = LoggerFactory.getLogger(MessageDispatcher.class);
37
38     DeviceFeature feature;
39     @Nullable
40     Map<String, String> parameters = new HashMap<>();
41
42     /**
43      * Constructor
44      *
45      * @param f DeviceFeature to which this MessageDispatcher belongs
46      */
47     MessageDispatcher(DeviceFeature f) {
48         feature = f;
49     }
50
51     public void setParameters(@Nullable Map<String, String> map) {
52         parameters = map;
53     }
54
55     /**
56      * Generic handling of incoming ALL LINK messages
57      *
58      * @param msg the message received
59      * @return true if the message was handled by this function
60      */
61     protected boolean handleAllLinkMessage(Msg msg) {
62         if (!msg.isAllLink()) {
63             return false;
64         }
65         try {
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();
75             }
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
79
80             int group = (msg.isCleanup() ? msg.getByte("command2") : a.getLowByte()) & 0xff;
81             MessageHandler h = feature.getMsgHandlers().get(cmd1 & 0xFF);
82             if (h == null) {
83                 logger.debug("msg is not for this feature");
84                 return true;
85             }
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);
91                 } else {
92                     logger.debug("message ignored because matches group: {} matches filter: {}", h.matchesGroup(group),
93                             h.matches(msg));
94                 }
95             } else {
96                 logger.debug("message ignored as duplicate. Matches group: {} matches filter: {}",
97                         h.matchesGroup(group), h.matches(msg));
98             }
99         } catch (FieldException e) {
100             logger.warn("couldn't parse ALL_LINK message: {}", msg, e);
101         }
102         return true;
103     }
104
105     /**
106      * Checks if this message is in response to previous query by this feature
107      *
108      * @param msg
109      * @return true;
110      */
111     boolean isMyDirectAck(Msg msg) {
112         return msg.isAckOfDirect() && (feature.getQueryStatus() == DeviceFeature.QueryStatus.QUERY_PENDING)
113                 && feature.getDevice().getFeatureQueried() == feature;
114     }
115
116     /**
117      * Dispatches message
118      *
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
122      */
123     public abstract boolean dispatch(Msg msg);
124
125     //
126     //
127     // ------------ implementations of MessageDispatchers start here ------------------
128     //
129     //
130
131     @NonNullByDefault
132     public static class DefaultDispatcher extends MessageDispatcher {
133         DefaultDispatcher(DeviceFeature f) {
134             super(f);
135         }
136
137         @Override
138         public boolean dispatch(Msg msg) {
139             byte cmd = 0x00;
140             byte cmd1 = 0x00;
141             boolean isConsumed = false;
142             int key = -1;
143             try {
144                 cmd = msg.getByte("Cmd");
145                 cmd1 = msg.getByte("command1");
146             } catch (FieldException e) {
147                 logger.debug("no command found, dropping msg {}", msg);
148                 return false;
149             }
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
153                 return false;
154             }
155             if (handleAllLinkMessage(msg)) {
156                 return false;
157             }
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);
165                     isConsumed = true;
166                     if (cmd == 0x50) {
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
170                     }
171                 }
172             } else {
173                 key = (cmd1 & 0xFF);
174             }
175             if (key != -1 || feature.isStatusFeature()) {
176                 MessageHandler h = feature.getMsgHandlers().get(key);
177                 if (h == null) {
178                     h = feature.getDefaultMsgHandler();
179                 }
180                 if (h.matches(msg)) {
181                     if (!isConsumed) {
182                         logger.debug("{}:{}->{} DIRECT", feature.getDevice().getAddress(), feature.getName(),
183                                 h.getClass().getSimpleName());
184                     }
185                     h.handleMessage(-1, cmd1, msg, feature);
186                 }
187             }
188             if (isConsumed) {
189                 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
190                 logger.debug("defdisp: {}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
191                         feature.getQueryStatus());
192             }
193             return isConsumed;
194         }
195     }
196
197     @NonNullByDefault
198     public static class DefaultGroupDispatcher extends MessageDispatcher {
199         DefaultGroupDispatcher(DeviceFeature f) {
200             super(f);
201         }
202
203         @Override
204         public boolean dispatch(Msg msg) {
205             byte cmd = 0x00;
206             byte cmd1 = 0x00;
207             boolean isConsumed = false;
208             int key = -1;
209             try {
210                 cmd = msg.getByte("Cmd");
211                 cmd1 = msg.getByte("command1");
212             } catch (FieldException e) {
213                 logger.debug("no command found, dropping msg {}", msg);
214                 return false;
215             }
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
219                 return false;
220             }
221             if (handleAllLinkMessage(msg)) {
222                 return false;
223             }
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);
231                     isConsumed = true;
232                     if (cmd == 0x50) {
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
236                     }
237                 }
238             } else {
239                 key = (cmd1 & 0xFF);
240             }
241             if (key != -1) {
242                 for (DeviceFeature f : feature.getConnectedFeatures()) {
243                     MessageHandler h = f.getMsgHandlers().get(key);
244                     if (h == null) {
245                         h = f.getDefaultMsgHandler();
246                     }
247                     if (h.matches(msg)) {
248                         if (!isConsumed) {
249                             logger.debug("{}:{}->{} DIRECT", f.getDevice().getAddress(), f.getName(),
250                                     h.getClass().getSimpleName());
251                         }
252                         h.handleMessage(-1, cmd1, msg, f);
253                     }
254
255                 }
256             }
257             if (isConsumed) {
258                 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
259                 logger.debug("{}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
260                         feature.getQueryStatus());
261             }
262             return isConsumed;
263         }
264     }
265
266     @NonNullByDefault
267     public static class PollGroupDispatcher extends MessageDispatcher {
268         PollGroupDispatcher(DeviceFeature f) {
269             super(f);
270         }
271
272         @Override
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
277                 return false;
278             }
279             if (handleAllLinkMessage(msg)) {
280                 return false;
281             }
282             if (msg.isAckOfDirect()) {
283                 boolean isMyAck = isMyDirectAck(msg);
284                 if (isMyAck) {
285                     logger.debug("{}:{} got poll ACK", feature.getDevice().getAddress(), feature.getName());
286                 }
287                 return (isMyAck);
288             }
289             return (false); // not a direct ack, so we didn't consume it either
290         }
291     }
292
293     @NonNullByDefault
294     public static class SimpleDispatcher extends MessageDispatcher {
295         SimpleDispatcher(DeviceFeature f) {
296             super(f);
297         }
298
299         @Override
300         public boolean dispatch(Msg msg) {
301             byte cmd1 = 0x00;
302             try {
303                 if (handleAllLinkMessage(msg)) {
304                     return false;
305                 }
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
309                     return false;
310                 }
311                 cmd1 = msg.getByte("command1");
312             } catch (FieldException e) {
313                 logger.debug("no cmd1 found, dropping msg {}", msg);
314                 return false;
315             }
316             boolean isConsumed = isMyDirectAck(msg);
317             int key = (cmd1 & 0xFF);
318             MessageHandler h = feature.getMsgHandlers().get(key);
319             if (h == null) {
320                 h = feature.getDefaultMsgHandler();
321             }
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);
326             }
327             return isConsumed;
328         }
329     }
330
331     @NonNullByDefault
332     public static class X10Dispatcher extends MessageDispatcher {
333         X10Dispatcher(DeviceFeature f) {
334             super(f);
335         }
336
337         @Override
338         public boolean dispatch(Msg msg) {
339             try {
340                 byte rawX10 = msg.getByte("rawX10");
341                 int cmd = (rawX10 & 0x0f);
342                 MessageHandler h = feature.getMsgHandlers().get(cmd);
343                 if (h == null) {
344                     h = feature.getDefaultMsgHandler();
345                 }
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);
350                 }
351             } catch (FieldException e) {
352                 logger.warn("error parsing {}: ", msg, e);
353             }
354             return false;
355         }
356     }
357
358     @NonNullByDefault
359     public static class PassThroughDispatcher extends MessageDispatcher {
360         PassThroughDispatcher(DeviceFeature f) {
361             super(f);
362         }
363
364         @Override
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);
371             }
372             return false;
373         }
374     }
375
376     /**
377      * Drop all incoming messages silently
378      */
379     @NonNullByDefault
380     public static class NoOpDispatcher extends MessageDispatcher {
381         NoOpDispatcher(DeviceFeature f) {
382             super(f);
383         }
384
385         @Override
386         public boolean dispatch(Msg msg) {
387             return false;
388         }
389     }
390
391     /**
392      * Factory method for creating a dispatcher of a given name using java reflection
393      *
394      * @param name the name of the dispatcher to create
395      * @param params
396      * @param f the feature for which to create the dispatcher
397      * @return the handler which was created
398      */
399     @Nullable
400     public static <T extends MessageDispatcher> T makeHandler(String name, @Nullable Map<String, String> params,
401             DeviceFeature f) {
402         String cname = MessageDispatcher.class.getName() + "$" + name;
403         try {
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);
409             return ch;
410         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
411                 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
412             logger.warn("error trying to create dispatcher: {}", name, e);
413         }
414         return null;
415     }
416 }