]> git.basschouten.com Git - openhab-addons.git/blob
71cd0f8d9c3f0a02f9e0109e99691af21e780a53
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.rotel.internal.protocol.ascii;
14
15 import java.nio.charset.StandardCharsets;
16 import java.util.Arrays;
17 import java.util.regex.PatternSyntaxException;
18
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;
25
26 /**
27  * Abstract class for handling a Rotel ASCII protocol (build of command messages, decoding of incoming data)
28  *
29  * @author Laurent Garnier - Initial contribution
30  */
31 @NonNullByDefault
32 public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractProtocolHandler {
33
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 } };
50
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 } };
55
56     /** Empty table of special characters */
57     public static final byte[][] NO_SPECIAL_CHARACTERS = {};
58
59     private final Logger logger = LoggerFactory.getLogger(RotelAbstractAsciiProtocolHandler.class);
60
61     private final char terminatingChar;
62     private final int size;
63     private final byte[] dataBuffer;
64
65     private int index;
66
67     /**
68      * Constructor
69      *
70      * @param model the Rotel model in use
71      * @param protocol the protocol to be used
72      */
73     public RotelAbstractAsciiProtocolHandler(RotelModel model, char terminatingChar) {
74         super(model);
75         this.terminatingChar = terminatingChar;
76         this.size = 64;
77         this.dataBuffer = new byte[size];
78         this.index = 0;
79     }
80
81     @Override
82     public void handleIncomingData(byte[] inDataBuffer, int length) {
83         for (int i = 0; i < length; i++) {
84             if (index < size) {
85                 dataBuffer[index++] = inDataBuffer[i];
86             }
87             if (inDataBuffer[i] == terminatingChar) {
88                 if (index >= size) {
89                     dataBuffer[index - 1] = (byte) terminatingChar;
90                 }
91                 byte[] msg = Arrays.copyOf(dataBuffer, index);
92                 handleIncomingMessage(msg);
93                 index = 0;
94             }
95         }
96     }
97
98     /**
99      * Validate the content of a feedback message
100      *
101      * @param responseMessage the buffer containing the feedback message
102      *
103      * @throws RotelException - If the message has unexpected content
104      */
105     @Override
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");
111         }
112
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");
117         }
118     }
119
120     /**
121      * Analyze a valid ASCII message and dispatch corresponding (key, value) to the event listeners
122      *
123      * @param incomingMessage the received message
124      */
125     @Override
126     protected void handleValidMessage(byte[] incomingMessage) {
127         byte[] message = filterMessage(incomingMessage, model.getSpecialCharacters());
128
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) {
132                 message[i] = 0x20;
133             }
134         }
135
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()) {
140             return;
141         }
142         try {
143             String[] splittedValue = value.split("=");
144             if (splittedValue.length != 2) {
145                 logger.debug("handleValidAsciiMessage: ignored message {}", value);
146             } else {
147                 dispatchKeyValue(splittedValue[0].trim().toLowerCase(), splittedValue[1]);
148             }
149         } catch (PatternSyntaxException e) {
150             logger.debug("handleValidAsciiMessage: ignored message {}", value);
151         }
152     }
153
154     /**
155      * Suppress certain sequences of bytes from a message
156      *
157      * @param message the message as a table of bytes
158      * @param bytesSequences the table containing the sequence of bytes to be ignored
159      *
160      * @return the message without the unexpected sequence of bytes
161      */
162     private byte[] filterMessage(byte[] message, byte[][] bytesSequences) {
163         if (bytesSequences.length == 0) {
164             return message;
165         }
166         byte[] filteredMsg = new byte[message.length];
167         int srcIdx = 0;
168         int dstIdx = 0;
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]) {
177                             match = false;
178                             break;
179                         }
180                     }
181                     if (match) {
182                         ignoredLength = size;
183                         break;
184                     }
185                 }
186             }
187             if (ignoredLength > 0) {
188                 srcIdx += ignoredLength;
189             } else {
190                 filteredMsg[dstIdx++] = message[srcIdx++];
191             }
192         }
193         return Arrays.copyOf(filteredMsg, dstIdx);
194     }
195 }