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.anthem.internal.handler;
15 import static org.openhab.binding.anthem.internal.AnthemBindingConstants.*;
17 import java.util.HashMap;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
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;
31 * The {@link AnthemCommandParser} is responsible for parsing and handling
32 * commands received from the Anthem processor.
34 * @author Mark Hilbush - Initial contribution
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])");
46 private Logger logger = LoggerFactory.getLogger(AnthemCommandParser.class);
48 private Map<String, String> inputShortNamesMap = new HashMap<>();
49 private Map<String, String> inputLongNamesMap = new HashMap<>();
51 public @Nullable AnthemUpdate parseCommand(String command) {
52 if (!isValidCommand(command)) {
55 // Strip off the termination char and any whitespace
56 String cmd = command.substring(0, command.indexOf(COMMAND_TERMINATION_CHAR)).trim();
59 if (cmd.startsWith("Z")) {
60 return parseZoneCommand(cmd);
62 // Information command
63 else if (cmd.startsWith("ID")) {
64 return parseInformationCommand(cmd);
67 else if (cmd.startsWith("ICN")) {
68 return parseNumberOfAvailableInputsCommand(cmd);
71 else if (cmd.startsWith("ISN")) {
72 parseInputShortNameCommand(cmd);
75 else if (cmd.startsWith("ILN")) {
76 parseInputLongNameCommand(cmd);
78 // Error response to command
79 else if (cmd.startsWith("!")) {
80 parseErrorCommand(cmd);
82 // Unknown/unhandled command
84 logger.trace("Command parser doesn't know how to handle command: '{}'", cmd);
89 public @Nullable String getInputShortName(String input) {
90 return inputShortNamesMap.get(input);
93 public @Nullable String getInputLongName(String input) {
94 return inputLongNamesMap.get(input);
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);
106 private @Nullable AnthemUpdate parseZoneCommand(String command) {
108 if (command.contains("POW")) {
109 return parsePower(command);
112 else if (command.contains("VOL")) {
113 return parseVolume(command);
116 else if (command.contains("MUT")) {
117 return parseMute(command);
120 else if (command.contains("INP")) {
121 return parseActiveInput(command);
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)) {
131 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_MODEL_ID, value);
134 update = AnthemUpdate.createPropertyUpdate(PROPERTY_REGION, value);
137 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_FIRMWARE_VERSION, value);
140 update = AnthemUpdate.createPropertyUpdate(PROPERTY_SOFTWARE_BUILD_DATE, value);
143 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_HARDWARE_VERSION, value);
146 update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_MAC_ADDRESS, value);
152 logger.debug("Unknown info type");
158 private @Nullable AnthemUpdate parseNumberOfAvailableInputsCommand(String command) {
159 Matcher matcher = NUM_AVAILABLE_INPUTS_PATTERN.matcher(command);
160 if (matcher != null) {
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);
171 private void parseInputShortNameCommand(String command) {
172 parseInputName(command, INPUT_SHORT_NAME_PATTERN.matcher(command), inputShortNamesMap);
175 private void parseInputLongNameCommand(String command) {
176 parseInputName(command, INPUT_LONG_NAME_PATTERN.matcher(command), inputLongNamesMap);
179 private void parseErrorCommand(String command) {
180 logger.info("Command was not processed successfully by the device: '{}'", command);
183 private void parseInputName(String command, @Nullable Matcher matcher, Map<String, String> map) {
184 if (matcher != null) {
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);
196 private @Nullable AnthemUpdate parsePower(String command) {
197 Matcher mmatcher = POWER_PATTERN.matcher(command);
198 if (mmatcher != null) {
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);
211 private @Nullable AnthemUpdate parseVolume(String command) {
212 Matcher matcher = VOLUME_PATTERN.matcher(command);
213 if (matcher != null) {
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);
226 private @Nullable AnthemUpdate parseMute(String command) {
227 Matcher matcher = MUTE_PATTERN.matcher(command);
228 if (matcher != null) {
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);
241 private @Nullable AnthemUpdate parseActiveInput(String command) {
242 Matcher matcher = ACTIVE_INPUT_PATTERN.matcher(command);
243 if (matcher != null) {
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);