diff --git a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/MpdBindingProvider.java b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/MpdBindingProvider.java index de7ccc24814..eb0bede06e0 100644 --- a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/MpdBindingProvider.java +++ b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/MpdBindingProvider.java @@ -20,13 +20,15 @@ * taken into account. * * @author Thomas.Eichstaedt-Engelen + * @author Matthew Bowman + * * @since 0.8.0 */ public interface MpdBindingProvider extends BindingProvider { /** * Returns the matching player command (associated to itemName - * and comnand) or null if no playerCommand could + * and command) or null if no playerCommand could * be found. * * @param itemName the item for which to find a mpdPlayerCommand @@ -37,6 +39,21 @@ public interface MpdBindingProvider extends BindingProvider { */ String getPlayerCommand(String itemName, String command); + /** + * Returns the matching player command param (associated to itemName + * and command) or null if no playerCommand param could + * be found. + * + * @param itemName the item for which to find a mpdPlayerCommand param + * @param command the openHAB command for which to find a mpdPlayerCommand param + * + * @return the matching mpdPlayerCommand param or null if no matching + * mpdPlayerCommand param could be found. + * + * @since 1.6.0 + */ + String getPlayerCommandParam(String itemName, String command); + /** * Returns all Items associated to playerId and playerCommand * @@ -48,4 +65,19 @@ public interface MpdBindingProvider extends BindingProvider { */ String[] getItemNamesByPlayerAndPlayerCommand(String playerId, PlayerCommandTypeMapping playerCommand); + /** + * Returns all Items associated to playerId and that have a + * playerCommand=outputId binding. + * + * @param playerId the id of the player for which items should be returned + * @param playerCommand the openHAB command for which items should be returned + * @param outputId the MPD output id for which items should be returned + * + * @return the name of all items which are associated to playerId + * and have a playerCommand=outputId binding. + * + * @since 1.6.0 + */ + String[] getItemNamesByPlayerOutputCommand(String playerId, PlayerCommandTypeMapping playerCommand, int outputId); + } diff --git a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdBinding.java b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdBinding.java index ce8e7d0f283..c59a0bf503d 100644 --- a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdBinding.java +++ b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdBinding.java @@ -26,11 +26,17 @@ import org.apache.commons.lang.StringUtils; import org.bff.javampd.MPD; +import org.bff.javampd.MPDAdmin; +import org.bff.javampd.MPDOutput; import org.bff.javampd.MPDPlayer; import org.bff.javampd.MPDPlayer.PlayerStatus; +import org.bff.javampd.events.OutputChangeEvent; +import org.bff.javampd.events.OutputChangeListener; import org.bff.javampd.events.PlayerBasicChangeEvent; import org.bff.javampd.events.PlayerBasicChangeListener; import org.bff.javampd.events.PlayerChangeEvent; +import org.bff.javampd.events.TrackPositionChangeEvent; +import org.bff.javampd.events.TrackPositionChangeListener; import org.bff.javampd.events.VolumeChangeEvent; import org.bff.javampd.events.VolumeChangeListener; import org.bff.javampd.exception.MPDConnectionException; @@ -45,6 +51,7 @@ import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StringType; 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.quartz.CronScheduleBuilder; @@ -70,6 +77,7 @@ * * @author Thomas.Eichstaedt-Engelen * @author Petr.Klus + * @author Matthew Bowman * * @since 0.8.0 */ @@ -135,6 +143,11 @@ public void internalReceiveCommand(String itemName, Command command) { String playerCommand = provider.getPlayerCommand(itemName, matchingPlayerCommand); if (StringUtils.isNotBlank(playerCommand)) { + String playerCommandParam = + provider.getPlayerCommandParam(itemName, matchingPlayerCommand); + if (playerCommandParam != null) { + params = playerCommandParam; + } executePlayerCommand(playerCommand, params); } @@ -196,6 +209,17 @@ private void executePlayerCommand(String playerCommandLine, Object commandParams case VOLUME_DECREASE: player.setVolume(player.getVolume() - VOLUME_CHANGE_SIZE); break; case NEXT: player.playNext(); break; case PREV: player.playPrev(); break; + case ENABLE: + case DISABLE: + Integer outputId = Integer.valueOf((String) commandParams); + MPDAdmin admin = daemon.getMPDAdmin(); + MPDOutput output = new MPDOutput(outputId - 1); // internally mpd uses 0-based indexing + if (pCommand == PlayerCommandTypeMapping.ENABLE) { + admin.enableOutput(output); + } else { + admin.disableOutput(output); + } + break; case VOLUME: logger.debug("Volume adjustment received: '{}' '{}'", pCommand, commandParams); player.setVolume(((PercentType) commandParams).intValue()); @@ -424,6 +448,39 @@ public void volumeChanged(VolumeChangeEvent vce) { } } + + /** + * Handles MPD output change events. + * + * @param playerId the playerId which generated the event + * @param event the {@link OutputChangeEvent} that occurred + * + * @since 1.6.0 + */ + private void outputChanged(String playerId, OutputChangeEvent event) { + MPDOutput output = (MPDOutput) event.getSource(); + logger.debug("Output {} changed on player {}, enabled = {}", output.getId(), playerId, output.isEnabled()); + PlayerCommandTypeMapping playerCommand = + output.isEnabled() ? PlayerCommandTypeMapping.ENABLE + : PlayerCommandTypeMapping.DISABLE; + String[] itemNames = getItemsByPlayerCommandAndOutput(playerId, playerCommand, output); + for (String itemName : itemNames) { + eventPublisher.postUpdate(itemName, (State) playerCommand.type); + } + } + + + private String[] getItemsByPlayerCommandAndOutput(String playerId, PlayerCommandTypeMapping playerCommand, MPDOutput output) { + Set itemNames = new HashSet(); + int outputId = output.getId() + 1; // internally mpd uses 0-based indexes + for (MpdBindingProvider provider : this.providers) { + itemNames.addAll(Arrays.asList( + provider.getItemNamesByPlayerOutputCommand(playerId, playerCommand, outputId))); + } + return itemNames.toArray(new String[itemNames.size()]); + } + + private String[] getItemNamesByPlayerAndPlayerCommand(String playerId, PlayerCommandTypeMapping playerCommand) { Set itemNames = new HashSet(); for (MpdBindingProvider provider : this.providers) { @@ -546,7 +603,7 @@ private void connectAllPlayersAndMonitors() { * @param host * @param port */ - private void connect(String playerId) { + private void connect(final String playerId) { MpdPlayerConfig config = null; try { @@ -559,6 +616,24 @@ private void connect(String playerId) { mpdStandAloneMonitor.addVolumeChangeListener(this); mpdStandAloneMonitor.addPlayerChangeListener(this); mpdStandAloneMonitor.addTrackPositionChangeListener(this); + + final MpdBinding self = this; // 'this' glue for the inner anon instance + mpdStandAloneMonitor.addOutputChangeListener(new OutputChangeListener() { + + @Override + public void outputChanged(OutputChangeEvent e) { + // We have to 'wrap' the OutputChangeEvent listener + // callback and add the playerId so we know which + // player generated the event. There's not enough + // info on just the OutputChangeEvent to derive + // the source player. This 'workaround' is necessary + // to support output control on multiple MPD players. + self.outputChanged(playerId, e); + + } + + }); + Thread monitorThread = new Thread( mpdStandAloneMonitor, "MPD Monitor (player:" + playerId + ")"); monitorThread.start(); diff --git a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdGenericBindingProvider.java b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdGenericBindingProvider.java index a11bb9b163c..edeba46e04d 100644 --- a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdGenericBindingProvider.java +++ b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/MpdGenericBindingProvider.java @@ -35,10 +35,14 @@ * * * @author Thomas.Eichstaedt-Engelen + * @author Matthew Bowman + * * @since 0.8.0 */ public class MpdGenericBindingProvider extends AbstractGenericBindingProvider implements MpdBindingProvider { + private static final String PARAM_SUFFIX = ":param"; + /** * {@inheritDoc} */ @@ -86,6 +90,15 @@ protected void parseBindingConfig(String bindingConfigs, MpdBindingConfig config String command = StringUtils.trim(configParts[0]); String playerId = StringUtils.trim(configParts[1]); String playerCommand = StringUtils.trim(configParts[2]); + // check for optional command=param binding + String[] playerCommandParts = playerCommand.split("="); + if (playerCommandParts.length == 2) { + // rewrite command=param -> command + playerCommand = StringUtils.trim(playerCommandParts[0]); + String playerCommandParam = StringUtils.trim(playerCommandParts[1]); + // save the param in the config + config.put(command + PARAM_SUFFIX, playerCommandParam); + } // if there are more commands to parse do that recursively ... if (StringUtils.isNotBlank(bindingConfigTail)) { @@ -103,6 +116,14 @@ public String getPlayerCommand(String itemName, String command) { return config != null ? config.get(command) : null; } + /** + * {@inheritDoc} + */ + public String getPlayerCommandParam(String itemName, String command) { + MpdBindingConfig config = (MpdBindingConfig) bindingConfigs.get(itemName); + return config != null ? config.get(command + PARAM_SUFFIX) : null; + } + /** * {@inheritDoc} */ @@ -118,6 +139,39 @@ public String[] getItemNamesByPlayerAndPlayerCommand(String playerId, PlayerComm itemNames.add(itemName); } else if (mpdConfig.containsKey(playerCommand.type.toString())) { + // we check to make sure the binding config contains + // playerId:playerCommand otherwise we get extra items + String actual = mpdConfig.get(playerCommand.type.toString()); + String expected = playerId + ":" + playerCommand.toString().toLowerCase(); + if (StringUtils.equals(actual, expected)) { + itemNames.add(itemName); + } + } + } + return itemNames.toArray(new String[itemNames.size()]); + } + + /** + * {@inheritDoc} + */ + public String[] getItemNamesByPlayerOutputCommand(String playerId, PlayerCommandTypeMapping command, int outputId) { + Set itemNames = new HashSet(); + for (String itemName : bindingConfigs.keySet()) { + MpdBindingConfig config = (MpdBindingConfig) bindingConfigs.get(itemName); + // We're looking for either... + // --- + // ON = :enable + // ON:param = + // --- or --- + // OFF = :disable + // OFF:param = + // --- + String k1 = command.type.toString(); + String v1 = playerId + ":" + command.toString().toLowerCase(); + String k2 = command.type.toString() + PARAM_SUFFIX; + String v2 = String.valueOf(outputId); + if (StringUtils.equals(config.get(k1), v1) + && StringUtils.equals(config.get(k2), v2)) { itemNames.add(itemName); } } diff --git a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/PlayerCommandTypeMapping.java b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/PlayerCommandTypeMapping.java index 7d12cbbe21d..df78061e67a 100644 --- a/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/PlayerCommandTypeMapping.java +++ b/bundles/binding/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/PlayerCommandTypeMapping.java @@ -87,6 +87,20 @@ public enum PlayerCommandTypeMapping { command = "prev"; type = OnOffType.OFF; } + }, + + ENABLE { + { + command = "enable"; + type = OnOffType.ON; + } + }, + + DISABLE { + { + command = "disable"; + type = OnOffType.OFF; + } }; /** Represents the player command as it will be used in *.items configuration */