diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/XbmcBindingProvider.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/XbmcBindingProvider.java index 49d7d610d9d..cefed59448b 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/XbmcBindingProvider.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/XbmcBindingProvider.java @@ -20,4 +20,5 @@ public interface XbmcBindingProvider extends BindingProvider { String getXbmcInstance(String itemname); String getProperty(String itemname); boolean isInBound(String itemname); + boolean isOutBound(String itemname); } 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 1dee0031e8d..4943ebcb2ed 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 @@ -131,6 +131,10 @@ private void registerWatch(XbmcBindingProvider xbmcProvider, String itemName) { // update the player status so any current value is initialised if (connector.isConnected()) connector.updatePlayerStatus(); + + if (property.startsWith("Application")) { + connector.requestApplicationUpdate(); + } } } @@ -170,6 +174,18 @@ private boolean isInBound(String 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) @@ -249,7 +265,7 @@ protected void execute() { @Override protected void internalReceiveCommand(String itemName, Command command) { // only interested in 'outbound' items - if (isInBound(itemName)) { + if (!isOutBound(itemName)) { logger.warn("Received command ({}) for item {} which is configured as 'in-bound', ignoring", command.toString(), itemName); return; } @@ -272,14 +288,16 @@ protected void internalReceiveCommand(String itemName, Command command) { // TODO: handle other commands if (property.equals("Player.PlayPause")) connector.playerPlayPause(); - if (property.equals("Player.Open")) + else if (property.equals("Player.Open")) connector.playerOpen(command.toString()); - if (property.equals("Player.Stop")) + else if (property.equals("Player.Stop")) connector.playerStop(); - if (property.equals("GUI.ShowNotification")) + else if (property.equals("GUI.ShowNotification")) connector.showNotification("openHAB", command.toString()); - if (property.equals("System.Shutdown") && command == OnOffType.OFF) + else if (property.equals("System.Shutdown") && command == OnOffType.OFF) connector.systemShutdown(); + else if (property.equals("Application.Volume")) + connector.applicationSetVolume(command.toString()); } catch (Exception e) { logger.error("Error handling command", e); } @@ -292,23 +310,28 @@ protected void internalReceiveCommand(String itemName, Command command) { protected void internalReceiveUpdate(String itemName, State newState) { try { String property = getProperty(itemName); - - // TODO: handle other updates - if (property.equals("GUI.ShowNotification")) { - 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; - } + String xbmcInstance = getXbmcInstance(itemName); + XbmcConnector connector = getXbmcConnector(xbmcInstance); - connector.showNotification("openHAB", newState.toString()); + 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); } diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcGenericBindingProvider.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcGenericBindingProvider.java index dae140beecf..381d028916f 100644 --- a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcGenericBindingProvider.java +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/internal/XbmcGenericBindingProvider.java @@ -14,6 +14,7 @@ import org.openhab.binding.xbmc.XbmcBindingProvider; import org.openhab.core.binding.BindingConfig; import org.openhab.core.items.Item; +import org.openhab.core.library.items.DimmerItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.model.item.binding.AbstractGenericBindingProvider; @@ -35,10 +36,11 @@ public String getBindingType() { @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { - if (!(item instanceof StringItem) && !(item instanceof SwitchItem)){ + if (!(item instanceof StringItem) && !(item instanceof SwitchItem) && + !(item instanceof DimmerItem)){ throw new BindingConfigParseException( "item '"+item.getName() + "' is of type '" + item.getClass().getSimpleName() - + "', but only String or Switch items are allowed."); + + "', but only String, Switch or Dimmer items are allowed."); } } @@ -55,11 +57,26 @@ public void processBindingConfiguration(String context, Item item, String bindin } else if (bindingConfig.startsWith(">")) { XbmcBindingConfig config = parseOutgoingBindingConfig(item, bindingConfig); addBindingConfig(item, config); + } else if (bindingConfig.startsWith("=")) { + XbmcBindingConfig config = parseBidirectionalBindingConfig(item, bindingConfig); + addBindingConfig(item, config); } else { - throw new BindingConfigParseException("Item '"+item.getName()+"' does not start with < or >."); + throw new BindingConfigParseException("Item '"+item.getName()+"' does not start with <, > or =."); } } + private XbmcBindingConfig parseBidirectionalBindingConfig( Item item, String bindingConfig) throws BindingConfigParseException{ + Matcher matcher = CONFIG_PATTERN.matcher(bindingConfig); + + if( !matcher.matches()) + throw new BindingConfigParseException("Config for item '"+item.getName()+"' could not be parsed."); + + String xbmcInstance = matcher.group(1); + String property = matcher.group(2); + + return new XbmcBindingConfig(xbmcInstance, property, true, true); + } + private XbmcBindingConfig parseIncomingBindingConfig( Item item, String bindingConfig) throws BindingConfigParseException{ Matcher matcher = CONFIG_PATTERN.matcher(bindingConfig); @@ -69,7 +86,7 @@ private XbmcBindingConfig parseIncomingBindingConfig( Item item, String bindingC String xbmcInstance = matcher.group(1); String property = matcher.group(2); - return new XbmcBindingConfig(xbmcInstance, property, true); + return new XbmcBindingConfig(xbmcInstance, property, true, false); } private XbmcBindingConfig parseOutgoingBindingConfig( Item item, String bindingConfig) throws BindingConfigParseException{ @@ -81,7 +98,7 @@ private XbmcBindingConfig parseOutgoingBindingConfig( Item item, String bindingC String xbmcInstance = matcher.group(1); String property = matcher.group(2); - return new XbmcBindingConfig(xbmcInstance, property, false); + return new XbmcBindingConfig(xbmcInstance, property, false, true); } @Override @@ -102,18 +119,32 @@ public boolean isInBound(String itemname) { return bindingConfig != null ? bindingConfig.isInBound(): false; } + @Override + public boolean isOutBound(String itemname) { + XbmcBindingConfig bindingConfig = (XbmcBindingConfig) bindingConfigs.get(itemname); + return bindingConfig != null ? bindingConfig.isOutBound(): false; + } + class XbmcBindingConfig implements BindingConfig { private String xbmcInstance; private String property; - private boolean inBound; + private boolean inBound = false; + private boolean outBound = false; public XbmcBindingConfig(String xbmcInstance, String property, boolean inBound) { this.xbmcInstance = xbmcInstance; this.property = property; this.inBound = inBound; } - + + public XbmcBindingConfig(String xbmcInstance, String property, boolean inBound, boolean outBound) { + this.xbmcInstance = xbmcInstance; + this.property = property; + this.inBound = inBound; + this.outBound = outBound; + } + public String getXbmcInstance() { return xbmcInstance; } @@ -125,5 +156,9 @@ public String getProperty() { public boolean isInBound() { return inBound; } + + public boolean isOutBound() { + return outBound; + } } } 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 b8b7d814c44..548a95911cb 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 @@ -9,6 +9,7 @@ package org.openhab.binding.xbmc.rpc; import java.io.IOException; +import java.math.BigDecimal; import java.net.ConnectException; import java.util.ArrayList; import java.util.HashMap; @@ -19,6 +20,8 @@ import java.util.concurrent.TimeoutException; import org.openhab.binding.xbmc.internal.XbmcHost; +import org.openhab.binding.xbmc.rpc.calls.ApplicationGetProperties; +import org.openhab.binding.xbmc.rpc.calls.ApplicationSetVolume; import org.openhab.binding.xbmc.rpc.calls.FilesPrepareDownload; import org.openhab.binding.xbmc.rpc.calls.GUIShowNotification; import org.openhab.binding.xbmc.rpc.calls.JSONRPCPing; @@ -29,6 +32,7 @@ import org.openhab.binding.xbmc.rpc.calls.PlayerStop; import org.openhab.binding.xbmc.rpc.calls.SystemShutdown; import org.openhab.core.events.EventPublisher; +import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StringType; import org.slf4j.Logger; @@ -52,7 +56,7 @@ /** * Manages the web socket connection for a single XBMC instance. * - * @author tlan, Ben Jones + * @author tlan, Ben Jones, Ard van der Leeuw * @since 1.5.0 */ public class XbmcConnector { @@ -61,6 +65,9 @@ public class XbmcConnector { // request timeout (configurable?) private static final int REQUEST_TIMEOUT_MS = 60000; + + // the amount to increase/decrease the volume when receiving INCREASE/DECREASE commands + private static final BigDecimal VOLUMESTEP = new BigDecimal(10); // the XBMC instance and openHAB event publisher handles private final XbmcHost xbmc; @@ -78,6 +85,9 @@ public class XbmcConnector { private WebSocket webSocket; private boolean connected = false; + // the current volume + private BigDecimal volume = BigDecimal.ZERO; + // the current player state private State currentState = State.Stop; @@ -174,6 +184,8 @@ class XbmcWebSocketListener implements WebSocketTextListener { public void onOpen(WebSocket webSocket) { logger.debug("[{}]: Websocket opened", xbmc.getHostname()); connected = true; + requestApplicationUpdate(); + updatePlayerStatus(); } @Override @@ -219,6 +231,9 @@ public void onMessage(String message) { String method = (String)json.get("method"); if (method.startsWith("Player.On")) { processPlayerStateChanged(method, json); + } + else if (method.startsWith("Application.On")) { + processApplicationStateChanged(method, json); } } } catch (Exception e) { @@ -319,6 +334,35 @@ public void playerOpen(String file) { playeropen.execute(); } + public void applicationSetVolume(String volume) { + final ApplicationSetVolume applicationsetvolume = new ApplicationSetVolume(client, httpUri); + + if (volume.equals("ON")) { + this.volume = new BigDecimal(100); + } + else if (volume.equals("OFF")) { + this.volume = new BigDecimal(0); + } + else if (volume.equals("DECREASE")) { + this.volume = this.volume.subtract(VOLUMESTEP); + } + else if (volume.equals("INCREASE")) { + this.volume = this.volume.add(VOLUMESTEP); + } + else { + try { + this.volume = new BigDecimal(volume); + } + catch (NumberFormatException e) { + logger.error("applicationSetVolume cannot parse volume parameter: " + volume); + this.volume = BigDecimal.ZERO; + } + } + + applicationsetvolume.setVolume(this.volume.intValue()); + applicationsetvolume.execute(); + } + private void processPlayerStateChanged(String method, Map json) { if ("Player.OnPlay".equals(method)) { // get the player id and make a new request for the media details @@ -347,6 +391,30 @@ private void processPlayerStateChanged(String method, Map json) } } + private void processApplicationStateChanged(String method, Map json) { + if ("Application.OnVolumeChanged".equals(method)) { + // get the player id and make a new request for the media details + Map params = RpcCall.getMap(json, "params"); + Map data = RpcCall.getMap(params, "data"); + + Object o = data.get("volume"); + PercentType volume = new PercentType(0); + + if (o instanceof Integer) { + volume = new PercentType((Integer)o); + } + else { + if (o instanceof Double) { + volume = new PercentType(((Double)o).intValue()); + } + } + + updateProperty("Application.Volume", volume); + this.volume = new BigDecimal(volume.intValue()); + } + + } + private void updateState(State state) { // sometimes get a Pause immediately after a Stop - so just ignore if (currentState.equals(State.Stop) && state.equals(State.Pause)) @@ -358,14 +426,27 @@ private void updateState(State state) { // if this is a Stop then clear everything else if (state == State.Stop) { for (String property : getPlayerProperties()) { - updateProperty(property, null); + updateProperty(property, (String)null); } } // keep track of our current state currentState = state; } - + + public void requestApplicationUpdate() { + final ApplicationGetProperties app = new ApplicationGetProperties(client, httpUri); + + app.execute(new Runnable() { + public void run() { + // now update each of the openHAB items for each property + volume = new BigDecimal(app.getVolume()); + updateProperty("Application.Volume", new PercentType(volume)); + } + }); + + } + private void requestPlayerUpdate(int playerId) { // CRIT: if a PVR recording is played in XBMC the playerId is reported as -1 if (playerId == -1) { @@ -425,6 +506,16 @@ private void updateProperty(String property, String value) { } } } + + private void updateProperty(String property, PercentType value) { + value = (value == null ? new PercentType(0) : value); + + for (Entry e : watches.entrySet()) { + if (property.equals(e.getValue())) { + eventPublisher.postUpdate(e.getKey(), value); + } + } + } private List getPlayerProperties() { // get a distinct list of player properties we have items configured for diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/ApplicationGetProperties.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/ApplicationGetProperties.java new file mode 100644 index 00000000000..1e1f786b7b2 --- /dev/null +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/ApplicationGetProperties.java @@ -0,0 +1,85 @@ +/** + * 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.xbmc.rpc.calls; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.openhab.binding.xbmc.rpc.RpcCall; + +import com.ning.http.client.AsyncHttpClient; + +/** + * Player.GetProperties RPC + * + * @author Ard van der Leeuw + * @since 1.6.0 + */ +public class ApplicationGetProperties extends RpcCall { + + private int volume; + private int version_major; + private int version_minor; + private String version_tag; + private String version_revision; + private String name; + private boolean muted; + + public ApplicationGetProperties(AsyncHttpClient client, String uri) { + super(client, uri); + } + + @Override + protected String getName() { + return "Application.GetProperties"; + } + + @Override + protected Map getParams() { + List properties = new ArrayList(); + properties.add("volume"); + properties.add("version"); + properties.add("name"); + properties.add("muted"); + + Map params = new HashMap(); + params.put("properties", properties); + return params; + } + + @Override + protected void processResponse(Map response) { + Map result = getMap(response, "result"); + + if (result.containsKey("volume")) { + Object o = result.get("volume"); + if (o instanceof Double) { + volume = ((Double)o).intValue(); + } else { + if (o instanceof Integer) { + volume = (Integer)o; + } + } + } + if (result.containsKey("name")) { + name = (String)result.get("name"); + } + } + + public boolean isMuted() { + return muted; + } + + public Integer getVolume() { + return volume; + } + +} diff --git a/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/ApplicationSetVolume.java b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/ApplicationSetVolume.java new file mode 100644 index 00000000000..cdb63fae196 --- /dev/null +++ b/bundles/binding/org.openhab.binding.xbmc/src/main/java/org/openhab/binding/xbmc/rpc/calls/ApplicationSetVolume.java @@ -0,0 +1,51 @@ +/** + * 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.xbmc.rpc.calls; + +import java.util.HashMap; +import java.util.Map; + +import org.openhab.binding.xbmc.rpc.RpcCall; + +import com.ning.http.client.AsyncHttpClient; + +/** + * Player.Stop RPC + * + * @author Ard van der Leeuw + * @since 1.6.0 + */ +public class ApplicationSetVolume extends RpcCall { + + private Integer volume; + + public ApplicationSetVolume(AsyncHttpClient client, String uri) { + super(client, uri); + } + + @Override + protected String getName() { + return "Application.SetVolume"; + } + + public void setVolume(Integer volume) { + this.volume = volume; + } + + @Override + protected Map getParams() { + Map params = new HashMap(); + params.put("volume", volume); + return params; + } + + @Override + protected void processResponse(Map response) { + } +}