2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.globalcache.internal.command;
15 import java.util.concurrent.LinkedBlockingQueue;
16 import java.util.concurrent.TimeUnit;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
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;
28 * The {@link AbstractCommand} class implements the basic functionality needed for all GlobalCache commands.
30 * @author Mark Hilbush - Initial contribution
32 public abstract class AbstractCommand implements CommandInterface {
33 private final Logger logger = LoggerFactory.getLogger(AbstractCommand.class);
35 private LinkedBlockingQueue<RequestMessage> requestQueue;
37 private final int RESPONSE_QUEUE_MAX_DEPTH = 1;
38 private final int RESPONSE_QUEUE_TIMEOUT = 3000;
40 protected Thing thing;
42 protected String ipAddress;
44 // Actual command strings sent to/received from the device (without CR)
45 protected String deviceCommand;
46 protected String deviceReply;
48 // Short human-readable name of the command
49 protected String commandName;
51 // Determines which TCP port will be used for the command
52 private CommandType commandType;
54 protected String module;
55 protected String connector;
57 protected String errorModule;
58 protected String errorConnector;
59 protected String errorCode;
60 protected String errorMessage;
62 private boolean isQuiet;
65 * The {@link AbstractCommand} abstract class is the basis for all GlobalCache device command classes.
67 * @author Mark Hilbush - Initial contribution
69 public AbstractCommand(Thing t, LinkedBlockingQueue<RequestMessage> q, String n, CommandType c) {
83 ipAddress = ((GlobalCacheHandler) thing.getHandler()).getIP();
86 public String getCommandName() {
91 public String getModule() {
95 public void setModule(String m) {
100 public String getConnector() {
104 public void setConnector(String c) {
108 public void setCommandType(CommandType commandType) {
109 this.commandType = commandType;
112 public boolean isGC100Model12() {
113 return thing.getThingTypeUID().equals(GlobalCacheBindingConstants.THING_TYPE_GC_100_12);
117 public abstract void parseSuccessfulReply();
119 public boolean isSuccessful() {
120 return errorCode == null ? true : false;
123 public String successAsString() {
124 return errorCode == null ? "succeeded" : "failed";
127 public String getErrorCode() {
131 public String getErrorMessage() {
135 public String getErrorModule() {
139 public String getErrorConnector() {
140 return errorConnector;
143 protected boolean isQuiet() {
147 private void setQuiet(boolean quiet) {
152 * Execute a GlobalCache device command
154 public void executeQuiet() {
159 public void execute() {
160 if (requestQueue == null) {
161 createGenericError("Execute method was called with a null requestQueue");
165 if (deviceCommand == null) {
166 createGenericError("Execute method was called with a null deviceCommand");
171 createGenericError("Execute method was called with a null thing");
175 // Send command & get response
177 parseSuccessfulReply();
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.
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);
198 // Create the request message
199 RequestMessage requestMsg = new RequestMessage(commandName, commandType, deviceCommand, responseQueue);
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);
206 // Wait on the response queue for the response message
207 ResponseMessage responseMsg = responseQueue.poll(RESPONSE_QUEUE_TIMEOUT, TimeUnit.MILLISECONDS);
209 if (responseMsg == null) {
210 createGenericError("Timed out waiting on response queue for message");
214 deviceReply = responseMsg.getDeviceReply();
215 logger.trace("Got response message off response queue, received reply '{}'", deviceReply);
217 if (isErrorReply(deviceReply)) {
220 } catch (InterruptedException e) {
221 createGenericError("Poll of response queue was interrupted");
229 * Parse the reply and set error values if an error occurred.
231 private boolean isErrorReply(String reply) {
232 logger.trace("Checking device reply for error condition: {}", reply);
237 // Generic (generated by binding) errors are of the form
239 if (reply.startsWith("ERROR:")) {
240 createGenericError(reply);
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);
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) {
258 errorCode = reply.substring(15);
259 errorMessage = lookupErrorMessage(errorCode, GC100_ERROR_MESSAGES);
260 logger.debug("Device reply indicates GC-100 error condition");
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");
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()) {
283 errorCode = reply.substring(4);
284 errorMessage = lookupErrorMessage(errorCode, FLEX_ERROR_MESSAGES);
285 logger.debug("Device reply indicates Flex general error condition");
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()) {
295 errorCode = reply.substring(6);
296 errorMessage = lookupErrorMessage(errorCode, FLEX_IR_ERROR_MESSAGES);
297 logger.debug("Device reply indicates Flex IR error condition");
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()) {
307 errorCode = reply.substring(6);
308 errorMessage = lookupErrorMessage(errorCode, FLEX_SL_ERROR_MESSAGES);
309 logger.debug("Device reply indicates Flex SL error condition");
317 private void createGenericError(String s) {
319 errorConnector = "N/A";
324 private String lookupErrorMessage(String errorCode, String[] errorMessageArray) {
327 eCode = Integer.parseInt(errorCode);
328 } catch (NumberFormatException e) {
330 logger.debug("Badly formatted error code '{}' received from device: {}", errorCode, e.getMessage());
333 if (eCode < 1 || eCode > errorMessageArray.length) {
336 return errorMessageArray[eCode];
340 * Errors returned by GlobalCache iTach devices
342 private static final String[] ITACH_ERROR_MESSAGES = {
346 "Invalid command. Command not found.",
348 "Invalid module address (does not exist).",
350 "Invalid connector address (does not exist).",
354 "Invalid frequency value",
356 "Invalid repeat value.",
358 "Invalid offset value.",
360 "Invalid pulse count.",
362 "Invalid pulse data.",
364 "Uneven amount of <on|off> statements.",
366 "No carriage return found.",
368 "Repeat count exceeded.",
370 "IR command sent to input connector.",
372 "Blaster command sent to non-blaster connector.",
374 "No carriage return before buffer full.",
376 "No carriage return.",
378 "Bad command syntax.",
380 "Sensor command sent to non-input connector.",
382 "Repeated IR transmission failure.",
384 "Above designated IR <on|off> pair limit.",
386 "Symbol odd boundary.",
392 "Invalid baud rate setting.",
394 "Invalid flow control setting.",
396 "Invalid parity setting.",
398 "Settings are locked."
403 * Errors returned by GlobalCache GC-100 devices
405 private static final String[] GC100_ERROR_MESSAGES = {
409 "Time out occurred because <CR> not received. Request not processed",
411 "Invalid module address (module does not exist) received with getversion.",
413 "Invalid module address (module does not exist).",
415 "Invalid connector address.",
417 "Connector address 1 is set up as “sensor in” when attempting IR command.",
419 "Connector address 2 is set up as “sensor in” when attempting IR command.",
421 "Connector address 3 is set up as “sensor in” when attempting IR command.",
423 "Offset is set to an even transition number, but should be odd.",
425 "Maximum number of transitions exceeded (256 total allowed).",
427 "Number of transitions in the IR command is not even.",
429 "Contact closure command sent to a module that is not a relay.",
431 "Missing carriage return. All commands must end with a carriage return.",
433 "State was requested of an invalid connector address.",
435 "Command sent to the unit is not supported by the GC-100.",
437 "Maximum number of IR transitions exceeded. (SM_IR_INPROCESS)",
439 "Invalid number of IR transitions (must be an even number).",
449 "Attempted to send an IR command to a non-IR module.",
453 "Command sent is not supported by this type of module."
458 * General errors returned by Flex devices
460 private static final String[] FLEX_ERROR_MESSAGES = {
464 "Invalid command. Command not found.",
466 "Bad command syntax used with a known command.",
468 "Invalid connector address (does not exist).",
470 "No carriage return found.",
472 "Command not supported by current Flex Link Port setting.",
474 "Settings are locked.",
479 * Infrared errors returned by Flex devices
481 private static final String[] FLEX_IR_ERROR_MESSAGES = {
487 "Invalid frequency.",
493 "Invalid IR pulse value.",
495 "Odd amount of IR pulse values (must be even).",
497 "Maximum pulse pair limit exceeded.",
502 * Serial errors returned by Flex devices
504 private static final String[] FLEX_SL_ERROR_MESSAGES = {
508 "Invalid baud rate.",
510 "Invalid flow control setting.",
512 "Invalid parity setting.",