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