2 * Copyright (c) 2010-2022 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.bosesoundtouch.internal;
15 import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
17 import java.util.Collection;
18 import java.util.HashMap;
21 import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
22 import org.openhab.core.library.types.DecimalType;
23 import org.openhab.core.library.types.NextPreviousType;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.library.types.PercentType;
26 import org.openhab.core.library.types.PlayPauseType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.types.Command;
29 import org.openhab.core.types.State;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * The {@link CommandExecutor} class executes commands on the websocket
36 * @author Thomas Traunbauer - Initial contribution
37 * @author Kai Kreuzer - code clean up
39 public class CommandExecutor implements AvailableSources {
40 private final Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
42 private final BoseSoundTouchHandler handler;
44 private boolean currentMuted;
45 private ContentItem currentContentItem;
46 private OperationModeType currentOperationMode;
48 private Map<String, Boolean> mapOfAvailableFunctions;
51 * Creates a new instance of this class
53 * @param handler the handler that created this CommandExecutor
55 public CommandExecutor(BoseSoundTouchHandler handler) {
56 this.handler = handler;
61 * Synchronizes the underlying storage container with the current value for the presets stored on the player
62 * by updating the available ones and deleting the cleared ones
64 * @param playerPresets a Map<Integer, ContentItems> containing the items currently stored on the player
66 public void updatePresetContainerFromPlayer(Map<Integer, ContentItem> playerPresets) {
67 playerPresets.forEach((k, v) -> {
70 handler.getPresetContainer().put(k, v);
72 handler.getPresetContainer().remove(k);
74 } catch (ContentItemNotPresetableException e) {
75 logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
79 handler.refreshPresetChannel();
83 * Adds a ContentItem to the PresetContainer
85 * @param id the id the ContentItem should be reached
86 * @param contentItem the contentItem that should be saved as PRESET. Note that a eventually set presetID of the
87 * ContentItem will be overwritten with id
89 public void addContentItemToPresetContainer(int id, ContentItem contentItem) {
90 contentItem.setPresetID(id);
92 handler.getPresetContainer().put(id, contentItem);
93 } catch (ContentItemNotPresetableException e) {
94 logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
96 handler.refreshPresetChannel();
100 * Adds the current selected ContentItem to the PresetContainer
102 * @param command the command is a DecimalType, thats intValue will be used as id. The id the ContentItem should be
105 public void addCurrentContentItemToPresetContainer(DecimalType command) {
106 if (command.intValue() > 6) {
107 addContentItemToPresetContainer(command.intValue(), currentContentItem);
109 logger.warn("{}: Only PresetID >6 is allowed", handler.getDeviceName());
114 * Initializes a API Request on this device
116 * @param apiRequest the apiRequest thats informations should be collected
118 public void getInformations(APIRequest apiRequest) {
119 String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + apiRequest
120 + "\" method=\"GET\"><request requestID=\"0\"><info type=\"new\"/></request></header></msg>";
121 handler.getSession().getRemote().sendStringByFuture(msg);
122 logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
126 * Sets the current ContentItem if it is valid, and inits an update of the operating values
130 public void setCurrentContentItem(ContentItem contentItem) {
131 if ((contentItem != null) && (contentItem.isValid())) {
132 ContentItem psFound = null;
133 if (handler.getPresetContainer() != null) {
134 Collection<ContentItem> listOfPresets = handler.getPresetContainer().getAllPresets();
135 for (ContentItem ps : listOfPresets) {
136 if (ps.isPresetable()) {
137 if (ps.getLocation().equals(contentItem.getLocation())) {
143 if (psFound != null) {
144 presetID = psFound.getPresetID();
146 contentItem.setPresetID(presetID);
148 currentContentItem = contentItem;
151 updateOperatingValues();
155 * Sets the device is currently muted
159 public void setCurrentMuted(boolean muted) {
160 currentMuted = muted;
164 * Post Bass on the device
166 * @param command the command is Type of DecimalType
168 public void postBass(DecimalType command) {
169 if (isBassAvailable()) {
170 sendPostRequestInWebSocket("bass",
171 "<bass deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</bass>");
173 logger.warn("{}: Bass modification not supported for this device", handler.getDeviceName());
178 * Post OperationMode on the device
180 * @param command the command is Type of OperationModeType
182 public void postOperationMode(OperationModeType command) {
183 if (command == OperationModeType.STANDBY) {
184 postPower(OnOffType.OFF);
187 ContentItemMaker contentItemMaker = new ContentItemMaker(this, handler.getPresetContainer());
188 ContentItem contentItem = contentItemMaker.getContentItem(command);
189 postContentItem(contentItem);
190 } catch (OperationModeNotAvailableException e) {
191 logger.warn("{}: OperationMode \"{}\" is not supported yet", handler.getDeviceName(),
193 } catch (NoInternetRadioPresetFoundException e) {
194 logger.warn("{}: Unable to switch to mode \"INTERNET_RADIO\". No PRESET defined",
195 handler.getDeviceName());
196 } catch (NoStoredMusicPresetFoundException e) {
197 logger.warn("{}: Unable to switch to mode: \"STORED_MUSIC\". No PRESET defined",
198 handler.getDeviceName());
200 updateOperatingValues();
205 * Post PlayerControl on the device
207 * @param command the command is Type of Command
209 public void postPlayerControl(Command command) {
210 if (command.equals(PlayPauseType.PLAY)) {
211 if (currentOperationMode == OperationModeType.STANDBY) {
212 postRemoteKey(RemoteKeyType.POWER);
214 postRemoteKey(RemoteKeyType.PLAY);
216 } else if (command.equals(PlayPauseType.PAUSE)) {
217 postRemoteKey(RemoteKeyType.PAUSE);
218 } else if (command.equals(NextPreviousType.NEXT)) {
219 postRemoteKey(RemoteKeyType.NEXT_TRACK);
220 } else if (command.equals(NextPreviousType.PREVIOUS)) {
221 postRemoteKey(RemoteKeyType.PREV_TRACK);
226 * Post Power on the device
228 * @param command the command is Type of OnOffType
230 public void postPower(OnOffType command) {
231 if (command.equals(OnOffType.ON)) {
232 if (currentOperationMode == OperationModeType.STANDBY) {
233 postRemoteKey(RemoteKeyType.POWER);
235 } else if (command.equals(OnOffType.OFF)) {
236 if (currentOperationMode != OperationModeType.STANDBY) {
237 postRemoteKey(RemoteKeyType.POWER);
240 updateOperatingValues();
244 * Post Preset on the device
246 * @param command the command is Type of DecimalType
248 public void postPreset(DecimalType command) {
249 ContentItem item = null;
251 item = handler.getPresetContainer().get(command.intValue());
252 postContentItem(item);
253 } catch (NoPresetFoundException e) {
254 logger.warn("{}: No preset found at id: {}", handler.getDeviceName(), command.intValue());
259 * Post RemoteKey on the device
261 * @param command the command is Type of RemoteKeyType
263 public void postRemoteKey(RemoteKeyType key) {
264 sendPostRequestInWebSocket("key", "mainNode=\"keyPress\"",
265 "<key state=\"press\" sender=\"Gabbo\">" + key.name() + "</key>");
266 sendPostRequestInWebSocket("key", "mainNode=\"keyRelease\"",
267 "<key state=\"release\" sender=\"Gabbo\">" + key.name() + "</key>");
271 * Post Volume on the device
273 * @param command the command is Type of PercentType
275 public void postVolume(PercentType command) {
276 sendPostRequestInWebSocket("volume",
277 "<volume deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</volume>");
281 * Post VolumeMute on the device
283 * @param command the command is Type of OnOffType
285 public void postVolumeMuted(OnOffType command) {
286 if (command.equals(OnOffType.ON)) {
289 postRemoteKey(RemoteKeyType.MUTE);
291 } else if (command.equals(OnOffType.OFF)) {
293 currentMuted = false;
294 postRemoteKey(RemoteKeyType.MUTE);
300 * Update GUI for Basslevel
302 * @param state the state is Type of DecimalType
304 public void updateBassLevelGUIState(DecimalType state) {
305 handler.updateState(CHANNEL_BASS, state);
309 * Update GUI for Volume
311 * @param state the state is Type of PercentType
313 public void updateVolumeGUIState(PercentType state) {
314 handler.updateState(CHANNEL_VOLUME, state);
318 * Update GUI for OperationMode
320 * @param state the state is Type of StringType
322 public void updateOperationModeGUIState(StringType state) {
323 handler.updateState(CHANNEL_OPERATIONMODE, state);
327 * Update GUI for PlayerControl
329 * @param state the state is Type of State
331 public void updatePlayerControlGUIState(State state) {
332 handler.updateState(CHANNEL_PLAYER_CONTROL, state);
336 * Update GUI for Power
338 * @param state the state is Type of OnOffType
340 public void updatePowerStateGUIState(OnOffType state) {
341 handler.updateState(CHANNEL_POWER, state);
345 * Update GUI for Preset
347 * @param state the state is Type of DecimalType
349 public void updatePresetGUIState(DecimalType state) {
350 handler.updateState(CHANNEL_PRESET, state);
353 private void init() {
354 getInformations(APIRequest.INFO);
355 currentOperationMode = OperationModeType.OFFLINE;
356 currentContentItem = null;
358 mapOfAvailableFunctions = new HashMap<>();
361 private void postContentItem(ContentItem contentItem) {
362 if (contentItem != null) {
363 setCurrentContentItem(contentItem);
364 sendPostRequestInWebSocket("select", "", contentItem.generateXML());
368 private void sendPostRequestInWebSocket(String url, String postData) {
369 sendPostRequestInWebSocket(url, "", postData);
372 private void sendPostRequestInWebSocket(String url, String infoAddon, String postData) {
374 String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
375 + "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
376 + " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
378 handler.getSession().getRemote().sendStringByFuture(msg);
379 logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
380 } catch (NullPointerException e) {
381 handler.onWebSocketError(e);
385 private void updateOperatingValues() {
386 OperationModeType operationMode;
387 if (currentContentItem != null) {
388 updatePresetGUIState(new DecimalType(currentContentItem.getPresetID()));
389 operationMode = currentContentItem.getOperationMode();
391 operationMode = OperationModeType.STANDBY;
394 updateOperationModeGUIState(new StringType(operationMode.toString()));
395 currentOperationMode = operationMode;
396 if (currentOperationMode == OperationModeType.STANDBY) {
397 updatePowerStateGUIState(OnOffType.OFF);
398 updatePlayerControlGUIState(PlayPauseType.PAUSE);
400 updatePowerStateGUIState(OnOffType.ON);
405 public boolean isBluetoothAvailable() {
406 return isSourceAvailable("bluetooth");
410 public boolean isAUXAvailable() {
411 return isSourceAvailable("aux");
415 public boolean isAUX1Available() {
416 return isSourceAvailable("aux1");
420 public boolean isAUX2Available() {
421 return isSourceAvailable("aux2");
425 public boolean isAUX3Available() {
426 return isSourceAvailable("aux3");
430 public boolean isTVAvailable() {
431 return isSourceAvailable("tv");
435 public boolean isHDMI1Available() {
436 return isSourceAvailable("hdmi1");
440 public boolean isInternetRadioAvailable() {
441 return isSourceAvailable("internetRadio");
445 public boolean isStoredMusicAvailable() {
446 return isSourceAvailable("storedMusic");
450 public boolean isBassAvailable() {
451 return isSourceAvailable("bass");
455 public void setBluetoothAvailable(boolean bluetooth) {
456 mapOfAvailableFunctions.put("bluetooth", bluetooth);
460 public void setAUXAvailable(boolean aux) {
461 mapOfAvailableFunctions.put("aux", aux);
465 public void setAUX1Available(boolean aux1) {
466 mapOfAvailableFunctions.put("aux1", aux1);
470 public void setAUX2Available(boolean aux2) {
471 mapOfAvailableFunctions.put("aux2", aux2);
475 public void setAUX3Available(boolean aux3) {
476 mapOfAvailableFunctions.put("aux3", aux3);
480 public void setStoredMusicAvailable(boolean storedMusic) {
481 mapOfAvailableFunctions.put("storedMusic", storedMusic);
485 public void setInternetRadioAvailable(boolean internetRadio) {
486 mapOfAvailableFunctions.put("internetRadio", internetRadio);
490 public void setTVAvailable(boolean tv) {
491 mapOfAvailableFunctions.put("tv", tv);
495 public void setHDMI1Available(boolean hdmi1) {
496 mapOfAvailableFunctions.put("hdmi1", hdmi1);
500 public void setBassAvailable(boolean bass) {
501 mapOfAvailableFunctions.put("bass", bass);
504 private boolean isSourceAvailable(String source) {
505 Boolean isAvailable = mapOfAvailableFunctions.get(source);
506 if (isAvailable == null) {
513 public void playNotificationSound(String appKey, BoseSoundTouchNotificationChannelConfiguration notificationConfig,
515 String msg = "<play_info>" + "<app_key>" + appKey + "</app_key>" + "<url>" + fileUrl + "</url>" + "<service>"
516 + notificationConfig.notificationService + "</service>"
517 + (notificationConfig.notificationReason != null
518 ? "<reason>" + notificationConfig.notificationReason + "</reason>"
520 + (notificationConfig.notificationMessage != null
521 ? "<message>" + notificationConfig.notificationMessage + "</message>"
523 + (notificationConfig.notificationVolume != null
524 ? "<volume>" + notificationConfig.notificationVolume + "</volume>"
528 sendPostRequestInWebSocket("speaker", msg);