]> git.basschouten.com Git - openhab-addons.git/blob
8349093013b226433466663c91c732b3a44d9613
[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.library.types.StringType;
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])");
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 AnthemHandler handler;
49
50     private Map<Integer, String> inputShortNamesMap = new HashMap<>();
51     private Map<Integer, String> inputLongNamesMap = new HashMap<>();
52
53     private int numAvailableInputs;
54
55     public AnthemCommandParser(AnthemHandler anthemHandler) {
56         this.handler = anthemHandler;
57     }
58
59     public int getNumAvailableInputs() {
60         return numAvailableInputs;
61     }
62
63     public void parseMessage(String command) {
64         if (!isValidCommand(command)) {
65             return;
66         }
67         // Strip off the termination char and any whitespace
68         String cmd = command.substring(0, command.indexOf(COMMAND_TERMINATION_CHAR)).trim();
69
70         // Zone command
71         if (cmd.startsWith("Z")) {
72             parseZoneCommand(cmd);
73         }
74         // Information command
75         else if (cmd.startsWith("ID")) {
76             parseInformationCommand(cmd);
77         }
78         // Number of inputs
79         else if (cmd.startsWith("ICN")) {
80             parseNumberOfAvailableInputsCommand(cmd);
81         }
82         // Input short name
83         else if (cmd.startsWith("ISN")) {
84             parseInputShortNameCommand(cmd);
85         }
86         // Input long name
87         else if (cmd.startsWith("ILN")) {
88             parseInputLongNameCommand(cmd);
89         }
90         // Error response to command
91         else if (cmd.startsWith("!")) {
92             parseErrorCommand(cmd);
93         }
94         // Unknown/unhandled command
95         else {
96             logger.trace("Command parser doesn't know how to handle command: '{}'", cmd);
97         }
98     }
99
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);
104             return false;
105         }
106         return true;
107     }
108
109     private void parseZoneCommand(String command) {
110         // Power update
111         if (command.contains("POW")) {
112             parsePower(command);
113         }
114         // Volume update
115         else if (command.contains("VOL")) {
116             parseVolume(command);
117         }
118         // Mute update
119         else if (command.contains("MUT")) {
120             parseMute(command);
121         }
122         // Active input
123         else if (command.contains("INP")) {
124             parseActiveInput(command);
125         }
126     }
127
128     private void parseInformationCommand(String command) {
129         String value = command.substring(3, command.length());
130         switch (command.substring(2, 3)) {
131             case "M":
132                 handler.setModel(value);
133                 break;
134             case "R":
135                 handler.setRegion(value);
136                 break;
137             case "S":
138                 handler.setSoftwareVersion(value);
139                 break;
140             case "B":
141                 handler.setSoftwareBuildDate(value);
142                 break;
143             case "H":
144                 handler.setHardwareVersion(value);
145                 break;
146             case "N":
147                 handler.setMacAddress(value);
148                 break;
149             case "Q":
150                 // Ignore
151                 break;
152             default:
153                 logger.debug("Unknown info type");
154                 break;
155         }
156     }
157
158     private void parseNumberOfAvailableInputsCommand(String command) {
159         Matcher matcher = NUM_AVAILABLE_INPUTS_PATTERN.matcher(command);
160         if (matcher != null) {
161             try {
162                 matcher.find();
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);
169             }
170         }
171     }
172
173     private void parseInputShortNameCommand(String command) {
174         parseInputName(command, INPUT_SHORT_NAME_PATTERN.matcher(command), inputShortNamesMap);
175     }
176
177     private void parseInputLongNameCommand(String command) {
178         parseInputName(command, INPUT_LONG_NAME_PATTERN.matcher(command), inputLongNamesMap);
179     }
180
181     private void parseErrorCommand(String command) {
182         logger.info("Command was not processed successfully by the device: '{}'", command);
183     }
184
185     private void parseInputName(String command, @Nullable Matcher matcher, Map<Integer, String> map) {
186         if (matcher != null) {
187             try {
188                 matcher.find();
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);
194             }
195         }
196     }
197
198     private void parsePower(String command) {
199         Matcher mmatcher = POWER_PATTERN.matcher(command);
200         if (mmatcher != null) {
201             try {
202                 mmatcher.find();
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);
209             }
210         }
211     }
212
213     private void parseVolume(String command) {
214         Matcher matcher = VOLUME_PATTERN.matcher(command);
215         if (matcher != null) {
216             try {
217                 matcher.find();
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);
223             }
224         }
225     }
226
227     private void parseMute(String command) {
228         Matcher matcher = MUTE_PATTERN.matcher(command);
229         if (matcher != null) {
230             try {
231                 matcher.find();
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);
237             }
238         }
239     }
240
241     private void 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                 handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT, activeInput);
249                 String name;
250                 name = inputShortNamesMap.get(activeInput.intValue());
251                 if (name != null) {
252                     handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT_SHORT_NAME, new StringType(name));
253                 }
254                 name = inputShortNamesMap.get(activeInput.intValue());
255                 if (name != null) {
256                     handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT_LONG_NAME, new StringType(name));
257                 }
258             } catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
259                 logger.debug("Parsing exception on command: {}", command, e);
260             }
261         }
262     }
263 }