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;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.rotel.internal.RotelException;
23 import org.openhab.binding.rotel.internal.RotelModel;
24 import org.openhab.binding.rotel.internal.communication.RotelCommand;
25 import org.openhab.binding.rotel.internal.protocol.RotelProtocol;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 * Class for handling the Rotel ASCII V2 protocol (build of command messages, decoding of incoming data)
32 * @author Laurent Garnier - Initial contribution
35 public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandler {
37 private static final char CHAR_END_RESPONSE = '$';
39 private static final Set<String> KEYSET = Set.of(KEY_DISC_NAME, KEY_DISC_TYPE, KEY_TRACK_NAME, KEY_TIME, KEY_FM_RDS,
42 private final Logger logger = LoggerFactory.getLogger(RotelAsciiV2ProtocolHandler.class);
44 private boolean searchKey = true;
45 private boolean variableLength;
46 private boolean prevIsEndCharacter;
51 * @param model the Rotel model in use
53 public RotelAsciiV2ProtocolHandler(RotelModel model) {
58 public RotelProtocol getProtocol() {
59 return RotelProtocol.ASCII_V2;
63 public byte[] buildCommandMessage(RotelCommand cmd, @Nullable Integer value) throws RotelException {
64 String messageStr = cmd.getAsciiCommandV2();
65 if (messageStr == null) {
66 throw new RotelException("Command \"" + cmd.getLabel() + "\" ignored: not available for ASCII V2 protocol");
71 case ZONE1_VOLUME_SET:
72 case ZONE2_VOLUME_SET:
73 case ZONE3_VOLUME_SET:
74 case ZONE4_VOLUME_SET:
75 messageStr += String.format("%02d", value);
83 case ZONE1_TREBLE_SET:
84 case ZONE2_TREBLE_SET:
85 case ZONE3_TREBLE_SET:
86 case ZONE4_TREBLE_SET:
89 } else if (value > 0) {
90 messageStr += String.format("+%02d", value);
92 messageStr += String.format("-%02d", -value);
96 case ZONE1_BALANCE_SET:
97 case ZONE2_BALANCE_SET:
98 case ZONE3_BALANCE_SET:
99 case ZONE4_BALANCE_SET:
102 } else if (value > 0) {
103 messageStr += String.format("r%02d", value);
105 messageStr += String.format("l%02d", -value);
108 case DIMMER_LEVEL_SET:
109 if (value > 0 && model.getDimmerLevelMin() < 0) {
110 messageStr += String.format("+%d", value);
112 messageStr += String.format("%d", value);
116 case CALL_DAB_PRESET:
117 messageStr += String.format("%02d", value);
123 if (!messageStr.endsWith("?")) {
126 byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII);
127 logger.debug("Command \"{}\" => {}", cmd, messageStr);
132 public void handleIncomingData(byte[] inDataBuffer, int length) {
133 for (int i = 0; i < length; i++) {
135 if (searchKey && inDataBuffer[i] == '=') {
136 // End of key reading, check if the value is a fixed or variable length
138 byte[] dataKey = getDataBuffer();
139 String key = new String(dataKey, 0, dataKey.length, StandardCharsets.US_ASCII).trim();
140 variableLength = KEYSET.contains(key);
141 logger.trace("handleIncomingData: key = *{}* {}", key, variableLength ? "variable" : "fixed");
142 fillDataBuffer(inDataBuffer[i]);
143 } else if (searchKey) {
145 fillDataBuffer(inDataBuffer[i]);
146 } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
147 end = !variableLength || prevIsEndCharacter;
149 if (prevIsEndCharacter) {
150 // End character inside a variable length value
151 fillDataBuffer((byte) CHAR_END_RESPONSE);
154 fillDataBuffer(inDataBuffer[i]);
157 // End of value reading
158 handleIncomingMessage(getDataBuffer());
161 variableLength = false;
162 prevIsEndCharacter = false;
164 prevIsEndCharacter = inDataBuffer[i] == CHAR_END_RESPONSE;
170 protected void dispatchKeyValue(String key, String value) {
171 // For distribution amplifiers, we need to split certain values to get the value for each zone
172 if (model == RotelModel.C8 && value.contains(",")) {
181 String[] splitValues = value.split(",");
182 int nb = splitValues.length;
183 if (nb > MAX_NUMBER_OF_ZONES) {
184 nb = MAX_NUMBER_OF_ZONES;
186 for (int i = 1; i <= nb; i++) {
187 String val = KEY_INPUT.equals(key) ? String.format("z%d:input_%s", i, splitValues[i - 1])
188 : splitValues[i - 1];
189 dispatchKeyValue(String.format("%s_zone%d", key, i), val);
193 super.dispatchKeyValue(key, value);
197 super.dispatchKeyValue(key, value);