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

Java tutorial

Introduction

Here is the source code for org.openhab.binding.sonos.internal.SonosBinding.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 static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.IllegalClassException;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.sonos.SonosBindingProvider;
import org.openhab.binding.sonos.SonosCommandType;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.TypeParser;
import org.openhab.model.item.binding.BindingConfigParseException;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teleal.cling.DefaultUpnpServiceConfiguration;
import org.teleal.cling.UpnpService;
import org.teleal.cling.UpnpServiceImpl;
import org.teleal.cling.controlpoint.SubscriptionCallback;
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.message.header.UDAServiceTypeHeader;
import org.teleal.cling.model.message.header.UDNHeader;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.state.StateVariableValue;
import org.teleal.cling.model.types.UDAServiceId;
import org.teleal.cling.model.types.UDAServiceType;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.registry.Registry;
import org.teleal.cling.registry.RegistryListener;
import org.teleal.cling.transport.impl.apache.StreamClientConfigurationImpl;
import org.teleal.cling.transport.impl.apache.StreamServerConfigurationImpl;
import org.teleal.cling.transport.impl.apache.StreamServerImpl;
import org.teleal.cling.transport.spi.NetworkAddressFactory;
import org.teleal.cling.transport.spi.StreamClient;
import org.teleal.cling.transport.spi.StreamServer;
import org.xml.sax.SAXException;

/**
 * @author Karel Goderis
 * @author Pauli Anttila
 * @since 1.1.0
 * 
 */
public class SonosBinding extends AbstractActiveBinding<SonosBindingProvider> implements ManagedService {

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

    private static final Pattern EXTRACT_SONOS_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.(udn)$");

    static protected UpnpService upnpService;
    static protected SonosBinding self;

    /** the refresh interval which is used to check for changes in the binding configurations */
    private static long refreshInterval = 5000;
    /** polling interval in milliseconds for variables that need to be polled */
    private int pollingPeriod = 1000;
    /** timeout interval used by the Cling GENA subscriptions */
    static protected int interval = 600;

    static protected boolean bindingStarted = false;

    private PlayerCache sonosZonePlayerCache = new PlayerCache();
    private List<SonosZoneGroup> sonosZoneGroups = null;
    private Map<String, SonosZonePlayerState> sonosSavedPlayerState = null;
    private List<SonosZoneGroup> sonosSavedGroupState = null;

    private class PlayerCache extends ArrayList<SonosZonePlayer> {

        private static final long serialVersionUID = 7973128806169191738L;

        public boolean contains(String id) {
            Iterator<SonosZonePlayer> it = this.iterator();
            while (it.hasNext()) {
                SonosZonePlayer aPlayer = it.next();
                if (aPlayer.getUdn().getIdentifierString().equals(id) || aPlayer.getId().equals(id)) {
                    return true;
                }
            }
            return false;
        }

        public SonosZonePlayer getById(String id) {
            Iterator<SonosZonePlayer> it = this.iterator();
            while (it.hasNext()) {
                SonosZonePlayer aPlayer = it.next();
                if (aPlayer.getUdn().getIdentifierString().equals(id) || aPlayer.getId().equals(id)) {
                    return aPlayer;
                }
            }
            return null;
        }

        public SonosZonePlayer getByUDN(String udn) {
            Iterator<SonosZonePlayer> it = this.iterator();
            while (it.hasNext()) {
                SonosZonePlayer aPlayer = it.next();
                if (aPlayer.getUdn().getIdentifierString().equals(udn)) {
                    return aPlayer;
                }
            }
            return null;
        }

        public SonosZonePlayer getByDevice(RemoteDevice device) {
            if (device != null) {
                Iterator<SonosZonePlayer> it = this.iterator();
                while (it.hasNext()) {
                    SonosZonePlayer aPlayer = it.next();
                    if (aPlayer.getDevice() != null && aPlayer.getDevice().equals(device)) {
                        return aPlayer;
                    }
                }
            }
            return null;
        }

    }

    public class SonosUpnpServiceConfiguration extends DefaultUpnpServiceConfiguration {

