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 static org.openhab.binding.rotel.internal.RotelBindingConstants.*;
17 import java.nio.charset.StandardCharsets;
18 import java.util.Arrays;
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;
31 * Class for handling the Rotel ASCII V1 protocol (build of command messages, decoding of incoming data)
33 * @author Laurent Garnier - Initial contribution
36 public class RotelAsciiV1ProtocolHandler extends RotelAbstractAsciiProtocolHandler {
38 private static final char CHAR_END_RESPONSE = '!';
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);
45 private final Logger logger = LoggerFactory.getLogger(RotelAsciiV1ProtocolHandler.class);
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;
56 * @param model the Rotel model in use
58 public RotelAsciiV1ProtocolHandler(RotelModel model) {
63 public RotelProtocol getProtocol() {
64 return RotelProtocol.ASCII_V1;
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");
76 messageStr += String.format("%d", value);
82 } else if (value > 0) {
83 messageStr += String.format("+%02d", value);
85 messageStr += String.format("-%02d", -value);
91 } else if (value > 0) {
92 messageStr += String.format("R%02d", value);
94 messageStr += String.format("L%02d", -value);
97 case DIMMER_LEVEL_SET:
98 if (value > 0 && model.getDimmerLevelMin() < 0) {
99 messageStr += String.format("+%d", value);
101 messageStr += String.format("%d", value);
105 case CALL_DAB_PRESET:
106 case CALL_IRADIO_PRESET:
107 messageStr += String.format("%02d", value);
113 if (!messageStr.endsWith("?")) {
116 byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII);
117 logger.debug("Command \"{}\" => {}", cmd, messageStr);
122 public void handleIncomingData(byte[] inDataBuffer, int length) {
123 for (int i = 0; i < length; i++) {
125 if (searchKey && inDataBuffer[i] == '=') {
126 // End of key reading, check if the value is a fixed or variable length
128 byte[] dataKey = getDataBuffer();
129 String key = new String(dataKey, 0, dataKey.length, StandardCharsets.US_ASCII).trim();
130 searchLength = isVariableLengthApplicable(key);
131 indexLengthBuffer = 0;
133 logger.trace("handleIncomingData: key = *{}* {}", key, searchLength ? "variable" : "fixed");
134 fillDataBuffer(inDataBuffer[i]);
135 } else if (searchKey) {
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) {
147 "handleIncomingData: the size of the internal buffer is too small, reponse will be truncated");
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]);
157 end = valueLength == 0;
158 } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
159 // End of value reading
162 // Reading value (fixed length)
163 fillDataBuffer(inDataBuffer[i]);
166 handleIncomingMessage(getDataBuffer());
169 searchLength = false;
174 private boolean isVariableLengthApplicable(String key) {
175 return KEYSET1.contains(key) || KEYSET2.stream().filter(k -> key.startsWith(k)).count() > 0;