/** * Copyright (c) 2010-2016 by the respective copyright holders. * * 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.xbmc.internal; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.openhab.binding.xbmc.XbmcBindingProvider; import org.openhab.binding.xbmc.rpc.XbmcConnector; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.binding.BindingProvider; import org.openhab.core.library.types.OnOffType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This refresh service for the XBMC binding is used to periodically check to * ensure all XBMC web sockets are still open and alive. * * All item updates are received asynchronously via the web socket All item * commands are sent via the web socket * * @author tlan, Ben Jones, Plebs * @since 1.5.0 */ public class XbmcActiveBinding extends AbstractActiveBinding<XbmcBindingProvider> implements ManagedService { private static final Logger logger = LoggerFactory.getLogger(XbmcActiveBinding.class); private Map<String, XbmcConnector> connectors = new HashMap<String, XbmcConnector>(); private Map<String, XbmcHost> nameHostMapper = null; /** * the refresh interval which is used to check for lost connections * (optional, defaults to 60000ms) */ private long refreshInterval = 60000L; @Override public void activate() { logger.debug(getName() + " activate()"); } @Override public void deactivate() { logger.debug(getName() + " deactivate()"); // close any open connections for (XbmcConnector connector : connectors.values()) { if (connector.isConnected()) { connector.close(); } } } /** * {@inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * {@inheritDoc} */ @Override protected String getName() { return "XBMC Refresh Service"; } /** * @{inheritDoc */ @Override public void bindingChanged(BindingProvider provider, String itemName) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; registerWatch(xbmcProvider, itemName); } } /** * {@inheritDoc} */ @Override public void allBindingsChanged(BindingProvider provider) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; for (String itemName : xbmcProvider.getItemNames()) { registerWatch(xbmcProvider, itemName); } } } private void registerAllWatches() { for (BindingProvider provider : providers) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; for (String itemName : xbmcProvider.getItemNames()) { registerWatch(xbmcProvider, itemName); } } } } private void registerWatch(XbmcBindingProvider xbmcProvider, String itemName) { // only interested in watching 'inbound' items if (!xbmcProvider.isInBound(itemName)) { return; } String xbmcInstance = xbmcProvider.getXbmcInstance(itemName); String property = xbmcProvider.getProperty(itemName); XbmcConnector connector = getXbmcConnector(xbmcInstance); if (connector != null) { // add the new 'watch' connector.addItem(itemName, property); // update the player status so any current value is initialised if (connector.isConnected()) { connector.updatePlayerStatus(); } if (property.startsWith("Application")) { connector.requestApplicationUpdate(); } else if (property.equals("System.State")) { connector.updateSystemStatus(); } } } private String getXbmcInstance(String itemName) { for (BindingProvider provider : providers) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; if (xbmcProvider.getItemNames().contains(itemName)) { return xbmcProvider.getXbmcInstance(itemName); } } } return null; } private String getProperty(String itemName) { for (BindingProvider provider : providers) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; if (xbmcProvider.getItemNames().contains(itemName)) { return xbmcProvider.getProperty(itemName); } } } return null; } private boolean isInBound(String itemName) { for (BindingProvider provider : providers) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; if (xbmcProvider.getItemNames().contains(itemName)) { return xbmcProvider.isInBound(itemName); } } } return false; } private boolean isOutBound(String itemName) { for (BindingProvider provider : providers) { if (provider instanceof XbmcBindingProvider) { XbmcBindingProvider xbmcProvider = (XbmcBindingProvider) provider; if (xbmcProvider.getItemNames().contains(itemName)) { return xbmcProvider.isOutBound(itemName); } } } return false; } private XbmcConnector getXbmcConnector(String xbmcInstance) { // sanity check if (xbmcInstance == null) { return null; } // check if the connector for this instance already exists XbmcConnector connector = connectors.get(xbmcInstance); if (connector != null) { return connector; } XbmcHost xbmcHost; if (xbmcInstance.startsWith("#")) { // trim off the '#' identifier String instance = xbmcInstance.substring(1); // check if we have been initialised yet - can't process // named instances until we have read the binding config if (nameHostMapper == null) { logger.trace("Attempting to access the named instance '{}' before the binding config has been loaded", instance); return null; } // check this instance name exists in our config if (!nameHostMapper.containsKey(instance)) { logger.error("Named instance '{}' does not exist in the binding config", instance); return null; } xbmcHost = nameHostMapper.get(instance); } else { xbmcHost = new XbmcHost(); xbmcHost.setHostname(xbmcInstance); } // create a new connection handler logger.debug("Creating new XbmcConnector for '{}' on {}", xbmcInstance, xbmcHost.getHostname()); connector = new XbmcConnector(xbmcHost, eventPublisher); connectors.put(xbmcInstance, connector); // attempt to open the connection straight away try { connector.open(); } catch (Exception e) { logger.error("Connection failed for '{}' on {}", xbmcInstance, xbmcHost.getHostname()); } return connector; } /** * {@inheritDoc} */ @Override protected void execute() { for (Map.Entry<String, XbmcConnector> entry : connectors.entrySet()) { XbmcConnector connector = entry.getValue(); if (connector.isConnected()) { // we are still connected but send a ping to make sure connector.ping(); // refresh all players connector.updatePlayerStatus(true); // refresh screensaverupdate connector.requestScreenSaverStateUpdate(); } else { // broken connection so attempt to reconnect logger.debug("Broken connection found for '{}', attempting to reconnect...", entry.getKey()); try { connector.open(); } catch (Exception e) { logger.debug("Reconnect failed for '{}', will retry in {}s", entry.getKey(), refreshInterval / 1000); } } } } /** * {@inheritDoc} */ @Override protected void internalReceiveCommand(String itemName, Command command) { // only interested in 'outbound' items if (!isOutBound(itemName)) { logger.warn("Received command ({}) for item {} which is configured as 'in-bound', ignoring", command.toString(), itemName); return; } try { // lookup the XBMC instance name and property for this item String xbmcInstance = getXbmcInstance(itemName); String property = getProperty(itemName); XbmcConnector connector = getXbmcConnector(xbmcInstance); if (connector == null) { logger.warn("Received command ({}) for item {} but no XBMC connector found for {}, ignoring", command.toString(), itemName, xbmcInstance); return; } if (!connector.isConnected()) { logger.warn( "Received command ({}) for item {} but the connection to the XBMC instance {} is down, ignoring", command.toString(), itemName, xbmcInstance); return; } // TODO: handle other commands if (property.equals("Player.PlayPause")) { connector.playerPlayPause(); } else if (property.equals("Player.Open")) { connector.playerOpen(command.toString()); } else if (property.equals("Player.Stop")) { connector.playerStop(); } else if (property.equals("Input.ExecuteAction")) { connector.inputExecuteAction(command.toString()); } else if (property.startsWith("Input.ExecuteAction.")) { connector.inputExecuteAction(property.substring(20).toLowerCase()); } else if (property.equals("GUI.ShowNotification")) { connector.showNotification("openHAB", command.toString()); } else if (property.equals("System.Shutdown") && command == OnOffType.OFF) { connector.systemShutdown(); } else if (property.equals("System.Suspend") && command == OnOffType.OFF) { connector.systemSuspend(); } else if (property.equals("System.Hibernate") && command == OnOffType.OFF) { connector.systemHibernate(); } else if (property.equals("System.Reboot") && command == OnOffType.OFF) { connector.systemReboot(); } else if (property.equals("Application.Volume")) { connector.applicationSetVolume(command.toString()); } else if (property.equals("PVR.OpenTV")) { connector.playerOpenPVR(command.toString(), 2); } else if (property.equals("PVR.OpenRadio")) { connector.playerOpenPVR(command.toString(), 1); } else if (property.equals("Refresh")) { execute(); } } catch (Exception e) { logger.error("Error handling command", e); } } /** * {@inheritDoc} */ @Override protected void internalReceiveUpdate(String itemName, State newState) { try { String property = getProperty(itemName); String xbmcInstance = getXbmcInstance(itemName); XbmcConnector connector = getXbmcConnector(xbmcInstance); if (connector == null) { logger.warn("Received update ({}) for item {} but no XBMC connector found for {}, ignoring", newState.toString(), itemName, xbmcInstance); return; } if (!connector.isConnected()) { logger.warn( "Received update ({}) for item {} but the connection to the XBMC instance {} is down, ignoring", newState.toString(), itemName, xbmcInstance); return; } // TODO: handle other updates if (property.equals("GUI.ShowNotification")) { connector.showNotification("openHAB", newState.toString()); } else if (property.equals("Player.Open")) { connector.playerOpen(newState.toString()); } else if (property.equals("Application.SetVolume")) { connector.applicationSetVolume(newState.toString()); } } catch (Exception e) { logger.error("Error handling update", e); } } protected void addBindingProvider(XbmcBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(XbmcBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { logger.debug(getName() + " updated()"); Map<String, XbmcHost> hosts = new HashMap<String, XbmcHost>(); if (config != null) { String refreshIntervalString = (String) config.get("refreshInterval"); if (StringUtils.isNotBlank(refreshIntervalString)) { refreshInterval = Long.parseLong(refreshIntervalString); } Enumeration<String> keys = config.keys(); while (keys.hasMoreElements()) { // Ignore "refreshInterval" key String key = keys.nextElement(); if ("refreshInterval".equals(key)) { continue; } if ("service.pid".equals(key)) { continue; } String[] parts = key.split("\\."); String hostname = parts[0]; XbmcHost host = hosts.get(hostname); if (host == null) { host = new XbmcHost(); } String value = ((String) config.get(key)).trim(); if ("host".equals(parts[1])) { host.setHostname(value); } if ("rsPort".equals(parts[1])) { host.setRsPort(Integer.valueOf(value)); } if ("wsPort".equals(parts[1])) { host.setWsPort(Integer.valueOf(value)); } if ("username".equals(parts[1])) { host.setUsername(value); } if ("password".equals(parts[1])) { host.setPassword(value); } hosts.put(hostname, host); } setProperlyConfigured(true); nameHostMapper = hosts; registerAllWatches(); } } }