/******************************************************************************* * --------------------------- * SONOS-Script: Bietet diverse Zusatz-Funktionen zur Steuerung von SONOS-Geräten * mit dem SONOS-Adapter (https://github.com/ioBroker/ioBroker.sonos). * --------------------------- * Quelle: https://github.com/Mic-M/iobroker.sonos-script * Autor: Mic (ioBroker) | Mic-M (github) * Support: https://forum.iobroker.net/topic/24743/ * Change log: * 1.2 + Use new Sonos adapter state (since 2.0.0, pull request 55) 'favorites_list_array' to allow comma * in favorite name, see https://github.com/ioBroker/ioBroker.sonos/pull/55 * + Replaced state 'customFavoriteList' with 'customFavoriteListArray', to allow semicolon in fav name * 1.1 + On script start, push all Sonos favorites into custom favorites initially * 1.0 + Major release, added several additional functions and improvements * 0.3 + Create states for each Sonos device automatically * + New state 'allStop' to stop all Sonos devices * 0.2 - Fix: added missing function isLikeEmpty() * 0.1 - initial version ******************************************************************************/ /**************************************************************************************** * Einstellungen: Allgemein ****************************************************************************************/ // Datenpunkt-Pfad, unter dem die entsprechenden Script-Datenpunkte angelegt werden. const SCRIPT_STATE_PATH = 'javascript.'+ instance + '.' + 'Sonos'; // Instanz des SONOS-Adapters. Standard ist 0. const SONOS_ADAPTER_INSTANCE = 0; /**************************************************************************************** * Einstellungen: Favoriten-Liste für VIS ****************************************************************************************/ // Favoriten-Liste: alphabetisch sortieren? true = ja, false = nein const SORT_LIST = true; // Favoriten-Liste: Den Favoriten eine fortlaufende Nummer voranstellen (1, 2, 3, ...)? const LIST_NO_ADD = true; // auf false setzen, wenn nicht gewünscht. const LIST_NO_SEP = '. ' // Trennzeichen nach der Nummer. Gilt nur, wenn LIST_NO_ADD = true gesetzt. // Favoriten-Liste: CSS-Bezeichnungen. Kann man einfach so stehen lassen. const CSS_CURRENT_SEL = 'currentSonosFavorite'; // CSS-ID für die aktuelle Auswahl const CSS_FAVORITE_ELEM = 'favoriteSonosTitle' // CSS-Klasse für jeden Eintrag der Liste /**************************************************************************************** * Einstellungen: 'favoritesPlayPrevious' und 'favoritesPlayNext' ****************************************************************************************/ // Normalerweise wird bei Klicken auf Datenpunkt favoritesPlayNext/favoritesPlayPrevious // der nächste/vorherige Favorit lt. SONOS-App abgespielt. Wir können dies aber hiermit // alphabetisch sortieren, so dass der nächste/vorherige lt. Alphabet gespielt wird. // true = alphabetisch sortieren, false = nicht alphabetisch sortieren const FAVORITES_PLAY_PREV_NEXT_SORT = true; /**************************************************************************************** * Einstellungen: Buttons volumeUp/volumeDown zum erhöhen/verringern der Lautstärke ****************************************************************************************/ // um wie viel % wird erhöht/verringert beim klicken auf volumeUp/volumeDown? const VOL_VALUE = 1; // Maximale Lautstärke in %, mehr wird nicht erhöht. const MAX_VOLUME = 200; /**************************************************************************************** * Einstellungen: Beim Abspielen immer Sonos-Geräte als Gruppe hinzufügen ****************************************************************************************/ // Hiermit kann man Sonos-Geräte definieren, zu denen immer beim Abspielen weitere Geräte // als Gruppe hinzugefügt werden. // Zum Einschalten: auf true setzen. const GROUP_ON_PLAY = true; // Falls GROUP_ON_PLAY = false, fann kann man folgendes ignorieren. // Es können beliebig viele Zeilen hinzugefügt werden. // channelMain: Hier den Channel des 1. Gerätes eintragen, also die IP, aber "_" statt Punkt, also z.B. '192_168_10_12' // channelsToAdd: Hier Geräte eintragen, welche zum ersten Gerät als Gruppe hinzugefügt werden sollen // Außerdem unter volumeAdjust das Volumen gegenüber dem channelMain nach oben oder unten anpassen. const GROUP_ON_PLAY_DEVICES = [ {channelMain: '192_168_178_32', channelsToAdd: [{channel:'192_168_178_46', volumeAdjust:0}, {channel:'192_168_178_49', volumeAdjust:-4}]}, ]; /**************************************************************************************** * Einstellungen: Sonstige ****************************************************************************************/ // Standard-Lautstärke beim Starten mit customFavoritesPlay / .customFavoritesPlayG const PRESET_VOLUME = 15; // Ein paar Infos im Log anzeigen? const LOG_INFO = true; /************************************************************************************************************************* * Das war es auch schon. Ab hier nichts mehr ändern! *************************************************************************************************************************/ /**************************************************************************************** * Global variables and constants ****************************************************************************************/ // Alle Sonos-States (Geräte) in Array, also z.B. ['sonos.0.root.192_168_0_12', 'sonos.0.root.192_168_0_13'] const SONOS_CHANNELS = getAllSonosChannels(SONOS_ADAPTER_INSTANCE); /**************************************************************************************** * Initialize ****************************************************************************************/ init(); function init() { createStates(); setTimeout(function(){ // Subscribe to states subscribeToStates(); for (let lpChannel of SONOS_CHANNELS) { let sonosFavsArray = sonosFavoritesArray(lpChannel); let customFavsArray = getState(scriptPath(lpChannel) + '.customFavoriteListArray').val; // If custom favorites list is empty, we push all Sonos Favorites into it in the beginning. if (isLikeEmpty(customFavsArray)) { let customFavsArray = [...sonosFavsArray]; // copy array if (SORT_LIST) customFavsArray = arraySortCaseInsensitive(customFavsArray); setState(scriptPath(lpChannel) + '.customFavoriteListArray', customFavsArray, true); } // Refresh global HTML playlist initially refreshFavoritesHtmlList(lpChannel, sonosFavsArray, scriptPath(lpChannel) + '.sonosFavoriteListHtml'); // Refresh custom HTML playlist initially refreshFavoritesHtmlList(lpChannel, customFavsArray, scriptPath(lpChannel) + '.customFavoriteListHtml'); // Refresh Configuration HTML refreshConfigurationHtml(lpChannel); setTimeout(function(){ // Clean Custom Favorites List, if Sonos Favorite was deleted. // We perfom this also in the beginning. cleanCustomFavoritesList(lpChannel); }, 2000); } }, 2000); } function createStates() { for (let lpChannel of SONOS_CHANNELS) { createState(scriptPath(lpChannel) + '.sonosFavoriteListHtml', {'name':'Sonos Favorites HTML List', 'type':'string', 'read':true, 'write':true, 'role':'media.list', 'def':'' }); createState(scriptPath(lpChannel) + '.sonosFavoritesPlayNext', {'name':'Favorites: play next', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.sonosFavoritesPlayPrevious', {'name':'Favorites: play previous', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.customFavoriteAdd', {'name':'Custom Favorites: Add a favorite', 'type':'string', 'read':true, 'write':true, 'role':'state', 'def':'' }); createState(scriptPath(lpChannel) + '.customFavoriteRemove', {'name':'Custom Favorites: Remove a favorite', 'type':'string', 'read':true, 'write':true, 'role':'state', 'def':'' }); createState(scriptPath(lpChannel) + '.customFavoriteListArray', {'name':'Custom Favorites List Array', 'type':'array', 'read':true, 'write':true, 'role':'state', 'def':'' }); createState(scriptPath(lpChannel) + '.customFavoriteListHtml', {'name':'Custom Favorites HTML List', 'type':'string', 'read':true, 'write':true, 'role':'media.list', 'def':'' }); createState(scriptPath(lpChannel) + '.customFavoriteConfigHtml', {'name':'Custom Favorites HTML Configuration', 'type':'string', 'read':true, 'write':true, 'role':'state', 'def':'' }); createState(scriptPath(lpChannel) + '.customFavoriteToggleConfigVis', {'name':'Custom Favorites: For Vis to toggle config', 'type':'boolean', 'read':true, 'write':true, 'role':'state', 'def':false }); createState(scriptPath(lpChannel) + '.customFavoritesPlay', {'name':'Custom Favorites: Start playing and set default volume for device', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.customFavoritesPlayG', {'name':'Custom Favorites: Start playing and set default volume for GROUP', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.customFavoritesPlayNext', {'name':'Custom Favorites: play next', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.customFavoritesPlayPrevious', {'name':'Custom Favorites: play previous', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.customFavoritesPlayByNumber', {'name':'Custom Favorites: Play a favorite by number (1-x)', 'type':'number', 'min':1, 'max':999, 'read':true, 'write':true, 'role':'state' }); createState(scriptPath(lpChannel) + '.volumeUp', {'name':'Increase volume', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.volumeDown', {'name':'Decrease volume', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.volumeGroupUp', {'name':'Increase volume of group', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); createState(scriptPath(lpChannel) + '.volumeGroupDown', {'name':'Decrease volume of group', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); } createState(SCRIPT_STATE_PATH + '.' + 'allStop', {'name':'Stop all Sonos devices', 'type':'boolean', 'read':false, 'write':true, 'role':'button', 'def':false }); } function subscribeToStates() { for (let lpChannel of SONOS_CHANNELS) { /******************************************* * Global Favorites *******************************************/ /** * Refresh if the Sonos Favorites list changes */ on({id: sonosPath(lpChannel) + '.favorites_list', change: 'ne'}, function (obj) { let channel = getChannel(obj.id); // Refresh HTML refreshFavoritesHtmlList(channel, sonosFavoritesArray(channel), scriptPath(channel) + '.sonosFavoriteListHtml'); // Nun können wir refreshen // Refresh Configuration HTML refreshConfigurationHtml(channel); // Clean Custom Favorites List, if Sonos Favorite was deleted cleanCustomFavoritesList(channel); }); /** * Refresh if the current favorite changes */ on({id: sonosPath(lpChannel) + '.favorites_set', change: 'ne'}, function (obj) { let channel = getChannel(obj.id) refreshFavoritesHtmlList(channel, sonosFavoritesArray(channel), scriptPath(channel) + '.sonosFavoriteListHtml'); // Nun können wir refreshen }); /** * Play next Sonos favorite */ on({id: scriptPath(lpChannel) + '.sonosFavoritesPlayNext', change: 'any', val:true}, function (obj) { let favsArray = sonosFavoritesArray(lpChannel); if (FAVORITES_PLAY_PREV_NEXT_SORT) favsArray = arraySortCaseInsensitive(favsArray); favoritesPlayNext(lpChannel, favsArray, true); }); /** * Play previous Sonos favorite */ on({id: scriptPath(lpChannel) + '.sonosFavoritesPlayPrevious', change: 'any', val:true}, function (obj) { let favsArray = sonosFavoritesArray(lpChannel); if (FAVORITES_PLAY_PREV_NEXT_SORT) favsArray = arraySortCaseInsensitive(favsArray); favoritesPlayNext(lpChannel, favsArray, false); }); /******************************************* * Custom Favorites *******************************************/ /** * Refresh if the Custom Favorites list changes */ on({id: scriptPath(lpChannel) + '.customFavoriteListArray', change: 'ne'}, function (obj) { let channel = getChannel(obj.id); // Refresh Custom Favorites HTML List refreshFavoritesHtmlList(channel, getState(scriptPath(channel) + '.customFavoriteListArray').val, scriptPath(channel) + '.customFavoriteListHtml'); // Nun können wir refreshen // Refresh Configuration HTML refreshConfigurationHtml(channel); }); /** * Refresh if the current Sonos favorite changes */ on({id: sonosPath(lpChannel) + '.favorites_set', change: 'ne'}, function (obj) { let channel = getChannel(obj.id); refreshFavoritesHtmlList(channel, getState(scriptPath(lpChannel) + '.customFavoriteListArray').val, scriptPath(channel) + '.customFavoriteListHtml'); // Nun können wir refreshen }); /** * Add a Favorite to Custom Favorite List */ on({id: scriptPath(lpChannel) + '.customFavoriteAdd', change: 'any'}, function (obj) { if(! isLikeEmpty(obj.state.val)) { customFavoritesAddRemove(lpChannel, obj.state.val, true); if(LOG_INFO) log('[' + obj.state.val + '] added to custom favorite list.'); setStateDelayed(obj.id, '', true, 500); } }); /** * Remove a Favorite from Custom Favorite List */ on({id: scriptPath(lpChannel) + '.customFavoriteRemove', change: 'any'}, function (obj) { if(! isLikeEmpty(obj.state.val)) { customFavoritesAddRemove(lpChannel, obj.state.val, false); if(LOG_INFO) log('[' + obj.state.val + '] removed from custom favorite list.'); setStateDelayed(obj.id, '', true, 500); } }); /** * Play next custom favorite */ on({id: scriptPath(lpChannel) + '.customFavoritesPlayNext', change: 'any', val:true}, function (obj) { let favsArray = getState(scriptPath(lpChannel) + '.customFavoriteListArray').val; favoritesPlayNext(lpChannel, favsArray, true); }); /** * Play previous custom favorite */ on({id: scriptPath(lpChannel) + '.customFavoritesPlayPrevious', change: 'any', val:true}, function (obj) { let favsArray = getState(scriptPath(lpChannel) + '.customFavoriteListArray').val; favoritesPlayNext(lpChannel, favsArray, false); }); /******************************************* * Volume *******************************************/ /** * Volume Up: Device */ on({id: scriptPath(lpChannel) + '.volumeUp', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); volumeUp(channel, 'volume', true, VOL_VALUE); }); /** * Volume Down: Device */ on({id: scriptPath(lpChannel) + '.volumeDown', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); volumeUp(channel, 'volume', false, VOL_VALUE); }); /** * Volume Up: Group */ on({id: scriptPath(lpChannel) + '.volumeGroupUp', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); volumeUp(channel, 'group_volume', true, VOL_VALUE); }); /** * Volume Down: Group */ on({id: scriptPath(lpChannel) + '.volumeGroupDown', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); volumeUp(channel, 'group_volume', false, VOL_VALUE); }); /******************************************* * Others *******************************************/ /** * Custom Favorites: Play favorite by number. */ on({id: scriptPath(lpChannel) + '.customFavoritesPlayByNumber', change: 'any'}, function (obj) { let channel = getChannel(obj.id); playCustomFavoriteByNumber(channel, obj.state.val); }); /** * Custom Favorites: Start playing and set standard volume level to device */ on({id: scriptPath(lpChannel) + '.customFavoritesPlay', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); sonosStart(channel, 'volume', PRESET_VOLUME); }); /** * Custom Favorites: Start playing and set standard volume level to GROUP */ on({id: scriptPath(lpChannel) + '.customFavoritesPlayG', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); sonosStart(channel, 'group_volume', PRESET_VOLUME); }); } /** * Group Sonos devices once status is play */ if (GROUP_ON_PLAY) { for (let lpItem of GROUP_ON_PLAY_DEVICES) { let channelMain = lpItem['channelMain']; on({id: sonosPath(channelMain) + '.state_simple', change: 'any', val:true}, function (obj) { let channel = getChannel(obj.id); groupSonos(channel); }); } } /** * Stop playing at all Sonos devices */ on({id: SCRIPT_STATE_PATH + '.' + 'allStop', change: "any", val: true}, function(obj) { for (let lpChannel of SONOS_CHANNELS) { setState(sonosPath(lpChannel) + '.stop', true); } setState(obj.id, false, true); // jetzt Datenpunkt wieder auf false setzen. https://forum.iobroker.net/topic/12708/ }); } /*********** * Clean custom favorites list, if a Sonos Favorite was removed. * @param {string} channel The channel xx_xx_xx_xx */ function cleanCustomFavoritesList(channel){ // Sonos Favorites in Array let sonosFavsArray = sonosFavoritesArray(channel); // Custom Favorites in Array let customFavsArray = getState(scriptPath(channel) + '.customFavoriteListArray').val; // Now remove all items from Custom Favs Array, if not existing in Sonos Favorites let resultArray = []; for (let lpCustomItem of customFavsArray) { if (sonosFavsArray.indexOf(lpCustomItem) != -1) { resultArray.push(lpCustomItem); } } setState(scriptPath(channel) + '.customFavoriteListArray', resultArray, true); } /*********** * Adds or removes a favorite to/from custom favorite list * @param {string} channel The channel xx_xx_xx_xx * @param {string} favorite The favorite to add or remove * @param {boolean} add add if true, or remove, if false */ function customFavoritesAddRemove(channel, favorite, add) { // favorite = favorite.replace (/,/g, ''); // Remove any comma from string. // 12-Sep-2019: removed as Sonos adapter now allows with state favorites_list_array commas as well let statePth = scriptPath(channel) + '.customFavoriteListArray'; let customFavorites = getState(statePth).val; if (! isLikeEmpty(favorite)) { if(add) { if (customFavorites.indexOf(favorite) == -1) { // Check if given favorite is member of Sonos favorites. let sonosFavs = sonosFavoritesArray(channel); if (sonosFavs.indexOf(favorite) != -1) { customFavorites.push(favorite); customFavorites = cleanArray(customFavorites); // just in case if (SORT_LIST) customFavorites = arraySortCaseInsensitive(customFavorites); setState(statePth, customFavorites, true); } } } else { // remove if (customFavorites.indexOf(favorite) != -1) { customFavorites = arrayRemoveElementsByValue(customFavorites, favorite, true); customFavorites = cleanArray(customFavorites); // just in case setState(statePth, customFavorites, true); // wegen {"val:..."} siehe https://forum.iobroker.net/topic/14699/array-in-state-speichern/4 } } } } /*********** * Returnes the Sonos path for a given channel. * @param {string} channel The channel xx_xx_xx_xx * @return {string} the Sonos path, e.g. 'sonos.0.root.192_168_0_15' */ function sonosPath(channel) { return 'sonos.' + SONOS_ADAPTER_INSTANCE + '.root.' + channel; } /*********** * Returnes the Script path for a given channel. * @param {string} channel The channel xx_xx_xx_xx * @return {string} the Script path, e.g. 'javascript.0.Sonos.192_168_0_15' */ function scriptPath(channel) { return SCRIPT_STATE_PATH + '.' + channel; } /*********** * Refreshes the current Favorites HTML List. * @param {string} channel The channel xx_xx_xx_xx * @param {string} favArray Array of the Favorites * @param {string} state The state to update */ function refreshFavoritesHtmlList(channel, favArray, state) { let current = getState(sonosPath(channel) + '.favorites_set').val; let favArrayDisplay; let htmlResult; /***** * Sort Array case insensitive ****/ if (SORT_LIST) favArray = arraySortCaseInsensitive(favArray); /***** * After sorting, we do some stuff to the displayed value ****/ favArrayDisplay = [...favArray]; // copy for (let i = 0; i < favArrayDisplay.length; i++) { let strResult = favArrayDisplay[i]; // Strip HTML: https://stackoverflow.com/questions/822452/strip-html-from-text-javascript strResult = strResult.replace(/<[^>]*>?/gm, ''); // Add number to each element if(LIST_NO_ADD) strResult = (i+1) + LIST_NO_SEP + strResult; // Finally, set to element favArrayDisplay[i] = strResult; } if (SORT_LIST) favArray = arraySortCaseInsensitive(favArray); /***** * Build Playlist ****/ htmlResult = '
| ';
let strCSSCurrPlaylist = '';
if (isLikeEmpty(current) === false) {
if( current == favArray[i] ) {
strCSSCurrPlaylist = ' id="' + CSS_CURRENT_SEL + '"';
}
}
htmlResult += ' ';
htmlResult += favArrayDisplay[i];
htmlResult += ' ';
htmlResult += ' | ' + '\n';
htmlResult += '\t' + '
| ';
htmlResult += ' ';
htmlResult += favArrayDisplay[i];
htmlResult += ' ';
htmlResult += ' | ' + '\n';
htmlResult += '\t' + '