Java tutorial
/** * Copyright (c) 2014-2015 openHAB UG (haftungsbeschraenkt) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.sonos.handler; import static org.openhab.binding.sonos.SonosBindingConstants.*; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.config.discovery.DiscoveryListener; import org.eclipse.smarthome.config.discovery.DiscoveryResult; import org.eclipse.smarthome.config.discovery.DiscoveryService; import org.eclipse.smarthome.config.discovery.DiscoveryServiceRegistry; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; import org.eclipse.smarthome.core.library.types.NextPreviousType; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.OpenClosedType; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.library.types.PlayPauseType; import org.eclipse.smarthome.core.library.types.RewindFastforwardType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.library.types.UpDownType; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; import org.eclipse.smarthome.io.net.http.HttpUtil; import org.eclipse.smarthome.io.transport.upnp.UpnpIOParticipant; import org.eclipse.smarthome.io.transport.upnp.UpnpIOService; import org.openhab.binding.sonos.internal.SonosAlarm; import org.openhab.binding.sonos.internal.SonosEntry; import org.openhab.binding.sonos.internal.SonosMetaData; import org.openhab.binding.sonos.internal.SonosXMLParser; import org.openhab.binding.sonos.internal.SonosZoneGroup; import org.openhab.binding.sonos.internal.SonosZonePlayerState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import static org.openhab.binding.sonos.config.ZonePlayerConfiguration.UDN; /** * The {@link ZonePlayerHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Karel Goderis - Initial contribution */ public class ZonePlayerHandler extends BaseThingHandler implements UpnpIOParticipant, DiscoveryListener { private Logger logger = LoggerFactory.getLogger(ZonePlayerHandler.class); private UpnpIOService service; private DiscoveryServiceRegistry discoveryServiceRegistry; private ScheduledFuture<?> pollingJob; private Calendar lastOPMLQuery = null; private SonosZonePlayerState savedState = null; private final static Collection<String> SERVICE_SUBSCRIPTIONS = Lists.newArrayList("DeviceProperties", "AVTransport", "ZoneGroupTopology", "GroupManagement", "RenderingControl", "AudioIn"); protected final static int SUBSCRIPTION_DURATION = 600; private static final int SOCKET_TIMEOUT = 5000; /** * The default refresh interval when not specified in channel configuration. */ private static final int DEFAULT_REFRESH_INTERVAL = 60; private Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<String, String>()); private Runnable pollingRunnable = new Runnable() { @Override public void run() { try { updateZoneInfo(); updateRunningAlarmProperties(); updateLed(); updateMediaInfo(); } catch (Exception e) { logger.debug("Exception during poll : {}", e); } } }; private String opmlPartnerID; public ZonePlayerHandler(Thing thing, UpnpIOService upnpIOService, DiscoveryServiceRegistry discoveryServiceRegistry, String opmlPartnerID) { super(thing); this.opmlPartnerID = opmlPartnerID; logger.debug("Creating a ZonePlayerHandler for thing '{}'", getThing().getUID()); if (upnpIOService != null) { this.service = upnpIOService; } if (discoveryServiceRegistry != null) { this.discoveryServiceRegistry = discoveryServiceRegistry; } } @Override public void dispose() { logger.debug("Handler disposed."); if (pollingJob != null && !pollingJob.isCancelled()) { pollingJob.cancel(true); pollingJob = null; } this.discoveryServiceRegistry.removeDiscoveryListener(this); removeSubscription(); } @Override public void initialize() { Configuration configuration = getConfig(); if (configuration.get("udn") != null) { this.discoveryServiceRegistry.addDiscoveryListener(this); onSubscription(); onUpdate(); super.initialize(); } else { logger.warn("Cannot initalize the zoneplayer. UDN not set."); } } @Override public void thingDiscovered(DiscoveryService source, DiscoveryResult result) { if (result.getThingUID().equals(this.getThing().getUID())) { if (getThing().getConfiguration().get(UDN).equals(result.getProperties().get(UDN))) { logger.debug("Discovered UDN '{}' for thing '{}'", result.getProperties().get(UDN), getThing().getUID()); updateStatus(ThingStatus.ONLINE); onSubscription(); onUpdate(); } } } @Override public void thingRemoved(DiscoveryService source, ThingUID thingUID) { if (thingUID.equals(this.getThing().getUID())) { logger.debug("Setting status for thing '{}' to OFFLINE", getThing().getUID()); updateStatus(ThingStatus.OFFLINE); } } @Override public void handleCommand(ChannelUID channelUID, Command command) { switch (channelUID.getId()) { case LED: this.setLed(command); break; case MUTE: this.setMute(command); break; case STOP: stop(); break; case VOLUME: setVolume(command); break; case ADD: addMember(command); break; case REMOVE: removeMember(command); break; case STANDALONE: becomeStandAlonePlayer(); break; case PUBLICADDRESS: publicAddress(); break; case RADIO: playRadio(command); break; case FAVORITE: playFavorite(command); break; case ALARM: setAlarm(command); break; case SNOOZE: snoozeAlarm(command); break; case SAVEALL: saveAllPlayerState(); break; case RESTOREALL: restoreAllPlayerState(); break; case SAVE: saveState(); break; case RESTORE: restoreState(); break; case PLAYLIST: playPlayList(command); break; case PLAYQUEUE: playQueue(command); break; case PLAYTRACK: playTrack(command); break; case PLAYURI: playURI(command); break; case PLAYLINEIN: playLineIn(command); break; case CONTROL: if (command instanceof PlayPauseType) { if (command == PlayPauseType.PLAY) { play(); } else if (command == PlayPauseType.PAUSE) { pause(); } } if (command instanceof NextPreviousType) { if (command == NextPreviousType.NEXT) { next(); } else if (command == NextPreviousType.PREVIOUS) { previous(); } } if (command instanceof RewindFastforwardType) { //Rewind and Fast Forward are currently not implemented by the binding } break; default: break; } } private void restoreAllPlayerState() { Collection<Thing> allThings = thingRegistry.getAll(); for (Thing aThing : allThings) { if (aThing.getThingTypeUID().equals(this.getThing().getThingTypeUID())) { ZonePlayerHandler handler = (ZonePlayerHandler) aThing.getHandler(); handler.restoreState(); } } } private void saveAllPlayerState() { Collection<Thing> allThings = thingRegistry.getAll(); for (Thing aThing : allThings) { if (aThing.getThingTypeUID().equals(this.getThing().getThingTypeUID())) { ZonePlayerHandler handler = (ZonePlayerHandler) aThing.getHandler(); handler.saveState(); } } } public void onValueReceived(String variable, String value, String service) { if (getThing().getStatus() == ThingStatus.ONLINE) { logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'", new Object[] { variable, value, service, this.getThing().getUID() }); this.stateMap.put(variable, value); // pre-process some variables, eg XML processing if (service.equals("AVTransport") && variable.equals("LastChange")) { Map<String, String> parsedValues = SonosXMLParser.getAVTransportFromXML(value); for (String parsedValue : parsedValues.keySet()) { onValueReceived(parsedValue, parsedValues.get(parsedValue), "AVTransport"); } } if (service.equals("RenderingControl") && variable.equals("LastChange")) { Map<String, String> parsedValues = SonosXMLParser.getRenderingControlFromXML(value); for (String parsedValue : parsedValues.keySet()) { onValueReceived(parsedValue, parsedValues.get(parsedValue), "RenderingControl"); } } // update the appropriate channel switch (variable) { case "TransportState": { updateState(new ChannelUID(getThing().getUID(), STATE), (stateMap.get("TransportState") != null) ? new StringType(stateMap.get("TransportState")) : UnDefType.UNDEF); if (stateMap.get("TransportState").equals("PLAYING")) { updateState(new ChannelUID(getThing().getUID(), CONTROL), PlayPauseType.PLAY); } if (stateMap.get("TransportState").equals("STOPPED")) { updateState(new ChannelUID(getThing().getUID(), CONTROL), PlayPauseType.PAUSE); } if (stateMap.get("TransportState").equals("PAUSED_PLAYBACK")) { updateState(new ChannelUID(getThing().getUID(), CONTROL), PlayPauseType.PAUSE); } break; } case "CurrentLEDState": { State newState = UnDefType.UNDEF; if (stateMap.get("CurrentLEDState") != null) { if (stateMap.get("CurrentLEDState").equals("On")) { newState = OnOffType.ON; } else { newState = OnOffType.OFF; } } updateState(new ChannelUID(getThing().getUID(), LED), newState); break; } case "CurrentZoneName": { updateState(new ChannelUID(getThing().getUID(), ZONENAME), (stateMap.get("CurrentZoneName") != null) ? new StringType(stateMap.get("CurrentZoneName")) : UnDefType.UNDEF); } case "ZoneGroupState": { updateState(new ChannelUID(getThing().getUID(), ZONEGROUP), (stateMap.get("ZoneGroupState") != null) ? new StringType(stateMap.get("ZoneGroupState")) : UnDefType.UNDEF); break; } case "LocalGroupUUID": { updateState(new ChannelUID(getThing().getUID(), ZONEGROUPID), (stateMap.get("LocalGroupUUID") != null) ? new StringType(stateMap.get("LocalGroupUUID")) : UnDefType.UNDEF); break; } case "GroupCoordinatorIsLocal": { State newState = UnDefType.UNDEF; if (stateMap.get("GroupCoordinatorIsLocal") != null) { if (stateMap.get("GroupCoordinatorIsLocal").equals("On")) { newState = OnOffType.ON; } else { newState = OnOffType.OFF; } } updateState(new ChannelUID(getThing().getUID(), LOCALCOORDINATOR), newState); break; } case "VolumeMaster": { updateState(new ChannelUID(getThing().getUID(), VOLUME), (stateMap.get("VolumeMaster") != null) ? new PercentType(stateMap.get("VolumeMaster")) : UnDefType.UNDEF); break; } case "MuteMaster": { State newState = UnDefType.UNDEF; if (stateMap.get("MuteMaster") != null) { if (stateMap.get("MuteMaster").equals("On")) { newState = OnOffType.ON; } else { newState = OnOffType.OFF; } } updateState(new ChannelUID(getThing().getUID(), MUTE), newState); break; } case "LineInConnected": { State newState = UnDefType.UNDEF; if (stateMap.get("LineInConnected") != null) { if (stateMap.get("LineInConnected").equals("On")) { newState = OnOffType.ON; } else { newState = OnOffType.OFF; } } updateState(new ChannelUID(getThing().getUID(), LINEIN), newState); break; } case "AlarmRunning": { State newState = UnDefType.UNDEF; if (stateMap.get("AlarmRunning") != null) { if (stateMap.get("AlarmRunning").equals("On")) { newState = OnOffType.ON; } else { newState = OnOffType.OFF; } } updateState(new ChannelUID(getThing().getUID(), ALARMRUNNING), newState); break; } case "RunningAlarmProperties": { updateState(new ChannelUID(getThing().getUID(), ALARMPROPERTIES), (stateMap.get("RunningAlarmProperties") != null) ? new StringType(stateMap.get("RunningAlarmProperties")) : UnDefType.UNDEF); break; } case "CurrentURIFormatted": { updateState(new ChannelUID(getThing().getUID(), CURRENTTRACK), (stateMap.get("CurrentURIFormatted") != null) ? new StringType(stateMap.get("CurrentURIFormatted")) : UnDefType.UNDEF); break; } case "CurrentTitle": { updateState(new ChannelUID(getThing().getUID(), CURRENTTITLE), (stateMap.get("CurrentTitle") != null) ? new StringType(stateMap.get("CurrentTitle")) : UnDefType.UNDEF); break; } case "CurrentArtist": { updateState(new ChannelUID(getThing().getUID(), CURRENTARTIST), (stateMap.get("CurrentArtist") != null) ? new StringType(stateMap.get("CurrentArtist")) : UnDefType.UNDEF); break; } case "CurrentAlbum": { updateState(new ChannelUID(getThing().getUID(), CURRENTALBUM), (stateMap.get("CurrentAlbum") != null) ? new StringType(stateMap.get("CurrentAlbum")) : UnDefType.UNDEF); break; } case "CurrentTrackMetaData": { updateTrackMetaData(); break; } case "CurrentURI": { updateCurrentURIFormatted(value); break; } } } } private synchronized void onSubscription() { // Set up GENA Subscriptions if (service.isRegistered(this)) { for (String subscription : SERVICE_SUBSCRIPTIONS) { service.addSubscription(this, subscription, SUBSCRIPTION_DURATION); } } } private synchronized void removeSubscription() { // Set up GENA Subscriptions if (service.isRegistered(this)) { for (String subscription : SERVICE_SUBSCRIPTIONS) { service.removeSubscription(this, subscription); } service.unregisterParticipant(this); } } private synchronized void onUpdate() { if (service.isRegistered(this)) { if (pollingJob == null || pollingJob.isCancelled()) { Configuration config = getThing().getConfiguration(); // use default if not specified int refreshInterval = DEFAULT_REFRESH_INTERVAL; Object refreshConfig = config.get("refresh"); if (refreshConfig != null) { refreshInterval = ((BigDecimal) refreshConfig).intValue(); } pollingJob = scheduler.scheduleAtFixedRate(pollingRunnable, 0, refreshInterval, TimeUnit.SECONDS); } } } protected void updateMediaInfo() { Map<String, String> inputs = new HashMap<String, String>(); inputs.put("InstanceID", "0"); Map<String, String> result = service.invokeAction(this, "AVTransport", "GetMediaInfo", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } protected void updateCurrentZoneName() { Map<String, String> result = service.invokeAction(this, "DeviceProperties", "GetZoneAttributes", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "DeviceProperties"); } } protected void updateLed() { Map<String, String> result = service.invokeAction(this, "DeviceProperties", "GetLEDState", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "DeviceProperties"); } } protected void updateTime() { Map<String, String> result = service.invokeAction(this, "AlarmClock", "GetTimeNow", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AlarmClock"); } } protected void updatePosition() { Map<String, String> result = service.invokeAction(this, "AVTransport", "GetPositionInfo", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } protected void updateRunningAlarmProperties() { Map<String, String> result = service.invokeAction(this, "AVTransport", "GetRunningAlarmProperties", null); String alarmID = result.get("AlarmID"); String loggedStartTime = result.get("LoggedStartTime"); String newStringValue = null; if (alarmID != null && loggedStartTime != null) { newStringValue = alarmID + " - " + loggedStartTime; } else { newStringValue = "No running alarm"; } result.put("RunningAlarmProperties", newStringValue); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } protected void updateZoneInfo() { Map<String, String> result = service.invokeAction(this, "DeviceProperties", "GetZoneInfo", null); Map<String, String> result2 = service.invokeAction(this, "DeviceProperties", "GetZoneAttributes", null); result.putAll(result2); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "DeviceProperties"); } } public String getCoordinator() { if (stateMap.get("ZoneGroupState") != null) { Collection<SonosZoneGroup> zoneGroups = SonosXMLParser .getZoneGroupFromXML(stateMap.get("ZoneGroupState")); for (SonosZoneGroup zg : zoneGroups) { if (zg.getMembers().contains(getThing().getConfiguration().get(UDN))) { return zg.getCoordinator(); } } } return (String) getThing().getConfiguration().get(UDN); } public boolean isCoordinator() { return getUDN().equals(getCoordinator()); } protected void updateTrackMetaData() { String coordinator = getCoordinator(); ZonePlayerHandler coordinatorHandler = getHandlerByName(coordinator); SonosMetaData currentTrack = getTrackMetadata(); if (coordinatorHandler != null && coordinatorHandler != this) { coordinatorHandler.updateMediaInfo(); currentTrack = coordinatorHandler.getTrackMetadata(); } if (currentTrack != null) { String artist = null; if (currentTrack.getAlbumArtist().equals("")) { artist = currentTrack.getCreator(); } else { artist = currentTrack.getAlbumArtist(); } String album = currentTrack.getAlbum(); String title = null; if (!currentTrack.getTitle().contains("x-sonosapi-stream")) { title = currentTrack.getTitle(); } // update individual variables this.onValueReceived("CurrentArtist", (artist != null) ? artist : "", "AVTransport"); if (title != null) { this.onValueReceived("CurrentTitle", (title != null) ? title : "", "AVTransport"); } this.onValueReceived("CurrentAlbum", (album != null) ? album : "", "AVTransport"); updateMediaInfo(); } } protected void updateCurrentURIFormatted(String URI) { String currentURI = URI; SonosMetaData currentTrack = null; String coordinator = getCoordinator(); ZonePlayerHandler coordinatorHandler = getHandlerByName(coordinator); if (coordinatorHandler != null && coordinatorHandler != this) { if (currentURI.contains("x-rincon-stream")) { coordinatorHandler.updateMediaInfo(); } currentURI = coordinatorHandler.getCurrentURI(); currentTrack = coordinatorHandler.getTrackMetadata(); } else { // currentURI = getCurrentURI(); currentTrack = getTrackMetadata(); } if (currentURI != null) { String title = stateMap.get("CurrentTitle"); String resultString = stateMap.get("CurrentURIFormatted"); boolean needsUpdating = false; if (opmlPartnerID != null && currentURI.contains("x-sonosapi-stream")) { String stationID = StringUtils.substringBetween(currentURI, ":s", "?sid"); String previousStationID = stateMap.get("StationID"); Calendar now = Calendar.getInstance(); now.setTime(new Date()); now.add(Calendar.MINUTE, -1); if (previousStationID == null || !previousStationID.equals(stationID) || lastOPMLQuery == null || lastOPMLQuery.before(now)) { this.onValueReceived("StationID", stationID, "AVTransport"); String url = "http://opml.radiotime.com/Describe.ashx?c=nowplaying" + "&id=" + stationID + "&partnerId=" + opmlPartnerID + "&serial=" + getMACAddress(); String response = HttpUtil.executeUrl("GET", url, SOCKET_TIMEOUT); if (lastOPMLQuery == null) { lastOPMLQuery = Calendar.getInstance(); } lastOPMLQuery.setTime(new Date()); if (response != null) { List<String> fields = SonosXMLParser.getRadioTimeFromXML(response); if (fields != null && fields.size() > 0) { resultString = new String(); // radio name should be first field title = fields.get(0); Iterator<String> listIterator = fields.listIterator(); while (listIterator.hasNext()) { String field = listIterator.next(); resultString = resultString + field; if (listIterator.hasNext()) { resultString = resultString + " - "; } } needsUpdating = true; } } } } if (currentURI.contains("x-rincon-stream")) { if (currentTrack != null) { resultString = stateMap.get("CurrentTitle"); needsUpdating = true; } } if (!currentURI.contains("x-rincon-mp3") && !currentURI.contains("x-rincon-stream") && !currentURI.contains("x-sonosapi")) { if (currentTrack != null) { if (currentTrack.getAlbumArtist().equals("")) { resultString = currentTrack.getCreator() + " - " + currentTrack.getAlbum() + " - " + currentTrack.getTitle(); } else { resultString = currentTrack.getAlbumArtist() + " - " + currentTrack.getAlbum() + " - " + currentTrack.getTitle(); } needsUpdating = true; } } if (needsUpdating) { this.onValueReceived("CurrentURIFormatted", (resultString != null) ? resultString : "", "AVTransport"); this.onValueReceived("CurrentTitle", (title != null) ? title : "", "AVTransport"); } } } public boolean isGroupCoordinator() { String value = stateMap.get("GroupCoordinatorIsLocal"); if (value != null) { return value.equals("1") ? true : false; } return false; } public String getUDN() { return (String) this.getThing().getConfiguration().get(UDN); } public String getCurrentURI() { return stateMap.get("CurrentURI"); } public SonosMetaData getCurrentURIMetadata() { if (stateMap.get("CurrentURIMetaData") != null) { return SonosXMLParser.getMetaDataFromXML(stateMap.get("CurrentURIMetaData")); } else { return null; } } public SonosMetaData getTrackMetadata() { if (stateMap.get("CurrentTrackMetaData") != null) { return SonosXMLParser.getMetaDataFromXML(stateMap.get("CurrentTrackMetaData")); } else { return null; } } public SonosMetaData getEnqueuedTransportURIMetaData() { if (stateMap.get("EnqueuedTransportURIMetaData") != null) { return SonosXMLParser.getMetaDataFromXML(stateMap.get("EnqueuedTransportURIMetaData")); } else { return null; } } public String getMACAddress() { updateZoneInfo(); return stateMap.get("MACAddress"); } public String getPosition() { updatePosition(); return stateMap.get("RelTime"); } public long getCurrenTrackNr() { updatePosition(); String value = stateMap.get("Track"); if (value != null) { return Long.valueOf(value); } else { return (long) -1; } } public String getVolume() { return stateMap.get("VolumeMaster"); } public String getTransportState() { return stateMap.get("TransportState"); } public List<SonosEntry> getArtists(String filter) { return getEntries("A:", filter); } public List<SonosEntry> getArtists() { return getEntries("A:", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } public List<SonosEntry> getAlbums(String filter) { return getEntries("A:ALBUM", filter); } public List<SonosEntry> getAlbums() { return getEntries("A:ALBUM", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } public List<SonosEntry> getTracks(String filter) { return getEntries("A:TRACKS", filter); } public List<SonosEntry> getTracks() { return getEntries("A:TRACKS", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } public List<SonosEntry> getQueue(String filter) { return getEntries("Q:0", filter); } public List<SonosEntry> getQueue() { return getEntries("Q:0", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } public List<SonosEntry> getPlayLists(String filter) { return getEntries("SQ:", filter); } public List<SonosEntry> getPlayLists() { return getEntries("SQ:", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } public List<SonosEntry> getFavoriteRadios(String filter) { return getEntries("R:0/0", filter); } public List<SonosEntry> getFavoriteRadios() { return getEntries("R:0/0", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } /** * Searches for entries in the 'favorites' list on a sonos account * @return */ public List<SonosEntry> getFavorites() { return getEntries("FV:2", "dc:title,res,dc:creator,upnp:artist,upnp:album"); } protected List<SonosEntry> getEntries(String type, String filter) { long startAt = 0; Map<String, String> inputs = new HashMap<String, String>(); inputs.put("ObjectID", type); inputs.put("BrowseFlag", "BrowseDirectChildren"); inputs.put("Filter", filter); inputs.put("StartingIndex", Long.toString(startAt)); inputs.put("RequestedCount", Integer.toString(200)); inputs.put("SortCriteria", ""); List<SonosEntry> resultList = null; Map<String, String> result = service.invokeAction(this, "ContentDirectory", "Browse", inputs); Long totalMatches = Long.valueOf(result.get("TotalMatches")); Long initialNumberReturned = Long.valueOf(result.get("NumberReturned")); String initialResult = result.get("Result"); resultList = SonosXMLParser.getEntriesFromString(initialResult); startAt = startAt + initialNumberReturned; while (startAt < totalMatches) { inputs.put("StartingIndex", Long.toString(startAt)); result = service.invokeAction(this, "ContentDirectory", "Browse", inputs); // Execute this action synchronously String nextResult = result.get("Result"); Long numberReturned = Long.valueOf(result.get("NumberReturned")); resultList.addAll(SonosXMLParser.getEntriesFromString(nextResult)); startAt = startAt + numberReturned; } return resultList; } /** * Save the state (track, position etc) of the Sonos Zone player. * * @return true if no error occurred. */ protected void saveState() { synchronized (this) { savedState = new SonosZonePlayerState(); String currentURI = getCurrentURI(); savedState.transportState = getTransportState(); savedState.volume = getVolume(); if (currentURI != null) { if (currentURI.contains("x-sonosapi-stream:")) { // we are streaming music SonosMetaData track = getTrackMetadata(); SonosMetaData current = getCurrentURIMetadata(); if (track != null) { savedState.entry = new SonosEntry("", current.getTitle(), "", "", track.getAlbumArtUri(), "", current.getUpnpClass(), currentURI); } } else if (currentURI.contains("x-rincon:")) { // we are a slave to some coordinator savedState.entry = new SonosEntry("", "", "", "", "", "", "", currentURI); } else if (currentURI.contains("x-rincon-stream:")) { // we are streaming from the Line In connection savedState.entry = new SonosEntry("", "", "", "", "", "", "", currentURI); } else if (currentURI.contains("x-rincon-queue:")) { // we are playing something that sits in the queue SonosMetaData queued = getEnqueuedTransportURIMetaData(); if (queued != null) { savedState.track = getCurrenTrackNr(); if (queued.getUpnpClass().contains("object.container.playlistContainer")) { // we are playing a real 'saved' playlist List<SonosEntry> playLists = getPlayLists(); for (SonosEntry someList : playLists) { if (someList.getTitle().equals(queued.getTitle())) { savedState.entry = new SonosEntry(someList.getId(), someList.getTitle(), someList.getParentId(), "", "", "", someList.getUpnpClass(), someList.getRes()); break; } } } else if (queued.getUpnpClass().contains("object.container")) { // we are playing some other sort of // 'container' - we will save that to a // playlist for our convenience logger.debug("Save State for a container of type {}", queued.getUpnpClass()); // save the playlist String existingList = ""; List<SonosEntry> playLists = getPlayLists(); for (SonosEntry someList : playLists) { if (someList.getTitle().equals("openHAB-" + getUDN())) { existingList = someList.getId(); break; } } saveQueue("openHAB-" + getUDN(), existingList); // get all the playlists and a ref to our // saved list playLists = getPlayLists(); for (SonosEntry someList : playLists) { if (someList.getTitle().equals("openHAB-" + getUDN())) { savedState.entry = new SonosEntry(someList.getId(), someList.getTitle(), someList.getParentId(), "", "", "", someList.getUpnpClass(), someList.getRes()); break; } } } } else { savedState.entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon-queue:" + getUDN() + "#0"); } } savedState.relTime = getPosition(); } else { savedState.entry = null; } } } /** * Restore the state (track, position etc) of the Sonos Zone player. * * @return true if no error occurred. */ protected void restoreState() { synchronized (this) { if (savedState != null) { // put settings back if (savedState.volume != null) { setVolume(DecimalType.valueOf(savedState.volume)); } if (isCoordinator()) { if (savedState.entry != null) { // check if we have a playlist to deal with if (savedState.entry.getUpnpClass().contains("object.container.playlistContainer")) { addURIToQueue(savedState.entry.getRes(), SonosXMLParser.compileMetadataString(savedState.entry), 0, true); SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon-queue:" + getUDN() + "#0"); setCurrentURI(entry); setPositionTrack(savedState.track); } else { setCurrentURI(savedState.entry); setPosition(savedState.relTime); } } if (savedState.transportState != null) { if (savedState.transportState.equals("PLAYING")) { play(); } else if (savedState.transportState.equals("STOPPED")) { stop(); } else if (savedState.transportState.equals("PAUSED_PLAYBACK")) { pause(); } } } } } } public void saveQueue(String name, String queueID) { if (name != null && queueID != null) { Map<String, String> inputs = new HashMap<String, String>(); inputs.put("Title", name); inputs.put("ObjectID", queueID); Map<String, String> result = service.invokeAction(this, "AVTransport", "SaveQueue", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } } public void setVolume(Command command) { if (command != null) { if (command instanceof OnOffType || command instanceof IncreaseDecreaseType || command instanceof DecimalType || command instanceof PercentType) { Map<String, String> inputs = new HashMap<String, String>(); String newValue = null; if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) { int i = Integer.valueOf(this.getVolume()); newValue = String.valueOf(Math.min(100, i + 1)); } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) { int i = Integer.valueOf(this.getVolume()); newValue = String.valueOf(Math.max(0, i - 1)); } else if (command instanceof OnOffType && command == OnOffType.ON) { newValue = "100"; } else if (command instanceof OnOffType && command == OnOffType.OFF) { newValue = "0"; } else if (command instanceof DecimalType) { newValue = command.toString(); } else { return; } inputs.put("Channel", "Master"); inputs.put("DesiredVolume", newValue); Map<String, String> result = service.invokeAction(this, "RenderingControl", "SetVolume", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "RenderingControl"); } } } } public void addURIToQueue(String URI, String meta, int desiredFirstTrack, boolean enqueueAsNext) { if (URI != null && meta != null) { Map<String, String> inputs = new HashMap<String, String>(); try { inputs.put("InstanceID", "0"); inputs.put("EnqueuedURI", URI); inputs.put("EnqueuedURIMetaData", meta); inputs.put("DesiredFirstTrackNumberEnqueued", Integer.toString(desiredFirstTrack)); inputs.put("EnqueueAsNext", Boolean.toString(enqueueAsNext)); } catch (NumberFormatException ex) { logger.error("Action Invalid Value Format Exception {}", ex.getMessage()); } Map<String, String> result = service.invokeAction(this, "AVTransport", "AddURIToQueue", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } } public void setCurrentURI(SonosEntry newEntry) { setCurrentURI(newEntry.getRes(), SonosXMLParser.compileMetadataString(newEntry)); } public void setCurrentURI(String URI, String URIMetaData) { if (URI != null && URIMetaData != null) { Map<String, String> inputs = new HashMap<String, String>(); try { inputs.put("InstanceID", "0"); inputs.put("CurrentURI", URI); inputs.put("CurrentURIMetaData", URIMetaData); } catch (NumberFormatException ex) { logger.error("Action Invalid Value Format Exception {}", ex.getMessage()); } Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } } public void setPosition(String relTime) { seek("REL_TIME", relTime); } public void setPositionTrack(long tracknr) { seek("TRACK_NR", Long.toString(tracknr)); } public void setPositionTrack(String tracknr) { seek("TRACK_NR", tracknr); } protected void seek(String unit, String target) { if (unit != null && target != null) { Map<String, String> inputs = new HashMap<String, String>(); try { inputs.put("InstanceID", "0"); inputs.put("Unit", unit); inputs.put("Target", target); } catch (NumberFormatException ex) { logger.error("Action Invalid Value Format Exception {}", ex.getMessage()); } Map<String, String> result = service.invokeAction(this, "AVTransport", "Seek", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } } public void play() { Map<String, String> inputs = new HashMap<String, String>(); inputs.put("Speed", "1"); Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } public void stop() { Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } public void pause() { Map<String, String> result = service.invokeAction(this, "AVTransport", "Pause", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } /** * Clear all scheduled music from the current queue. * */ public void removeAllTracksFromQueue() { Map<String, String> inputs = new HashMap<String, String>(); inputs.put("InstanceID", "0"); Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } /** * Play music from the line-in of the given Player referenced by the given UDN or name * * @param udn or name */ public void playLineIn(Command command) { if (command != null && command instanceof StringType) { String remotePlayerName = command.toString(); String coordinatorUDN = getCoordinator(); ZonePlayerHandler coordinatorHandler = getHandlerByName(coordinatorUDN); ZonePlayerHandler remoteHandler = getHandlerByName(remotePlayerName); if (coordinatorHandler != null && remoteHandler != null) { // stop whatever is currently playing coordinatorHandler.stop(); // set the URI coordinatorHandler.setCurrentURI("x-rincon-stream:" + remoteHandler.getConfig().get(UDN), ""); // take the system off mute coordinatorHandler.setMute(OnOffType.OFF); // start jammin' coordinatorHandler.play(); } } } protected ZonePlayerHandler getHandlerByName(String remotePlayerName) { if (thingRegistry != null) { Thing thing = thingRegistry.get(new ThingUID(ZONEPLAYER_THING_TYPE_UID, remotePlayerName)); if (thing == null) { Collection<Thing> allThings = thingRegistry.getAll(); for (Thing aThing : allThings) { if (aThing.getThingTypeUID().equals(this.getThing().getThingTypeUID())) { if (aThing.getConfiguration().get(UDN).equals(remotePlayerName)) { thing = aThing; break; } } } } if (thing != null) { return (ZonePlayerHandler) thing.getHandler(); } } return null; } public void setMute(Command command) { if (command != null) { if (command instanceof OnOffType || command instanceof OpenClosedType || command instanceof UpDownType) { Map<String, String> inputs = new HashMap<String, String>(); inputs.put("Channel", "Master"); if (command.equals(OnOffType.ON) || command.equals(UpDownType.UP) || command.equals(OpenClosedType.OPEN)) { inputs.put("DesiredMute", "True"); } else if (command.equals(OnOffType.OFF) || command.equals(UpDownType.DOWN) || command.equals(OpenClosedType.CLOSED)) { inputs.put("DesiredMute", "False"); } Map<String, String> result = service.invokeAction(this, "RenderingControl", "SetMute", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "RenderingControl"); } } } } public List<SonosAlarm> getCurrentAlarmList() { Map<String, String> result = service.invokeAction(this, "AlarmClock", "ListAlarms", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AlarmClock"); } return SonosXMLParser.getAlarmsFromStringResult(result.get("CurrentAlarmList")); } public void updateAlarm(SonosAlarm alarm) { if (alarm != null) { Map<String, String> inputs = new HashMap<String, String>(); try { inputs.put("ID", Integer.toString(alarm.getID())); inputs.put("StartLocalTime", alarm.getStartTime()); inputs.put("Duration", alarm.getDuration()); inputs.put("Recurrence", alarm.getRecurrence()); inputs.put("RoomUUID", alarm.getRoomUUID()); inputs.put("ProgramURI", alarm.getProgramURI()); inputs.put("ProgramMetaData", alarm.getProgramMetaData()); inputs.put("PlayMode", alarm.getPlayMode()); inputs.put("Volume", Integer.toString(alarm.getVolume())); if (alarm.getIncludeLinkedZones()) { inputs.put("IncludeLinkedZones", "1"); } else { inputs.put("IncludeLinkedZones", "0"); } if (alarm.getEnabled()) { inputs.put("Enabled", "1"); } else { inputs.put("Enabled", "0"); } } catch (NumberFormatException ex) { logger.error("Action Invalid Value Format Exception {}", ex.getMessage()); } Map<String, String> result = service.invokeAction(this, "AlarmClock", "UpdateAlarm", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AlarmClock"); } } } public void setAlarm(Command command) { if (command instanceof OnOffType || command instanceof OpenClosedType || command instanceof UpDownType) { if (command.equals(OnOffType.ON) || command.equals(UpDownType.UP) || command.equals(OpenClosedType.OPEN)) { setAlarm(true); } else if (command.equals(OnOffType.OFF) || command.equals(UpDownType.DOWN) || command.equals(OpenClosedType.CLOSED)) { setAlarm(false); } } } public void setAlarm(boolean alarmSwitch) { List<SonosAlarm> sonosAlarms = getCurrentAlarmList(); // find the nearest alarm - take the current time from the Sonos System, // not the system where openhab is running SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); fmt.setTimeZone(TimeZone.getTimeZone("GMT")); String currentLocalTime = getTime(); Date currentDateTime = null; try { currentDateTime = fmt.parse(currentLocalTime); } catch (ParseException e) { logger.error("An exception occurred while formatting a date"); e.printStackTrace(); } if (currentDateTime != null) { Calendar currentDateTimeCalendar = Calendar.getInstance(); currentDateTimeCalendar.setTimeZone(TimeZone.getTimeZone("GMT")); currentDateTimeCalendar.setTime(currentDateTime); currentDateTimeCalendar.add(Calendar.DAY_OF_YEAR, 10); long shortestDuration = currentDateTimeCalendar.getTimeInMillis() - currentDateTime.getTime(); SonosAlarm firstAlarm = null; for (SonosAlarm anAlarm : sonosAlarms) { SimpleDateFormat durationFormat = new SimpleDateFormat("HH:mm:ss"); durationFormat.setTimeZone(TimeZone.getTimeZone("GMT")); Date durationDate = null; try { durationDate = durationFormat.parse(anAlarm.getDuration()); } catch (ParseException e) { logger.error("An exception occurred while parsing a date : '{}'", e.getMessage()); } long duration = durationDate.getTime(); if (duration < shortestDuration && anAlarm.getRoomUUID().equals(getUDN())) { shortestDuration = duration; firstAlarm = anAlarm; } } // Set the Alarm if (firstAlarm != null) { if (alarmSwitch) { firstAlarm.setEnabled(true); } else { firstAlarm.setEnabled(false); } updateAlarm(firstAlarm); } } } public String getTime() { updateTime(); return stateMap.get("CurrentLocalTime"); } public Boolean isAlarmRunning() { return stateMap.get("AlarmRunning").equals("1") ? true : false; } public void snoozeAlarm(Command command) { if (isAlarmRunning() && command instanceof DecimalType) { int minutes = ((DecimalType) command).intValue(); Map<String, String> inputs = new HashMap<String, String>(); Calendar snoozePeriod = Calendar.getInstance(); snoozePeriod.setTimeZone(TimeZone.getTimeZone("GMT")); snoozePeriod.setTimeInMillis(0); snoozePeriod.add(Calendar.MINUTE, minutes); SimpleDateFormat pFormatter = new SimpleDateFormat("HH:mm:ss"); pFormatter.setTimeZone(TimeZone.getTimeZone("GMT")); try { inputs.put("Duration", pFormatter.format(snoozePeriod.getTime())); } catch (NumberFormatException ex) { logger.error("Action Invalid Value Format Exception {}", ex.getMessage()); } Map<String, String> result = service.invokeAction(this, "AVTransport", "SnoozeAlarm", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } else { logger.warn("There is no alarm running on {} ", this); } } public Boolean isLineInConnected() { return stateMap.get("LineInConnected").equals("1") ? true : false; } public void becomeStandAlonePlayer() { Map<String, String> result = service.invokeAction(this, "AVTransport", "BecomeCoordinatorOfStandaloneGroup", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } public void addMember(Command command) { if (command != null && command instanceof StringType) { SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon:" + getUDN()); getHandlerByName(command.toString()).setCurrentURI(entry); } } public boolean publicAddress() { // check if sourcePlayer has a line-in connected if (isLineInConnected()) { // first remove this player from its own group if any becomeStandAlonePlayer(); List<SonosZoneGroup> currentSonosZoneGroups = new ArrayList<SonosZoneGroup>(); for (SonosZoneGroup grp : SonosXMLParser.getZoneGroupFromXML(stateMap.get("ZoneGroupState"))) { currentSonosZoneGroups.add((SonosZoneGroup) grp.clone()); } // add all other players to this new group for (SonosZoneGroup group : currentSonosZoneGroups) { for (String player : group.getMembers()) { ZonePlayerHandler somePlayer = getHandlerByName(player); if (somePlayer != this) { somePlayer.becomeStandAlonePlayer(); somePlayer.stop(); addMember(StringType.valueOf(somePlayer.getUDN())); } } } // set the URI of the group to the line-in ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon-stream:" + getUDN()); coordinator.setCurrentURI(entry); coordinator.play(); return true; } else { logger.warn("Line-in of {} is not connected", this); return false; } } /** * Play a given url to music in one of the music libraries. * * @param url * in the format of //host/folder/filename.mp3 */ public void playURI(Command command) { if (command != null && command instanceof StringType) { String url = command.toString(); ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); // stop whatever is currently playing coordinator.stop(); // clear any tracks which are pending in the queue coordinator.removeAllTracksFromQueue(); // add the new track we want to play to the queue // The url will be prefixed with x-file-cifs if it is NOT a http URL if (!url.startsWith("x-") && (!url.startsWith("http"))) { // default to file based url url = "x-file-cifs:" + url; } coordinator.addURIToQueue(url, "", 0, true); // set the current playlist to our new queue coordinator.setCurrentURI("x-rincon-queue:" + getUDN() + "#0", ""); // take the system off mute coordinator.setMute(OnOffType.OFF); // start jammin' coordinator.play(); } } public void playQueue(Command command) { ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); // set the current playlist to our new queue coordinator.setCurrentURI("x-rincon-queue:" + getUDN() + "#0", ""); // take the system off mute coordinator.setMute(OnOffType.OFF); // start jammin' coordinator.play(); } public void setLed(Command command) { if (command != null) { if (command instanceof OnOffType || command instanceof OpenClosedType || command instanceof UpDownType) { Map<String, String> inputs = new HashMap<String, String>(); if (command.equals(OnOffType.ON) || command.equals(UpDownType.UP) || command.equals(OpenClosedType.OPEN)) { inputs.put("DesiredLEDState", "On"); } else if (command.equals(OnOffType.OFF) || command.equals(UpDownType.DOWN) || command.equals(OpenClosedType.CLOSED)) { inputs.put("DesiredLEDState", "Off"); } Map<String, String> result = service.invokeAction(this, "DeviceProperties", "SetLEDState", inputs); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "DeviceProperties"); } } } } public void removeMember(Command command) { if (command != null && command instanceof StringType) { ZonePlayerHandler oldmemberHandler = getHandlerByName(command.toString()); oldmemberHandler.becomeStandAlonePlayer(); SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon-queue:" + oldmemberHandler.getUDN() + "#0"); oldmemberHandler.setCurrentURI(entry); } } public void previous() { Map<String, String> result = service.invokeAction(this, "AVTransport", "Previous", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } public void next() { Map<String, String> result = service.invokeAction(this, "AVTransport", "Next", null); for (String variable : result.keySet()) { this.onValueReceived(variable, result.get(variable), "AVTransport"); } } public void playRadio(Command command) { List<SonosEntry> stations = getFavoriteRadios(); SonosEntry theEntry = null; if (command instanceof StringType) { String station = command.toString(); // search for the appropriate radio based on its name (title) for (SonosEntry someStation : stations) { if (someStation.getTitle().equals(station)) { theEntry = someStation; break; } } // set the URI of the group coordinator if (theEntry != null) { ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); coordinator.setCurrentURI(theEntry); coordinator.play(); } } } /** * This will attempt to match the station string with a entry in the * favorites list, this supports both single entries and playlists * * @param favorite to match * @return true if a match was found and played. */ public void playFavorite(Command command) { if (command instanceof StringType) { String favorite = command.toString(); List<SonosEntry> favorites = getFavorites(); SonosEntry theEntry = null; // search for the appropriate favorite based on its name (title) for (SonosEntry entry : favorites) { if (entry.getTitle().equals(favorite)) { theEntry = entry; break; } } // set the URI of the group coordinator if (theEntry != null) { ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); /** * If this is a playlist we need to treat it as such */ if (theEntry.getResourceMetaData() != null && theEntry.getResourceMetaData().getUpnpClass() .equals("object.container.playlistContainer")) { coordinator.removeAllTracksFromQueue(); coordinator.addURIToQueue(theEntry); coordinator.setCurrentURI("x-rincon-queue:" + coordinator.getUDN() + "#0", ""); if (stateMap != null) { String firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued"); if (firstTrackNumberEnqueued != null) { coordinator.seek("TRACK_NR", firstTrackNumberEnqueued); } } } else { coordinator.setCurrentURI(theEntry); } coordinator.play(); } } } public void playTrack(Command command) { if (command != null && command instanceof DecimalType) { ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); String trackNumber = command.toString(); // seek the track - warning, we do not check if the tracknumber falls in the boundary of the queue setPositionTrack(trackNumber); // take the system off mute coordinator.setMute(OnOffType.OFF); // start jammin' coordinator.play(); } } public void playPlayList(Command command) { List<SonosEntry> playlists = getPlayLists(); SonosEntry theEntry = null; if (command != null && command instanceof StringType) { String playlist = command.toString(); // search for the appropriate play list based on its name (title) for (SonosEntry somePlaylist : playlists) { if (somePlaylist.getTitle().equals(playlist)) { theEntry = somePlaylist; break; } } // set the URI of the group coordinator if (theEntry != null) { ZonePlayerHandler coordinator = getHandlerByName(getCoordinator()); // coordinator.setCurrentURI(theEntry); coordinator.addURIToQueue(theEntry); if (stateMap != null) { String firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued"); if (firstTrackNumberEnqueued != null) { coordinator.seek("TRACK_NR", firstTrackNumberEnqueued); } } coordinator.play(); } } } public void addURIToQueue(SonosEntry newEntry) { addURIToQueue(newEntry.getRes(), SonosXMLParser.compileMetadataString(newEntry), 1, true); } public String getZoneName() { return stateMap.get("ZoneName"); } public String getZoneGroupID() { return stateMap.get("LocalGroupUUID"); } public String getRunningAlarmProperties() { updateRunningAlarmProperties(); return stateMap.get("RunningAlarmProperties"); } public String getMute() { return stateMap.get("MuteMaster"); } public boolean getLed() { return stateMap.get("CurrentLEDState").equals("On") ? true : false; } public String getCurrentZoneName() { updateCurrentZoneName(); return stateMap.get("CurrentZoneName"); } public String getCurrentURIFormatted() { updateCurrentURIFormatted(getCurrentURI()); return stateMap.get("CurrentURIFormatted"); } @Override public void onStatusChanged(boolean status) { // TODO Auto-generated method stub } @Override public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp, Collection<ThingTypeUID> thingTypeUIDs) { // TODO Auto-generated method stub return null; } }