]> git.basschouten.com Git - openhab-addons.git/blob
164ddfecd74b65e0bb23602dc3133e8375d8ecf1
[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.anthem.internal.handler;
14
15 import static org.openhab.binding.anthem.internal.AnthemBindingConstants.*;
16
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.thing.Thing;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * The {@link AnthemCommandParser} is responsible for parsing and handling
32  * commands received from the Anthem processor.
33  *
34  * @author Mark Hilbush - Initial contribution
35  */
36 @NonNullByDefault
37 public class AnthemCommandParser {
38     private static final Pattern NUM_AVAILABLE_INPUTS_PATTERN = Pattern.compile("ICN([0-9]{1,2})");
39     private static final Pattern INPUT_SHORT_NAME_PATTERN = Pattern.compile("ISN([0-9][0-9])(\\p{ASCII}*)");
40     private static final Pattern INPUT_LONG_NAME_PATTERN = Pattern.compile("ILN([0-9][0-9])(\\p{ASCII}*)");
41     private static final Pattern POWER_PATTERN = Pattern.compile("Z([0-9])POW([01])");
42     private static final Pattern VOLUME_PATTERN = Pattern.compile("Z([0-9])VOL(-?[0-9]*)");
43     private static final Pattern MUTE_PATTERN = Pattern.compile("Z([0-9])MUT([01])");
44     private static final Pattern ACTIVE_INPUT_PATTERN = Pattern.compile("Z([0-9])INP([1-9])");
45
46     private Logger logger = LoggerFactory.getLogger(AnthemCommandParser.class);
47
48     private Map<String, String> inputShortNamesMap = new HashMap<>();
49     private Map<String, String> inputLongNamesMap = new HashMap<>();
50
51     public @Nullable AnthemUpdate parseCommand(String command) {
52         if (!isValidCommand(command)) {
53             return null;
54         }
55         // Strip off the termination char and any whitespace
56         String cmd = command.substring(0, command.indexOf(COMMAND_TERMINATION_CHAR)).trim();
57
58         // Zone command
59         if (cmd.startsWith("Z")) {
60             return parseZoneCommand(cmd);
61         }
62         // Information command
63         else if (cmd.startsWith("ID")) {
64             return parseInformationCommand(cmd);
65         }
66         // Number of inputs
67         else if (cmd.startsWith("ICN")) {
68             return parseNumberOfAvailableInputsCommand(cmd);
69         }
70         // Input short name
71         else if (cmd.startsWith("ISN")) {
72             parseInputShortNameCommand(cmd);
73         }
74         // Input long name
75         else if (cmd.startsWith("ILN")) {
76             parseInputLongNameCommand(cmd);
77         }
78         // Error response to command
79         else if (cmd.startsWith("!")) {
80             parseErrorCommand(cmd);
81         }
82         // Unknown/unhandled command
83         else {
84             logger.trace("Command parser doesn't know how to handle command: '{}'", cmd);
85         }
86         return null;
87     }
88
89     public @Nullable String getInputShortName(String input) {
90         return inputShortNamesMap.get(input);
91     }
92
93     public @Nullable String getInputLongName(String input) {
94         return inputLongNamesMap.get(input);
95     }
96
97     private boolean isValidCommand(String command) {
98         if (command.isEmpty() || command.isBlank() || command.length() < 4
99                 || command.indexOf(COMMAND_TERMINATION_CHAR) == -1) {
100             logger.trace("Parser received invalid command: '{}'", command);
101             return false;
102         }
103         return true;
104     }
105
106     private @Nullable AnthemUpdate parseZoneCommand(String command) {
107         // Power update
108         if (command.contains("POW")) {
109             return parsePower(command);
110         }
111         // Volume update
112         else if (command.contains("VOL")) {
113             return parseVolume(command);
114         }
115         // Mute update
116         else if (command.contains("MUT")) {
117             return parseMute(command);
118         }
119         // Active input
120         else if (command.contains("INP")) {
121             return parseActiveInput(command);
122         }
123         return null;
124     }
125
126     private @Nullable AnthemUpdate parseInformationCommand(String command) {
127         String value = command.substring(3, command.length());
128         AnthemUpdate update = null;
129         switch (command.substring(2, 3)) {
130             case "M":
131                 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_MODEL_ID, value);
132                 break;
133             case "R":
134                 update = AnthemUpdate.createPropertyUpdate(PROPERTY_REGION, value);
135                 break;
136             case "S":
137                 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_FIRMWARE_VERSION, value);
138                 break;
139             case "B":
140                 update = AnthemUpdate.createPropertyUpdate(PROPERTY_SOFTWARE_BUILD_DATE, value);
141                 break;
142             case "H":
143                 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_HARDWARE_VERSION, value);
144                 break;
145             case "N":
146                 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_MAC_ADDRESS, value);
147                 break;
148             case "Q":
149                 // Ignore
150                 break;
151             default:
152                 logger.debug("Unknown info type");
153                 break;
154         }
155         return update;
156     }
157
158     private @Nullable AnthemUpdate parseNumberOfAvailableInputsCommand(String command) {
159         Matcher matcher = NUM_AVAILABLE_INPUTS_PATTERN.matcher(command);
160         if (matcher != null) {
161             try {
162                 matcher.find();
163                 return AnthemUpdate.createPropertyUpdate(PROPERTY_NUM_AVAILABLE_INPUTS, matcher.group(1));
164             } catch (IndexOutOfBoundsException | IllegalStateException e) {
165                 logger.debug("Parsing exception on command: {}", command, e);
166             }
167         }
168         return null;
169     }
170
171     private void parseInputShortNameCommand(String command) {
172         parseInputName(command, INPUT_SHORT_NAME_PATTERN.matcher(command), inputShortNamesMap);
173     }
174
175     private void parseInputLongNameCommand(String command) {
176         parseInputName(command, INPUT_LONG_NAME_PATTERN.matcher(command), inputLongNamesMap);
177     }
178
179     private void parseErrorCommand(String command) {
180         logger.info("Command was not processed successfully by the device: '{}'", command);
181     }
182
183     private void parseInputName(String command, @Nullable Matcher matcher, Map<String, String> map) {
184         if (matcher != null) {
185             try {
186                 matcher.find();
187                 String input = matcher.group(1);
188                 String inputName = matcher.group(2);
189                 map.putIfAbsent(input, inputName);
190             } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
191                 logger.debug("Parsing exception on command: {}", command, e);
192             }
193         }
194     }
195
196     private @Nullable AnthemUpdate parsePower(String command) {
197         Matcher mmatcher = POWER_PATTERN.matcher(command);
198         if (mmatcher != null) {
199             try {
200                 mmatcher.find();
201                 String zone = mmatcher.group(1);
202                 String power = mmatcher.group(2);
203                 return AnthemUpdate.createStateUpdate(zone, CHANNEL_POWER, OnOffType.from("1".equals(power)));
204             } catch (IndexOutOfBoundsException | IllegalStateException e) {
205                 logger.debug("Parsing exception on command: {}", command, e);
206             }
207         }
208         return null;
209     }
210
211     private @Nullable AnthemUpdate parseVolume(String command) {
212         Matcher matcher = VOLUME_PATTERN.matcher(command);
213         if (matcher != null) {
214             try {
215                 matcher.find();
216                 String zone = matcher.group(1);
217                 String volume = matcher.group(2);
218                 return AnthemUpdate.createStateUpdate(zone, CHANNEL_VOLUME_DB, DecimalType.valueOf(volume));
219             } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
220                 logger.debug("Parsing exception on command: {}", command, e);
221             }
222         }
223         return null;
224     }
225
226     private @Nullable AnthemUpdate parseMute(String command) {
227         Matcher matcher = MUTE_PATTERN.matcher(command);
228         if (matcher != null) {
229             try {
230                 matcher.find();
231                 String zone = matcher.group(1);
232                 String mute = matcher.group(2);
233                 return AnthemUpdate.createStateUpdate(zone, CHANNEL_MUTE, OnOffType.from("1".equals(mute)));
234             } catch (IndexOutOfBoundsException | IllegalStateException e) {
235                 logger.debug("Parsing exception on command: {}", command, e);
236             }
237         }
238         return null;
239     }
240
241     private @Nullable AnthemUpdate parseActiveInput(String command) {
242         Matcher matcher = ACTIVE_INPUT_PATTERN.matcher(command);
243         if (matcher != null) {
244             try {
245                 matcher.find();
246                 String zone = matcher.group(1);
247                 DecimalType activeInput = DecimalType.valueOf(matcher.group(2));
248                 return AnthemUpdate.createStateUpdate(zone, CHANNEL_ACTIVE_INPUT, activeInput);
249             } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
250                 logger.debug("Parsing exception on command: {}", command, e);
251             }
252         }
253         return null;
254     }
255 }