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.emotiva.internal.protocol;
15 import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.*;
16 import static org.openhab.binding.emotiva.internal.EmotivaCommandHelper.clamp;
17 import static org.openhab.binding.emotiva.internal.EmotivaCommandHelper.volumePercentageToDecibel;
18 import static org.openhab.binding.emotiva.internal.protocol.EmotivaCommandType.*;
19 import static org.openhab.binding.emotiva.internal.protocol.EmotivaDataType.FREQUENCY_HERTZ;
20 import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_band;
21 import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_channel;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.emotiva.internal.dto.EmotivaControlDTO;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.PercentType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.library.types.UpDownType;
32 import org.openhab.core.types.Command;
33 import org.openhab.core.types.State;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * Binds channels to a given command with datatype.
40 * @author Espen Fossen - Initial contribution
43 public class EmotivaControlRequest {
44 private final Logger logger = LoggerFactory.getLogger(EmotivaControlRequest.class);
46 private final EmotivaDataType dataType;
47 private String channel;
48 private final EmotivaControlCommands defaultCommand;
49 private final EmotivaControlCommands setCommand;
50 private final EmotivaControlCommands onCommand;
51 private final EmotivaControlCommands offCommand;
52 private final EmotivaControlCommands upCommand;
53 private final EmotivaControlCommands downCommand;
54 private double maxValue;
55 private double minValue;
56 private final Map<String, Map<EmotivaControlCommands, String>> commandMaps;
57 private final EmotivaProtocolVersion protocolVersion;
59 public EmotivaControlRequest(String channel, EmotivaSubscriptionTags channelSubscription,
60 EmotivaControlCommands controlCommand, Map<String, Map<EmotivaControlCommands, String>> commandMaps,
61 EmotivaProtocolVersion protocolVersion) {
62 if (channelSubscription.equals(EmotivaSubscriptionTags.unknown)) {
63 if (controlCommand.equals(EmotivaControlCommands.none)) {
64 this.defaultCommand = EmotivaControlCommands.none;
65 this.onCommand = EmotivaControlCommands.none;
66 this.offCommand = EmotivaControlCommands.none;
67 this.setCommand = EmotivaControlCommands.none;
68 this.upCommand = EmotivaControlCommands.none;
69 this.downCommand = EmotivaControlCommands.none;
71 this.defaultCommand = controlCommand;
72 this.onCommand = resolveOnCommand(controlCommand);
73 this.offCommand = resolveOffCommand(controlCommand);
74 this.setCommand = resolveSetCommand(controlCommand);
75 this.upCommand = resolveUpCommand(controlCommand);
76 this.downCommand = resolveDownCommand(controlCommand);
79 this.defaultCommand = resolveControlCommand(channelSubscription.getEmotivaName(), controlCommand);
80 if (controlCommand.equals(EmotivaControlCommands.none)) {
81 this.onCommand = resolveOnCommand(defaultCommand);
82 this.offCommand = resolveOffCommand(defaultCommand);
83 this.setCommand = resolveSetCommand(defaultCommand);
84 this.upCommand = resolveUpCommand(defaultCommand);
85 this.downCommand = resolveDownCommand(defaultCommand);
87 this.onCommand = controlCommand;
88 this.offCommand = controlCommand;
89 this.setCommand = controlCommand;
90 this.upCommand = controlCommand;
91 this.downCommand = controlCommand;
94 this.name = defaultCommand.name();
95 this.dataType = defaultCommand.getDataType();
96 this.channel = channel;
97 this.commandMaps = commandMaps;
98 this.protocolVersion = protocolVersion;
99 if (name.equals(EmotivaControlCommands.volume.name())
100 || name.equals(EmotivaControlCommands.zone2_volume.name())) {
101 minValue = DEFAULT_VOLUME_MIN_DECIBEL;
102 maxValue = DEFAULT_VOLUME_MAX_DECIBEL;
103 } else if (setCommand.name().endsWith(TRIM_SET_COMMAND_SUFFIX)) {
104 minValue = DEFAULT_TRIM_MIN_DECIBEL * 2;
105 maxValue = DEFAULT_TRIM_MAX_DECIBEL * 2;
109 public EmotivaControlDTO createDTO(Command ohCommand, @Nullable State previousState) {
110 switch (defaultCommand.getCommandType()) {
112 return EmotivaControlDTO.create(defaultCommand);
114 case MENU_CONTROL -> {
115 if (ohCommand instanceof StringType value) {
117 return EmotivaControlDTO.create(EmotivaControlCommands.valueOf(value.toString().toLowerCase()));
118 } catch (IllegalArgumentException e) {
119 return EmotivaControlDTO.create(EmotivaControlCommands.none);
124 if (ohCommand instanceof StringType value) {
125 // Check if value can be interpreted as a mode-<command>
127 OHChannelToEmotivaCommand ohChannelToEmotivaCommand = OHChannelToEmotivaCommand
128 .valueOf(value.toString());
129 return EmotivaControlDTO.create(ohChannelToEmotivaCommand.getCommand());
130 } catch (IllegalArgumentException e) {
131 if ("1".equals(value.toString())) {
132 return EmotivaControlDTO.create(getUpCommand(), 1);
133 } else if ("-1".equals(value.toString())) {
134 return EmotivaControlDTO.create(getDownCommand(), -1);
136 return EmotivaControlDTO.create(EmotivaControlCommands.none);
138 } else if (ohCommand instanceof Number value) {
139 if (value.intValue() >= 1) {
140 return EmotivaControlDTO.create(getUpCommand(), 1);
141 } else if (value.intValue() <= -1) {
142 return EmotivaControlDTO.create(getDownCommand(), -1);
147 if (ohCommand instanceof Number value) {
148 return handleNumberTypes(getSetCommand(), ohCommand, value);
150 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
151 NUMBER, ohCommand.getClass().getSimpleName());
152 return EmotivaControlDTO.create(EmotivaControlCommands.none);
157 case CHANNEL_TUNER_BAND -> {
158 return matchToCommandMap(ohCommand, tuner_band.getEmotivaName());
160 case CHANNEL_TUNER_CHANNEL_SELECT -> {
161 return matchToCommandMap(ohCommand, tuner_channel.getEmotivaName());
163 case CHANNEL_SOURCE -> {
164 return matchToCommandMap(ohCommand, MAP_SOURCES_MAIN_ZONE);
166 case CHANNEL_ZONE2_SOURCE -> {
167 return matchToCommandMap(ohCommand, MAP_SOURCES_ZONE_2);
170 return EmotivaControlDTO.create(EmotivaControlCommands.none);
175 if (ohCommand instanceof StringType value) {
176 return EmotivaControlDTO.create(getSetCommand(), value.toString());
177 } else if (ohCommand instanceof Number value) {
178 return handleNumberTypes(getSetCommand(), ohCommand, value);
179 } else if (ohCommand instanceof OnOffType value) {
180 if (value.equals(OnOffType.ON)) {
181 return EmotivaControlDTO.create(getOnCommand());
183 return EmotivaControlDTO.create(getOffCommand());
186 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name, SET,
187 ohCommand.getClass().getSimpleName());
188 return EmotivaControlDTO.create(EmotivaControlCommands.none);
191 case SPEAKER_PRESET -> {
192 if (ohCommand instanceof StringType value) {
194 return EmotivaControlDTO.create(EmotivaControlCommands.valueOf(value.toString()));
195 } catch (IllegalArgumentException e) {
196 // No match found for preset command, default to cycling
197 return EmotivaControlDTO.create(defaultCommand);
200 return EmotivaControlDTO.create(defaultCommand);
204 if (ohCommand instanceof OnOffType value) {
205 if (value.equals(OnOffType.ON)) {
206 return EmotivaControlDTO.create(getOnCommand());
208 return EmotivaControlDTO.create(getOffCommand());
211 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
212 TOGGLE, ohCommand.getClass().getSimpleName());
213 return EmotivaControlDTO.create(EmotivaControlCommands.none);
216 case UP_DOWN_SINGLE -> {
217 if (ohCommand instanceof Number value) {
218 if (dataType.equals(FREQUENCY_HERTZ)) {
219 if (previousState instanceof Number pre) {
220 if (value.doubleValue() > pre.doubleValue()) {
221 return EmotivaControlDTO.create(getUpCommand(), 1);
222 } else if (value.doubleValue() < pre.doubleValue()) {
223 return EmotivaControlDTO.create(getDownCommand(), -1);
227 if (value.intValue() <= maxValue || value.intValue() >= minValue) {
228 if (value.intValue() >= 1) {
229 return EmotivaControlDTO.create(getUpCommand(), 1);
230 } else if (value.intValue() <= -1) {
231 return EmotivaControlDTO.create(getDownCommand(), -1);
234 // Reached max or min value, not sending anything
235 return EmotivaControlDTO.create(EmotivaControlCommands.none);
236 } else if (ohCommand instanceof StringType value) {
237 if ("1".equals(value.toString())) {
238 return EmotivaControlDTO.create(getUpCommand(), 1);
239 } else if ("-1".equals(value.toString())) {
240 return EmotivaControlDTO.create(getDownCommand(), -1);
242 } else if (ohCommand instanceof UpDownType value) {
243 if (value.equals(UpDownType.UP)) {
244 return EmotivaControlDTO.create(getUpCommand(), 1);
246 return EmotivaControlDTO.create(getDownCommand(), -1);
249 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
250 UP_DOWN_SINGLE, ohCommand.getClass().getSimpleName());
252 return EmotivaControlDTO.create(EmotivaControlCommands.none);
254 case UP_DOWN_HALF -> {
255 if (ohCommand instanceof Number value) {
256 if (value.intValue() <= maxValue || value.intValue() >= minValue) {
257 Number pre = (Number) previousState;
259 if (value.doubleValue() > 0) {
260 return EmotivaControlDTO.create(getUpCommand());
261 } else if (value.doubleValue() < 0) {
262 return EmotivaControlDTO.create(getDownCommand());
265 if (value.doubleValue() > pre.doubleValue()) {
266 return EmotivaControlDTO.create(getUpCommand());
267 } else if (value.doubleValue() < pre.doubleValue()) {
268 return EmotivaControlDTO.create(getDownCommand());
273 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
274 UP_DOWN_HALF, ohCommand.getClass().getSimpleName());
275 return EmotivaControlDTO.create(EmotivaControlCommands.none);
279 return EmotivaControlDTO.create(EmotivaControlCommands.none);
282 return EmotivaControlDTO.create(EmotivaControlCommands.none);
285 private EmotivaControlDTO matchToCommandMap(Command ohCommand, String mapName) {
286 if (ohCommand instanceof StringType value) {
287 Map<EmotivaControlCommands, String> commandMap = commandMaps.get(mapName);
288 if (commandMap != null) {
289 for (EmotivaControlCommands command : commandMap.keySet()) {
290 String map = commandMap.get(command);
291 if (map != null && map.equals(value.toString())) {
292 return EmotivaControlDTO.create(EmotivaControlCommands.matchToInput(command.toString()));
293 } else if (command.name().equalsIgnoreCase(value.toString())) {
294 return EmotivaControlDTO.create(command);
299 return EmotivaControlDTO.create(EmotivaControlCommands.none);
302 private EmotivaControlDTO handleNumberTypes(EmotivaControlCommands setCommand, Command ohCommand, Number value) {
304 case DIMENSIONLESS_PERCENT -> {
305 if (name.equals(EmotivaControlCommands.volume.name())) {
306 return EmotivaControlDTO.create(EmotivaControlCommands.set_volume,
307 volumePercentageToDecibel(value.intValue()));
308 } else if (name.equals(EmotivaControlCommands.zone2_set_volume.name())) {
309 return EmotivaControlDTO.create(EmotivaControlCommands.zone2_set_volume,
310 volumePercentageToDecibel(value.intValue()));
312 return EmotivaControlDTO.create(setCommand, value.intValue());
315 case DIMENSIONLESS_DECIBEL -> {
316 if (name.equals(EmotivaControlCommands.volume.name())) {
317 return createForVolumeSetCommand(ohCommand, value, EmotivaControlCommands.set_volume);
318 } else if (name.equals(EmotivaControlCommands.zone2_volume.name())) {
319 return createForVolumeSetCommand(ohCommand, value, EmotivaControlCommands.zone2_set_volume);
321 double doubleValue = setCommand.name().endsWith(TRIM_SET_COMMAND_SUFFIX)
322 ? value.doubleValue() * PROTOCOL_V3_LEVEL_MULTIPLIER
323 : value.doubleValue();
324 if (doubleValue >= maxValue) {
325 return EmotivaControlDTO.create(getSetCommand(), maxValue);
326 } else if (doubleValue <= minValue) {
327 return EmotivaControlDTO.create(getSetCommand(), minValue);
329 return EmotivaControlDTO.create(getSetCommand(), doubleValue);
333 case FREQUENCY_HERTZ -> {
334 return EmotivaControlDTO.create(getDefaultCommand(), value.intValue());
337 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
338 setCommand.getDataType(), ohCommand.getClass().getSimpleName());
339 return EmotivaControlDTO.create(EmotivaControlCommands.none);
344 private EmotivaControlDTO createForVolumeSetCommand(Command ohCommand, Number value,
345 EmotivaControlCommands emotivaControlCommands) {
346 if (ohCommand instanceof PercentType) {
347 return EmotivaControlDTO.create(emotivaControlCommands, volumePercentageToDecibel(value.intValue()));
349 return EmotivaControlDTO.create(emotivaControlCommands, clamp(value, minValue, maxValue));
353 private EmotivaControlCommands resolveUpCommand(EmotivaControlCommands controlCommand) {
355 return EmotivaControlCommands.valueOf("%s_up".formatted(controlCommand.name()));
356 } catch (IllegalArgumentException e) {
357 // not found, setting original command
358 return controlCommand;
362 private EmotivaControlCommands resolveDownCommand(EmotivaControlCommands controlCommand) {
364 return EmotivaControlCommands.valueOf("%s_down".formatted(controlCommand.name()));
365 } catch (IllegalArgumentException e) {
366 // not found, setting original command
367 return controlCommand;
371 private EmotivaControlCommands resolveControlCommand(String name, EmotivaControlCommands controlCommand) {
373 return controlCommand.equals(EmotivaControlCommands.none) ? EmotivaControlCommands.valueOf(name)
375 } catch (IllegalArgumentException e) {
378 return EmotivaControlCommands.none;
381 private EmotivaControlCommands resolveOnCommand(EmotivaControlCommands controlCommand) {
383 return EmotivaControlCommands.valueOf("%s_on".formatted(controlCommand.name()));
384 } catch (IllegalArgumentException e) {
385 // not found, setting original command
386 return controlCommand;
390 private EmotivaControlCommands resolveOffCommand(EmotivaControlCommands controlCommand) {
392 return EmotivaControlCommands.valueOf("%s_off".formatted(controlCommand.name()));
393 } catch (IllegalArgumentException e) {
394 // not found, using original command
395 return controlCommand;
400 * Checks for commands with _trim_set suffix, which indicate speaker trims with a fixed min/max value.
402 private EmotivaControlCommands resolveSetCommand(EmotivaControlCommands controlCommand) {
404 return EmotivaControlCommands.valueOf("%s_trim_set".formatted(controlCommand.name()));
405 } catch (IllegalArgumentException e) {
406 // not found, using original command
407 return controlCommand;
411 public String getName() {
415 public EmotivaDataType getDataType() {
419 public String getChannel() {
423 public EmotivaControlCommands getDefaultCommand() {
424 return defaultCommand;
427 public void setName(String name) {
431 public void setChannel(String channel) {
432 this.channel = channel;
435 public EmotivaControlCommands getSetCommand() {
439 public EmotivaControlCommands getOnCommand() {
443 public EmotivaControlCommands getOffCommand() {
447 public EmotivaControlCommands getUpCommand() {
451 public EmotivaControlCommands getDownCommand() {
455 public double getMaxValue() {
459 public double getMinValue() {
463 public EmotivaProtocolVersion getProtocolVersion() {
464 return protocolVersion;
468 public String toString() {
469 return "EmotivaControlRequest{" + "name='" + name + '\'' + ", dataType=" + dataType + ", channel='" + channel
470 + '\'' + ", defaultCommand=" + defaultCommand + ", setCommand=" + setCommand + ", onCommand="
471 + onCommand + ", offCommand=" + offCommand + ", upCommand=" + upCommand + ", downCommand=" + downCommand
472 + ", maxValue=" + maxValue + ", minValue=" + minValue + ", commandMaps=" + commandMaps
473 + ", protocolVersion=" + protocolVersion + '}';