]> git.basschouten.com Git - openhab-addons.git/blob
16d78a85467f8f89285c6e1da6b396c6b991d876
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 static final int MAX_SIZE_RESPONSE = 128;
60
61     private final Logger logger = LoggerFactory.getLogger(RotelAbstractAsciiProtocolHandler.class);
62
63     private final byte[] dataBuffer;
64
65     private int index;
66
67     /**
68      * Constructor
69      *
70      * @param model the Rotel model in use
71      */
72     public RotelAbstractAsciiProtocolHandler(RotelModel model) {
73         super(model);
74         this.dataBuffer = new byte[MAX_SIZE_RESPONSE];
75         this.index = 0;
76     }
77
78     protected boolean fillDataBuffer(byte data) {
79         if (index < MAX_SIZE_RESPONSE) {
80             dataBuffer[index++] = data;
81             return true;
82         }
83         return false;
84     }
85
86     protected byte[] getDataBuffer() {
87         return Arrays.copyOf(dataBuffer, index);
88     }
89
90     protected void resetDataBuffer() {
91         index = 0;
92     }
93
94     protected int getRemainingSizeInDataBuffer() {
95         return MAX_SIZE_RESPONSE - index;
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
114     /**
115      * Analyze a valid ASCII message and dispatch corresponding (key, value) to the event listeners
116      *
117      * @param incomingMessage the received message
118      */
119     @Override
120     protected void handleValidMessage(byte[] incomingMessage) {
121         byte[] message = filterMessage(incomingMessage, model.getSpecialCharacters());
122
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) {
126                 message[i] = 0x20;
127             }
128         }
129
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()) {
134             return;
135         }
136         try {
137             int idxSeparator = value.indexOf("=");
138             if (idxSeparator < 0) {
139                 logger.debug("handleValidAsciiMessage: ignored message {}", value);
140             } else {
141                 dispatchKeyValue(value.substring(0, idxSeparator).trim().toLowerCase(),
142                         value.substring(idxSeparator + 1));
143             }
144         } catch (PatternSyntaxException e) {
145             logger.debug("handleValidAsciiMessage: ignored message {}", value);
146         }
147     }
148
149     /**
150      * Suppress certain sequences of bytes from a message
151      *
152      * @param message the message as a table of bytes
153      * @param bytesSequences the table containing the sequence of bytes to be ignored
154      *
155      * @return the message without the unexpected sequence of bytes
156      */
157     private byte[] filterMessage(byte[] message, byte[][] bytesSequences) {
158         if (bytesSequences.length == 0) {
159             return message;
160         }
161         byte[] filteredMsg = new byte[message.length];
162         int srcIdx = 0;
163         int dstIdx = 0;
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]) {
172                             match = false;
173                             break;
174                         }
175                     }
176                     if (match) {
177                         ignoredLength = size;
178                         break;
179                     }
180                 }
181             }
182             if (ignoredLength > 0) {
183                 srcIdx += ignoredLength;
184             } else {
185                 filteredMsg[dstIdx++] = message[srcIdx++];
186             }
187         }
188         return Arrays.copyOf(filteredMsg, dstIdx);
189     }
190 }