org.openhab.binding.sonos.internal.SonosZonePlayer.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.sonos.internal.SonosZonePlayer.java

Source

/**
 * Copyright (c) 2010-2015, openHAB.org 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.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import org.openhab.binding.sonos.SonosCommandType;
import org.openhab.binding.sonos.internal.SonosBinding.SonosZonePlayerState;
import org.openhab.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teleal.cling.UpnpService;
import org.teleal.cling.controlpoint.ActionCallback;
import org.teleal.cling.controlpoint.SubscriptionCallback;
import org.teleal.cling.model.action.ActionArgumentValue;
import org.teleal.cling.model.action.ActionException;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.gena.CancelReason;
import org.teleal.cling.model.gena.GENASubscription;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Action;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.meta.StateVariable;
import org.teleal.cling.model.meta.StateVariableTypeDetails;
import org.teleal.cling.model.state.StateVariableValue;
import org.teleal.cling.model.types.Datatype;
import org.teleal.cling.model.types.InvalidValueException;
import org.teleal.cling.model.types.UDAServiceId;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.model.types.UnsignedIntegerFourBytes;
import org.xml.sax.SAXException;

/**
 * Internal data structure which carries the connection details of one Sonos player
 * (there could be several)
 * 
 * @author Karel Goderis 
 * @since 1.1.0
 * 
 */
class SonosZonePlayer {

    private static Logger logger = LoggerFactory.getLogger(SonosZonePlayer.class);

    protected final int interval = 600;
    private boolean isConfigured = false;

    /** the default socket timeout when requesting an url */
    private static final int SO_TIMEOUT = 5000;

    private RemoteDevice device = null;
    private UDN udn;
    private String id;
    private DateTime lastOPMLQuery;
    private SonosZonePlayerState savedState = null;

    static protected UpnpService upnpService;
    protected SonosBinding sonosBinding;

    private Map<String, StateVariableValue> stateMap = Collections
            .synchronizedMap(new HashMap<String, StateVariableValue>());

    /**
     * @return the stateMap
     */
    public Map<String, StateVariableValue> getStateMap() {
        return stateMap;
    }

    public boolean isConfigured() {
        return isConfigured;
    }

    SonosZonePlayer(String id, SonosBinding binding) {

        if (binding != null) {
            this.id = id;
            sonosBinding = binding;
        }
    }

    private void enableGENASubscriptions() {

        if (device != null && isConfigured()) {

            // Create a GENA subscription of each service for this device, if supported by the device        
            List<SonosCommandType> subscriptionCommands = SonosCommandType.getSubscriptions();
            List<String> addedSubscriptions = new ArrayList<String>();

            for (SonosCommandType c : subscriptionCommands) {
                Service service = device.findService(new UDAServiceId(c.getService()));
                if (service != null && !addedSubscriptions.contains(c.getService())) {
                    SonosPlayerSubscriptionCallback callback = new SonosPlayerSubscriptionCallback(service,
                            interval);
                    addedSubscriptions.add(c.getService());
                    upnpService.getControlPoint().execute(callback);
                }
            }
        }
    }

