2 * Copyright (c) 2010-2024 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.monopriceaudio.internal.communication;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.List;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21 import java.util.stream.IntStream;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.monopriceaudio.internal.configuration.MonopriceAudioThingConfiguration;
25 import org.openhab.binding.monopriceaudio.internal.dto.MonopriceAudioZoneDTO;
26 import org.openhab.core.types.StateOption;
29 * The {@link AmplifierModel} is responsible for mapping low level communications for each supported amplifier model.
31 * @author Michael Lobstein - Initial contribution
34 public enum AmplifierModel {
36 // Monoprice 10761/Dayton Audio DAX66
37 MONOPRICE("<", "\r", "?", "", "#>", "PR", "CH", "VO", "MU", "TR", "BS", "BL", "DT", 38, -7, 7, 7, -10, 10, 10, 18,
38 6, true, List.of("11", "12", "13", "14", "15", "16", "21", "22", "23", "24", "25", "26", "31", "32", "33",
41 public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
42 return getMonopriceZoneData(newZoneData);
46 public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
47 return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
48 new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
49 new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6));
52 // Monoprice 44519 4 zone variant
53 MONOPRICE4("<", "\r", "?", "", "#>", "PR", "CH", "VO", "MU", "TR", "BS", "BL", "DT", 38, -7, 7, 7, -10, 10, 10, 12,
54 6, true, List.of("11", "12", "13", "14", "21", "22", "23", "24", "31", "32", "33", "34")) {
56 public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
57 return getMonopriceZoneData(newZoneData);
61 public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
62 return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
63 new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
64 new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6));
68 DAX88("<", "\r", "?", "", ">", "PR", "CH", "VO", "MU", "TR", "BS", "BL", "DT", 38, -12, 12, 12, -10, 10, 10, 8, 8,
69 true, List.of("01", "02", "03", "04", "05", "06", "07", "08")) {
71 public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
72 return getMonopriceZoneData(newZoneData);
76 public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
77 return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
78 new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
79 new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6),
80 new StateOption("7", config.inputLabel7), new StateOption("8", config.inputLabel8));
83 MONOPRICE70("!", "+\r", "?", "ZS", "?", "PR", "IS", "VO", "MU", "TR", "BS", "BA", "", 38, -7, 7, 7, -32, 31, 32, 6,
84 2, false, List.of("1", "2", "3", "4", "5", "6")) {
86 public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
87 final MonopriceAudioZoneDTO zoneData = new MonopriceAudioZoneDTO();
89 Matcher matcher = MONOPRICE70_PATTERN.matcher(newZoneData);
91 zoneData.setZone(matcher.group(1));
92 zoneData.setVolume(Integer.parseInt(matcher.group(2)));
93 zoneData.setPower(matcher.group(3));
94 zoneData.setMute(matcher.group(4));
95 zoneData.setSource(matcher.group(5));
99 matcher = MONOPRICE70_TREB_PATTERN.matcher(newZoneData);
100 if (matcher.find()) {
101 zoneData.setZone(matcher.group(1));
102 zoneData.setTreble(Integer.parseInt(matcher.group(2)));
106 matcher = MONOPRICE70_BASS_PATTERN.matcher(newZoneData);
107 if (matcher.find()) {
108 zoneData.setZone(matcher.group(1));
109 zoneData.setBass(Integer.parseInt(matcher.group(2)));
113 matcher = MONOPRICE70_BALN_PATTERN.matcher(newZoneData);
114 if (matcher.find()) {
115 zoneData.setZone(matcher.group(1));
116 zoneData.setBalance(Integer.parseInt(matcher.group(2)));
124 public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
125 return List.of(new StateOption("0", config.inputLabel1), new StateOption("1", config.inputLabel2));
128 XANTECH("!", "+\r", "?", "ZD", "#", "PR", "SS", "VO", "MU", "TR", "BS", "BL", "", 38, -7, 7, 7, -32, 31, 32, 16, 8,
129 false, List.of("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16")) {
131 public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
132 final MonopriceAudioZoneDTO zoneData = new MonopriceAudioZoneDTO();
133 final Matcher matcher = XANTECH_PATTERN.matcher(newZoneData);
135 if (matcher.find()) {
136 zoneData.setZone(matcher.group(1));
137 zoneData.setPower(matcher.group(2));
138 zoneData.setSource(matcher.group(3));
139 zoneData.setVolume(Integer.parseInt(matcher.group(4)));
140 zoneData.setMute(matcher.group(5));
141 zoneData.setTreble(Integer.parseInt(matcher.group(6)));
142 zoneData.setBass(Integer.parseInt(matcher.group(7)));
143 zoneData.setBalance(Integer.parseInt(matcher.group(8)));
144 zoneData.setKeypad(matcher.group(9));
145 zoneData.setPage(matcher.group(10));
151 public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
152 return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
153 new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
154 new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6),
155 new StateOption("7", config.inputLabel7), new StateOption("8", config.inputLabel8));
159 // Used by 10761/DAX66 and DAX88
160 private static MonopriceAudioZoneDTO getMonopriceZoneData(String newZoneData) {
161 final MonopriceAudioZoneDTO zoneData = new MonopriceAudioZoneDTO();
162 final Matcher matcher = MONOPRICE_PATTERN.matcher(newZoneData);
164 if (matcher.find()) {
165 zoneData.setZone(matcher.group(1));
166 zoneData.setPage(matcher.group(2));
167 zoneData.setPower(matcher.group(3));
168 zoneData.setMute(matcher.group(4));
169 zoneData.setDnd(matcher.group(5));
170 zoneData.setVolume(Integer.parseInt(matcher.group(6)));
171 zoneData.setTreble(Integer.parseInt(matcher.group(7)));
172 zoneData.setBass(Integer.parseInt(matcher.group(8)));
173 zoneData.setBalance(Integer.parseInt(matcher.group(9)));
174 zoneData.setSource(matcher.group(10));
175 zoneData.setKeypad(matcher.group(11));
180 // Monoprice 10761/DAX66 status string: #>1200010000130809100601
181 // DAX88 status string is the same but does not have leading '#': >xxaabbccddeeffgghhiijj
182 private static final Pattern MONOPRICE_PATTERN = Pattern
183 .compile("^#?>(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})");
185 // Monoprice 31028 / PAM1270 status string: ?6ZS VO8 PO1 MU0 IS0+ (does not include treble, bass & balance)
186 private static final Pattern MONOPRICE70_PATTERN = Pattern
187 .compile("^\\?(\\d{1})ZS VO(\\d{1,2}) PO(\\d{1}) MU(\\d{1}) IS(\\d{1})+");
188 private static final Pattern MONOPRICE70_TREB_PATTERN = Pattern.compile("^\\?(\\d{1})TR(\\d{1,2})+");
189 private static final Pattern MONOPRICE70_BASS_PATTERN = Pattern.compile("^\\?(\\d{1})BS(\\d{1,2})+");
190 private static final Pattern MONOPRICE70_BALN_PATTERN = Pattern.compile("^\\?(\\d{1})BA(\\d{1,2})+");
192 // Xantech status string: #1ZS PR0 SS1 VO0 MU1 TR7 BS7 BA32 LS0 PS0+
193 private static final Pattern XANTECH_PATTERN = Pattern.compile(
194 "^#(\\d{1,2})ZS PR(\\d{1}) SS(\\d{1}) VO(\\d{1,2}) MU(\\d{1}) TR(\\d{1,2}) BS(\\d{1,2}) BA(\\d{1,2}) LS(\\d{1}) PS(\\d{1})+");
196 private final String cmdPrefix;
197 private final String cmdSuffix;
198 private final String queryPrefix;
199 private final String querySuffix;
200 private final String respPrefix;
201 private final String powerCmd;
202 private final String sourceCmd;
203 private final String volumeCmd;
204 private final String muteCmd;
205 private final String trebleCmd;
206 private final String bassCmd;
207 private final String balanceCmd;
208 private final String dndCmd;
209 private final int maxVol;
210 private final int minTone;
211 private final int maxTone;
212 private final int toneOffset;
213 private final int minBal;
214 private final int maxBal;
215 private final int balOffset;
216 private final int maxZones;
217 private final int numSources;
218 private final boolean padNumbers;
219 private final List<String> zoneIds;
220 private final Map<String, String> zoneIdMap;
222 private static final String ON_STR = "1";
223 private static final String OFF_STR = "0";
225 private static final String ON_STR_PAD = "01";
226 private static final String OFF_STR_PAD = "00";
229 * Constructor for all the enum parameters
232 AmplifierModel(String cmdPrefix, String cmdSuffix, String queryPrefix, String querySuffix, String respPrefix,
233 String powerCmd, String sourceCmd, String volumeCmd, String muteCmd, String trebleCmd, String bassCmd,
234 String balanceCmd, String dndCmd, int maxVol, int minTone, int maxTone, int toneOffset, int minBal,
235 int maxBal, int balOffset, int maxZones, int numSources, boolean padNumbers, List<String> zoneIds) {
236 this.cmdPrefix = cmdPrefix;
237 this.cmdSuffix = cmdSuffix;
238 this.queryPrefix = queryPrefix;
239 this.querySuffix = querySuffix;
240 this.respPrefix = respPrefix;
241 this.powerCmd = powerCmd;
242 this.sourceCmd = sourceCmd;
243 this.volumeCmd = volumeCmd;
244 this.muteCmd = muteCmd;
245 this.trebleCmd = trebleCmd;
246 this.bassCmd = bassCmd;
247 this.balanceCmd = balanceCmd;
248 this.dndCmd = dndCmd;
249 this.maxVol = maxVol;
250 this.minTone = minTone;
251 this.maxTone = maxTone;
252 this.toneOffset = toneOffset;
253 this.minBal = minBal;
254 this.maxBal = maxBal;
255 this.balOffset = balOffset;
256 this.maxZones = maxZones;
257 this.numSources = numSources;
258 this.padNumbers = padNumbers;
259 this.zoneIds = zoneIds;
261 // Build a map between the amp's physical zone IDs and the thing's logical zone names
262 final Map<String, String> zoneIdMap = new HashMap<>();
263 IntStream.range(0, zoneIds.size()).forEach(i -> zoneIdMap.put(zoneIds.get(i), "zone" + (i + 1)));
264 this.zoneIdMap = Collections.unmodifiableMap(zoneIdMap);
267 public abstract MonopriceAudioZoneDTO getZoneData(String newZoneData);
269 public abstract List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config);
271 public String getZoneIdFromZoneName(String zoneName) {
272 for (String zoneId : zoneIdMap.keySet()) {
273 if (zoneIdMap.get(zoneId).equals(zoneName)) {
280 public String getZoneName(String zoneId) {
281 String zoneName = zoneIdMap.get(zoneId);
282 if (zoneName != null) {
289 public String getCmdPrefix() {
293 public String getQueryPrefix() {
297 public String getQuerySuffix() {
301 public String getRespPrefix() {
305 public String getPowerCmd() {
309 public String getSourceCmd() {
313 public String getVolumeCmd() {
317 public String getMuteCmd() {
321 public String getTrebleCmd() {
325 public String getBassCmd() {
329 public String getBalanceCmd() {
333 public String getDndCmd() {
337 public int getMaxVol() {
341 public int getMinTone() {
345 public int getMaxTone() {
349 public int getMinBal() {
353 public int getMaxBal() {
357 public int getBalOffset() {
361 public int getToneOffset() {
365 public int getMaxZones() {
369 public int getNumSources() {
373 public String getCmdSuffix() {
377 public List<String> getZoneIds() {
381 public String getFormattedValue(Integer value) {
382 return padNumbers ? String.format("%02d", value) : value.toString();
385 public String getOnStr() {
386 return padNumbers ? ON_STR_PAD : ON_STR;
389 public String getOffStr() {
390 return padNumbers ? OFF_STR_PAD : OFF_STR;