]> git.basschouten.com Git - openhab-addons.git/blob
7a01af6069857d8cae62e659ba464c7fcbcb172c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.bosesoundtouch.internal;
14
15 import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.Map;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.websocket.api.Session;
24 import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.NextPreviousType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.PercentType;
29 import org.openhab.core.library.types.PlayPauseType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.State;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * The {@link CommandExecutor} class executes commands on the websocket
38  *
39  * @author Thomas Traunbauer - Initial contribution
40  * @author Kai Kreuzer - code clean up
41  */
42 @NonNullByDefault
43 public class CommandExecutor implements AvailableSources {
44     private final Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
45
46     private final BoseSoundTouchHandler handler;
47
48     private boolean currentMuted;
49     private @Nullable ContentItem currentContentItem = null;
50     private @Nullable OperationModeType currentOperationMode;
51
52     private final Map<String, Boolean> mapOfAvailableFunctions = new HashMap<>();
53
54     /**
55      * Creates a new instance of this class
56      *
57      * @param handler the handler that created this CommandExecutor
58      */
59     public CommandExecutor(BoseSoundTouchHandler handler) {
60         this.handler = handler;
61         getInformations(APIRequest.INFO);
62         currentOperationMode = OperationModeType.OFFLINE;
63     }
64
65     /**
66      * Synchronizes the underlying storage container with the current value for the presets stored on the player
67      * by updating the available ones and deleting the cleared ones
68      *
69      * @param playerPresets a Map<Integer, ContentItems> containing the items currently stored on the player
70      */
71     public void updatePresetContainerFromPlayer(Map<Integer, ContentItem> playerPresets) {
72         playerPresets.forEach((k, v) -> {
73             try {
74                 handler.getPresetContainer().put(k, v);
75             } catch (ContentItemNotPresetableException e) {
76                 logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
77             }
78         });
79
80         handler.refreshPresetChannel();
81     }
82
83     /**
84      * Adds a ContentItem to the PresetContainer
85      *
86      * @param id the id the ContentItem should be reached
87      * @param contentItem the contentItem that should be saved as PRESET. Note that an eventually set presetID of the
88      *            ContentItem will be overwritten with id
89      */
90     public void addContentItemToPresetContainer(int id, ContentItem contentItem) {
91         contentItem.setPresetID(id);
92         try {
93             handler.getPresetContainer().put(id, contentItem);
94         } catch (ContentItemNotPresetableException e) {
95             logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
96         }
97         handler.refreshPresetChannel();
98     }
99
100     /**
101      * Adds the current selected ContentItem to the PresetContainer
102      *
103      * @param command the command is a DecimalType, thats intValue will be used as id. The id the ContentItem should be
104      *            reached
105      */
106     public void addCurrentContentItemToPresetContainer(DecimalType command) {
107         if (command.intValue() > 6) {
108             ContentItem localContentItem = currentContentItem;
109             if (localContentItem != null) {
110                 addContentItemToPresetContainer(command.intValue(), localContentItem);
111             }
112         } else {
113             logger.warn("{}: Only PresetID >6 is allowed", handler.getDeviceName());
114         }
115     }
116
117     /**
118      * Initializes an API Request on this device
119      *
120      * @param apiRequest the apiRequest thats informations should be collected
121      */
122     public void getInformations(APIRequest apiRequest) {
123         String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + apiRequest
124                 + "\" method=\"GET\"><request requestID=\"0\"><info type=\"new\"/></request></header></msg>";
125         Session localSession = handler.getSession();
126         if (localSession != null) {
127             localSession.getRemote().sendStringByFuture(msg);
128             logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
129         }
130     }
131
132     /**
133      * Sets the current ContentItem if it is valid, and inits an update of the operating values
134      *
135      * @param contentItem
136      */
137     public void setCurrentContentItem(ContentItem contentItem) {
138         if (contentItem.isValid()) {
139             ContentItem psFound = null;
140             Collection<ContentItem> listOfPresets = handler.getPresetContainer().getAllPresets();
141             for (ContentItem ps : listOfPresets) {
142                 if (ps.isPresetable()) {
143                     String localLocation = ps.getLocation();
144                     if (localLocation != null) {
145                         if (localLocation.equals(contentItem.getLocation())) {
146                             psFound = ps;
147                         }
148                     }
149                 }
150             }
151             int presetID = 0;
152             if (psFound != null) {
153                 presetID = psFound.getPresetID();
154             }
155             contentItem.setPresetID(presetID);
156
157             currentContentItem = contentItem;
158
159         }
160         updateOperatingValues();
161     }
162
163     /**
164      * Sets the device is currently muted
165      *
166      * @param muted
167      */
168     public void setCurrentMuted(boolean muted) {
169         currentMuted = muted;
170     }
171
172     /**
173      * Post Bass on the device
174      *
175      * @param command the command is Type of DecimalType
176      */
177     public void postBass(DecimalType command) {
178         if (isBassAvailable()) {
179             sendPostRequestInWebSocket("bass",
180                     "<bass deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</bass>");
181         } else {
182             logger.warn("{}: Bass modification not supported for this device", handler.getDeviceName());
183         }
184     }
185
186     /**
187      * Post OperationMode on the device
188      *
189      * @param command the command is Type of OperationModeType
190      */
191     public void postOperationMode(OperationModeType command) {
192         if (command == OperationModeType.STANDBY) {
193             postPower(OnOffType.OFF);
194         } else {
195             try {
196                 ContentItemMaker contentItemMaker = new ContentItemMaker(this, handler.getPresetContainer());
197                 ContentItem contentItem = contentItemMaker.getContentItem(command);
198                 postContentItem(contentItem);
199             } catch (OperationModeNotAvailableException e) {
200                 logger.warn("{}: OperationMode \"{}\" is not supported yet", handler.getDeviceName(),
201                         command.toString());
202             } catch (NoInternetRadioPresetFoundException e) {
203                 logger.warn("{}: Unable to switch to mode \"INTERNET_RADIO\". No PRESET defined",
204                         handler.getDeviceName());
205             } catch (NoStoredMusicPresetFoundException e) {
206                 logger.warn("{}: Unable to switch to mode: \"STORED_MUSIC\". No PRESET defined",
207                         handler.getDeviceName());
208             }
209             updateOperatingValues();
210         }
211     }
212
213     /**
214      * Post PlayerControl on the device
215      *
216      * @param command the command is Type of Command
217      */
218     public void postPlayerControl(Command command) {
219         if (command.equals(PlayPauseType.PLAY)) {
220             if (currentOperationMode == OperationModeType.STANDBY) {
221                 postRemoteKey(RemoteKeyType.POWER);
222             } else {
223                 postRemoteKey(RemoteKeyType.PLAY);
224             }
225         } else if (command.equals(PlayPauseType.PAUSE)) {
226             postRemoteKey(RemoteKeyType.PAUSE);
227         } else if (command.equals(NextPreviousType.NEXT)) {
228             postRemoteKey(RemoteKeyType.NEXT_TRACK);
229         } else if (command.equals(NextPreviousType.PREVIOUS)) {
230             postRemoteKey(RemoteKeyType.PREV_TRACK);
231         }
232     }
233
234     /**
235      * Post Power on the device
236      *
237      * @param command the command is Type of OnOffType
238      */
239     public void postPower(OnOffType command) {
240         if (command.equals(OnOffType.ON)) {
241             if (currentOperationMode == OperationModeType.STANDBY) {
242                 postRemoteKey(RemoteKeyType.POWER);
243             }
244         } else if (command.equals(OnOffType.OFF)) {
245             if (currentOperationMode != OperationModeType.STANDBY) {
246                 postRemoteKey(RemoteKeyType.POWER);
247             }
248         }
249         updateOperatingValues();
250     }
251
252     /**
253      * Post Preset on the device
254      *
255      * @param command the command is Type of DecimalType
256      */
257     public void postPreset(DecimalType command) {
258         ContentItem item = null;
259         try {
260             item = handler.getPresetContainer().get(command.intValue());
261             postContentItem(item);
262         } catch (NoPresetFoundException e) {
263             logger.warn("{}: No preset found at id: {}", handler.getDeviceName(), command.intValue());
264         }
265     }
266
267     /**
268      * Post RemoteKey on the device
269      *
270      * @param command the command is Type of RemoteKeyType
271      */
272     public void postRemoteKey(RemoteKeyType key) {
273         sendPostRequestInWebSocket("key", "mainNode=\"keyPress\"",
274                 "<key state=\"press\" sender=\"Gabbo\">" + key.name() + "</key>");
275         sendPostRequestInWebSocket("key", "mainNode=\"keyRelease\"",
276                 "<key state=\"release\" sender=\"Gabbo\">" + key.name() + "</key>");
277     }
278
279     /**
280      * Post Volume on the device
281      *
282      * @param command the command is Type of PercentType
283      */
284     public void postVolume(PercentType command) {
285         sendPostRequestInWebSocket("volume",
286                 "<volume deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</volume>");
287     }
288
289     /**
290      * Post VolumeMute on the device
291      *
292      * @param command the command is Type of OnOffType
293      */
294     public void postVolumeMuted(OnOffType command) {
295         if (command.equals(OnOffType.ON)) {
296             if (!currentMuted) {
297                 currentMuted = true;
298                 postRemoteKey(RemoteKeyType.MUTE);
299             }
300         } else if (command.equals(OnOffType.OFF)) {
301             if (currentMuted) {
302                 currentMuted = false;
303                 postRemoteKey(RemoteKeyType.MUTE);
304             }
305         }
306     }
307
308     /**
309      * Update GUI for Basslevel
310      *
311      * @param state the state is Type of DecimalType
312      */
313     public void updateBassLevelGUIState(DecimalType state) {
314         handler.updateState(CHANNEL_BASS, state);
315     }
316
317     /**
318      * Update GUI for Volume
319      *
320      * @param state the state is Type of PercentType
321      */
322     public void updateVolumeGUIState(PercentType state) {
323         handler.updateState(CHANNEL_VOLUME, state);
324     }
325
326     /**
327      * Update GUI for OperationMode
328      *
329      * @param state the state is Type of StringType
330      */
331     public void updateOperationModeGUIState(StringType state) {
332         handler.updateState(CHANNEL_OPERATIONMODE, state);
333     }
334
335     /**
336      * Update GUI for PlayerControl
337      *
338      * @param state the state is Type of State
339      */
340     public void updatePlayerControlGUIState(State state) {
341         handler.updateState(CHANNEL_PLAYER_CONTROL, state);
342     }
343
344     /**
345      * Update GUI for Power
346      *
347      * @param state the state is Type of OnOffType
348      */
349     public void updatePowerStateGUIState(OnOffType state) {
350         handler.updateState(CHANNEL_POWER, state);
351     }
352
353     /**
354      * Update GUI for Preset
355      *
356      * @param state the state is Type of DecimalType
357      */
358     public void updatePresetGUIState(DecimalType state) {
359         handler.updateState(CHANNEL_PRESET, state);
360     }
361
362     private void postContentItem(ContentItem contentItem) {
363         setCurrentContentItem(contentItem);
364         sendPostRequestInWebSocket("select", "", contentItem.generateXML());
365     }
366
367     private void sendPostRequestInWebSocket(String url, String postData) {
368         sendPostRequestInWebSocket(url, "", postData);
369     }
370
371     private void sendPostRequestInWebSocket(String url, String infoAddon, String postData) {
372         int id = 0;
373         String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
374                 + "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
375                 + " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
376         Session localSession = handler.getSession();
377         if (localSession != null) {
378             localSession.getRemote().sendStringByFuture(msg);
379             logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
380         } else {
381             handler.onWebSocketError(new NullPointerException("NPE: Session is unexpected null"));
382         }
383     }
384
385     private void updateOperatingValues() {
386         OperationModeType operationMode;
387         ContentItem localContentItem = currentContentItem;
388         if (localContentItem != null) {
389             updatePresetGUIState(new DecimalType(localContentItem.getPresetID()));
390             operationMode = localContentItem.getOperationMode();
391         } else {
392             operationMode = OperationModeType.STANDBY;
393         }
394
395         updateOperationModeGUIState(new StringType(operationMode.toString()));
396         currentOperationMode = operationMode;
397         if (currentOperationMode == OperationModeType.STANDBY) {
398             updatePowerStateGUIState(OnOffType.OFF);
399             updatePlayerControlGUIState(PlayPauseType.PAUSE);
400         } else {
401             updatePowerStateGUIState(OnOffType.ON);
402         }
403     }
404
405     @Override
406     public boolean isBluetoothAvailable() {
407         return isSourceAvailable("bluetooth");
408     }
409
410     @Override
411     public boolean isAUXAvailable() {
412         return isSourceAvailable("aux");
413     }
414
415     @Override
416     public boolean isAUX1Available() {
417         return isSourceAvailable("aux1");
418     }
419
420     @Override
421     public boolean isAUX2Available() {
422         return isSourceAvailable("aux2");
423     }
424
425     @Override
426     public boolean isAUX3Available() {
427         return isSourceAvailable("aux3");
428     }
429
430     @Override
431     public boolean isTVAvailable() {
432         return isSourceAvailable("tv");
433     }
434
435     @Override
436     public boolean isHDMI1Available() {
437         return isSourceAvailable("hdmi1");
438     }
439
440     @Override
441     public boolean isInternetRadioAvailable() {
442         return isSourceAvailable("internetRadio");
443     }
444
445     @Override
446     public boolean isStoredMusicAvailable() {
447         return isSourceAvailable("storedMusic");
448     }
449
450     @Override
451     public boolean isBassAvailable() {
452         return isSourceAvailable("bass");
453     }
454
455     @Override
456     public void setBluetoothAvailable(boolean bluetooth) {
457         mapOfAvailableFunctions.put("bluetooth", bluetooth);
458     }
459
460     @Override
461     public void setAUXAvailable(boolean aux) {
462         mapOfAvailableFunctions.put("aux", aux);
463     }
464
465     @Override
466     public void setAUX1Available(boolean aux1) {
467         mapOfAvailableFunctions.put("aux1", aux1);
468     }
469
470     @Override
471     public void setAUX2Available(boolean aux2) {
472         mapOfAvailableFunctions.put("aux2", aux2);
473     }
474
475     @Override
476     public void setAUX3Available(boolean aux3) {
477         mapOfAvailableFunctions.put("aux3", aux3);
478     }
479
480     @Override
481     public void setStoredMusicAvailable(boolean storedMusic) {
482         mapOfAvailableFunctions.put("storedMusic", storedMusic);
483     }
484
485     @Override
486     public void setInternetRadioAvailable(boolean internetRadio) {
487         mapOfAvailableFunctions.put("internetRadio", internetRadio);
488     }
489
490     @Override
491     public void setTVAvailable(boolean tv) {
492         mapOfAvailableFunctions.put("tv", tv);
493     }
494
495     @Override
496     public void setHDMI1Available(boolean hdmi1) {
497         mapOfAvailableFunctions.put("hdmi1", hdmi1);
498     }
499
500     @Override
501     public void setBassAvailable(boolean bass) {
502         mapOfAvailableFunctions.put("bass", bass);
503     }
504
505     private boolean isSourceAvailable(String source) {
506         Boolean isAvailable = mapOfAvailableFunctions.get(source);
507         if (isAvailable == null) {
508             return false;
509         } else {
510             return isAvailable;
511         }
512     }
513
514     public void playNotificationSound(String appKey, BoseSoundTouchNotificationChannelConfiguration notificationConfig,
515             String fileUrl) {
516         String msg = "<play_info>" + "<app_key>" + appKey + "</app_key>" + "<url>" + fileUrl + "</url>" + "<service>"
517                 + notificationConfig.notificationService + "</service>"
518                 + (notificationConfig.notificationReason != null
519                         ? "<reason>" + notificationConfig.notificationReason + "</reason>"
520                         : "")
521                 + (notificationConfig.notificationMessage != null
522                         ? "<message>" + notificationConfig.notificationMessage + "</message>"
523                         : "")
524                 + (notificationConfig.notificationVolume != null
525                         ? "<volume>" + notificationConfig.notificationVolume + "</volume>"
526                         : "")
527                 + "</play_info>";
528
529         sendPostRequestInWebSocket("speaker", msg);
530     }
531 }