]> git.basschouten.com Git - openhab-addons.git/blob
cf69ab4cbecc7530d290ff951520ae796b5dbb2c
[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.Set;
19
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;
28
29 /**
30  * Class for handling the Rotel ASCII V2 protocol (build of command messages, decoding of incoming data)
31  *
32  * @author Laurent Garnier - Initial contribution
33  */
34 @NonNullByDefault
35 public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandler {
36
37     private static final char CHAR_END_RESPONSE = '$';
38
39     private static final Set<String> KEYSET = Set.of(KEY_DISC_NAME, KEY_DISC_TYPE, KEY_TRACK_NAME, KEY_TIME, KEY_FM_RDS,
40             KEY_DAB_STATION);
41
42     private final Logger logger = LoggerFactory.getLogger(RotelAsciiV2ProtocolHandler.class);
43
44     private boolean searchKey = true;
45     private boolean variableLength;
46     private boolean prevIsEndCharacter;
47
48     /**
49      * Constructor
50      *
51      * @param model the Rotel model in use
52      */
53     public RotelAsciiV2ProtocolHandler(RotelModel model) {
54         super(model);
55     }
56
57     @Override
58     public RotelProtocol getProtocol() {
59         return RotelProtocol.ASCII_V2;
60     }
61
62     @Override
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");
67         }
68         if (value != null) {
69             switch (cmd) {
70                 case VOLUME_SET:
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);
76                     break;
77                 case BASS_SET:
78                 case ZONE1_BASS_SET:
79                 case ZONE2_BASS_SET:
80                 case ZONE3_BASS_SET:
81                 case ZONE4_BASS_SET:
82                 case TREBLE_SET:
83                 case ZONE1_TREBLE_SET:
84                 case ZONE2_TREBLE_SET:
85                 case ZONE3_TREBLE_SET:
86                 case ZONE4_TREBLE_SET:
87                     if (value == 0) {
88                         messageStr += "000";
89                     } else if (value > 0) {
90                         messageStr += String.format("+%02d", value);
91                     } else {
92                         messageStr += String.format("-%02d", -value);
93                     }
94                     break;
95                 case BALANCE_SET:
96                 case ZONE1_BALANCE_SET:
97                 case ZONE2_BALANCE_SET:
98                 case ZONE3_BALANCE_SET:
99                 case ZONE4_BALANCE_SET:
100                     if (value == 0) {
101                         messageStr += "000";
102                     } else if (value > 0) {
103                         messageStr += String.format("r%02d", value);
104                     } else {
105                         messageStr += String.format("l%02d", -value);
106                     }
107                     break;
108                 case DIMMER_LEVEL_SET:
109                     if (value > 0 && model.getDimmerLevelMin() < 0) {
110                         messageStr += String.format("+%d", value);
111                     } else {
112                         messageStr += String.format("%d", value);
113                     }
114                     break;
115                 case CALL_FM_PRESET:
116                 case CALL_DAB_PRESET:
117                     messageStr += String.format("%02d", value);
118                     break;
119                 default:
120                     break;
121             }
122         }
123         if (!messageStr.endsWith("?")) {
124             messageStr += "!";
125         }
126         byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII);
127         logger.debug("Command \"{}\" => {}", cmd, messageStr);
128         return message;
129     }
130
131     @Override
132     public void handleIncomingData(byte[] inDataBuffer, int length) {
133         for (int i = 0; i < length; i++) {
134             boolean end = false;
135             if (searchKey && inDataBuffer[i] == '=') {
136                 // End of key reading, check if the value is a fixed or variable length
137                 searchKey = false;
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) {
144                 // Reading key
145                 fillDataBuffer(inDataBuffer[i]);
146             } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
147                 end = !variableLength || prevIsEndCharacter;
148             } else {
149                 if (prevIsEndCharacter) {
150                     // End character inside a variable length value
151                     fillDataBuffer((byte) CHAR_END_RESPONSE);
152                 }
153                 // Reading value
154                 fillDataBuffer(inDataBuffer[i]);
155             }
156             if (end) {
157                 // End of value reading
158                 handleIncomingMessage(getDataBuffer());
159                 resetDataBuffer();
160                 searchKey = true;
161                 variableLength = false;
162                 prevIsEndCharacter = false;
163             } else {
164                 prevIsEndCharacter = inDataBuffer[i] == CHAR_END_RESPONSE;
165             }
166         }
167     }
168
169     @Override
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(",")) {
173             switch (key) {
174                 case KEY_INPUT:
175                 case KEY_VOLUME:
176                 case KEY_MUTE:
177                 case KEY_BASS:
178                 case KEY_TREBLE:
179                 case KEY_BALANCE:
180                 case KEY_FREQ:
181                     String[] splitValues = value.split(",");
182                     int nb = splitValues.length;
183                     if (nb > MAX_NUMBER_OF_ZONES) {
184                         nb = MAX_NUMBER_OF_ZONES;
185                     }
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);
190                     }
191                     break;
192                 default:
193                     super.dispatchKeyValue(key, value);
194                     break;
195             }
196         } else {
197             super.dispatchKeyValue(key, value);
198         }
199     }
200 }