]> git.basschouten.com Git - openhab-addons.git/blob
5b88c3cfabd76c32403fc99ce7ade20dd1376308
[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         } catch (InterruptedException e) {
221             createGenericError("Poll of response queue was interrupted");
222             return false;
223         }
224
225         return true;
226     }
227
228     /*
229      * Parse the reply and set error values if an error occurred.
230      */
231     private boolean isErrorReply(String reply) {
232         logger.trace("Checking device reply for error condition: {}", reply);
233
234         Pattern pattern;
235         Matcher matcher;
236
237         // Generic (generated by binding) errors are of the form
238         // ERROR: message
239         if (reply.startsWith("ERROR:")) {
240             createGenericError(reply);
241             return true;
242         }
243
244         // iTach response for unknown command are of the form
245         // unknowncommand,eee, where eee is the error number
246         if (reply.startsWith("unknowncommand,") && reply.length() >= 16) {
247             String eCode = reply.substring(15);
248             createGenericError("Device does not understand command, error code is " + eCode);
249             errorCode = eCode;
250             return true;
251         }
252
253         // GC-100 response for unknown command are of the form
254         // unknowncommand ee, where ee is the error number
255         if (reply.startsWith("unknowncommand ") && reply.length() >= 16) {
256             errorModule = "";
257             errorConnector = "";
258             errorCode = reply.substring(15);
259             errorMessage = lookupErrorMessage(errorCode, GC100_ERROR_MESSAGES);
260             logger.debug("Device reply indicates GC-100 error condition");
261             return true;
262         }
263
264         // iTach error replies are of the form ERR_m:c,eee, where m is the module number,
265         // c is the connector number, and eee is the error number
266         pattern = Pattern.compile("ERR_[0-9]:[0-3],\\d\\d\\d");
267         matcher = pattern.matcher(reply);
268         if (matcher.find()) {
269             errorModule = reply.substring(4, 5);
270             errorConnector = reply.substring(6, 7);
271             errorCode = reply.substring(8, 11);
272             errorMessage = lookupErrorMessage(errorCode, ITACH_ERROR_MESSAGES);
273             logger.debug("Device reply indicates iTach error condition");
274             return true;
275         }
276
277         // Flex general error replies are of the form ERR eee, where eee is the error number
278         pattern = Pattern.compile("ERR \\d\\d\\d");
279         matcher = pattern.matcher(reply);
280         if (matcher.find()) {
281             errorModule = "";
282             errorConnector = "";
283             errorCode = reply.substring(4);
284             errorMessage = lookupErrorMessage(errorCode, FLEX_ERROR_MESSAGES);
285             logger.debug("Device reply indicates Flex general error condition");
286             return true;
287         }
288
289         // Flex infrared error replies are of the form ERR IReee, where eee is the error number
290         pattern = Pattern.compile("ERR IR\\d\\d\\d");
291         matcher = pattern.matcher(reply);
292         if (matcher.find()) {
293             errorModule = "";
294             errorConnector = "";
295             errorCode = reply.substring(6);
296             errorMessage = lookupErrorMessage(errorCode, FLEX_IR_ERROR_MESSAGES);
297             logger.debug("Device reply indicates Flex IR error condition");
298             return true;
299         }
300
301         // Flex serial error replies are of the form ERR SLeee, where eee is the error number
302         pattern = Pattern.compile("ERR SL\\d\\d\\d");
303         matcher = pattern.matcher(reply);
304         if (matcher.find()) {
305             errorModule = "";
306             errorConnector = "";
307             errorCode = reply.substring(6);
308             errorMessage = lookupErrorMessage(errorCode, FLEX_SL_ERROR_MESSAGES);
309             logger.debug("Device reply indicates Flex SL error condition");
310             return true;
311         }
312
313         errorCode = null;
314         return false;
315     }
316
317     private void createGenericError(String s) {
318         errorModule = "N/A";
319         errorConnector = "N/A";
320         errorCode = "N/A";
321         errorMessage = s;
322     }
323
324     private String lookupErrorMessage(String errorCode, String[] errorMessageArray) {
325         int eCode;
326         try {
327             eCode = Integer.parseInt(errorCode);
328         } catch (NumberFormatException e) {
329             eCode = 0;
330             logger.debug("Badly formatted error code '{}' received from device: {}", errorCode, e.getMessage());
331         }
332
333         if (eCode < 1 || eCode > errorMessageArray.length) {
334             eCode = 0;
335         }
336         return errorMessageArray[eCode];
337     }
338
339     /*
340      * Errors returned by GlobalCache iTach devices
341      */
342     private static final String[] ITACH_ERROR_MESSAGES = {
343             // 0
344             "Unknown error",
345             // 1
346             "Invalid command. Command not found.",
347             // 2
348             "Invalid module address (does not exist).",
349             // 3
350             "Invalid connector address (does not exist).",
351             // 4
352             "Invalid ID value.",
353             // 5
354             "Invalid frequency value",
355             // 6
356             "Invalid repeat value.",
357             // 7
358             "Invalid offset value.",
359             // 8
360             "Invalid pulse count.",
361             // 9
362             "Invalid pulse data.",
363             // 10
364             "Uneven amount of <on|off> statements.",
365             // 11
366             "No carriage return found.",
367             // 12
368             "Repeat count exceeded.",
369             // 13
370             "IR command sent to input connector.",
371             // 14
372             "Blaster command sent to non-blaster connector.",
373             // 15
374             "No carriage return before buffer full.",
375             // 16
376             "No carriage return.",
377             // 17
378             "Bad command syntax.",
379             // 18
380             "Sensor command sent to non-input connector.",
381             // 19
382             "Repeated IR transmission failure.",
383             // 20
384             "Above designated IR <on|off> pair limit.",
385             // 21
386             "Symbol odd boundary.",
387             // 22
388             "Undefined symbol.",
389             // 23
390             "Unknown option.",
391             // 24
392             "Invalid baud rate setting.",
393             // 25
394             "Invalid flow control setting.",
395             // 26
396             "Invalid parity setting.",
397             // 27
398             "Settings are locked."
399             //
400     };
401
402     /*
403      * Errors returned by GlobalCache GC-100 devices
404      */
405     private static final String[] GC100_ERROR_MESSAGES = {
406             // 0
407             "Unknown error.",
408             // 1
409             "Time out occurred because <CR> not received. Request not processed",
410             // 2
411             "Invalid module address (module does not exist) received with getversion.",
412             // 3
413             "Invalid module address (module does not exist).",
414             // 4
415             "Invalid connector address.",
416             // 5
417             "Connector address 1 is set up as “sensor in” when attempting IR command.",
418             // 6
419             "Connector address 2 is set up as “sensor in” when attempting IR command.",
420             // 7
421             "Connector address 3 is set up as “sensor in” when attempting IR command.",
422             // 8
423             "Offset is set to an even transition number, but should be odd.",
424             // 9
425             "Maximum number of transitions exceeded (256 total allowed).",
426             // 10
427             "Number of transitions in the IR command is not even.",
428             // 11
429             "Contact closure command sent to a module that is not a relay.",
430             // 12
431             "Missing carriage return. All commands must end with a carriage return.",
432             // 13
433             "State was requested of an invalid connector address.",
434             // 14
435             "Command sent to the unit is not supported by the GC-100.",
436             // 15
437             "Maximum number of IR transitions exceeded. (SM_IR_INPROCESS)",
438             // 16
439             "Invalid number of IR transitions (must be an even number).",
440             // 17
441             "Unknown error.",
442             // 18
443             "Unknown error.",
444             // 19
445             "Unknown error.",
446             // 20
447             "Unknown error.",
448             // 21
449             "Attempted to send an IR command to a non-IR module.",
450             // 22
451             "Unknown error.",
452             // 23
453             "Command sent is not supported by this type of module."
454             //
455     };
456
457     /*
458      * General errors returned by Flex devices
459      */
460     private static final String[] FLEX_ERROR_MESSAGES = {
461             // 0
462             "Unknown error.",
463             // 1
464             "Invalid command.  Command not found.",
465             // 2
466             "Bad command syntax used with a known command.",
467             // 3
468             "Invalid connector address (does not exist).",
469             // 4
470             "No carriage return found.",
471             // 5
472             "Command not supported by current Flex Link Port setting.",
473             // 6
474             "Settings are locked.",
475             //
476     };
477
478     /*
479      * Infrared errors returned by Flex devices
480      */
481     private static final String[] FLEX_IR_ERROR_MESSAGES = {
482             // 0
483             "Unknown error.",
484             // 1
485             "Invalid ID value.",
486             // 2
487             "Invalid frequency.",
488             // 3
489             "Invalid repeat.",
490             // 4
491             "Invalid offset.",
492             // 5
493             "Invalid IR pulse value.",
494             // 6
495             "Odd amount of IR pulse values (must be even).",
496             // 7
497             "Maximum pulse pair limit exceeded.",
498             //
499     };
500
501     /*
502      * Serial errors returned by Flex devices
503      */
504     private static final String[] FLEX_SL_ERROR_MESSAGES = {
505             // 0
506             "Unknown error.",
507             // 1
508             "Invalid baud rate.",
509             // 2
510             "Invalid flow control setting.",
511             // 3
512             "Invalid parity setting.",
513             //
514     };
515 }