]> git.basschouten.com Git - openhab-addons.git/blob
ab529c742b464e8e67faf59309169534ee0b4637
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.emotiva.internal.protocol;
14
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;
22
23 import java.util.Map;
24
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;
36
37 /**
38  * Binds channels to a given command with datatype.
39  *
40  * @author Espen Fossen - Initial contribution
41  */
42 @NonNullByDefault
43 public class EmotivaControlRequest {
44     private final Logger logger = LoggerFactory.getLogger(EmotivaControlRequest.class);
45     private String name;
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;
58
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;
70             } else {
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);
77             }
78         } else {
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);
86             } else {
87                 this.onCommand = controlCommand;
88                 this.offCommand = controlCommand;
89                 this.setCommand = controlCommand;
90                 this.upCommand = controlCommand;
91                 this.downCommand = controlCommand;
92             }
93         }
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;
106         }
107     }
108
109     public EmotivaControlDTO createDTO(Command ohCommand, @Nullable State previousState) {
110         switch (defaultCommand.getCommandType()) {
111             case CYCLE -> {
112                 return EmotivaControlDTO.create(defaultCommand);
113             }
114             case MENU_CONTROL -> {
115                 if (ohCommand instanceof StringType value) {
116                     try {
117                         return EmotivaControlDTO.create(EmotivaControlCommands.valueOf(value.toString().toLowerCase()));
118                     } catch (IllegalArgumentException e) {
119                         return EmotivaControlDTO.create(EmotivaControlCommands.none);
120                     }
121                 }
122             }
123             case MODE -> {
124                 if (ohCommand instanceof StringType value) {
125                     // Check if value can be interpreted as a mode-<command>
126                     try {
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);
135                         }
136                         return EmotivaControlDTO.create(EmotivaControlCommands.none);
137                     }
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);
143                     }
144                 }
145             }
146             case NUMBER -> {
147                 if (ohCommand instanceof Number value) {
148                     return handleNumberTypes(getSetCommand(), ohCommand, value);
149                 } else {
150                     logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
151                             NUMBER, ohCommand.getClass().getSimpleName());
152                     return EmotivaControlDTO.create(EmotivaControlCommands.none);
153                 }
154             }
155             case NONE -> {
156                 switch (channel) {
157                     case CHANNEL_TUNER_BAND -> {
158                         return matchToCommandMap(ohCommand, tuner_band.getEmotivaName());
159                     }
160                     case CHANNEL_TUNER_CHANNEL_SELECT -> {
161                         return matchToCommandMap(ohCommand, tuner_channel.getEmotivaName());
162                     }
163                     case CHANNEL_SOURCE -> {
164                         return matchToCommandMap(ohCommand, MAP_SOURCES_MAIN_ZONE);
165                     }
166                     case CHANNEL_ZONE2_SOURCE -> {
167                         return matchToCommandMap(ohCommand, MAP_SOURCES_ZONE_2);
168                     }
169                     default -> {
170                         return EmotivaControlDTO.create(EmotivaControlCommands.none);
171                     }
172                 }
173             }
174             case SET -> {
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());
182                     } else {
183                         return EmotivaControlDTO.create(getOffCommand());
184                     }
185                 } else {
186                     logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name, SET,
187                             ohCommand.getClass().getSimpleName());
188                     return EmotivaControlDTO.create(EmotivaControlCommands.none);
189                 }
190             }
191             case SPEAKER_PRESET -> {
192                 if (ohCommand instanceof StringType value) {
193                     try {
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);
198                     }
199                 } else {
200                     return EmotivaControlDTO.create(defaultCommand);
201                 }
202             }
203             case TOGGLE -> {
204                 if (ohCommand instanceof OnOffType value) {
205                     if (value.equals(OnOffType.ON)) {
206                         return EmotivaControlDTO.create(getOnCommand());
207                     } else {
208                         return EmotivaControlDTO.create(getOffCommand());
209                     }
210                 } else {
211                     logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
212                             TOGGLE, ohCommand.getClass().getSimpleName());
213                     return EmotivaControlDTO.create(EmotivaControlCommands.none);
214                 }
215             }
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);
224                             }
225                         }
226                     }
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);
232                         }
233                     }
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);
241                     }
242                 } else if (ohCommand instanceof UpDownType value) {
243                     if (value.equals(UpDownType.UP)) {
244                         return EmotivaControlDTO.create(getUpCommand(), 1);
245                     } else {
246                         return EmotivaControlDTO.create(getDownCommand(), -1);
247                     }
248                 } else {
249                     logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
250                             UP_DOWN_SINGLE, ohCommand.getClass().getSimpleName());
251                 }
252                 return EmotivaControlDTO.create(EmotivaControlCommands.none);
253             }
254             case UP_DOWN_HALF -> {
255                 if (ohCommand instanceof Number value) {
256                     if (value.intValue() <= maxValue || value.intValue() >= minValue) {
257                         Number pre = (Number) previousState;
258                         if (pre == null) {
259                             if (value.doubleValue() > 0) {
260                                 return EmotivaControlDTO.create(getUpCommand());
261                             } else if (value.doubleValue() < 0) {
262                                 return EmotivaControlDTO.create(getDownCommand());
263                             }
264                         } else {
265                             if (value.doubleValue() > pre.doubleValue()) {
266                                 return EmotivaControlDTO.create(getUpCommand());
267                             } else if (value.doubleValue() < pre.doubleValue()) {
268                                 return EmotivaControlDTO.create(getDownCommand());
269                             }
270                         }
271                     }
272                 } else {
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);
276                 }
277             }
278             default -> {
279                 return EmotivaControlDTO.create(EmotivaControlCommands.none);
280             }
281         }
282         return EmotivaControlDTO.create(EmotivaControlCommands.none);
283     }
284
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);
295                     }
296                 }
297             }
298         }
299         return EmotivaControlDTO.create(EmotivaControlCommands.none);
300     }
301
302     private EmotivaControlDTO handleNumberTypes(EmotivaControlCommands setCommand, Command ohCommand, Number value) {
303         switch (dataType) {
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()));
311                 } else {
312                     return EmotivaControlDTO.create(setCommand, value.intValue());
313                 }
314             }
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);
320                 } else {
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);
328                     } else {
329                         return EmotivaControlDTO.create(getSetCommand(), doubleValue);
330                     }
331                 }
332             }
333             case FREQUENCY_HERTZ -> {
334                 return EmotivaControlDTO.create(getDefaultCommand(), value.intValue());
335             }
336             default -> {
337                 logger.debug("Could not create EmotivaControlDTO for {}:{}:{}, ohCommand is {}", channel, name,
338                         setCommand.getDataType(), ohCommand.getClass().getSimpleName());
339                 return EmotivaControlDTO.create(EmotivaControlCommands.none);
340             }
341         }
342     }
343
344     private EmotivaControlDTO createForVolumeSetCommand(Command ohCommand, Number value,
345             EmotivaControlCommands emotivaControlCommands) {
346         if (ohCommand instanceof PercentType) {
347             return EmotivaControlDTO.create(emotivaControlCommands, volumePercentageToDecibel(value.intValue()));
348         } else {
349             return EmotivaControlDTO.create(emotivaControlCommands, clamp(value, minValue, maxValue));
350         }
351     }
352
353     private EmotivaControlCommands resolveUpCommand(EmotivaControlCommands controlCommand) {
354         try {
355             return EmotivaControlCommands.valueOf("%s_up".formatted(controlCommand.name()));
356         } catch (IllegalArgumentException e) {
357             // not found, setting original command
358             return controlCommand;
359         }
360     }
361
362     private EmotivaControlCommands resolveDownCommand(EmotivaControlCommands controlCommand) {
363         try {
364             return EmotivaControlCommands.valueOf("%s_down".formatted(controlCommand.name()));
365         } catch (IllegalArgumentException e) {
366             // not found, setting original command
367             return controlCommand;
368         }
369     }
370
371     private EmotivaControlCommands resolveControlCommand(String name, EmotivaControlCommands controlCommand) {
372         try {
373             return controlCommand.equals(EmotivaControlCommands.none) ? EmotivaControlCommands.valueOf(name)
374                     : controlCommand;
375         } catch (IllegalArgumentException e) {
376             // ignore
377         }
378         return EmotivaControlCommands.none;
379     }
380
381     private EmotivaControlCommands resolveOnCommand(EmotivaControlCommands controlCommand) {
382         try {
383             return EmotivaControlCommands.valueOf("%s_on".formatted(controlCommand.name()));
384         } catch (IllegalArgumentException e) {
385             // not found, setting original command
386             return controlCommand;
387         }
388     }
389
390     private EmotivaControlCommands resolveOffCommand(EmotivaControlCommands controlCommand) {
391         try {
392             return EmotivaControlCommands.valueOf("%s_off".formatted(controlCommand.name()));
393         } catch (IllegalArgumentException e) {
394             // not found, using original command
395             return controlCommand;
396         }
397     }
398
399     /**
400      * Checks for commands with _trim_set suffix, which indicate speaker trims with a fixed min/max value.
401      */
402     private EmotivaControlCommands resolveSetCommand(EmotivaControlCommands controlCommand) {
403         try {
404             return EmotivaControlCommands.valueOf("%s_trim_set".formatted(controlCommand.name()));
405         } catch (IllegalArgumentException e) {
406             // not found, using original command
407             return controlCommand;
408         }
409     }
410
411     public String getName() {
412         return name;
413     }
414
415     public EmotivaDataType getDataType() {
416         return dataType;
417     }
418
419     public String getChannel() {
420         return channel;
421     }
422
423     public EmotivaControlCommands getDefaultCommand() {
424         return defaultCommand;
425     }
426
427     public void setName(String name) {
428         this.name = name;
429     }
430
431     public void setChannel(String channel) {
432         this.channel = channel;
433     }
434
435     public EmotivaControlCommands getSetCommand() {
436         return setCommand;
437     }
438
439     public EmotivaControlCommands getOnCommand() {
440         return onCommand;
441     }
442
443     public EmotivaControlCommands getOffCommand() {
444         return offCommand;
445     }
446
447     public EmotivaControlCommands getUpCommand() {
448         return upCommand;
449     }
450
451     public EmotivaControlCommands getDownCommand() {
452         return downCommand;
453     }
454
455     public double getMaxValue() {
456         return maxValue;
457     }
458
459     public double getMinValue() {
460         return minValue;
461     }
462
463     public EmotivaProtocolVersion getProtocolVersion() {
464         return protocolVersion;
465     }
466
467     @Override
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 + '}';
474     }
475 }