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.library.types.StringType;
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])");
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 AnthemHandler handler;
50 private Map<Integer, String> inputShortNamesMap = new HashMap<>();
51 private Map<Integer, String> inputLongNamesMap = new HashMap<>();
53 private int numAvailableInputs;
55 public AnthemCommandParser(AnthemHandler anthemHandler) {
56 this.handler = anthemHandler;
59 public int getNumAvailableInputs() {
60 return numAvailableInputs;
63 public void parseMessage(String command) {
64 if (!isValidCommand(command)) {
67 // Strip off the termination char and any whitespace
68 String cmd = command.substring(0, command.indexOf(COMMAND_TERMINATION_CHAR)).trim();
71 if (cmd.startsWith("Z")) {
72 parseZoneCommand(cmd);
74 // Information command
75 else if (cmd.startsWith("ID")) {
76 parseInformationCommand(cmd);
79 else if (cmd.startsWith("ICN")) {
80 parseNumberOfAvailableInputsCommand(cmd);
83 else if (cmd.startsWith("ISN")) {
84 parseInputShortNameCommand(cmd);
87 else if (cmd.startsWith("ILN")) {
88 parseInputLongNameCommand(cmd);
90 // Error response to command
91 else if (cmd.startsWith("!")) {
92 parseErrorCommand(cmd);
94 // Unknown/unhandled command
96 logger.trace("Command parser doesn't know how to handle command: '{}'", cmd);
100 private boolean isValidCommand(String command) {
101 if (command.isEmpty() || command.isBlank() || command.length() < 4
102 || command.indexOf(COMMAND_TERMINATION_CHAR) == -1) {
103 logger.trace("Parser received invalid command: '{}'", command);
109 private void parseZoneCommand(String command) {
111 if (command.contains("POW")) {
115 else if (command.contains("VOL")) {
116 parseVolume(command);
119 else if (command.contains("MUT")) {
123 else if (command.contains("INP")) {
124 parseActiveInput(command);
128 private void parseInformationCommand(String command) {
129 String value = command.substring(3, command.length());
130 switch (command.substring(2, 3)) {
132 handler.setModel(value);
135 handler.setRegion(value);
138 handler.setSoftwareVersion(value);
141 handler.setSoftwareBuildDate(value);
144 handler.setHardwareVersion(value);
147 handler.setMacAddress(value);
153 logger.debug("Unknown info type");
158 private void parseNumberOfAvailableInputsCommand(String command) {
159 Matcher matcher = NUM_AVAILABLE_INPUTS_PATTERN.matcher(command);
160 if (matcher != null) {
163 String numAvailableInputsStr = matcher.group(1);
164 DecimalType numAvailableInputs = DecimalType.valueOf(numAvailableInputsStr);
165 handler.setNumAvailableInputs(numAvailableInputs.intValue());
166 this.numAvailableInputs = numAvailableInputs.intValue();
167 } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
168 logger.debug("Parsing exception on command: {}", command, e);
173 private void parseInputShortNameCommand(String command) {
174 parseInputName(command, INPUT_SHORT_NAME_PATTERN.matcher(command), inputShortNamesMap);
177 private void parseInputLongNameCommand(String command) {
178 parseInputName(command, INPUT_LONG_NAME_PATTERN.matcher(command), inputLongNamesMap);
181 private void parseErrorCommand(String command) {
182 logger.info("Command was not processed successfully by the device: '{}'", command);
185 private void parseInputName(String command, @Nullable Matcher matcher, Map<Integer, String> map) {
186 if (matcher != null) {
189 int input = Integer.parseInt(matcher.group(1));
190 String inputName = matcher.group(2);
191 map.putIfAbsent(input, inputName);
192 } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
193 logger.debug("Parsing exception on command: {}", command, e);
198 private void parsePower(String command) {
199 Matcher mmatcher = POWER_PATTERN.matcher(command);
200 if (mmatcher != null) {
203 String zone = mmatcher.group(1);
204 String power = mmatcher.group(2);
205 handler.updateChannelState(zone, CHANNEL_POWER, "1".equals(power) ? OnOffType.ON : OnOffType.OFF);
206 handler.checkPowerStatusChange(zone, power);
207 } catch (IndexOutOfBoundsException | IllegalStateException e) {
208 logger.debug("Parsing exception on command: {}", command, e);
213 private void parseVolume(String command) {
214 Matcher matcher = VOLUME_PATTERN.matcher(command);
215 if (matcher != null) {
218 String zone = matcher.group(1);
219 String volume = matcher.group(2);
220 handler.updateChannelState(zone, CHANNEL_VOLUME_DB, DecimalType.valueOf(volume));
221 } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
222 logger.debug("Parsing exception on command: {}", command, e);
227 private void parseMute(String command) {
228 Matcher matcher = MUTE_PATTERN.matcher(command);
229 if (matcher != null) {
232 String zone = matcher.group(1);
233 String mute = matcher.group(2);
234 handler.updateChannelState(zone, CHANNEL_MUTE, "1".equals(mute) ? OnOffType.ON : OnOffType.OFF);
235 } catch (IndexOutOfBoundsException | IllegalStateException e) {
236 logger.debug("Parsing exception on command: {}", command, e);
241 private void 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 handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT, activeInput);
250 name = inputShortNamesMap.get(activeInput.intValue());
252 handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT_SHORT_NAME, new StringType(name));
254 name = inputShortNamesMap.get(activeInput.intValue());
256 handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT_LONG_NAME, new StringType(name));
258 } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
259 logger.debug("Parsing exception on command: {}", command, e);