    protected boolean isUpdatedValue(String valueName, StateVariableValue newValue) {
        if (newValue != null && valueName != null) {
            StateVariableValue oldValue = stateMap.get(valueName);

            if (newValue.getValue() == null) {
                // we will *not* store an empty value, thank you.
                return false;
            } else {
                if (oldValue == null) {
                    // there was nothing stored before
                    return true;
                } else {
                    if (oldValue.getValue() == null) {
                        // something was defined, but no value present
                        return true;
                    } else {
                        if (newValue.getValue().equals(oldValue.getValue())) {
                            return false;
                        } else {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    protected void processStateVariableValue(String valueName, StateVariableValue newValue) {
        if (newValue != null && isUpdatedValue(valueName, newValue)) {
            Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
            mapToProcess.put(valueName, newValue);
            stateMap.putAll(mapToProcess);
            sonosBinding.processVariableMap(device, mapToProcess);
        }
    }

    /**
     * @return the device
     */
    public RemoteDevice getDevice() {
        return device;
    }

    /**
     * @param device the device to set
     */
    public void setDevice(RemoteDevice device) {
        this.device = device;
        if (upnpService != null && device != null) {
            isConfigured = true;
            enableGENASubscriptions();
        }
    }

    public class SonosPlayerSubscriptionCallback extends SubscriptionCallback {

        public SonosPlayerSubscriptionCallback(Service service) {
            super(service);
        }

        public SonosPlayerSubscriptionCallback(Service service, int requestedDurationSeconds) {
            super(service, requestedDurationSeconds);
        }

        @Override
        public void established(GENASubscription sub) {
        }

        @Override
        protected void failed(GENASubscription subscription, UpnpResponse responseStatus, Exception exception,
                String defaultMsg) {
        }

        public void eventReceived(GENASubscription sub) {

            // get the device linked to this service linked to this subscription
            Map<String, StateVariableValue> values = sub.getCurrentValues();
            Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
            Map<String, StateVariableValue> parsedValues = null;

            // now, lets deal with the specials - some UPNP responses require some XML parsing
            // or we need to update our internal data structure
            // or are things we want to store for further reference

            for (String stateVariable : values.keySet()) {
                if (stateVariable.equals("LastChange")
                        && service.getServiceType().getType().equals("AVTransport")) {
                    try {
                        parsedValues = SonosXMLParser.getAVTransportFromXML(values.get(stateVariable).toString());
                        for (String someValue : parsedValues.keySet()) {
                            //                     logger.debug("Lastchange parsed into {}:{}",someValue,parsedValues.get(someValue));
                            if (isUpdatedValue(someValue, parsedValues.get(someValue))) {
                                mapToProcess.put(someValue, parsedValues.get(someValue));
                            }
                        }
                    } catch (SAXException e) {
                        logger.error("Could not parse AVTransport from String {}",
                                values.get(stateVariable).toString());
                    }

                } else if (stateVariable.equals("LastChange")
                        && service.getServiceType().getType().equals("RenderingControl")) {
                    try {
                        parsedValues = SonosXMLParser
                                .getRenderingControlFromXML(values.get(stateVariable).toString());
                        for (String someValue : parsedValues.keySet()) {
                            if (isUpdatedValue(someValue, parsedValues.get(someValue))) {
                                mapToProcess.put(someValue, parsedValues.get(someValue));
                            }
                        }
                    } catch (SAXException e) {
                        logger.error("Could not parse RenderingControl from String {}",
                                values.get(stateVariable).toString());
                    }
                } else if (isUpdatedValue(stateVariable, values.get(stateVariable))) {
                    mapToProcess.put(stateVariable, values.get(stateVariable));
                }

            }

            if (isConfigured) {
                stateMap.putAll(mapToProcess);
                sonosBinding.processVariableMap(device, mapToProcess);
            }
        }

        public void eventsMissed(GENASubscription sub, int numberOfMissedEvents) {
            logger.warn("Missed events: " + numberOfMissedEvents);
        }

        @Override
        protected void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {

            if (device != null && isConfigured()) {
                //rebooting the GENA subscription
                Service service = subscription.getService();
                SonosPlayerSubscriptionCallback callback = new SonosPlayerSubscriptionCallback(service, interval);
                upnpService.getControlPoint().execute(callback);

            }
        }
    }

    public void setService(UpnpService service) {
        if (upnpService == null) {
            upnpService = service;
        }
        if (upnpService != null && device != null) {
            isConfigured = true;
            enableGENASubscriptions();
        }

    }

    public String getModel() {
        if (device != null) {
            return device.getDetails().getModelDetails().getModelNumber();
        } else {
            return "Unknown";
        }
    }

    /**
     * @return the udn
     */
    public UDN getUdn() {
        return udn;
    }

    /**
     * @param udn the udn to set
     */
    public void setUdn(UDN udn) {
        this.udn = udn;
    }

    public String getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Sonos [udn=" + udn + ", device=" + device + "]";
    }

    public boolean play() {
        if (isConfigured()) {
            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("Play");
            ActionInvocation invocation = new ActionInvocation(action);

            invocation.setInput("Speed", "1");

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public boolean playRadio(String station) {

        if (isConfigured()) {

            List<SonosEntry> stations = getFavoriteRadios();

            SonosEntry theEntry = null;
            // 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) {

                SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
                coordinator.setCurrentURI(theEntry);
                coordinator.play();

                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }

    }

    /**
     * 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 boolean playFavorite(String station) {

        if (isConfigured()) {

            List<SonosEntry> stations = getFavorites();

            SonosEntry theEntry = null;
            // search for the appropriate favorite 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) {
                SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);

                /**
                 * 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:" + udn.getIdentifierString() + "#0", "");
                    if (stateMap != null && isConfigured()) {
                        StateVariableValue firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued");
                        if (firstTrackNumberEnqueued != null) {
                            coordinator.seek("TRACK_NR", firstTrackNumberEnqueued.getValue().toString());
                        }
                    }
                } else {
                    coordinator.setCurrentURI(theEntry);
                }

                coordinator.play();

                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }

    }

    public boolean playPlayList(String playlist) {

        if (isConfigured()) {

            List<SonosEntry> playlists = getPlayLists();

            SonosEntry theEntry = null;

            // 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) {

                SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
                //coordinator.setCurrentURI(theEntry);
                coordinator.addURIToQueue(theEntry);

                if (stateMap != null && isConfigured()) {
                    StateVariableValue firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued");
                    if (firstTrackNumberEnqueued != null) {
                        coordinator.seek("TRACK_NR", firstTrackNumberEnqueued.getValue().toString());
                    }
                }

                coordinator.play();

                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }

    }

    public boolean stop() {
        if (isConfigured()) {
            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("Stop");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public boolean pause() {
        if (isConfigured()) {
            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("Pause");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public boolean next() {
        if (isConfigured()) {
            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("Next");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public boolean previous() {
        if (isConfigured()) {
            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("Previous");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public String getZoneName() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("ZoneName");
            if (value != null) {
                return value.getValue().toString();
            }
        }
        return null;

    }

    public String getZoneGroupID() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("LocalGroupUUID");
            if (value != null) {
                return value.getValue().toString();
            }
        }
        return null;

    }

    public boolean isGroupCoordinator() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("GroupCoordinatorIsLocal");
            if (value != null) {
                return (Boolean) value.getValue();
            }
        }

        return false;

    }

    public SonosZonePlayer getCoordinator() {
        return sonosBinding.getCoordinatorForZonePlayer(this);
    }

    public boolean isCoordinator() {
        return this.equals(getCoordinator());
    }

    public boolean addMember(SonosZonePlayer newMember) {
        if (newMember != null && isConfigured()) {

            SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon:" + udn.getIdentifierString());
            return newMember.setCurrentURI(entry);

        } else {
            return false;
        }
    }

    public boolean removeMember(SonosZonePlayer oldMember) {
        if (oldMember != null && isConfigured()) {

            oldMember.becomeStandAlonePlayer();
            SonosEntry entry = new SonosEntry("", "", "", "", "", "", "",
                    "x-rincon-queue:" + oldMember.getUdn().getIdentifierString() + "#0");
            return oldMember.setCurrentURI(entry);

        } else {
            return false;
        }
    }

    public boolean becomeStandAlonePlayer() {

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("BecomeCoordinatorOfStandaloneGroup");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }

    }

    public boolean setMute(String string) {
        if (string != null && isConfigured()) {

            Service service = device.findService(new UDAServiceId("RenderingControl"));
            Action action = service.getAction("SetMute");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                invocation.setInput("Channel", "Master");

                if (string.equals("ON") || string.equals("OPEN") || string.equals("UP")) {
                    invocation.setInput("DesiredMute", "True");
                } else

                if (string.equals("OFF") || string.equals("CLOSED") || string.equals("DOWN")) {
                    invocation.setInput("DesiredMute", "False");
                } else {
                    return false;
                }
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }
            executeActionInvocation(invocation);

            return true;

        } else {
            return false;
        }
    }

    public String getMute() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("MuteMaster");
            if (value != null) {
                return value.getValue().toString();
            }
        }

        return null;

    }

    public boolean setVolume(String value) {
        if (value != null && isConfigured()) {

            Service service = device.findService(new UDAServiceId("RenderingControl"));
            Action action = service.getAction("SetVolume");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                String newValue = value;
                if (value.equals("INCREASE")) {
                    int i = Integer.valueOf(this.getVolume());
                    newValue = String.valueOf(Math.min(100, i + 1));
                } else if (value.equals("DECREASE")) {
                    int i = Integer.valueOf(this.getVolume());
                    newValue = String.valueOf(Math.max(0, i - 1));
                } else if (value.equals("ON")) {
                    newValue = "100";
                } else if (value.equals("OFF")) {
                    newValue = "0";
                } else {
                    newValue = value;
                }
                invocation.setInput("Channel", "Master");
                invocation.setInput("DesiredVolume", newValue);
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;

        } else {
            return false;
        }
    }

    public String getVolume() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("VolumeMaster");
            if (value != null) {
                return value.getValue().toString();
            }
        }

        return null;

    }

    public boolean updateTime() {

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("AlarmClock"));
            Action action = service.getAction("GetTimeNow");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }

    }

    public String getTime() {
        if (isConfigured()) {
            updateTime();
            if (stateMap != null) {
                StateVariableValue value = stateMap.get("CurrentLocalTime");
                if (value != null) {
                    return value.getValue().toString();
                }

            }
        }
        return null;

    }

    protected void executeActionInvocation(ActionInvocation invocation) {
        if (invocation != null) {
            new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();

            ActionException anException = invocation.getFailure();
            if (anException != null && anException.getMessage() != null) {
                logger.warn(anException.getMessage());
            }

            Map<String, ActionArgumentValue> result = invocation.getOutputMap();
            Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
            if (result != null) {

                // only process the variables that have changed value
                for (String variable : result.keySet()) {
                    ActionArgumentValue newArgument = result.get(variable);

                    StateVariable newVariable = new StateVariable(variable,
                            new StateVariableTypeDetails(newArgument.getDatatype()));
                    StateVariableValue newValue = new StateVariableValue(newVariable, newArgument.getValue());

                    if (isUpdatedValue(variable, newValue)) {
                        mapToProcess.put(variable, newValue);
                    }
                }

                stateMap.putAll(mapToProcess);
                sonosBinding.processVariableMap(device, mapToProcess);
            }
        }
    }

    public boolean updateRunningAlarmProperties() {

        if (stateMap != null && isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("GetRunningAlarmProperties");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            // for this property we would like to "compile" a more friendly variable.
            // this newly created "variable" is also store in the stateMap
            StateVariableValue alarmID = stateMap.get("AlarmID");
            StateVariableValue groupID = stateMap.get("GroupID");
            StateVariableValue loggedStartTime = stateMap.get("LoggedStartTime");
            String newStringValue = null;
            if (alarmID != null && loggedStartTime != null) {
                newStringValue = alarmID.getValue() + " - " + loggedStartTime.getValue();
            } else {
                newStringValue = "No running alarm";
            }

            StateVariable newVariable = new StateVariable("RunningAlarmProperties",
                    new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
            StateVariableValue newValue = new StateVariableValue(newVariable, newStringValue);

            processStateVariableValue(newVariable.getName(), newValue);

            return true;
        } else {
            return false;
        }
    }

    public String getRunningAlarmProperties() {
        if (isConfigured()) {
            updateRunningAlarmProperties();
            if (stateMap != null) {
                StateVariableValue value = stateMap.get("RunningAlarmProperties");
                if (value != null) {
                    return value.getValue().toString();
                }
            }
        }

        return null;

    }

    public boolean updateZoneInfo() {
        if (stateMap != null && isConfigured()) {
            Service service = device.findService(new UDAServiceId("DeviceProperties"));
            Action action = service.getAction("GetZoneInfo");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            Service anotherservice = device.findService(new UDAServiceId("DeviceProperties"));
            Action anotheraction = service.getAction("GetZoneAttributes");
            ActionInvocation anotherinvocation = new ActionInvocation(anotheraction);

            executeActionInvocation(anotherinvocation);

            //          anotherservice = device.findService(new UDAServiceId("ZoneGroupTopology"));
            //          anotheraction = service.getAction("GetZoneGroupState");
            //          anotherinvocation = new ActionInvocation(anotheraction);

            //         executeActionInvocation(anotherinvocation);

            return true;
        } else {
            return false;
        }
    }

    public String getMACAddress() {
        if (isConfigured()) {
            updateZoneInfo();
            if (stateMap != null) {
                StateVariableValue value = stateMap.get("MACAddress");
                if (value != null) {
                    return value.getValue().toString();
                }
            }
        }
        return null;

    }

    public boolean setLed(String string) {

        if (string != null && isConfigured()) {

            Service service = device.findService(new UDAServiceId("DeviceProperties"));
            Action action = service.getAction("SetLEDState");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                if (string.equals("ON") || string.equals("OPEN") || string.equals("UP")) {
                    invocation.setInput("DesiredLEDState", "On");
                } else

                if (string.equals("OFF") || string.equals("CLOSED") || string.equals("DOWN")) {
                    invocation.setInput("DesiredLEDState", "Off");
                } else {
                    return false;
                }
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }
            executeActionInvocation(invocation);

            return true;

        } else {
            return false;
        }
    }

    public boolean updateLed() {

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("DeviceProperties"));
            Action action = service.getAction("GetLEDState");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public boolean getLed() {

        if (isConfigured()) {

            updateLed();
            if (stateMap != null) {
                StateVariableValue variable = stateMap.get("CurrentLEDState");
                if (variable != null) {
                    return variable.getValue().equals("On") ? true : false;
                }
            }
        }

        return false;
    }

    public String getCurrentZoneName() {

        if (isConfigured()) {

            updateCurrentZoneName();
            if (stateMap != null) {
                StateVariableValue variable = stateMap.get("CurrentZoneName");
                if (variable != null) {
                    return variable.getValue().toString();
                }
            }
        }

        return null;
    }

    public boolean updateCurrentZoneName() {

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("DeviceProperties"));
            Action action = service.getAction("GetZoneAttributes");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }

    }

    public boolean updatePosition() {

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("GetPositionInfo");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }
    }

    public String getPosition() {
        if (stateMap != null && isConfigured()) {

            updatePosition();
            if (stateMap != null) {
                StateVariableValue variable = stateMap.get("RelTime");
                if (variable != null) {
                    return variable.getValue().toString();
                }
            }
        }
        return null;
    }

    public boolean setPosition(String relTime) {
        return seek("REL_TIME", relTime);
    }

    public boolean setPositionTrack(long tracknr) {
        return seek("TRACK_NR", Long.toString(tracknr));
    }

    protected boolean seek(String unit, String target) {
        if (isConfigured() && unit != null && target != null) {
            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("Seek");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                invocation.setInput("InstanceID", "0");
                invocation.setInput("Unit", unit);
                invocation.setInput("Target", target);
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;
        }

        return false;
    }

    public Boolean isLineInConnected() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue statusLineIn = stateMap.get("LineInConnected");
            if (statusLineIn != null) {
                return (Boolean) statusLineIn.getValue();
            }
        }
        return null;

    }

    public Boolean isAlarmRunning() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue status = stateMap.get("AlarmRunning");
            if (status != null) {
                return status.getValue().equals("1") ? true : false;
            }
        }
        return null;

    }

    public String getTransportState() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue status = stateMap.get("TransportState");
            if (status != null) {
                return status.getValue().toString();
            }
        }

        return null;

    }

    public boolean addURIToQueue(String URI, String meta, int desiredFirstTrack, boolean enqueueAsNext) {

        if (isConfigured() && URI != null && meta != null) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("AddURIToQueue");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                invocation.setInput("InstanceID", "0");
                invocation.setInput("EnqueuedURI", URI);
                invocation.setInput("EnqueuedURIMetaData", meta);
                invocation.setInput("DesiredFirstTrackNumberEnqueued",
                        new UnsignedIntegerFourBytes(desiredFirstTrack));
                invocation.setInput("EnqueueAsNext", enqueueAsNext);

            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;
        }

        return false;
    }

    public String getCurrentURI() {
        updateMediaInfo();
        if (stateMap != null && isConfigured()) {
            StateVariableValue status = stateMap.get("CurrentURI");
            if (status != null) {
                return status.getValue().toString();
            }
        }
        return null;

    }

    public long getCurrenTrackNr() {

        if (stateMap != null && isConfigured()) {

            updatePosition();
            if (stateMap != null) {
                StateVariableValue variable = stateMap.get("Track");
                if (variable != null) {
                    return ((UnsignedIntegerFourBytes) variable.getValue()).getValue();
                }
            }
        }
        return (long) -1;
    }

    public boolean updateMediaInfo() {

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("GetMediaInfo");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                invocation.setInput("InstanceID", "0");
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;
        }

        return false;

    }

    public boolean addURIToQueue(SonosEntry newEntry) {
        return addURIToQueue(newEntry.getRes(), SonosXMLParser.compileMetadataString(newEntry), 1, true);
    }

    public boolean setCurrentURI(String URI, String URIMetaData) {
        if (URI != null && URIMetaData != null && isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("SetAVTransportURI");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                invocation.setInput("InstanceID", "0");
                invocation.setInput("CurrentURI", URI);
                invocation.setInput("CurrentURIMetaData", URIMetaData);
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;

        } else {
            return false;
        }
    }

    public boolean setCurrentURI(SonosEntry newEntry) {
        return setCurrentURI(newEntry.getRes(), SonosXMLParser.compileMetadataString(newEntry));
    }

    public boolean updateCurrentURIFormatted() {

        if (stateMap != null && isConfigured()) {

            String currentURI = null;
            SonosMetaData currentURIMetaData = null;
            SonosMetaData currentTrack = null;

            if (!isGroupCoordinator()) {
                currentURI = getCoordinator().getCurrentURI();
                currentURIMetaData = getCoordinator().getCurrentURIMetadata();
                currentTrack = getCoordinator().getTrackMetadata();

            } else {
                currentURI = getCurrentURI();
                currentURIMetaData = getCurrentURIMetadata();
                currentTrack = getTrackMetadata();
            }

            if (currentURI != null) {

                String resultString = null;
                String artist = null;
                String album = null;
                String title = null;

                if (currentURI.contains("x-sonosapi-stream")) {
                    //TODO: Get partner ID for openhab.org

                    String stationID = StringUtils.substringBetween(currentURI, ":s", "?sid");

                    StateVariable newVariable = new StateVariable("StationID",
                            new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
                    StateVariableValue newValue = new StateVariableValue(newVariable, stationID);

                    if (this.isUpdatedValue("StationID", newValue) || lastOPMLQuery == null
                            || lastOPMLQuery.plusMinutes(1).isBeforeNow()) {

                        processStateVariableValue(newVariable.getName(), newValue);

                        String url = "http://opml.radiotime.com/Describe.ashx?c=nowplaying&id=" + stationID
                                + "&partnerId=IAeIhU42&serial=" + getMACAddress();

                        String response = HttpUtil.executeUrl("GET", url, SO_TIMEOUT);
                        //logger.debug("RadioTime Response: {}",response);

                        lastOPMLQuery = DateTime.now();

                        List<String> fields = null;
                        try {
                            fields = SonosXMLParser.getRadioTimeFromXML(response);
                        } catch (SAXException e) {
                            logger.error("Could not parse RadioTime from String {}", response);
                        }

                        resultString = new String();

                        if (fields != null && fields.size() > 1) {

                            artist = fields.get(0);
                            title = fields.get(1);

                            Iterator<String> listIterator = fields.listIterator();
                            while (listIterator.hasNext()) {
                                String field = listIterator.next();
                                resultString = resultString + field;
                                if (listIterator.hasNext()) {
                                    resultString = resultString + " - ";
                                }
                            }
                        }
                    } else {
                        resultString = stateMap.get("CurrentURIFormatted").getValue().toString();
                        title = stateMap.get("CurrentTitle").getValue().toString();
                        artist = stateMap.get("CurrentArtist").getValue().toString();
                    }

                } else {
                    if (currentTrack != null) {
                        if (currentTrack.getResource().contains("x-rincon-stream")) {
                            title = currentTrack.getTitle();
                            album = " ";
                            artist = " ";
                            resultString = title;
                        } else if (!currentTrack.getResource().contains("x-sonosapi-stream")) {
                            if (currentTrack.getAlbumArtist().equals("")) {
                                resultString = currentTrack.getCreator() + " - " + currentTrack.getAlbum() + " - "
                                        + currentTrack.getTitle();
                                artist = currentTrack.getCreator();
                            } else {
                                resultString = currentTrack.getAlbumArtist() + " - " + currentTrack.getAlbum()
                                        + " - " + currentTrack.getTitle();
                                artist = currentTrack.getAlbumArtist();
                            }

                            album = currentTrack.getAlbum();
                            title = currentTrack.getTitle();

                            if (album.equals("")) {
                                album = " ";
                            }

                            if (artist.equals("")) {
                                artist = " ";
                            }
                        }
                    } else {
                        title = " ";
                        album = " ";
                        artist = " ";
                        resultString = " ";
                    }
                }

                StateVariable newVariable = new StateVariable("CurrentURIFormatted",
                        new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
                StateVariableValue newValue = new StateVariableValue(newVariable, resultString);
                processStateVariableValue(newVariable.getName(), newValue);

                // update individual variables
                newVariable = new StateVariable("CurrentArtist",
                        new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));

                if (artist != null) {
                    newValue = new StateVariableValue(newVariable, artist);
                } else {
                    newValue = new StateVariableValue(newVariable, " ");
                }
                processStateVariableValue(newVariable.getName(), newValue);

                newVariable = new StateVariable("CurrentTitle",
                        new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
                if (title != null) {
                    newValue = new StateVariableValue(newVariable, title);
                } else {
                    newValue = new StateVariableValue(newVariable, " ");
                }
                processStateVariableValue(newVariable.getName(), newValue);

                newVariable = new StateVariable("CurrentAlbum",
                        new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
                if (album != null) {
                    newValue = new StateVariableValue(newVariable, album);
                } else {
                    newValue = new StateVariableValue(newVariable, " ");
                }
                processStateVariableValue(newVariable.getName(), newValue);

                return true;

            }
        }

        return false;
    }

    public String getCurrentURIFormatted() {
        updateCurrentURIFormatted();
        if (stateMap != null && isConfigured()) {
            StateVariableValue status = stateMap.get("CurrentURIFormatted");
            if (status != null) {
                return status.getValue().toString();
            }
        }

        return null;

    }

    public String getCurrentURIMetadataAsString() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("CurrentTrackMetaData");
            if (value != null) {
                return value.getValue().toString();
            }
        }
        return null;

    }

    public SonosMetaData getCurrentURIMetadata() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("CurrentURIMetaData");
            SonosMetaData currentTrack = null;
            if (value != null) {
                try {
                    if (((String) value.getValue()).length() != 0) {
                        currentTrack = SonosXMLParser.getMetaDataFromXML((String) value.getValue());
                    }
                } catch (SAXException e) {
                    logger.error("Could not parse MetaData from String {}", value.getValue().toString());
                }
                return currentTrack;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    public SonosMetaData getTrackMetadata() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("CurrentTrackMetaData");
            SonosMetaData currentTrack = null;
            if (value != null) {
                try {
                    if (((String) value.getValue()).length() != 0) {
                        currentTrack = SonosXMLParser.getMetaDataFromXML((String) value.getValue());
                    }
                } catch (SAXException e) {
                    logger.error("Could not parse MetaData from String {}", value.getValue().toString());
                }
                return currentTrack;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    public SonosMetaData getEnqueuedTransportURIMetaData() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue value = stateMap.get("EnqueuedTransportURIMetaData");
            SonosMetaData currentTrack = null;
            if (value != null) {
                try {
                    if (((String) value.getValue()).length() != 0) {
                        currentTrack = SonosXMLParser.getMetaDataFromXML((String) value.getValue());
                    }
                } catch (SAXException e) {
                    logger.error("Could not parse MetaData from String {}", value.getValue().toString());
                }
                return currentTrack;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    public String getCurrentVolume() {
        if (stateMap != null && isConfigured()) {
            StateVariableValue status = stateMap.get("VolumeMaster");
            return status.getValue().toString();
        } else {
            return null;
        }
    }

    protected List<SonosEntry> getEntries(String type, String filter) {

        List<SonosEntry> resultList = null;

        if (isConfigured()) {

            long startAt = 0;

            Service service = device.findService(new UDAServiceId("ContentDirectory"));
            Action action = service.getAction("Browse");
            ActionInvocation invocation = new ActionInvocation(action);
            try {
                invocation.setInput("ObjectID", type);
                invocation.setInput("BrowseFlag", "BrowseDirectChildren");
                invocation.setInput("Filter", filter);
                invocation.setInput("StartingIndex", new UnsignedIntegerFourBytes(startAt));
                invocation.setInput("RequestedCount", new UnsignedIntegerFourBytes(200));
                invocation.setInput("SortCriteria", "");
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }
            // Execute this action synchronously 
            new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();

            Long totalMatches = ((UnsignedIntegerFourBytes) invocation.getOutput("TotalMatches").getValue())
                    .getValue();
            Long initialNumberReturned = ((UnsignedIntegerFourBytes) invocation.getOutput("NumberReturned")
                    .getValue()).getValue();
            String initialResult = (String) invocation.getOutput("Result").getValue();

            try {
                resultList = SonosXMLParser.getEntriesFromString(initialResult);
            } catch (SAXException e) {
                logger.error("Could not parse Entries from String {}", initialResult);
            }

            startAt = startAt + initialNumberReturned;

            while (startAt < totalMatches) {
                invocation = new ActionInvocation(action);
                try {
                    invocation.setInput("ObjectID", type);
                    invocation.setInput("BrowseFlag", "BrowseDirectChildren");
                    invocation.setInput("Filter", filter);
                    invocation.setInput("StartingIndex", new UnsignedIntegerFourBytes(startAt));
                    invocation.setInput("RequestedCount", new UnsignedIntegerFourBytes(200));
                    invocation.setInput("SortCriteria", "");
                } catch (InvalidValueException ex) {
                    logger.error("Action Invalid Value Exception {}", ex.getMessage());
                } catch (NumberFormatException ex) {
                    logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
                }
                // Execute this action synchronously 
                new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();
                String result = (String) invocation.getOutput("Result").getValue();
                int numberReturned = (Integer) invocation.getOutput("NumberReturned").getValue();

                try {
                    resultList.addAll(SonosXMLParser.getEntriesFromString(result));
                } catch (SAXException e) {
                    logger.error("Could not parse Entries from String {}", result);
                }
                startAt = startAt + numberReturned;
            }
        }

        return resultList;

    }

    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");
    }

    public List<SonosAlarm> getCurrentAlarmList() {

        List<SonosAlarm> sonosAlarms = null;

        if (isConfigured()) {

            Service service = device.findService(new UDAServiceId("AlarmClock"));
            Action action = service.getAction("ListAlarms");
            ActionInvocation invocation = new ActionInvocation(action);

            executeActionInvocation(invocation);

            try {
                sonosAlarms = SonosXMLParser
                        .getAlarmsFromStringResult(invocation.getOutput("CurrentAlarmList").toString());
            } catch (SAXException e) {
                logger.error("Could not parse Alarms from String {}",
                        invocation.getOutput("CurrentAlarmList").toString());
            }
        }

        return sonosAlarms;
    }

    public boolean updateAlarm(SonosAlarm alarm) {
        if (alarm != null && isConfigured()) {
            Service service = device.findService(new UDAServiceId("AlarmClock"));
            Action action = service.getAction("ListAlarms");
            ActionInvocation invocation = new ActionInvocation(action);

            DateTimeFormatter formatter = DateTimeFormat.forPattern("HH:mm:ss");

            PeriodFormatter pFormatter = new PeriodFormatterBuilder().printZeroAlways().appendHours()
                    .appendSeparator(":").appendMinutes().appendSeparator(":").appendSeconds().toFormatter();

            try {
                invocation.setInput("ID", Integer.toString(alarm.getID()));
                invocation.setInput("StartLocalTime", formatter.print(alarm.getStartTime()));
                invocation.setInput("Duration", pFormatter.print(alarm.getDuration()));
                invocation.setInput("Recurrence", alarm.getRecurrence());
                invocation.setInput("RoomUUID", alarm.getRoomUUID());
                invocation.setInput("ProgramURI", alarm.getProgramURI());
                invocation.setInput("ProgramMetaData", alarm.getProgramMetaData());
                invocation.setInput("PlayMode", alarm.getPlayMode());
                invocation.setInput("Volume", Integer.toString(alarm.getVolume()));
                if (alarm.getIncludeLinkedZones()) {
                    invocation.setInput("IncludeLinkedZones", "1");
                } else {
                    invocation.setInput("IncludeLinkedZones", "0");
                }

                if (alarm.getEnabled()) {
                    invocation.setInput("Enabled", "1");
                } else {
                    invocation.setInput("Enabled", "0");
                }
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;
        } else {
            return false;
        }

    }

    public boolean setAlarm(String alarmSwitch) {
        if (alarmSwitch.equals("ON") || alarmSwitch.equals("OPEN") || alarmSwitch.equals("UP")) {
            return setAlarm(true);
        } else

        if (alarmSwitch.equals("OFF") || alarmSwitch.equals("CLOSED") || alarmSwitch.equals("DOWN")) {
            return setAlarm(false);
        } else {
            return false;
        }
    }

    public boolean setAlarm(boolean alarmSwitch) {

        List<SonosAlarm> sonosAlarms = getCurrentAlarmList();

        if (isConfigured()) {

            // find the nearest alarm - take the current time from the Sonos System, not the system where openhab is running

            String currentLocalTime = getTime();
            DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

            DateTime currentDateTime = fmt.parseDateTime(currentLocalTime);

            Duration shortestDuration = Period.days(10).toStandardDuration();
            SonosAlarm firstAlarm = null;

            for (SonosAlarm anAlarm : sonosAlarms) {
                Duration duration = new Duration(currentDateTime, anAlarm.getStartTime());
                if (anAlarm.getStartTime().isBefore(currentDateTime.plus(shortestDuration))
                        && anAlarm.getRoomUUID().equals(udn.getIdentifierString())) {
                    shortestDuration = duration;
                    firstAlarm = anAlarm;
                }
            }

            // Set the Alarm
            if (firstAlarm != null) {

                if (alarmSwitch) {
                    firstAlarm.setEnabled(true);
                } else {
                    firstAlarm.setEnabled(false);
                }

                return updateAlarm(firstAlarm);

            } else {
                return false;
            }
        } else {
            return false;
        }

    }

    public boolean snoozeAlarm(int minutes) {
        if (isAlarmRunning() && isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("SnoozeAlarm");
            ActionInvocation invocation = new ActionInvocation(action);

            Period snoozePeriod = Period.minutes(minutes);
            PeriodFormatter pFormatter = new PeriodFormatterBuilder().printZeroAlways().appendHours()
                    .appendSeparator(":").appendMinutes().appendSeparator(":").appendSeconds().toFormatter();

            try {
                invocation.setInput("Duration", pFormatter.print(snoozePeriod));
            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }

            executeActionInvocation(invocation);

            return true;

        } else {
            logger.warn("There is no alarm running on {} ", this);
            return false;
        }
    }

    public boolean publicAddress() {

        //check if sourcePlayer has a line-in connected
        if (isLineInConnected() && isConfigured()) {

            //first remove this player from its own group if any
            becomeStandAlonePlayer();

            List<SonosZoneGroup> currentSonosZoneGroups = new ArrayList<SonosZoneGroup>(
                    sonosBinding.getSonosZoneGroups().size());
            for (SonosZoneGroup grp : sonosBinding.getSonosZoneGroups()) {
                currentSonosZoneGroups.add((SonosZoneGroup) grp.clone());
            }

            //add all other players to this new group
            for (SonosZoneGroup group : currentSonosZoneGroups) {
                for (String player : group.getMembers()) {
                    SonosZonePlayer somePlayer = sonosBinding.getPlayerForID(player);
                    if (somePlayer != this) {
                        somePlayer.becomeStandAlonePlayer();
                        somePlayer.stop();
                        addMember(somePlayer);
                    }
                }
            }

            //set the URI of the group to the line-in
            //TODO : check if this needs to be set on the group coordinator or can be done on any member
            SonosZonePlayer coordinator = getCoordinator();
            SonosEntry entry = new SonosEntry("", "", "", "", "", "", "",
                    "x-rincon-stream:" + udn.getIdentifierString());
            coordinator.setCurrentURI(entry);
            coordinator.play();

            return true;

        } else {
            logger.warn("Line-in of {} is not connected", this);
            return false;
        }

    }

    public boolean saveQueue(String name, String queueID) {

        if (name != null && queueID != null && isConfigured()) {

            Service service = device.findService(new UDAServiceId("AVTransport"));
            Action action = service.getAction("SaveQueue");
            ActionInvocation invocation = new ActionInvocation(action);

            try {
                invocation.setInput("Title", name);
                invocation.setInput("ObjectID", queueID);

            } catch (InvalidValueException ex) {
                logger.error("Action Invalid Value Exception {}", ex.getMessage());
            } catch (NumberFormatException ex) {
                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
            }
            executeActionInvocation(invocation);

            return true;

        } else {
            return false;
        }

    }

    /**
     *    Play a given url to music in one of the music libraries.
     * 
     *    @param url in the format of //host/folder/filename.mp3
     *    @return true if the url started to play
     */
    public boolean playURI(String url) {
        if (!isConfigured) {
            return false;
        }

        SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);

        // 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
        if (!url.startsWith("x-")) {
            // 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:" + udn.getIdentifierString() + "#0", "");

        // take the system off mute
        coordinator.setMute("OFF");

        // start jammin'
        return coordinator.play();

    }

    /**
     *    Play music from the line-in of the Player given its name or UDN
     * 
     *    @param udn
     *    @return true if the sonos device started to play
     */
    public boolean playLineIn(String remotePlayerName) {
        if (!isConfigured) {
            return false;
        }

        SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
        SonosZonePlayer remotePlayer = sonosBinding.getPlayerForID(remotePlayerName);

        // stop whatever is currently playing
        coordinator.stop();

        // set the
        coordinator.setCurrentURI("x-rincon-stream:" + remotePlayer.getUdn().getIdentifierString(), "");

        // take the system off mute
        coordinator.setMute("OFF");

        // start jammin'
        return coordinator.play();

    }

    /**
     *   Clear all scheduled music from the current queue.
     * 
     * @return true if no error occurred.
     */
    public boolean removeAllTracksFromQueue() {

        if (!isConfigured) {
            return false;
        }

        Service service = device.findService(new UDAServiceId("AVTransport"));
        Action action = service.getAction("RemoveAllTracksFromQueue");
        ActionInvocation invocation = new ActionInvocation(action);

        try {
            invocation.setInput("InstanceID", "0");

        } catch (InvalidValueException ex) {
            logger.error("Action Invalid Value Exception {}", ex.getMessage());
        } catch (NumberFormatException ex) {
            logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
        }

        executeActionInvocation(invocation);

        return true;

    }

    /**
     *   Save the state (track, position etc) of the Sonos Zone player.
     * 
     * @return true if no error occurred.
     */
    protected boolean saveState() {

        synchronized (this) {

            savedState = sonosBinding.new SonosZonePlayerState();
            String currentURI = getCurrentURI();

            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().getIdentifierString() + "#0");
                    }
                }

                savedState.transportState = getTransportState();
                savedState.volume = getCurrentVolume();
                savedState.relTime = getPosition();
            } else {
                savedState.entry = null;
            }

            return true;
        }
    }

    /**
     *   Restore the state (track, position etc) of the Sonos Zone player.
     * 
     * @return true if no error occurred.
     */
    protected boolean restoreState() {

        synchronized (this) {
            if (savedState != null) {
                // put settings back
                setVolume(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().getIdentifierString() + "#0");
                            setCurrentURI(entry);
                            setPositionTrack(savedState.track);

                        } else {
                            setCurrentURI(savedState.entry);
                            setPosition(savedState.relTime);
                        }

                        if (savedState.transportState.equals("PLAYING")) {
                            play();
                        } else if (savedState.transportState.equals("STOPPED")) {
                            stop();
                        } else if (savedState.transportState.equals("PAUSED_PLAYBACK")) {
                            pause();
                        }
                    }
                }
            }

            return true;
        }
    }

}