]> git.basschouten.com Git - openhab-addons.git/blob
565e4f827800cc3a3a5fb51fe11cd3571bf3d9d0
[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 static org.openhab.binding.rotel.internal.RotelBindingConstants.*;
16
17 import java.nio.charset.StandardCharsets;
18 import java.util.Arrays;
19 import java.util.Set;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.rotel.internal.RotelException;
24 import org.openhab.binding.rotel.internal.RotelModel;
25 import org.openhab.binding.rotel.internal.communication.RotelCommand;
26 import org.openhab.binding.rotel.internal.protocol.RotelProtocol;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Class for handling the Rotel ASCII V1 protocol (build of command messages, decoding of incoming data)
32  *
33  * @author Laurent Garnier - Initial contribution
34  */
35 @NonNullByDefault
36 public class RotelAsciiV1ProtocolHandler extends RotelAbstractAsciiProtocolHandler {
37
38     private static final char CHAR_END_RESPONSE = '!';
39
40     private static final Set<String> KEYSET1 = Set.of(KEY_DISPLAY, KEY_DISPLAY1, KEY_DISPLAY2, KEY_DISPLAY3,
41             KEY_DISPLAY4, KEY_PRODUCT_TYPE, KEY_PRODUCT_VERSION, KEY_TC_VERSION, KEY_TRACK);
42     private static final Set<String> KEYSET2 = Set.of(KEY_FM_PRESET, KEY_FM_ALL_PRESET, KEY_DAB_PRESET,
43             KEY_DAB_ALL_PRESET, KEY_IRADIO_PRESET, KEY_IRADIO_ALL_PRESET);
44
45     private final Logger logger = LoggerFactory.getLogger(RotelAsciiV1ProtocolHandler.class);
46
47     private final byte[] lengthBuffer = new byte[8];
48     private boolean searchKey = true;
49     private boolean searchLength;
50     private int valueLength;
51     private int indexLengthBuffer;
52
53     /**
54      * Constructor
55      *
56      * @param model the Rotel model in use
57      */
58     public RotelAsciiV1ProtocolHandler(RotelModel model) {
59         super(model);
60     }
61
62     @Override
63     public RotelProtocol getProtocol() {
64         return RotelProtocol.ASCII_V1;
65     }
66
67     @Override
68     public byte[] buildCommandMessage(RotelCommand cmd, @Nullable Integer value) throws RotelException {
69         String messageStr = cmd.getAsciiCommandV1();
70         if (messageStr == null) {
71             throw new RotelException("Command \"" + cmd.getLabel() + "\" ignored: not available for ASCII V1 protocol");
72         }
73         if (value != null) {
74             switch (cmd) {
75                 case VOLUME_SET:
76                     messageStr += String.format("%d", value);
77                     break;
78                 case BASS_SET:
79                 case TREBLE_SET:
80                     if (value == 0) {
81                         messageStr += "000";
82                     } else if (value > 0) {
83                         messageStr += String.format("+%02d", value);
84                     } else {
85                         messageStr += String.format("-%02d", -value);
86                     }
87                     break;
88                 case BALANCE_SET:
89                     if (value == 0) {
90                         messageStr += "000";
91                     } else if (value > 0) {
92                         messageStr += String.format("R%02d", value);
93                     } else {
94                         messageStr += String.format("L%02d", -value);
95                     }
96                     break;
97                 case DIMMER_LEVEL_SET:
98                     if (value > 0 && model.getDimmerLevelMin() < 0) {
99                         messageStr += String.format("+%d", value);
100                     } else {
101                         messageStr += String.format("%d", value);
102                     }
103                     break;
104                 default:
105                     break;
106             }
107         }
108         if (!messageStr.endsWith("?")) {
109             messageStr += "!";
110         }
111         byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII);
112         logger.debug("Command \"{}\" => {}", cmd, messageStr);
113         return message;
114     }
115
116     @Override
117     public void handleIncomingData(byte[] inDataBuffer, int length) {
118         for (int i = 0; i < length; i++) {
119             boolean end = false;
120             if (searchKey && inDataBuffer[i] == '=') {
121                 // End of key reading, check if the value is a fixed or variable length
122                 searchKey = false;
123                 byte[] dataKey = getDataBuffer();
124                 String key = new String(dataKey, 0, dataKey.length, StandardCharsets.US_ASCII).trim();
125                 searchLength = isVariableLengthApplicable(key);
126                 indexLengthBuffer = 0;
127                 valueLength = 0;
128                 logger.trace("handleIncomingData: key = *{}* {}", key, searchLength ? "variable" : "fixed");
129                 fillDataBuffer(inDataBuffer[i]);
130             } else if (searchKey) {
131                 // Reading key
132                 fillDataBuffer(inDataBuffer[i]);
133             } else if (searchLength && inDataBuffer[i] == ',') {
134                 // End of value length reading
135                 searchLength = false;
136                 byte[] lengthData = Arrays.copyOf(lengthBuffer, indexLengthBuffer);
137                 String lengthStr = new String(lengthData, 0, lengthData.length, StandardCharsets.US_ASCII);
138                 valueLength = Integer.parseInt(lengthStr);
139                 logger.trace("handleIncomingData: valueLength = {}", valueLength);
140                 if (getRemainingSizeInDataBuffer() < valueLength) {
141                     logger.warn(
142                             "handleIncomingData: the size of the internal buffer is too small, reponse will be truncated");
143                 }
144                 end = valueLength == 0;
145             } else if (searchLength) {
146                 // Reading value length
147                 lengthBuffer[indexLengthBuffer++] = inDataBuffer[i];
148             } else if (valueLength > 0) {
149                 // Reading value (variable length)
150                 fillDataBuffer(inDataBuffer[i]);
151                 valueLength--;
152                 end = valueLength == 0;
153             } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
154                 // End of value reading
155                 end = true;
156             } else {
157                 // Reading value (fixed length)
158                 fillDataBuffer(inDataBuffer[i]);
159             }
160             if (end) {
161                 handleIncomingMessage(getDataBuffer());
162                 resetDataBuffer();
163                 searchKey = true;
164                 searchLength = false;
165             }
166         }
167     }
168
169     private boolean isVariableLengthApplicable(String key) {
170         return KEYSET1.contains(key) || KEYSET2.stream().filter(k -> key.startsWith(k)).count() > 0;
171     }
172 }