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.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 static final int MAX_SIZE_RESPONSE = 128;
61 private final Logger logger = LoggerFactory.getLogger(RotelAbstractAsciiProtocolHandler.class);
63 private final byte[] dataBuffer;
70 * @param model the Rotel model in use
72 public RotelAbstractAsciiProtocolHandler(RotelModel model) {
74 this.dataBuffer = new byte[MAX_SIZE_RESPONSE];
78 protected boolean fillDataBuffer(byte data) {
79 if (index < MAX_SIZE_RESPONSE) {
80 dataBuffer[index++] = data;
86 protected byte[] getDataBuffer() {
87 return Arrays.copyOf(dataBuffer, index);
90 protected void resetDataBuffer() {
94 protected int getRemainingSizeInDataBuffer() {
95 return MAX_SIZE_RESPONSE - index;
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");
115 * Analyze a valid ASCII message and dispatch corresponding (key, value) to the event listeners
117 * @param incomingMessage the received message
120 protected void handleValidMessage(byte[] incomingMessage) {
121 byte[] message = filterMessage(incomingMessage, model.getSpecialCharacters());
123 // Replace characters with code < 32 by a space before converting to a string
124 for (int i = 0; i < message.length; i++) {
125 if (message[i] < 0x20) {
130 String value = new String(message, 0, message.length, StandardCharsets.US_ASCII);
131 logger.debug("handleValidAsciiMessage: chars *{}*", value);
132 value = value.trim();
133 if (value.isEmpty()) {
137 int idxSeparator = value.indexOf("=");
138 if (idxSeparator < 0) {
139 logger.debug("handleValidAsciiMessage: ignored message {}", value);
141 dispatchKeyValue(value.substring(0, idxSeparator).trim().toLowerCase(),
142 value.substring(idxSeparator + 1));
144 } catch (PatternSyntaxException e) {
145 logger.debug("handleValidAsciiMessage: ignored message {}", value);
150 * Suppress certain sequences of bytes from a message
152 * @param message the message as a table of bytes
153 * @param bytesSequences the table containing the sequence of bytes to be ignored
155 * @return the message without the unexpected sequence of bytes
157 private byte[] filterMessage(byte[] message, byte[][] bytesSequences) {
158 if (bytesSequences.length == 0) {
161 byte[] filteredMsg = new byte[message.length];
164 while (srcIdx < message.length) {
165 int ignoredLength = 0;
166 for (int i = 0; i < bytesSequences.length; i++) {
167 int size = bytesSequences[i].length;
168 if ((message.length - srcIdx) >= size) {
169 boolean match = true;
170 for (int j = 0; j < size; j++) {
171 if (message[srcIdx + j] != bytesSequences[i][j]) {
177 ignoredLength = size;
182 if (ignoredLength > 0) {
183 srcIdx += ignoredLength;
185 filteredMsg[dstIdx++] = message[srcIdx++];
188 return Arrays.copyOf(filteredMsg, dstIdx);