]> git.basschouten.com Git - openhab-addons.git/blob
8fabbd52fd03be08876024079f1f4f12077dc6ca
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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         updateOperatingValues();
160     }
161
162     /**
163      * Sets the device is currently muted
164      *
165      * @param muted
166      */
167     public void setCurrentMuted(boolean muted) {
168         currentMuted = muted;
169     }
170
171     /**
172      * Post Bass on the device
173      *
174      * @param command the command is Type of DecimalType
175      */
176     public void postBass(DecimalType command) {
177         if (isBassAvailable()) {
178             sendPostRequestInWebSocket("bass",
179                     "<bass deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</bass>");
180         } else {
181             logger.warn("{}: Bass modification not supported for this device", handler.getDeviceName());
182         }
183     }
184
185     /**
186      * Post OperationMode on the device
187      *
188      * @param command the command is Type of OperationModeType
189      */
190     public void postOperationMode(OperationModeType command) {
191         if (command == OperationModeType.STANDBY) {
192             postPower(OnOffType.OFF);
193         } else {
194             try {
195                 ContentItemMaker contentItemMaker = new ContentItemMaker(this, handler.getPresetContainer());
196                 ContentItem contentItem = contentItemMaker.getContentItem(command);
197                 postContentItem(contentItem);
198             } catch (OperationModeNotAvailableException e) {
199                 logger.warn("{}: OperationMode \"{}\" is not supported yet", handler.getDeviceName(),
200                         command.toString());
201             } catch (NoInternetRadioPresetFoundException e) {
202                 logger.warn("{}: Unable to switch to mode \"INTERNET_RADIO\". No PRESET defined",
203                         handler.getDeviceName());
204             } catch (NoStoredMusicPresetFoundException e) {
205                 logger.warn("{}: Unable to switch to mode: \"STORED_MUSIC\". No PRESET defined",
206                         handler.getDeviceName());
207             }
208             updateOperatingValues();
209         }
210     }
211
212     /**
213      * Post PlayerControl on the device
214      *
215      * @param command the command is Type of Command
216      */
217     public void postPlayerControl(Command command) {
218         if (command.equals(PlayPauseType.PLAY)) {
219             if (currentOperationMode == OperationModeType.STANDBY) {
220                 postRemoteKey(RemoteKeyType.POWER);
221             } else {
222                 postRemoteKey(RemoteKeyType.PLAY);
223             }
224         } else if (command.equals(PlayPauseType.PAUSE)) {
225             postRemoteKey(RemoteKeyType.PAUSE);
226         } else if (command.equals(NextPreviousType.NEXT)) {
227             postRemoteKey(RemoteKeyType.NEXT_TRACK);
228         } else if (command.equals(NextPreviousType.PREVIOUS)) {
229             postRemoteKey(RemoteKeyType.PREV_TRACK);
230         }
231     }
232
233     /**
234      * Post Power on the device
235      *
236      * @param command the command is Type of OnOffType
237      */
238     public void postPower(OnOffType command) {
239         if (command.equals(OnOffType.ON)) {
240             if (currentOperationMode == OperationModeType.STANDBY) {
241                 postRemoteKey(RemoteKeyType.POWER);
242             }
243         } else if (command.equals(OnOffType.OFF)) {
244             if (currentOperationMode != OperationModeType.STANDBY) {
245                 postRemoteKey(RemoteKeyType.POWER);
246             }
247         }
248         updateOperatingValues();
249     }
250
251     /**
252      * Post Preset on the device
253      *
254      * @param command the command is Type of DecimalType
255      */
256     public void postPreset(DecimalType command) {
257         ContentItem item = null;
258         try {
259             item = handler.getPresetContainer().get(command.intValue());
260             postContentItem(item);
261         } catch (NoPresetFoundException e) {
262             logger.warn("{}: No preset found at id: {}", handler.getDeviceName(), command.intValue());
263         }
264     }
265
266     /**
267      * Post RemoteKey on the device
268      *
269      * @param command the command is Type of RemoteKeyType
270      */
271     public void postRemoteKey(RemoteKeyType key) {
272         sendPostRequestInWebSocket("key", "mainNode=\"keyPress\"",
273                 "<key state=\"press\" sender=\"Gabbo\">" + key.name() + "</key>");
274         sendPostRequestInWebSocket("key", "mainNode=\"keyRelease\"",
275                 "<key state=\"release\" sender=\"Gabbo\">" + key.name() + "</key>");
276     }
277
278     /**
279      * Post Volume on the device
280      *
281      * @param command the command is Type of PercentType
282      */
283     public void postVolume(PercentType command) {
284         sendPostRequestInWebSocket("volume",
285                 "<volume deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</volume>");
286     }
287
288     /**
289      * Post VolumeMute on the device
290      *
291      * @param command the command is Type of OnOffType
292      */
293     public void postVolumeMuted(OnOffType command) {
294         if (command.equals(OnOffType.ON)) {
295             if (!currentMuted) {
296                 currentMuted = true;
297                 postRemoteKey(RemoteKeyType.MUTE);
298             }
299         } else if (command.equals(OnOffType.OFF)) {
300             if (currentMuted) {
301                 currentMuted = false;
302                 postRemoteKey(RemoteKeyType.MUTE);
303             }
304         }
305     }
306
307     /**
308      * Update GUI for Basslevel
309      *
310      * @param state the state is Type of DecimalType
311      */
312     public void updateBassLevelGUIState(DecimalType state) {
313         handler.updateState(CHANNEL_BASS, state);
314     }
315
316     /**
317      * Update GUI for Volume
318      *
319      * @param state the state is Type of PercentType
320      */
321     public void updateVolumeGUIState(PercentType state) {
322         handler.updateState(CHANNEL_VOLUME, state);
323     }
324
325     /**
326      * Update GUI for OperationMode
327      *
328      * @param state the state is Type of StringType
329      */
330     public void updateOperationModeGUIState(StringType state) {
331         handler.updateState(CHANNEL_OPERATIONMODE, state);
332     }
333
334     /**
335      * Update GUI for PlayerControl
336      *
337      * @param state the state is Type of State
338      */
339     public void updatePlayerControlGUIState(State state) {
340         handler.updateState(CHANNEL_PLAYER_CONTROL, state);
341     }
342
343     /**
344      * Update GUI for Power
345      *
346      * @param state the state is Type of OnOffType
347      */
348     public void updatePowerStateGUIState(OnOffType state) {
349         handler.updateState(CHANNEL_POWER, state);
350     }
351
352     /**
353      * Update GUI for Preset
354      *
355      * @param state the state is Type of DecimalType
356      */
357     public void updatePresetGUIState(DecimalType state) {
358         handler.updateState(CHANNEL_PRESET, state);
359     }
360
361     private void postContentItem(ContentItem contentItem) {
362         setCurrentContentItem(contentItem);
363         sendPostRequestInWebSocket("select", "", contentItem.generateXML());
364     }
365
366     private void sendPostRequestInWebSocket(String url, String postData) {
367         sendPostRequestInWebSocket(url, "", postData);
368     }
369
370     private void sendPostRequestInWebSocket(String url, String infoAddon, String postData) {
371         int id = 0;
372         String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
373                 + "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
374                 + " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
375         Session localSession = handler.getSession();
376         if (localSession != null) {
377             localSession.getRemote().sendStringByFuture(msg);
378             logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
379         } else {
380             handler.onWebSocketError(new NullPointerException("NPE: Session is unexpected null"));
381         }
382     }
383
384     private void updateOperatingValues() {
385         OperationModeType operationMode;
386         ContentItem localContentItem = currentContentItem;
387         if (localContentItem != null) {
388             updatePresetGUIState(new DecimalType(localContentItem.getPresetID()));
389             operationMode = localContentItem.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 }