/**
* Copyright (c) 2010-2014, 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.info("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 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();
}
}
}