]> git.basschouten.com Git - openhab-addons.git/blob
b942887a5c5f3f98dbf0ac55d430ef8737aa3173
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.globalcache.internal.command;
14
15 import java.util.concurrent.LinkedBlockingQueue;
16 import java.util.concurrent.TimeUnit;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 import org.openhab.binding.globalcache.internal.GlobalCacheBindingConstants;
21 import org.openhab.binding.globalcache.internal.GlobalCacheBindingConstants.CommandType;
22 import org.openhab.binding.globalcache.internal.handler.GlobalCacheHandler;
23 import org.openhab.core.thing.Thing;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 /**
28  * The {@link AbstractCommand} class implements the basic functionality needed for all GlobalCache commands.
29  *
30  * @author Mark Hilbush - Initial contribution
31  */
32 public abstract class AbstractCommand implements CommandInterface {
33     private final Logger logger = LoggerFactory.getLogger(AbstractCommand.class);
34
35     private LinkedBlockingQueue<RequestMessage> requestQueue;
36
37     private final int RESPONSE_QUEUE_MAX_DEPTH = 1;
38     private final int RESPONSE_QUEUE_TIMEOUT = 3000;
39
40     protected Thing thing;
41
42     protected String ipAddress;
43
44     // Actual command strings sent to/received from the device (without CR)
45     protected String deviceCommand;
46     protected String deviceReply;
47
48     // Short human-readable name of the command
49     protected String commandName;
50
51     // Determines which TCP port will be used for the command
52     private CommandType commandType;
53
54     protected String module;
55     protected String connector;
56
57     protected String errorModule;
58     protected String errorConnector;
59     protected String errorCode;
60     protected String errorMessage;
61
62     private boolean isQuiet;
63
64     /*
65      * The {@link AbstractCommand} abstract class is the basis for all GlobalCache device command classes.
66      *
67      * @author Mark Hilbush - Initial contribution
68      */
69     public AbstractCommand(Thing t, LinkedBlockingQueue<RequestMessage> q, String n, CommandType c) {
70         thing = t;
71         requestQueue = q;
72         commandName = n;
73         commandType = c;
74         setQuiet(false);
75
76         module = null;
77         connector = null;
78         deviceCommand = null;
79         deviceReply = null;
80         errorCode = null;
81         errorMessage = null;
82
83         ipAddress = ((GlobalCacheHandler) thing.getHandler()).getIP();
84     }
85
86     public String getCommandName() {
87         return commandName;
88     }
89
90     @Override
91     public String getModule() {
92         return module;
93     }
94
95     public void setModule(String m) {
96         module = m;
97     }
98
99     @Override
100     public String getConnector() {
101         return connector;
102     }
103
104     public void setConnector(String c) {
105         connector = c;
106     }
107
108     public void setCommandType(CommandType commandType) {
109         this.commandType = commandType;
110     }
111
112     public boolean isGC100Model12() {
113         return thing.getThingTypeUID().equals(GlobalCacheBindingConstants.THING_TYPE_GC_100_12);
114     }
115
116     @Override
117     public abstract void parseSuccessfulReply();
118
119     public boolean isSuccessful() {
120         return errorCode == null ? true : false;
121     }
122
123     public String successAsString() {
124         return errorCode == null ? "succeeded" : "failed";
125     }
126
127     public String getErrorCode() {
128         return errorCode;
129     }
130
131     public String getErrorMessage() {
132         return errorMessage;
133     }
134
135     public String getErrorModule() {
136         return errorModule;
137     }
138
139     public String getErrorConnector() {
140         return errorConnector;
141     }
142
143     protected boolean isQuiet() {
144         return isQuiet;
145     }
146
147     private void setQuiet(boolean quiet) {
148         isQuiet = quiet;
149     }
150
151     /*
152      * Execute a GlobalCache device command
153      */
154     public void executeQuiet() {
155         setQuiet(true);
156         execute();
157     }
158
159     public void execute() {
160         if (requestQueue == null) {
161             createGenericError("Execute method was called with a null requestQueue");
162             return;
163         }
164
165         if (deviceCommand == null) {
166             createGenericError("Execute method was called with a null deviceCommand");
167             return;
168         }
169
170         if (thing == null) {
171             createGenericError("Execute method was called with a null thing");
172             return;
173         }
174
175         // Send command & get response
176         if (sendCommand()) {
177             parseSuccessfulReply();
178             if (!isQuiet()) {
179                 logSuccess();
180             }
181         } else {
182             if (!isQuiet()) {
183                 logFailure();
184             }
185         }
186         return;
187     }
188
189     /*
190      * Place a request message onto the request queue, then wait on the response queue for the
191      * response message. The CommandHandler private class in GlobalCacheHandler.java
192      * is responsible for the actual device interaction.
193      */
194     private boolean sendCommand() {
195         // Create a response queue. The command processor will use this queue to return the device's reply.
196         LinkedBlockingQueue<ResponseMessage> responseQueue = new LinkedBlockingQueue<>(RESPONSE_QUEUE_MAX_DEPTH);
197
198         // Create the request message
199         RequestMessage requestMsg = new RequestMessage(commandName, commandType, deviceCommand, responseQueue);
200
201         try {
202             // Put the request message on the request queue
203             requestQueue.put(requestMsg);
204             logger.trace("Put request on queue (depth={}), sent command '{}'", requestQueue.size(), deviceCommand);
205
206             // Wait on the response queue for the response message
207             ResponseMessage responseMsg = responseQueue.poll(RESPONSE_QUEUE_TIMEOUT, TimeUnit.MILLISECONDS);
208
209             if (responseMsg == null) {
210                 createGenericError("Timed out waiting on response queue for message");
211                 return false;
212             }
213
214             deviceReply = responseMsg.getDeviceReply();
215             logger.trace("Got response message off response queue, received reply '{}'", deviceReply);
216
217             if (isErrorReply(deviceReply)) {
218                 return false;
219             }
220
221         } catch (InterruptedException e) {
222             createGenericError("Poll of response queue was interrupted");
223             return false;
224         }
225
226         return true;
227     }
228
229     /*
230      * Parse the reply and set error values if an error occurred.
231      */
232     private boolean isErrorReply(String reply) {
233         logger.trace("Checking device reply for error condition: {}", reply);
234
235         Pattern pattern;
236         Matcher matcher;
237
238         // Generic (generated by binding) errors are of the form
239         // ERROR: message
240         if (reply.startsWith("ERROR:")) {
241             createGenericError(reply);
242             return true;
243         }
244
245         // iTach response for unknown command are of the form
246         // unknowncommand,eee, where eee is the error number
247         if (reply.startsWith("unknowncommand,") && reply.length() >= 16) {
248             String eCode = reply.substring(15);
249             createGenericError("Device does not understand command, error code is " + eCode);
250             errorCode = eCode;
251             return true;
252         }
253
254         // GC-100 response for unknown command are of the form
255         // unknowncommand ee, where ee is the error number
256         if (reply.startsWith("unknowncommand ") && reply.length() >= 16) {
257             errorModule = "";
258             errorConnector = "";
259             errorCode = reply.substring(15);
260             errorMessage = lookupErrorMessage(errorCode, GC100_ERROR_MESSAGES);
261             logger.debug("Device reply indicates GC-100 error condition");
262             return true;
263         }
264
265         // iTach error replies are of the form ERR_m:c,eee, where m is the module number,
266         // c is the connector number, and eee is the error number
267         pattern = Pattern.compile("ERR_[0-9]:[0-3],\\d\\d\\d");
268         matcher = pattern.matcher(reply);
269         if (matcher.find()) {
270             errorModule = reply.substring(4, 5);
271             errorConnector = reply.substring(6, 7);
272             errorCode = reply.substring(8, 11);
273             errorMessage = lookupErrorMessage(errorCode, ITACH_ERROR_MESSAGES);
274             logger.debug("Device reply indicates iTach error condition");
275             return true;
276         }
277
278         // Flex general error replies are of the form ERR eee, where eee is the error number
279         pattern = Pattern.compile("ERR \\d\\d\\d");
280         matcher = pattern.matcher(reply);
281         if (matcher.find()) {
282             errorModule = "";
283             errorConnector = "";
284             errorCode = reply.substring(4);
285             errorMessage = lookupErrorMessage(errorCode, FLEX_ERROR_MESSAGES);
286             logger.debug("Device reply indicates Flex general error condition");
287             return true;
288         }
289
290         // Flex infrared error replies are of the form ERR IReee, where eee is the error number
291         pattern = Pattern.compile("ERR IR\\d\\d\\d");
292         matcher = pattern.matcher(reply);
293         if (matcher.find()) {
294             errorModule = "";
295             errorConnector = "";
296             errorCode = reply.substring(6);
297             errorMessage = lookupErrorMessage(errorCode, FLEX_IR_ERROR_MESSAGES);
298             logger.debug("Device reply indicates Flex IR error condition");
299             return true;
300         }
301
302         // Flex serial error replies are of the form ERR SLeee, where eee is the error number
303         pattern = Pattern.compile("ERR SL\\d\\d\\d");
304         matcher = pattern.matcher(reply);
305         if (matcher.find()) {
306             errorModule = "";
307             errorConnector = "";
308             errorCode = reply.substring(6);
309             errorMessage = lookupErrorMessage(errorCode, FLEX_SL_ERROR_MESSAGES);
310             logger.debug("Device reply indicates Flex SL error condition");
311             return true;
312         }
313
314         errorCode = null;
315         return false;
316     }
317
318     private void createGenericError(String s) {
319         errorModule = "N/A";
320         errorConnector = "N/A";
321         errorCode = "N/A";
322         errorMessage = s;
323     }
324
325     private String lookupErrorMessage(String errorCode, String[] errorMessageArray) {
326         int eCode;
327         try {
328             eCode = Integer.parseInt(errorCode);
329
330         } catch (NumberFormatException e) {
331             eCode = 0;
332             logger.debug("Badly formatted error code '{}' received from device: {}", errorCode, e.getMessage());
333         }
334
335         if (eCode < 1 || eCode > errorMessageArray.length) {
336             eCode = 0;
337         }
338         return errorMessageArray[eCode];
339     }
340
341     /*
342      * Errors returned by GlobalCache iTach devices
343      */
344     private static final String[] ITACH_ERROR_MESSAGES = {
345             // 0
346             "Unknown error",
347             // 1
348             "Invalid command. Command not found.",
349             // 2
350             "Invalid module address (does not exist).",
351             // 3
352             "Invalid connector address (does not exist).",
353             // 4
354             "Invalid ID value.",
355             // 5
356             "Invalid frequency value",
357             // 6
358             "Invalid repeat value.",
359             // 7
360             "Invalid offset value.",
361             // 8
362             "Invalid pulse count.",
363             // 9
364             "Invalid pulse data.",
365             // 10
366             "Uneven amount of <on|off> statements.",
367             // 11
368             "No carriage return found.",
369             // 12
370             "Repeat count exceeded.",
371             // 13
372             "IR command sent to input connector.",
373             // 14
374             "Blaster command sent to non-blaster connector.",
375             // 15
376             "No carriage return before buffer full.",
377             // 16
378             "No carriage return.",
379             // 17
380             "Bad command syntax.",
381             // 18
382             "Sensor command sent to non-input connector.",
383             // 19
384             "Repeated IR transmission failure.",
385             // 20
386             "Above designated IR <on|off> pair limit.",
387             // 21
388             "Symbol odd boundary.",
389             // 22
390             "Undefined symbol.",
391             // 23
392             "Unknown option.",
393             // 24
394             "Invalid baud rate setting.",
395             // 25
396             "Invalid flow control setting.",
397             // 26
398             "Invalid parity setting.",
399             // 27
400             "Settings are locked."
401             //
402     };
403
404     /*
405      * Errors returned by GlobalCache GC-100 devices
406      */
407     private static final String[] GC100_ERROR_MESSAGES = {
408             // 0
409             "Unknown error.",
410             // 1
411             "Time out occurred because <CR> not received. Request not processed",
412             // 2
413             "Invalid module address (module does not exist) received with getversion.",
414             // 3
415             "Invalid module address (module does not exist).",
416             // 4
417             "Invalid connector address.",
418             // 5
419             "Connector address 1 is set up as “sensor in” when attempting IR command.",
420             // 6
421             "Connector address 2 is set up as “sensor in” when attempting IR command.",
422             // 7
423             "Connector address 3 is set up as “sensor in” when attempting IR command.",
424             // 8
425             "Offset is set to an even transition number, but should be odd.",
426             // 9
427             "Maximum number of transitions exceeded (256 total allowed).",
428             // 10
429             "Number of transitions in the IR command is not even.",
430             // 11
431             "Contact closure command sent to a module that is not a relay.",
432             // 12
433             "Missing carriage return. All commands must end with a carriage return.",
434             // 13
435             "State was requested of an invalid connector address.",
436             // 14
437             "Command sent to the unit is not supported by the GC-100.",
438             // 15
439             "Maximum number of IR transitions exceeded. (SM_IR_INPROCESS)",
440             // 16
441             "Invalid number of IR transitions (must be an even number).",
442             // 17
443             "Unknown error.",
444             // 18
445             "Unknown error.",
446             // 19
447             "Unknown error.",
448             // 20
449             "Unknown error.",
450             // 21
451             "Attempted to send an IR command to a non-IR module.",
452             // 22
453             "Unknown error.",
454             // 23
455             "Command sent is not supported by this type of module."
456             //
457     };
458
459     /*
460      * General errors returned by Flex devices
461      */
462     private static final String[] FLEX_ERROR_MESSAGES = {
463             // 0
464             "Unknown error.",
465             // 1
466             "Invalid command.  Command not found.",
467             // 2
468             "Bad command syntax used with a known command.",
469             // 3
470             "Invalid connector address (does not exist).",
471             // 4
472             "No carriage return found.",
473             // 5
474             "Command not supported by current Flex Link Port setting.",
475             // 6
476             "Settings are locked.",
477             //
478     };
479
480     /*
481      * Infrared errors returned by Flex devices
482      */
483     private static final String[] FLEX_IR_ERROR_MESSAGES = {
484             // 0
485             "Unknown error.",
486             // 1
487             "Invalid ID value.",
488             // 2
489             "Invalid frequency.",
490             // 3
491             "Invalid repeat.",
492             // 4
493             "Invalid offset.",
494             // 5
495             "Invalid IR pulse value.",
496             // 6
497             "Odd amount of IR pulse values (must be even).",
498             // 7
499             "Maximum pulse pair limit exceeded.",
500             //
501     };
502
503     /*
504      * Serial errors returned by Flex devices
505      */
506     private static final String[] FLEX_SL_ERROR_MESSAGES = {
507             // 0
508             "Unknown error.",
509             // 1
510             "Invalid baud rate.",
511             // 2
512             "Invalid flow control setting.",
513             // 3
514             "Invalid parity setting.",
515             //
516     };
517 }