diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcActiveBinding.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcActiveBinding.java index a66328a0911..3c4466e18fa 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcActiveBinding.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcActiveBinding.java @@ -13,6 +13,7 @@ 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; @@ -32,10 +33,10 @@ * All item updates are received asynchronously via the web socket All item * commands are sent via the web socket * - * @author tlan, Ben Jones + * @author tlan, Ben Jones, Plebs * @since 1.5.0 */ -public class XbmcActiveBinding extends AbstractActiveBindingimplements ManagedService { +public class XbmcActiveBinding extends AbstractActiveBinding implements ManagedService { private static final Logger logger = LoggerFactory.getLogger(XbmcActiveBinding.class); @@ -46,12 +47,11 @@ public class XbmcActiveBinding extends AbstractActiveBinding config) throws ConfigurationException Map hosts = new HashMap(); if (config != null) { + + String refreshIntervalString = (String) config.get("refreshInterval"); + if (StringUtils.isNotBlank(refreshIntervalString)) { + refreshInterval = Long.parseLong(refreshIntervalString); + } + Enumeration keys = config.keys(); while (keys.hasMoreElements()) { + // Ignore "refreshInterval" key String key = keys.nextElement(); + if ("refreshInterval".equals(key)) { + continue; + } if ("service.pid".equals(key)) { continue; @@ -422,10 +436,10 @@ public void updated(Dictionary config) throws ConfigurationException if ("password".equals(parts[1])) { host.setPassword(value); } - hosts.put(hostname, host); } + setProperlyConfigured(true); nameHostMapper = hosts; registerAllWatches(); } diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/RpcCall.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/RpcCall.java index 45aed70aee7..cfb6d08e850 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/RpcCall.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/RpcCall.java @@ -35,7 +35,7 @@ * * XBMC JSON RPC API: http://wiki.xbmc.org/?title=JSON-RPC_API * - * @author tlan, Ben Jones + * @author tlan, Ben Jones, Plebs * @since 1.5.0 */ public abstract class RpcCall { @@ -131,11 +131,14 @@ private void postRequest(Map request, Runnable completeHandler) try { // we fire this request off asynchronously and let the completeHandler // process any response as necessary (can be null) - ListenableFuture future = client.preparePost(uri).setBody(writeJson(request)) + String resultWrite = writeJson(request); + logger.debug("Write JSON: {}", resultWrite ); // Stefano: logging the JSON request + ListenableFuture future = client.preparePost(uri).setBody(resultWrite) .setHeader("content-type", "application/json").setHeader("accept", "application/json") .execute(new AsyncCompletionHandler() { @Override public Response onCompleted(Response response) throws Exception { + logger.debug("Read JSON: {}", response.getResponseBody()); // Stefano: logging the JSON response Map json = readJson(response.getResponseBody()); // if we get an error then throw an exception to stop the diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/XbmcConnector.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/XbmcConnector.java index 5c75ee3370c..412b814683c 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/XbmcConnector.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/XbmcConnector.java @@ -33,6 +33,8 @@ import org.openhab.binding.xbmc.rpc.calls.PVRGetChannels; import org.openhab.binding.xbmc.rpc.calls.PlayerGetActivePlayers; import org.openhab.binding.xbmc.rpc.calls.PlayerGetItem; +import org.openhab.binding.xbmc.rpc.calls.PlayerGetLabels; +import org.openhab.binding.xbmc.rpc.calls.PlayerGetProperties; import org.openhab.binding.xbmc.rpc.calls.PlayerOpen; import org.openhab.binding.xbmc.rpc.calls.PlayerPlayPause; import org.openhab.binding.xbmc.rpc.calls.PlayerStop; @@ -61,7 +63,7 @@ /** * Manages the web socket connection for a single XBMC instance. * - * @author tlan, Ben Jones, Ard van der Leeuw + * @author tlan, Ben Jones, Ard van der Leeuw, Plebs * @since 1.5.0 */ public class XbmcConnector { @@ -125,7 +127,7 @@ public XbmcConnector(XbmcHost xbmc, EventPublisher eventPublisher) { /*** * Check if the connection to the XBMC instance is active - * + * * @return true if an active connection to the XBMC instance exists, false otherwise */ public boolean isConnected() { @@ -139,7 +141,7 @@ public boolean isConnected() { /** * Attempts to create a connection to the XBMC host and begin listening * for updates over the async http web socket - * + * * @throws ExecutionException * @throws InterruptedException * @throws IOException @@ -276,15 +278,16 @@ public void run() { /** * Create a mapping between an item and an xbmc property - * + * * @param itemName * The name of the item which should receive updates * @param property * The property of this xbmc instance, which is to be * watched for changes - * + * */ public void addItem(String itemName, String property) { + logger.debug("Mapping: itemname = {} & property = {}", itemName, property); if (!watches.containsKey(itemName)) { watches.put(itemName, property); } @@ -307,7 +310,7 @@ public void updatePlayerStatus() { /** * Update the status of the current player - * + * * @param updatePolledPropertiesOnly * If updatePolledPropertiesOnly is true, only update the Player properties that need to be polled * If updatePolledPropertiesOnly is false, update the Player state itself as well @@ -543,7 +546,7 @@ public void run() { /** * Request an update for the Player properties from XBMC - * + * * @param playerId * The id of the currently active player */ @@ -553,7 +556,7 @@ private void requestPlayerUpdate(int playerId) { /** * Request an update for the Player properties from XBMC - * + * * @param playerId * The id of the currently active player * @param updatePolledPropertiesOnly @@ -581,25 +584,78 @@ private void requestPlayerUpdate(int playerId, boolean updatePolledPropertiesOnl if (!properties.isEmpty()) { logger.debug("[{}]: Retrieving properties ({}) for playerId {}", xbmc.getHostname(), properties.size(), playerId); - // make the request for the player item details - final PlayerGetItem item = new PlayerGetItem(client, httpUri); - item.setPlayerId(playerId); - item.setProperties(properties); - - item.execute(new Runnable() { - @Override - public void run() { - // now update each of the openHAB items for each property - for (String property : properties) { - String value = item.getPropertyValue(property); - if (property.equals("Player.Fanart")) { - updateFanartUrl(property, value); - } else { + final List propertiesInfo = new ArrayList(); + final List propertiesProperties = new ArrayList(); + final List propertiesLabels = new ArrayList(); + + for (String property : properties) { + if (property.startsWith("Player.")) { + propertiesInfo.add(property); + } + if (property.startsWith("Property.")) { + propertiesProperties.add(property); + } + if (property.startsWith("Label.")) { + propertiesLabels.add(property); + } + } + + // make the request for the player item details using GETINFO + if (!propertiesInfo.isEmpty()) { + final PlayerGetItem item = new PlayerGetItem(client, httpUri); + item.setPlayerId(playerId); + item.setProperties(propertiesInfo); + + item.execute(new Runnable() { + @Override + public void run() { + // now update each of the openHAB items for each property + for (String property : propertiesInfo) { + String value = item.getPropertyValue(property); + if (property.equals("Player.Fanart")) { + updateFanartUrl(property, value); + } else { + updateProperty(property, value); + } + } + } + }); + } + + // make the request for the player item2 details using GETPROPERTIES + if (!propertiesProperties.isEmpty()) { + final PlayerGetProperties item2 = new PlayerGetProperties(client, httpUri); + item2.setPlayerId(playerId); + item2.setProperties(propertiesProperties); + + item2.execute(new Runnable() { + @Override + public void run() { + // now update each of the openHAB items for each property + for (String property : propertiesProperties) { + String value = item2.getPropertyValue(property); updateProperty(property, value); } } - } - }); + }); + } + + // make the request for the player item3 details using GETLABELS + if (!propertiesLabels.isEmpty()) { + final PlayerGetLabels item3 = new PlayerGetLabels(client, httpUri); + item3.setProperties(propertiesLabels); + + item3.execute(new Runnable() { + @Override + public void run() { + // now update each of the openHAB items for each property + for (String property : propertiesLabels) { + String value = item3.getPropertyValue(property); + updateProperty(property, value); + } + } + }); + } } } @@ -652,7 +708,7 @@ private void updateProperty(String property, OnOffType value) { /** * get a distinct list of player properties we have items configured for - * + * * @return * A list of property names */ @@ -662,7 +718,7 @@ private List getPlayerProperties() { /** * get a distinct list of player properties we have items configured for - * + * * @param updatePolledPropertiesOnly * Only get the properties that need to be refreshed by polling if true, * otherwise get all the properties that have items configured for @@ -676,25 +732,37 @@ private List getPlayerProperties(boolean updatePolledPropertiesOnly) { for (String property : watches.values()) { if (properties.contains(property)) { continue; - } else if (property.equals("Player.Label")) { + } + if (property.equals("Player.State")) { + continue; + } + if (property.startsWith("Player.")) { properties.add(property); - } else if (property.equals("Player.Title")) { + } + if (property.startsWith("Property.")) { + properties.add(property); + } + if (property.startsWith("Label.")) { properties.add(property); } } } else { for (String property : watches.values()) { - if (!property.startsWith("Player.")) { + if (properties.contains(property)) { continue; } if (property.equals("Player.State")) { continue; } - if (properties.contains(property)) { - continue; + if (property.startsWith("Player.")) { + properties.add(property); + } + if (property.startsWith("Property.")) { + properties.add(property); + } + if (property.startsWith("Label.")) { + properties.add(property); } - - properties.add(property); } } return properties; diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetItem.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetItem.java index 37176706a1f..cb988eaac3b 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetItem.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetItem.java @@ -21,7 +21,7 @@ /** * Player.GetItem RPC * - * @author tlan, Ben Jones, Marcel Erkel + * @author tlan, Ben Jones, Marcel Erkel, Plebs * @since 1.5.0 */ public class PlayerGetItem extends RpcCall { @@ -58,8 +58,11 @@ protected Map getParams() { if (property.equals("Player.Label")) { continue; } - String paramProperty = getParamProperty(property); - paramProperties.add(paramProperty); + if (property.startsWith("Player.")) { + // properties entered as 'Player.Title' etc - so strip the first 7 chars + String paramProperty = getParamProperty(property); + paramProperties.add(paramProperty); + } } Map params = new HashMap(); diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetLabels.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetLabels.java new file mode 100644 index 00000000000..bba4138ef66 --- /dev/null +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetLabels.java @@ -0,0 +1,127 @@ +/** + * 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.rpc.calls; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.openhab.binding.xbmc.rpc.RpcCall; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ning.http.client.AsyncHttpClient; + +/** + * Player.GetLabels RPC + * + * @author Plebs + * @since 1.9.0 + */ +public class PlayerGetLabels extends RpcCall { + + private static final Logger logger = LoggerFactory.getLogger(RpcCall.class); + + private int playerId; + private List properties; + + private Map item; + + public PlayerGetLabels(AsyncHttpClient client, String uri) { + super(client, uri); + } + + public void setPlayerId(int playerId) { + this.playerId = playerId; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + @Override + protected String getName() { + return "XBMC.GetInfoLabels"; + } + + @Override + protected Map getParams() { + List paramProperties = new ArrayList(); + for (String property : properties) { + if (property.startsWith("Label.")) { + String paramProperty = getParamProperty(property); + paramProperties.add(paramProperty); + } + } + + Map params = new HashMap(); + params.put("labels", paramProperties); + return params; + } + + @Override + protected void processResponse(Map response) { + Map result = getMap(response, "result"); + item = result; + } + + public String getPropertyValue(String property) { + String paramProperty = getParamProperty(property); + if (!item.containsKey(paramProperty)) { + return null; + } + + Object value = item.get(paramProperty); + + if (value instanceof List) { + List values = (List) value; + + // check if list contains any values + if (values.size() == 0) { + return null; + } + + // some properties come back as a list with an indexer + String paramPropertyIndex = getPropertyValue(paramProperty + "id"); + int propertyIndex; + if (!StringUtils.isEmpty(paramPropertyIndex)) { + // attempt to parse the property index + try { + propertyIndex = Integer.parseInt(paramPropertyIndex); + } catch (NumberFormatException e) { + return null; + } + + // check if the index is valid + if (propertyIndex < 0 || propertyIndex >= values.size()) { + return null; + } + } else { + // some properties come back as a list without an indexer, + // e.g. artist, for these we return the first in the list + propertyIndex = 0; + } + + value = values.get(propertyIndex); + } + + if (value == null) { + return null; + } + + return value.toString(); + } + + private String getParamProperty(String property) { + // properties entered as 'Label.Title' etc - so strip the first 6 chars + return property.substring(6); // It should not be in lowercase + } +} diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetProperties.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetProperties.java index e9f06ac2e64..cdd5c636d57 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetProperties.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/PlayerGetProperties.java @@ -13,21 +13,27 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.openhab.binding.xbmc.rpc.RpcCall; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.ning.http.client.AsyncHttpClient; /** * Player.GetProperties RPC * - * @author Ben Jones + * @author Ben jones, Plebs * @since 1.5.0 */ public class PlayerGetProperties extends RpcCall { + private static final Logger logger = LoggerFactory.getLogger(RpcCall.class); + private int playerId; + private List properties; - private boolean paused = false; + private Map item; public PlayerGetProperties(AsyncHttpClient client, String uri) { super(client, uri); @@ -37,6 +43,10 @@ public void setPlayerId(int playerId) { this.playerId = playerId; } + public void setProperties(List properties) { + this.properties = properties; + } + @Override protected String getName() { return "Player.GetProperties"; @@ -44,25 +54,75 @@ protected String getName() { @Override protected Map getParams() { - List properties = new ArrayList(); - properties.add("speed"); - + List paramProperties = new ArrayList(); + for (String property : properties) { + if (property.startsWith("Property.")) { + String paramProperty = getParamProperty(property); + paramProperties.add(paramProperty); + } + } Map params = new HashMap(); params.put("playerid", playerId); - params.put("properties", properties); + params.put("properties", paramProperties); return params; } @Override protected void processResponse(Map response) { Map result = getMap(response, "result"); + item = result; + } - if (result.containsKey("speed")) { - paused = (Integer) result.get("speed") == 0; + public String getPropertyValue(String property) { + String paramProperty = getParamProperty(property); + if (!item.containsKey(paramProperty)) { + return null; } + + Object value = item.get(paramProperty); + + if (value instanceof List) { + List values = (List) value; + + // check if list contains any values + if (values.size() == 0) { + return null; + } + + // some properties come back as a list with an indexer + String paramPropertyIndex = getPropertyValue(paramProperty + "id"); + int propertyIndex; + if (!StringUtils.isEmpty(paramPropertyIndex)) { + // attempt to parse the property index + try { + propertyIndex = Integer.parseInt(paramPropertyIndex); + } catch (NumberFormatException e) { + return null; + } + + // check if the index is valid + if (propertyIndex < 0 || propertyIndex >= values.size()) { + return null; + } + } else { + // some properties come back as a list without an indexer, + // e.g. artist, for these we return the first in the list + propertyIndex = 0; + } + + value = values.get(propertyIndex); + } + + if (value == null) { + return null; + } + + return value.toString(); } - public boolean isPaused() { - return paused; + private String getParamProperty(String property) { + // properties entered as 'Property.Title' etc - so strip the first 9 chars + return property.substring(9).toLowerCase(); } + } diff --git a/distribution/openhabhome/configurations/openhab_default.cfg b/distribution/openhabhome/configurations/openhab_default.cfg index 6f230f26352..b80dae8fc8b 100644 --- a/distribution/openhabhome/configurations/openhab_default.cfg +++ b/distribution/openhabhome/configurations/openhab_default.cfg @@ -2473,20 +2473,23 @@ tcp:refreshinterval=250 ################################# XBMC Binding ###################################### -# Hostname / IP address of your XBMC host (required). Example: +# Hostname / IP address of your XBMC host (defaults to 127.0.0.1). #xbmc:livingRoom.host=192.168.1.6 -# Port number for the json rpc service (optional, defaults to 8080). Example: +# Port number for the json rpc service (optional, defaults to 8080). #xbmc:livingRoom.rsPort=8080 -# Port number for the web socket service (optional, defaults to 9090). Example: +# Port number for the web socket service (optional, defaults to 9090). #xbmc:livingRoom.wsPort=9090 -# Username to connect to XBMC. (optional, defaults to none). Example: +# Username to connect to XBMC. (optional, defaults to xbmc). #xbmc:livingRoom.username=xbmc -# Password to connect to XBMC. (optional, defaults to none). Example: +# Password to connect to XBMC. (optional, defaults to xbmc). #xbmc:livingRoom.password=xbmc + +#Refresh interval in ms (optional, defaults to 60000ms [1 minute]) +#xbmc:refreshInterval=60000 ################################ Garadget Binding ################################### # diff --git a/features/openhab-addons-external/src/main/resources/conf/xbmc.cfg b/features/openhab-addons-external/src/main/resources/conf/xbmc.cfg index 06bb2fb9f8a..1b4e1964548 100644 --- a/features/openhab-addons-external/src/main/resources/conf/xbmc.cfg +++ b/features/openhab-addons-external/src/main/resources/conf/xbmc.cfg @@ -7,9 +7,12 @@ # Port number for the web socket service (optional, defaults to 9090). Example: #livingRoom.wsPort=9090 -# Username to connect to XBMC. (optional, defaults to none). Example: +# Username to connect to XBMC. (optional, defaults to xbmc). Example: #livingRoom.username=xbmc -# Password to connect to XBMC. (optional, defaults to none). Example: +# Password to connect to XBMC. (optional, defaults to xbmc). Example: #livingRoom.password=xbmc +#Refresh interval in ms (optional, defaults to 60000ms [1 minute]) +#refreshInterval=60000 +