2 * Copyright (c) 2010-2022 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.rotel.internal.protocol.ascii;
15 import java.nio.charset.StandardCharsets;
16 import java.util.Arrays;
17 import java.util.regex.PatternSyntaxException;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.openhab.binding.rotel.internal.RotelException;
21 import org.openhab.binding.rotel.internal.RotelModel;
22 import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * Abstract class for handling a Rotel ASCII protocol (build of command messages, decoding of incoming data)
29 * @author Laurent Garnier - Initial contribution
32 public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractProtocolHandler {
34 /** Special characters that can be found in the feedback messages for several devices using the ASCII protocol */
35 public static final byte[][] SPECIAL_CHARACTERS = { { (byte) 0xEE, (byte) 0x82, (byte) 0x85 },
36 { (byte) 0xEE, (byte) 0x82, (byte) 0x84 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x92 },
37 { (byte) 0xEE, (byte) 0x82, (byte) 0x87 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8E },
38 { (byte) 0xEE, (byte) 0x82, (byte) 0x89 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x93 },
39 { (byte) 0xEE, (byte) 0x82, (byte) 0x8C }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8F },
40 { (byte) 0xEE, (byte) 0x82, (byte) 0x8A }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8B },
41 { (byte) 0xEE, (byte) 0x82, (byte) 0x81 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x82 },
42 { (byte) 0xEE, (byte) 0x82, (byte) 0x83 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x94 },
43 { (byte) 0xEE, (byte) 0x82, (byte) 0x97 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x98 },
44 { (byte) 0xEE, (byte) 0x82, (byte) 0x80 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x99 },
45 { (byte) 0xEE, (byte) 0x82, (byte) 0x9A }, { (byte) 0xEE, (byte) 0x82, (byte) 0x88 },
46 { (byte) 0xEE, (byte) 0x82, (byte) 0x95 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x96 },
47 { (byte) 0xEE, (byte) 0x82, (byte) 0x90 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x91 },
48 { (byte) 0xEE, (byte) 0x82, (byte) 0x8D }, { (byte) 0xEE, (byte) 0x80, (byte) 0x80, (byte) 0xEE,
49 (byte) 0x80, (byte) 0x81, (byte) 0xEE, (byte) 0x80, (byte) 0x82 } };
51 /** Special characters that can be found in the feedback messages for the RCD-1572 */
52 public static final byte[][] SPECIAL_CHARACTERS_RCD1572 = { { (byte) 0xC2, (byte) 0x8C },
53 { (byte) 0xC2, (byte) 0x54 }, { (byte) 0xC2, (byte) 0x81 }, { (byte) 0xC2, (byte) 0x82 },
54 { (byte) 0xC2, (byte) 0x83 } };
56 /** Empty table of special characters */
57 public static final byte[][] NO_SPECIAL_CHARACTERS = {};
59 private final Logger logger = LoggerFactory.getLogger(RotelAbstractAsciiProtocolHandler.class);
61 private final char terminatingChar;
62 private final int size;
63 private final byte[] dataBuffer;
70 * @param model the Rotel model in use
71 * @param protocol the protocol to be used
73 public RotelAbstractAsciiProtocolHandler(RotelModel model, char terminatingChar) {
75 this.terminatingChar = terminatingChar;
77 this.dataBuffer = new byte[size];
82 public void handleIncomingData(byte[] inDataBuffer, int length) {
83 for (int i = 0; i < length; i++) {
85 dataBuffer[index++] = inDataBuffer[i];
87 if (inDataBuffer[i] == terminatingChar) {
89 dataBuffer[index - 1] = (byte) terminatingChar;
91 byte[] msg = Arrays.copyOf(dataBuffer, index);
92 handleIncomingMessage(msg);
99 * Validate the content of a feedback message
101 * @param responseMessage the buffer containing the feedback message
103 * @throws RotelException - If the message has unexpected content
106 protected void validateResponse(byte[] responseMessage) throws RotelException {
107 // Check minimum message length
108 if (responseMessage.length < 1) {
109 logger.debug("Unexpected message length: {}", responseMessage.length);
110 throw new RotelException("Unexpected message length");
113 if (responseMessage[responseMessage.length - 1] != '!' && responseMessage[responseMessage.length - 1] != '$') {
114 logger.debug("Unexpected ending character in response: {}",
115 Integer.toHexString(responseMessage[responseMessage.length - 1] & 0x000000FF));
116 throw new RotelException("Unexpected ending character in response");
121 * Analyze a valid ASCII message and dispatch corresponding (key, value) to the event listeners
123 * @param incomingMessage the received message
126 protected void handleValidMessage(byte[] incomingMessage) {
127 byte[] message = filterMessage(incomingMessage, model.getSpecialCharacters());
129 // Replace characters with code < 32 by a space before converting to a string
130 for (int i = 0; i < message.length; i++) {
131 if (message[i] < 0x20) {
136 String value = new String(message, 0, message.length - 1, StandardCharsets.US_ASCII);
137 logger.debug("handleValidAsciiMessage: chars *{}*", value);
138 value = value.trim();
139 if (value.isEmpty()) {
143 String[] splittedValue = value.split("=");
144 if (splittedValue.length != 2) {
145 logger.debug("handleValidAsciiMessage: ignored message {}", value);
147 dispatchKeyValue(splittedValue[0].trim().toLowerCase(), splittedValue[1]);
149 } catch (PatternSyntaxException e) {
150 logger.debug("handleValidAsciiMessage: ignored message {}", value);
155 * Suppress certain sequences of bytes from a message
157 * @param message the message as a table of bytes
158 * @param bytesSequences the table containing the sequence of bytes to be ignored
160 * @return the message without the unexpected sequence of bytes
162 private byte[] filterMessage(byte[] message, byte[][] bytesSequences) {
163 if (bytesSequences.length == 0) {
166 byte[] filteredMsg = new byte[message.length];
169 while (srcIdx < message.length) {
170 int ignoredLength = 0;
171 for (int i = 0; i < bytesSequences.length; i++) {
172 int size = bytesSequences[i].length;
173 if ((message.length - srcIdx) >= size) {
174 boolean match = true;
175 for (int j = 0; j < size; j++) {
176 if (message[srcIdx + j] != bytesSequences[i][j]) {
182 ignoredLength = size;
187 if (ignoredLength > 0) {
188 srcIdx += ignoredLength;
190 filteredMsg[dstIdx++] = message[srcIdx++];
193 return Arrays.copyOf(filteredMsg, dstIdx);