]> git.basschouten.com Git - openhab-addons.git/blob
3ecb4df4dae63082f073ff4896fb9a4c19dd8ed3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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     @SuppressWarnings("PMD.CompareObjectsWithEquals")
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     public static class DefaultDispatcher extends MessageDispatcher {
132         DefaultDispatcher(DeviceFeature f) {
133             super(f);
134         }
135
136         @Override
137         public boolean dispatch(Msg msg) {
138             byte cmd = 0x00;
139             byte cmd1 = 0x00;
140             boolean isConsumed = false;
141             int key = -1;
142             try {
143                 cmd = msg.getByte("Cmd");
144                 cmd1 = msg.getByte("command1");
145             } catch (FieldException e) {
146                 logger.debug("no command found, dropping msg {}", msg);
147                 return false;
148             }
149             if (msg.isAllLinkCleanupAckOrNack()) {
150                 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
151                 // in response to a direct status query message
152                 return false;
153             }
154             if (handleAllLinkMessage(msg)) {
155                 return false;
156             }
157             if (msg.isAckOfDirect()) {
158                 // in the case of direct ack, the cmd1 code is useless.
159                 // you have to know what message was sent before to
160                 // interpret the reply message
161                 if (isMyDirectAck(msg)) {
162                     logger.debug("{}:{} DIRECT_ACK: q:{} cmd: {}", feature.getDevice().getAddress(), feature.getName(),
163                             feature.getQueryStatus(), cmd);
164                     isConsumed = true;
165                     if (cmd == 0x50) {
166                         // must be a reply to our message, tweak the cmd1 code!
167                         logger.debug("changing key to 0x19 for msg {}", msg);
168                         key = 0x19; // we have installed a handler under that command number
169                     }
170                 }
171             } else {
172                 key = (cmd1 & 0xFF);
173             }
174             if (key != -1 || feature.isStatusFeature()) {
175                 MessageHandler h = feature.getMsgHandlers().get(key);
176                 if (h == null) {
177                     h = feature.getDefaultMsgHandler();
178                 }
179                 if (h.matches(msg)) {
180                     if (!isConsumed) {
181                         logger.debug("{}:{}->{} DIRECT", feature.getDevice().getAddress(), feature.getName(),
182                                 h.getClass().getSimpleName());
183                     }
184                     h.handleMessage(-1, cmd1, msg, feature);
185                 }
186             }
187             if (isConsumed) {
188                 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
189                 logger.debug("defdisp: {}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
190                         feature.getQueryStatus());
191             }
192             return isConsumed;
193         }
194     }
195
196     public static class DefaultGroupDispatcher extends MessageDispatcher {
197         DefaultGroupDispatcher(DeviceFeature f) {
198             super(f);
199         }
200
201         @Override
202         public boolean dispatch(Msg msg) {
203             byte cmd = 0x00;
204             byte cmd1 = 0x00;
205             boolean isConsumed = false;
206             int key = -1;
207             try {
208                 cmd = msg.getByte("Cmd");
209                 cmd1 = msg.getByte("command1");
210             } catch (FieldException e) {
211                 logger.debug("no command found, dropping msg {}", msg);
212                 return false;
213             }
214             if (msg.isAllLinkCleanupAckOrNack()) {
215                 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
216                 // in response to a direct status query message
217                 return false;
218             }
219             if (handleAllLinkMessage(msg)) {
220                 return false;
221             }
222             if (msg.isAckOfDirect()) {
223                 // in the case of direct ack, the cmd1 code is useless.
224                 // you have to know what message was sent before to
225                 // interpret the reply message
226                 if (isMyDirectAck(msg)) {
227                     logger.debug("{}:{} qs:{} cmd: {}", feature.getDevice().getAddress(), feature.getName(),
228                             feature.getQueryStatus(), cmd);
229                     isConsumed = true;
230                     if (cmd == 0x50) {
231                         // must be a reply to our message, tweak the cmd1 code!
232                         logger.debug("changing key to 0x19 for msg {}", msg);
233                         key = 0x19; // we have installed a handler under that command number
234                     }
235                 }
236             } else {
237                 key = (cmd1 & 0xFF);
238             }
239             if (key != -1) {
240                 for (DeviceFeature f : feature.getConnectedFeatures()) {
241                     MessageHandler h = f.getMsgHandlers().get(key);
242                     if (h == null) {
243                         h = f.getDefaultMsgHandler();
244                     }
245                     if (h.matches(msg)) {
246                         if (!isConsumed) {
247                             logger.debug("{}:{}->{} DIRECT", f.getDevice().getAddress(), f.getName(),
248                                     h.getClass().getSimpleName());
249                         }
250                         h.handleMessage(-1, cmd1, msg, f);
251                     }
252
253                 }
254             }
255             if (isConsumed) {
256                 feature.setQueryStatus(DeviceFeature.QueryStatus.QUERY_ANSWERED);
257                 logger.debug("{}:{} set status to: {}", feature.getDevice().getAddress(), feature.getName(),
258                         feature.getQueryStatus());
259             }
260             return isConsumed;
261         }
262     }
263
264     public static class PollGroupDispatcher extends MessageDispatcher {
265         PollGroupDispatcher(DeviceFeature f) {
266             super(f);
267         }
268
269         @Override
270         public boolean dispatch(Msg msg) {
271             if (msg.isAllLinkCleanupAckOrNack()) {
272                 // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
273                 // in response to a direct status query message
274                 return false;
275             }
276             if (handleAllLinkMessage(msg)) {
277                 return false;
278             }
279             if (msg.isAckOfDirect()) {
280                 boolean isMyAck = isMyDirectAck(msg);
281                 if (isMyAck) {
282                     logger.debug("{}:{} got poll ACK", feature.getDevice().getAddress(), feature.getName());
283                 }
284                 return (isMyAck);
285             }
286             return (false); // not a direct ack, so we didn't consume it either
287         }
288     }
289
290     public static class SimpleDispatcher extends MessageDispatcher {
291         SimpleDispatcher(DeviceFeature f) {
292             super(f);
293         }
294
295         @Override
296         public boolean dispatch(Msg msg) {
297             byte cmd1 = 0x00;
298             try {
299                 if (handleAllLinkMessage(msg)) {
300                     return false;
301                 }
302                 if (msg.isAllLinkCleanupAckOrNack()) {
303                     // Had cases when a KeypadLinc would send an ALL_LINK_CLEANUP_ACK
304                     // in response to a direct status query message
305                     return false;
306                 }
307                 cmd1 = msg.getByte("command1");
308             } catch (FieldException e) {
309                 logger.debug("no cmd1 found, dropping msg {}", msg);
310                 return false;
311             }
312             boolean isConsumed = isMyDirectAck(msg);
313             int key = (cmd1 & 0xFF);
314             MessageHandler h = feature.getMsgHandlers().get(key);
315             if (h == null) {
316                 h = feature.getDefaultMsgHandler();
317             }
318             if (h.matches(msg)) {
319                 logger.trace("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
320                         h.getClass().getSimpleName(), msg);
321                 h.handleMessage(-1, cmd1, msg, feature);
322             }
323             return isConsumed;
324         }
325     }
326
327     public static class X10Dispatcher extends MessageDispatcher {
328         X10Dispatcher(DeviceFeature f) {
329             super(f);
330         }
331
332         @Override
333         public boolean dispatch(Msg msg) {
334             try {
335                 byte rawX10 = msg.getByte("rawX10");
336                 int cmd = (rawX10 & 0x0f);
337                 MessageHandler h = feature.getMsgHandlers().get(cmd);
338                 if (h == null) {
339                     h = feature.getDefaultMsgHandler();
340                 }
341                 logger.debug("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
342                         h.getClass().getSimpleName(), msg);
343                 if (h.matches(msg)) {
344                     h.handleMessage(-1, (byte) cmd, msg, feature);
345                 }
346             } catch (FieldException e) {
347                 logger.warn("error parsing {}: ", msg, e);
348             }
349             return false;
350         }
351     }
352
353     public static class PassThroughDispatcher extends MessageDispatcher {
354         PassThroughDispatcher(DeviceFeature f) {
355             super(f);
356         }
357
358         @Override
359         public boolean dispatch(Msg msg) {
360             MessageHandler h = feature.getDefaultMsgHandler();
361             if (h.matches(msg)) {
362                 logger.trace("{}:{}->{} {}", feature.getDevice().getAddress(), feature.getName(),
363                         h.getClass().getSimpleName(), msg);
364                 h.handleMessage(-1, (byte) 0x01, msg, feature);
365             }
366             return false;
367         }
368     }
369
370     /**
371      * Drop all incoming messages silently
372      */
373     public static class NoOpDispatcher extends MessageDispatcher {
374         NoOpDispatcher(DeviceFeature f) {
375             super(f);
376         }
377
378         @Override
379         public boolean dispatch(Msg msg) {
380             return false;
381         }
382     }
383
384     /**
385      * Factory method for creating a dispatcher of a given name using java reflection
386      *
387      * @param name the name of the dispatcher to create
388      * @param params
389      * @param f the feature for which to create the dispatcher
390      * @return the handler which was created
391      */
392     @Nullable
393     public static <T extends MessageDispatcher> T makeHandler(String name, @Nullable Map<String, String> params,
394             DeviceFeature f) {
395         String cname = MessageDispatcher.class.getName() + "$" + name;
396         try {
397             Class<?> c = Class.forName(cname);
398             @SuppressWarnings("unchecked")
399             Class<? extends T> dc = (Class<? extends T>) c;
400             @Nullable
401             T ch = dc.getDeclaredConstructor(DeviceFeature.class).newInstance(f);
402             ch.setParameters(params);
403             return ch;
404         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
405                 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
406             logger.warn("error trying to create dispatcher: {}", name, e);
407         }
408         return null;
409     }
410 }