]> git.basschouten.com Git - openhab-addons.git/blob
fdc2f06d4b591180857f07065edcdcbe1031a090
[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 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                 case CALL_FM_PRESET:
105                 case CALL_DAB_PRESET:
106                 case CALL_IRADIO_PRESET:
107                     messageStr += String.format("%02d", value);
108                     break;
109                 default:
110                     break;
111             }
112         }
113         if (!messageStr.endsWith("?")) {
114             messageStr += "!";
115         }
116         byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII);
117         logger.debug("Command \"{}\" => {}", cmd, messageStr);
118         return message;
119     }
120
121     @Override
122     public void handleIncomingData(byte[] inDataBuffer, int length) {
123         for (int i = 0; i < length; i++) {
124             boolean end = false;
125             if (searchKey && inDataBuffer[i] == '=') {
126                 // End of key reading, check if the value is a fixed or variable length
127                 searchKey = false;
128                 byte[] dataKey = getDataBuffer();
129                 String key = new String(dataKey, 0, dataKey.length, StandardCharsets.US_ASCII).trim();
130                 searchLength = isVariableLengthApplicable(key);
131                 indexLengthBuffer = 0;
132                 valueLength = 0;
133                 logger.trace("handleIncomingData: key = *{}* {}", key, searchLength ? "variable" : "fixed");
134                 fillDataBuffer(inDataBuffer[i]);
135             } else if (searchKey) {
136                 // Reading key
137                 fillDataBuffer(inDataBuffer[i]);
138             } else if (searchLength && inDataBuffer[i] == ',') {
139                 // End of value length reading
140                 searchLength = false;
141                 byte[] lengthData = Arrays.copyOf(lengthBuffer, indexLengthBuffer);
142                 String lengthStr = new String(lengthData, 0, lengthData.length, StandardCharsets.US_ASCII);
143                 valueLength = Integer.parseInt(lengthStr);
144                 logger.trace("handleIncomingData: valueLength = {}", valueLength);
145                 if (getRemainingSizeInDataBuffer() < valueLength) {
146                     logger.warn(
147                             "handleIncomingData: the size of the internal buffer is too small, reponse will be truncated");
148                 }
149                 end = valueLength == 0;
150             } else if (searchLength) {
151                 // Reading value length
152                 lengthBuffer[indexLengthBuffer++] = inDataBuffer[i];
153             } else if (valueLength > 0) {
154                 // Reading value (variable length)
155                 fillDataBuffer(inDataBuffer[i]);
156                 valueLength--;
157                 end = valueLength == 0;
158             } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
159                 // End of value reading
160                 end = true;
161             } else {
162                 // Reading value (fixed length)
163                 fillDataBuffer(inDataBuffer[i]);
164             }
165             if (end) {
166                 handleIncomingMessage(getDataBuffer());
167                 resetDataBuffer();
168                 searchKey = true;
169                 searchLength = false;
170             }
171         }
172     }
173
174     private boolean isVariableLengthApplicable(String key) {
175         return KEYSET1.contains(key) || KEYSET2.stream().filter(k -> key.startsWith(k)).count() > 0;
176     }
177 }