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)) {
221 } catch (InterruptedException e) {
222 createGenericError("Poll of response queue was interrupted");
230 * Parse the reply and set error values if an error occurred.
232 private boolean isErrorReply(String reply) {
233 logger.trace("Checking device reply for error condition: {}", reply);
238 // Generic (generated by binding) errors are of the form
240 if (reply.startsWith("ERROR:")) {
241 createGenericError(reply);
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);
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) {
259 errorCode = reply.substring(15);
260 errorMessage = lookupErrorMessage(errorCode, GC100_ERROR_MESSAGES);
261 logger.debug("Device reply indicates GC-100 error condition");
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");
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()) {
284 errorCode = reply.substring(4);
285 errorMessage = lookupErrorMessage(errorCode, FLEX_ERROR_MESSAGES);
286 logger.debug("Device reply indicates Flex general error condition");
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()) {
296 errorCode = reply.substring(6);
297 errorMessage = lookupErrorMessage(errorCode, FLEX_IR_ERROR_MESSAGES);
298 logger.debug("Device reply indicates Flex IR error condition");
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()) {
308 errorCode = reply.substring(6);
309 errorMessage = lookupErrorMessage(errorCode, FLEX_SL_ERROR_MESSAGES);
310 logger.debug("Device reply indicates Flex SL error condition");
318 private void createGenericError(String s) {
320 errorConnector = "N/A";
325 private String lookupErrorMessage(String errorCode, String[] errorMessageArray) {
328 eCode = Integer.parseInt(errorCode);
330 } catch (NumberFormatException e) {
332 logger.debug("Badly formatted error code '{}' received from device: {}", errorCode, e.getMessage());
335 if (eCode < 1 || eCode > errorMessageArray.length) {
338 return errorMessageArray[eCode];
342 * Errors returned by GlobalCache iTach devices
344 private static final String[] ITACH_ERROR_MESSAGES = {
348 "Invalid command. Command not found.",
350 "Invalid module address (does not exist).",
352 "Invalid connector address (does not exist).",
356 "Invalid frequency value",
358 "Invalid repeat value.",
360 "Invalid offset value.",
362 "Invalid pulse count.",
364 "Invalid pulse data.",
366 "Uneven amount of <on|off> statements.",
368 "No carriage return found.",
370 "Repeat count exceeded.",
372 "IR command sent to input connector.",
374 "Blaster command sent to non-blaster connector.",
376 "No carriage return before buffer full.",
378 "No carriage return.",
380 "Bad command syntax.",
382 "Sensor command sent to non-input connector.",
384 "Repeated IR transmission failure.",
386 "Above designated IR <on|off> pair limit.",
388 "Symbol odd boundary.",
394 "Invalid baud rate setting.",
396 "Invalid flow control setting.",
398 "Invalid parity setting.",
400 "Settings are locked."
405 * Errors returned by GlobalCache GC-100 devices
407 private static final String[] GC100_ERROR_MESSAGES = {
411 "Time out occurred because <CR> not received. Request not processed",
413 "Invalid module address (module does not exist) received with getversion.",
415 "Invalid module address (module does not exist).",
417 "Invalid connector address.",
419 "Connector address 1 is set up as “sensor in” when attempting IR command.",
421 "Connector address 2 is set up as “sensor in” when attempting IR command.",
423 "Connector address 3 is set up as “sensor in” when attempting IR command.",
425 "Offset is set to an even transition number, but should be odd.",
427 "Maximum number of transitions exceeded (256 total allowed).",
429 "Number of transitions in the IR command is not even.",
431 "Contact closure command sent to a module that is not a relay.",
433 "Missing carriage return. All commands must end with a carriage return.",
435 "State was requested of an invalid connector address.",
437 "Command sent to the unit is not supported by the GC-100.",
439 "Maximum number of IR transitions exceeded. (SM_IR_INPROCESS)",
441 "Invalid number of IR transitions (must be an even number).",
451 "Attempted to send an IR command to a non-IR module.",
455 "Command sent is not supported by this type of module."
460 * General errors returned by Flex devices
462 private static final String[] FLEX_ERROR_MESSAGES = {
466 "Invalid command. Command not found.",
468 "Bad command syntax used with a known command.",
470 "Invalid connector address (does not exist).",
472 "No carriage return found.",
474 "Command not supported by current Flex Link Port setting.",
476 "Settings are locked.",
481 * Infrared errors returned by Flex devices
483 private static final String[] FLEX_IR_ERROR_MESSAGES = {
489 "Invalid frequency.",
495 "Invalid IR pulse value.",
497 "Odd amount of IR pulse values (must be even).",
499 "Maximum pulse pair limit exceeded.",
504 * Serial errors returned by Flex devices
506 private static final String[] FLEX_SL_ERROR_MESSAGES = {
510 "Invalid baud rate.",
512 "Invalid flow control setting.",
514 "Invalid parity setting.",