        @SuppressWarnings("rawtypes")
        @Override
        public StreamClient createStreamClient() {
            return new StreamClientImpl(new StreamClientConfigurationImpl());
        }

        @SuppressWarnings("rawtypes")
        @Override
        public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory) {
            return new StreamServerImpl(
                    new StreamServerConfigurationImpl(networkAddressFactory.getStreamListenPort()));
        }

    }

    RegistryListener listener = new RegistryListener() {

        public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
            logger.debug("Discovery started: " + device.getDisplayString());
        }

        public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex) {
            logger.debug("Discovery failed: " + device.getDisplayString() + " => " + ex);
        }

        @SuppressWarnings("rawtypes")
        public void remoteDeviceAdded(Registry registry, RemoteDevice device) {

            // add only Sonos devices
            if (device.getDetails().getManufacturerDetails().getManufacturer().toUpperCase().contains("SONOS")) {

                UDN udn = device.getIdentity().getUdn();
                boolean existingDevice = false;

                logger.info("Found a Sonos device ({}) with UDN {}",
                        device.getDetails().getModelDetails().getModelNumber(), udn);

                // Check if we already received a configuration for this
                // device through the .cfg
                SonosZonePlayer thePlayer = sonosZonePlayerCache.getByUDN(udn.getIdentifierString());

                if (thePlayer == null) {
                    // Add device to the cached Configs
                    thePlayer = new SonosZonePlayer(udn.getIdentifierString(), self);
                    thePlayer.setUdn(udn);

                    sonosZonePlayerCache.add(thePlayer);
                }

                thePlayer.setDevice(device);
                thePlayer.setService(upnpService);
                thePlayer.updateCurrentZoneName();

                // add GENA service to capture zonegroup information
                Service service = device.findService(new UDAServiceId("ZoneGroupTopology"));
                SonosSubscriptionCallback callback = new SonosSubscriptionCallback(service, interval);
                upnpService.getControlPoint().execute(callback);
            } else {
                logger.debug("A non-Sonos device ({}) is found and will be ignored", device.getDisplayString());
            }
        }

        public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
            logger.trace("Remote device updated: " + device.getDisplayString());
        }

        public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
            logger.trace("Remote device removed: " + device.getDisplayString());
        }

        public void localDeviceAdded(Registry registry, LocalDevice device) {
            logger.trace("Local device added: " + device.getDisplayString());
        }

        public void localDeviceRemoved(Registry registry, LocalDevice device) {
            logger.trace("Local device removed: " + device.getDisplayString());
        }

        public void beforeShutdown(Registry registry) {
            logger.debug("Before shutdown, the registry has devices: " + registry.getDevices().size());
        }

        public void afterShutdown() {
            logger.debug("Shutdown of registry complete!");

        }
    };

    public SonosBinding() {
        self = this;
    }

    /**
     * Find the first matching {@link ChannelBindingProvider} according to
     * <code>itemName</code>
     * 
     * @param itemName
     * 
     * @return the matching binding provider or <code>null</code> if no binding
     *         provider could be found
     */
    protected SonosBindingProvider findFirstMatchingBindingProvider(String itemName) {
        SonosBindingProvider firstMatchingProvider = null;
        for (SonosBindingProvider provider : providers) {
            List<String> sonosIDs = provider.getSonosID(itemName);
            if (sonosIDs != null && sonosIDs.size() > 0) {
                firstMatchingProvider = provider;
                break;
            }
        }
        return firstMatchingProvider;
    }

    public void activate() {
        // Nothing to do here. We start the binding when the first item bindigconfig is processed
    }

    @Override
    protected void internalReceiveCommand(String itemName, Command command) {

        SonosBindingProvider provider = findFirstMatchingBindingProvider(itemName);
        String commandAsString = command.toString();

        if (command != null) {

            List<Command> commands = new ArrayList<Command>();

            if (command instanceof StringType || command instanceof DecimalType) {
                commands = provider.getVariableCommands(itemName);
            } else {
                commands.add(command);
            }

            for (Command someCommand : commands) {

                String sonosID = provider.getSonosID(itemName, someCommand);
                String sonosCommand = provider.getSonosCommand(itemName, someCommand);
                SonosCommandType sonosCommandType = null;

                try {
                    sonosCommandType = SonosCommandType.getCommandType(sonosCommand, Direction.OUT);
                } catch (Exception e) {
                    logger.error("An exception occured while verifying command compatibility ({})", e.getMessage());
                }

                if (sonosID != null) {
                    if (sonosCommandType != null) {
                        logger.debug(
                                "Executing command: item:{}, command:{}, ID:{}, CommandType:{}, commandString:{}",
                                new Object[] { itemName, someCommand, sonosID, sonosCommandType, commandAsString });
                        executeCommand(itemName, someCommand, sonosID, sonosCommandType, commandAsString);
                    } else {
                        logger.error("wrong command type for binding [Item={}, command={}]", itemName,
                                commandAsString);
                    }
                } else {
                    logger.error("{} is an unrecognised command for Item {}", commandAsString, itemName);
                }
            }

        }

    }

    @SuppressWarnings("unchecked")
    private Type createStateForType(Class<? extends State> ctype, String value) throws BindingConfigParseException {

        if (ctype != null && value != null) {

            List<Class<? extends State>> stateTypeList = new ArrayList<Class<? extends State>>();
            stateTypeList.add(ctype);

            String finalValue = value;

            // Note to Kai or Thomas: sonos devices return some "true" "false"
            // values for specific variables. We convert those
            // into ON OFF if the commandTypes allow so. This is a little hack,
            // but IMHO OnOffType should
            // be enhanced, or a TrueFalseType should be developed
            if (ctype.equals(OnOffType.class)) {
                finalValue = StringUtils.upperCase(value);
                if (finalValue.equals("TRUE") || finalValue.equals("1")) {
                    finalValue = "ON";
                } else if (finalValue.equals("FALSE") || finalValue.equals("0")) {
                    finalValue = "OFF";
                }
            }

            State state = TypeParser.parseState(stateTypeList, finalValue);

            return state;
        } else {
            return null;
        }
    }

    private String createStringFromCommand(Command command, String commandAsString) {

        String value = null;

        if (command instanceof StringType || command instanceof DecimalType) {
            value = commandAsString;
        } else {
            value = command.toString();
        }

        return value;

    }

    @SuppressWarnings("rawtypes")
    public void processVariableMap(RemoteDevice device, Map<String, StateVariableValue> values) {

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

            SonosZonePlayer associatedPlayer = sonosZonePlayerCache.getByDevice(device);

            if (associatedPlayer == null) {
                logger.debug("There is no Sonos Player defined matching the device {}", device);
                return;
            }

            for (String stateVariable : values.keySet()) {

                // find all the CommandTypes that are defined for each
                // StateVariable
                List<SonosCommandType> supportedCommands = SonosCommandType.getCommandByVariable(stateVariable);

                StateVariableValue status = values.get(stateVariable);

                for (SonosCommandType sonosCommandType : supportedCommands) {

                    // create a new State based on the type of Sonos Command and
                    // the status value in the map
                    Type newState = null;
                    try {
                        newState = createStateForType((Class<? extends State>) sonosCommandType.getTypeClass(),
                                status.getValue().toString());
                    } catch (BindingConfigParseException e) {
                        logger.error("Error parsing a value {} to a state variable of type {}", status.toString(),
                                sonosCommandType.getTypeClass().toString());
                    }

                    for (SonosBindingProvider provider : providers) {
                        List<String> qualifiedItems = provider.getItemNames(
                                sonosZonePlayerCache.getByDevice(device).getId(),
                                sonosCommandType.getSonosCommand());
                        List<String> qualifiedItemsByUDN = provider.getItemNames(
                                sonosZonePlayerCache.getByDevice(device).getUdn().getIdentifierString(),
                                sonosCommandType.getSonosCommand());

                        for (String item : qualifiedItemsByUDN) {
                            if (!qualifiedItems.contains(item)) {
                                qualifiedItems.add(item);
                            }
                        }

                        for (String anItem : qualifiedItems) {
                            // get the openHAB commands attached to each Item at
                            // this given Provider
                            List<Command> commands = provider.getCommands(anItem,
                                    sonosCommandType.getSonosCommand());

                            if (provider.getAcceptedDataTypes(anItem).contains(sonosCommandType.getTypeClass())) {
                                if (newState != null) {
                                    eventPublisher.postUpdate(anItem, (State) newState);
                                } else {
                                    throw new IllegalClassException("Cannot process update for the command of type "
                                            + sonosCommandType.toString());
                                }
                            } else {
                                logger.warn("Cannot cast {} to an accepted state type for item {}",
                                        sonosCommandType.getTypeClass().toString(), anItem);
                            }
                        }
                    }
                }
            }
        }
    }

    protected class SonosSubscriptionCallback extends SubscriptionCallback {

        @SuppressWarnings("rawtypes")
        public SonosSubscriptionCallback(Service service, Integer interval) {
            super(service, interval);
        }

        @SuppressWarnings("rawtypes")
        @Override
        public void established(GENASubscription sub) {
        }

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

        @SuppressWarnings({ "rawtypes", "unchecked" })
        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>();

            // 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("ZoneGroupState")) {
                    try {
                        setSonosZoneGroups(
                                SonosXMLParser.getZoneGroupFromXML(values.get(stateVariable).toString()));
                    } catch (SAXException e) {
                        logger.error("Could not parse XML variable {}", values.get(stateVariable).toString());
                    }
                }
            }
        }

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

        @SuppressWarnings("rawtypes")
        @Override
        protected void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
            //rebooting the GENA subscription
            Service service = subscription.getService();
            SonosSubscriptionCallback callback = new SonosSubscriptionCallback(service, interval);
            upnpService.getControlPoint().execute(callback);

        }
    }

    public class SonosZonePlayerState {

        public String transportState;
        public String volume;
        public String relTime;
        public SonosEntry entry;
        public long track;
    }

    protected boolean saveAllPlayerState() {

        synchronized (this) {

            sonosSavedGroupState = new ArrayList<SonosZoneGroup>();

            for (SonosZoneGroup group : getSonosZoneGroups()) {
                sonosSavedGroupState.add((SonosZoneGroup) group.clone());
            }

            for (SonosZoneGroup group : sonosSavedGroupState) {
                for (String playerName : group.getMembers()) {
                    SonosZonePlayer player = sonosZonePlayerCache.getById(playerName);
                    player.saveState();
                }
            }

            return true;

        }
    }

    protected boolean restoreAllPlayerState() {

        synchronized (this) {

            if (sonosSavedGroupState != null) {

                // make every player independent
                for (SonosZoneGroup group : sonosSavedGroupState) {
                    for (String playerName : group.getMembers()) {
                        SonosZonePlayer player = sonosZonePlayerCache.getById(playerName);
                        if (player != null) {
                            player.becomeStandAlonePlayer();
                        }
                    }
                }

                // re-create the groups
                for (SonosZoneGroup group : sonosSavedGroupState) {
                    SonosZonePlayer coordinator = sonosZonePlayerCache.getById(group.getCoordinator());
                    if (coordinator != null) {
                        for (String playerName : group.getMembers()) {
                            SonosZonePlayer player = sonosZonePlayerCache.getById(playerName);
                            if (player != null) {
                                coordinator.addMember(player);
                            }
                        }
                    }
                }

                // put settings back
                for (SonosZoneGroup group : sonosSavedGroupState) {
                    for (String playerName : group.getMembers()) {
                        SonosZonePlayer player = sonosZonePlayerCache.getById(playerName);
                        if (player != null) {
                            player.restoreState();
                        }
                    }
                }

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

    private void executeCommand(String itemName, Command command, String sonosID, SonosCommandType sonosCommandType,
            String commandAsString) {

        boolean result = false;

        if (sonosID != null && sonosZonePlayerCache.contains(sonosID)) {
            SonosZonePlayer player = sonosZonePlayerCache.getById(sonosID);
            if (player != null) {
                switch (sonosCommandType) {
                case SETLED:
                    result = player.setLed(createStringFromCommand(command, commandAsString));
                    break;
                case PLAY:
                    result = player.play();
                    break;
                case STOP:
                    result = player.stop();
                    break;
                case PAUSE:
                    result = player.pause();
                    break;
                case NEXT:
                    result = player.next();
                    break;
                case PREVIOUS:
                    result = player.previous();
                    break;
                case SETVOLUME:
                    result = player.setVolume(commandAsString);
                    break;
                case GETVOLUME:
                    break;
                case ADDMEMBER:
                    result = player.addMember(sonosZonePlayerCache.getById(commandAsString));
                    break;
                case REMOVEMEMBER:
                    result = player.removeMember(sonosZonePlayerCache.getById(commandAsString));
                    break;
                case BECOMESTANDALONEGROUP:
                    result = player.becomeStandAlonePlayer();
                    break;
                case SETMUTE:
                    result = player.setMute(commandAsString);
                    break;
                case PA:
                    result = player.publicAddress();
                    break;
                case RADIO:
                    result = player.playRadio(commandAsString);
                    break;
                case FAVORITE:
                    result = player.playFavorite(commandAsString);
                    break;
                case SETALARM:
                    result = player.setAlarm(commandAsString);
                    break;
                case SNOOZE:
                    result = player.snoozeAlarm(Integer.parseInt(commandAsString));
                    break;
                case SAVEALL:
                    result = saveAllPlayerState();
                    break;
                case RESTOREALL:
                    result = restoreAllPlayerState();
                    break;
                case SAVE:
                    result = player.saveState();
                    break;
                case RESTORE:
                    result = player.restoreState();
                    break;
                case PLAYLIST:
                    result = player.playPlayList(commandAsString);
                    break;
                case PLAYURI:
                    result = player.playURI(commandAsString);
                    break;
                case PLAYLINEIN:
                    result = player.playLineIn(commandAsString);
                    break;
                default:
                    break;

                }
                ;

            } else {
                logger.error("UPNP device is not defined for Sonos Player with ID {}", sonosID);
                return;
            }
        }
    }

    @SuppressWarnings("rawtypes")
    public void updated(Dictionary config) throws ConfigurationException {
        if (config != null) {
            Enumeration keys = config.keys();
            while (keys.hasMoreElements()) {

                String key = (String) keys.nextElement();

                // the config-key enumeration contains additional keys that we
                // don't want to process here ...
                if ("service.pid".equals(key)) {
                    continue;
                }

                if ("pollingPeriod".equals(key)) {
                    pollingPeriod = Integer.parseInt((String) config.get(key));
                    logger.debug("Setting polling period to {} ms", pollingPeriod);
                    continue;
                }

                Matcher matcher = EXTRACT_SONOS_CONFIG_PATTERN.matcher(key);
                if (!matcher.matches()) {
                    logger.debug("given sonos-config-key '" + key
                            + "' does not follow the expected pattern '<sonosId>.<udn>'");
                    continue;
                }

                matcher.reset();
                matcher.find();

                String sonosID = matcher.group(1);

                SonosZonePlayer sonosConfig = sonosZonePlayerCache.getById(sonosID);
                if (sonosConfig == null) {
                    sonosConfig = new SonosZonePlayer(sonosID, self);
                    sonosZonePlayerCache.add(sonosConfig);
                }

                String configKey = matcher.group(2);
                String value = (String) config.get(key);

                if ("udn".equals(configKey)) {
                    sonosConfig.setUdn(new UDN(value));
                    logger.debug("Add predefined Sonos device with UDN {}", sonosConfig.getUdn());
                } else {
                    throw new ConfigurationException(configKey,
                            "the given configKey '" + configKey + "' is unknown");
                }
            }
        }

        setProperlyConfigured(true);

    }

    @Override
    protected void execute() {
        if (isProperlyConfigured()) {

            if (!bindingStarted) {

                // This will create necessary network resources for UPnP right away
                upnpService = new UpnpServiceImpl(new SonosUpnpServiceConfiguration(), listener);

                try {

                    Iterator<SonosZonePlayer> it = sonosZonePlayerCache.iterator();
                    while (it.hasNext()) {
                        SonosZonePlayer aPlayer = it.next();
                        if (aPlayer.getDevice() == null) {
                            logger.info("Querying the network for a predefined Sonos device with UDN {}",
                                    aPlayer.getUdn());
                            upnpService.getControlPoint().search(new UDNHeader(aPlayer.getUdn()));
                        }
                    }

                    logger.info("Querying the network for any other Sonos device");

                    final UDAServiceType udaType = new UDAServiceType("AVTransport");
                    upnpService.getControlPoint().search(new UDAServiceTypeHeader(udaType));

                } catch (Exception e) {
                    logger.warn("An exception occurred while searching the network for Sonos devices: ",
                            e.getMessage());
                }

                bindingStarted = true;
            }

            Scheduler sched = null;
            try {
                sched = StdSchedulerFactory.getDefaultScheduler();
            } catch (SchedulerException e) {
                logger.error("An exception occurred while getting a reference to the Quartz Scheduler");
            }

            // Cycle through the Items and setup sonos zone players if required
            for (SonosBindingProvider provider : providers) {
                for (String itemName : provider.getItemNames()) {
                    for (String sonosID : ((SonosBindingProvider) provider).getSonosID(itemName)) {
                        if (!sonosZonePlayerCache.contains(sonosID)) {
                            // the device is not yet discovered on the network or not defined in the .cfg

                            //Verify that the sonosID has the format of a valid UDN
                            Pattern SONOS_UDN_PATTERN = Pattern.compile("RINCON_(\\w{17})");
                            Matcher matcher = SONOS_UDN_PATTERN.matcher(sonosID);
                            if (matcher.matches()) {
                                // Add device to the cached Configs
                                SonosZonePlayer thePlayer = new SonosZonePlayer(sonosID, self);
                                thePlayer.setUdn(new UDN(sonosID));

                                sonosZonePlayerCache.add(thePlayer);

                                //Query the network for this device
                                logger.info("Querying the network for a predefined Sonos device with UDN '{}'",
                                        thePlayer.getUdn());
                                upnpService.getControlPoint().search(new UDNHeader(thePlayer.getUdn()));
                            }

                        }
                    }
                }
            }

            // Cycle through the item binding configuration that define polling criteria
            for (SonosCommandType sonosCommandType : SonosCommandType.getPolling()) {
                for (SonosBindingProvider provider : providers) {
                    for (String itemName : provider.getItemNames(sonosCommandType.getSonosCommand())) {
                        for (Command aCommand : ((SonosBindingProvider) provider).getCommands(itemName,
                                sonosCommandType.getSonosCommand())) {

                            // We are dealing with a valid device
                            SonosZonePlayer thePlayer = sonosZonePlayerCache
                                    .getById(provider.getSonosID(itemName, aCommand));

                            if (thePlayer != null) {

                                RemoteDevice theDevice = thePlayer.getDevice();

                                // Only set up a polling job if the device supports the given SonosCommandType
                                // Not all Sonos devices have the same capabilities
                                if (theDevice != null) {
                                    if (theDevice
                                            .findService(new UDAServiceId(sonosCommandType.getService())) != null) {

                                        boolean jobExists = false;

                                        // enumerate each job group
                                        try {
                                            for (String group : sched.getJobGroupNames()) {
                                                // enumerate each job in group
                                                for (JobKey jobKey : sched.getJobKeys(jobGroupEquals(group))) {
                                                    if (jobKey.getName()
                                                            .equals(provider.getSonosID(itemName, aCommand) + "-"
                                                                    + sonosCommandType.getJobClass().toString())) {
                                                        jobExists = true;
                                                        break;
                                                    }
                                                }
                                            }
                                        } catch (SchedulerException e1) {
                                            logger.error(
                                                    "An exception occurred while quering the Quartz Scheduler ({})",
                                                    e1.getMessage());
                                        }

                                        if (!jobExists) {
                                            // set up the Quartz jobs
                                            JobDataMap map = new JobDataMap();
                                            map.put("Player", thePlayer);

                                            JobDetail job = newJob(sonosCommandType.getJobClass())
                                                    .withIdentity(
                                                            provider.getSonosID(itemName, aCommand) + "-"
                                                                    + sonosCommandType.getJobClass().toString(),
                                                            "Sonos-" + provider.toString())
                                                    .usingJobData(map).build();

                                            Trigger trigger = newTrigger()
                                                    .withIdentity(
                                                            provider.getSonosID(itemName, aCommand) + "-"
                                                                    + sonosCommandType.getJobClass().toString(),
                                                            "Sonos-" + provider.toString())
                                                    .startNow().withSchedule(simpleSchedule().repeatForever()
                                                            .withIntervalInMilliseconds(pollingPeriod))
                                                    .build();

                                            try {
                                                sched.scheduleJob(job, trigger);
                                            } catch (SchedulerException e) {
                                                logger.error(
                                                        "An exception occurred while scheduling a Quartz Job ({})",
                                                        e.getMessage());
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public String getCoordinatorForZonePlayer(String playerName) {

        SonosZonePlayer zonePlayer = sonosZonePlayerCache.getById(playerName);

        if (zonePlayer == null || getSonosZoneGroups() == null) {
            return playerName;
        }
        for (SonosZoneGroup zg : getSonosZoneGroups()) {
            if (zg.getMembers().contains(zonePlayer.getUdn().getIdentifierString())) {
                String coordinator = zg.getCoordinator();
                return sonosZonePlayerCache.getByUDN(coordinator).getId();
            }
        }
        return playerName;
    }

    public SonosZonePlayer getPlayerForID(String player) {
        return sonosZonePlayerCache.getById(player);
    }

    public SonosZonePlayer getCoordinatorForZonePlayer(SonosZonePlayer zonePlayer) {

        if (getSonosZoneGroups() == null) {
            return zonePlayer;
        }
        for (SonosZoneGroup zg : getSonosZoneGroups()) {
            if (zg.getMembers().contains(zonePlayer.getUdn().getIdentifierString())) {
                String coordinator = zg.getCoordinator();
                return sonosZonePlayerCache.getByUDN(coordinator);
            }
        }
        return zonePlayer;
    }

    public List<SonosZoneGroup> getSonosZoneGroups() {
        return sonosZoneGroups;
    }

    public void setSonosZoneGroups(List<SonosZoneGroup> sonosZoneGroups) {
        this.sonosZoneGroups = sonosZoneGroups;
    }

    @Override
    protected long getRefreshInterval() {
        return refreshInterval;
    }

    @Override
    protected String getName() {
        return "Sonos Refresh Service";
    }

    public static class LedJob implements Job {

        public void execute(JobExecutionContext context) throws JobExecutionException {

            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            SonosZonePlayer thePlayer = (SonosZonePlayer) dataMap.get("Player");

            thePlayer.getLed();

        }
    }

    public static class RunningAlarmPropertiesJob implements Job {

        public void execute(JobExecutionContext context) throws JobExecutionException {

            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            SonosZonePlayer thePlayer = (SonosZonePlayer) dataMap.get("Player");

            thePlayer.updateRunningAlarmProperties();

        }
    }

    public static class CurrentURIFormattedJob implements Job {

        public void execute(JobExecutionContext context) throws JobExecutionException {

            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            SonosZonePlayer thePlayer = (SonosZonePlayer) dataMap.get("Player");

            thePlayer.updateCurrentURIFormatted();

        }
    }

    public static class ZoneInfoJob implements Job {

        public void execute(JobExecutionContext context) throws JobExecutionException {

            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            SonosZonePlayer thePlayer = (SonosZonePlayer) dataMap.get("Player");

            thePlayer.updateZoneInfo();

        }
    }

    public static class MediaInfoJob implements Job {

        public void execute(JobExecutionContext context) throws JobExecutionException {

            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            SonosZonePlayer thePlayer = (SonosZonePlayer) dataMap.get("Player");

            thePlayer.updateMediaInfo();

        }
    }

}