]> git.basschouten.com Git - openhab-addons.git/blob
1c7486aa546f0303bec579c7f3c33215ae101d55
[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 public abstract class MessageDispatcher {
35     private static final Logger logger = LoggerFactory.getLogger(MessageDispatcher.class);
36
37     DeviceFeature feature;
38     @Nullable
39     Map<String, String> parameters = new HashMap<>();
40
41     /**
42      * Constructor
43      *
44      * @param f DeviceFeature to which this MessageDispatcher belongs
45      */
46     MessageDispatcher(DeviceFeature f) {
47         feature = f;
48     }
49
50     public void setParameters(@Nullable Map<String, String> map) {
51         parameters = map;
52     }
53
54     /**
55      * Generic handling of incoming ALL LINK messages
56      *
57      * @param msg the message received
58      * @return true if the message was handled by this function
59      */
60     protected boolean handleAllLinkMessage(Msg msg) {
61         if (!msg.isAllLink()) {
62             return false;
63         }
64         try {
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();
74             }
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
78
79             int group = (msg.isCleanup() ? msg.getByte("command2") : a.getLowByte()) & 0xff;
80             MessageHandler h = feature.getMsgHandlers().get(cmd1 & 0xFF);
81             if (h == null) {
82                 logger.debug("msg is not for this feature");
83                 return true;
84             }
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);
90                 } else {
91                     logger.debug("message ignored because matches group: {} matches filter: {}", h.matchesGroup(group),
92                             h.matches(msg));
93                 }
94             } else {
95                 logger.debug("message ignored as duplicate. Matches group: {} matches filter: {}",
96                         h.matchesGroup(group), h.matches(msg));
97             }
98         } catch (FieldException e) {
99             logger.warn("couldn't parse ALL_LINK message: {}", msg, e);
100         }
101         return true;
102     }
103
104     /**
105      * Checks if this message is in response to previous query by this feature
106      *
107      * @param msg
108      * @return true;
109      */
110     boolean isMyDirectAck(Msg msg) {
111         return msg.isAckOfDirect() && (feature.getQueryStatus() == DeviceFeature.QueryStatus.QUERY_PENDING)
112                 && feature.getDevice().getFeatureQueried() == feature;
113     }
114
115     /**
116      * Dispatches message
117      *
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
121      */
122     public abstract boolean dispatch(Msg msg);
123
124     //
125     //
126     // ------------ implementations of MessageDispatchers start here ------------------
127     //
128     //
129
130     public static class DefaultDispatcher extends MessageDispatcher {
131         DefaultDispatcher(DeviceFeature f) {
132             super(f);
133         }
134
135         @Override
136         public boolean dispatch(Msg msg) {
137             byte cmd = 0x00;
138             byte cmd1 = 0x00;
139             boolean isConsumed = false;
140             int key = -1;
141             try {
142                 cmd = msg.getByte("Cmd");
143                 cmd1 = msg.getByte("command1");
144             } catch (FieldException e) {
145                 logger.debug("no command found, dropping msg {}", msg);
146                 return false;
147             }
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
151                 return false;
152             }
153             if (handleAllLinkMessage(msg)) {
154                 return false;
155             }
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);
163                     isConsumed = true;
164                     if (cmd == 0x50) {
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
168                     }
169                 }
170             } else {
171                 key = (cmd1 & 0xFF);
172             }
173             if (key != -1 || feature.isStatusFeature()) {
174                 MessageHandler h = feature.getMsgHandlers().get(key);
175                 if (h == null) {
176                     h = feature.getDefaultMsgHandler();
177                 }
178                 if (h.matches(msg)) {
179                     if (!isConsumed) {
180                         logger.debug("{}:{}->{} DIRECT", feature.getDevice().getAddress(), feature.getName(),
181                                 h.getClass().getSimpleName());
182                     }
183                     h.handleMessage(-1, cmd1, msg, feature);
184                 }
185             }
186             if (isConsumed) {
187                 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
188                 logger.debug("defdisp: {}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
189                         feature.getQueryStatus());
190             }
191             return isConsumed;
192         }
193     }
194
195     public static class DefaultGroupDispatcher extends MessageDispatcher {
196         DefaultGroupDispatcher(DeviceFeature f) {
197             super(f);
198         }
199
200         @Override
201         public boolean dispatch(Msg msg) {
202             byte cmd = 0x00;
203             byte cmd1 = 0x00;
204             boolean isConsumed = false;
205             int key = -1;
206             try {
207                 cmd = msg.getByte("Cmd");
208                 cmd1 = msg.getByte("command1");
209             } catch (FieldException e) {
210                 logger.debug("no command found, dropping msg {}", msg);
211                 return false;
212             }
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
216                 return false;
217             }
218             if (handleAllLinkMessage(msg)) {
219                 return false;
220             }
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);
228                     isConsumed = true;
229                     if (cmd == 0x50) {
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
233                     }
234                 }
235             } else {
236                 key = (cmd1 & 0xFF);
237             }
238             if (key != -1) {
239                 for (DeviceFeature f : feature.getConnectedFeatures()) {
240                     MessageHandler h = f.getMsgHandlers().get(key);
241                     if (h == null) {
242                         h = f.getDefaultMsgHandler();
243                     }
244                     if (h.matches(msg)) {
245                         if (!isConsumed) {
246                             logger.debug("{}:{}->{} DIRECT", f.getDevice().getAddress(), f.getName(),
247                                     h.getClass().getSimpleName());
248                         }
249                         h.handleMessage(-1, cmd1, msg, f);
250                     }
251
252                 }
253             }
254             if (isConsumed) {
255                 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
256                 logger.debug("{}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
257                         feature.getQueryStatus());
258             }
259             return isConsumed;
260         }
261     }
262
263     public static class PollGroupDispatcher extends MessageDispatcher {
264         PollGroupDispatcher(DeviceFeature f) {
265             super(f);
266         }
267
268         @Override
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
273                 return false;
274             }
275             if (handleAllLinkMessage(msg)) {
276                 return false;
277             }
278             if (msg.isAckOfDirect()) {
279                 boolean isMyAck = isMyDirectAck(msg);
280                 if (isMyAck) {
281                     logger.debug("{}:{} got poll ACK", feature.getDevice().getAddress(), feature.getName());
282                 }
283                 return (isMyAck);
284             }
285             return (false); // not a direct ack, so we didn't consume it either
286         }
287     }
288
289     public static class SimpleDispatcher extends MessageDispatcher {
290         SimpleDispatcher(DeviceFeature f) {
291             super(f);
292         }
293
294         @Override
295         public boolean dispatch(Msg msg) {
296             byte cmd1 = 0x00;
297             try {
298                 if (handleAllLinkMessage(msg)) {
299                     return false;
300                 }
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
304                     return false;
305                 }
306                 cmd1 = msg.getByte("command1");
307             } catch (FieldException e) {
308                 logger.debug("no cmd1 found, dropping msg {}", msg);
309                 return false;
310             }
311             boolean isConsumed = isMyDirectAck(msg);
312             int key = (cmd1 & 0xFF);
313             MessageHandler h = feature.getMsgHandlers().get(key);
314             if (h == null) {
315                 h = feature.getDefaultMsgHandler();
316             }
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);
321             }
322             return isConsumed;
323         }
324     }
325
326     public static class X10Dispatcher extends MessageDispatcher {
327         X10Dispatcher(DeviceFeature f) {
328             super(f);
329         }
330
331         @Override
332         public boolean dispatch(Msg msg) {
333             try {
334                 byte rawX10 = msg.getByte("rawX10");
335                 int cmd = (rawX10 & 0x0f);
336                 MessageHandler h = feature.getMsgHandlers().get(cmd);
337                 if (h == null) {
338                     h = feature.getDefaultMsgHandler();
339                 }
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);
344                 }
345             } catch (FieldException e) {
346                 logger.warn("error parsing {}: ", msg, e);
347             }
348             return false;
349         }
350     }
351
352     public static class PassThroughDispatcher extends MessageDispatcher {
353         PassThroughDispatcher(DeviceFeature f) {
354             super(f);
355         }
356
357         @Override
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);
364             }
365             return false;
366         }
367     }
368
369     /**
370      * Drop all incoming messages silently
371      */
372     public static class NoOpDispatcher extends MessageDispatcher {
373         NoOpDispatcher(DeviceFeature f) {
374             super(f);
375         }
376
377         @Override
378         public boolean dispatch(Msg msg) {
379             return false;
380         }
381     }
382
383     /**
384      * Factory method for creating a dispatcher of a given name using java reflection
385      *
386      * @param name the name of the dispatcher to create
387      * @param params
388      * @param f the feature for which to create the dispatcher
389      * @return the handler which was created
390      */
391     @Nullable
392     public static <T extends MessageDispatcher> T makeHandler(String name, @Nullable Map<String, String> params,
393             DeviceFeature f) {
394         String cname = MessageDispatcher.class.getName() + "$" + name;
395         try {
396             Class<?> c = Class.forName(cname);
397             @SuppressWarnings("unchecked")
398             Class<? extends T> dc = (Class<? extends T>) c;
399             @Nullable
400             T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f);
401             ch.setParameters(params);
402             return ch;
403         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
404                 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
405             logger.warn("error trying to create dispatcher: {}", name, e);
406         }
407         return null;
408     }
409 }