Package org.openhab.binding.mpd.internal

Source Code of org.openhab.binding.mpd.internal.MpdBinding$MpdPlayerConfig

/**
* 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.mpd.internal;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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;
import org.bff.javampd.exception.MPDPlayerException;
import org.bff.javampd.exception.MPDResponseException;
import org.bff.javampd.monitor.MPDStandAloneMonitor;
import org.bff.javampd.objects.MPDSong;
import org.openhab.binding.mpd.MpdBindingProvider;
import org.openhab.binding.mpd.internal.MultiClickDetector.MultiClickListener;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.library.types.OnOffType;
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;
import org.quartz.CronTrigger;
import org.quartz.Job;
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.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// to manually detect changes
import org.bff.javampd.events.TrackPositionChangeEvent;
import org.bff.javampd.events.TrackPositionChangeListener;


/**
* Binding which communicates with (one or many) MPDs. It registers as listener
* of the MPDs to accomplish real bidirectional communication.
*
* @author Thomas.Eichstaedt-Engelen
* @author Petr.Klus
* @author Matthew Bowman
*
* @since 0.8.0
*/
public class MpdBinding extends AbstractBinding<MpdBindingProvider> implements ManagedService, VolumeChangeListener, PlayerBasicChangeListener, TrackPositionChangeListener, MultiClickListener<Command> {

  private static final String MPD_SCHEDULER_GROUP = "MPD";

  private static final Logger logger = LoggerFactory.getLogger(MpdBinding.class);
   
  private Map<String, MpdPlayerConfig> playerConfigCache = new HashMap<String, MpdPlayerConfig>();
 
  /** RegEx to validate a mpdPlayer config <code>'^(.*?)\\.(host|port|password)$'</code> */
  private static final Pattern EXTRACT_PLAYER_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.(host|port|password)$");
 
  /** The value by which the volume is changed by each INCREASE or DECREASE-Event */
  private static final int VOLUME_CHANGE_SIZE = 5;

  /** The connection timeout to wait for a MPD connection */
  private static final int CONNECTION_TIMEOUT = 5000;
 
  private static MultiClickDetector<Command> clickDetector;
 
 
  public MpdBinding() {
    playerConfigCache = new HashMap<String, MpdBinding.MpdPlayerConfig>();
    clickDetector = new MultiClickDetector<Command>(this, 300);
  }
 
 
  public void activate() {
    connectAllPlayersAndMonitors();
  }
 
  public void deactivate() {
    disconnectPlayersAndMonitors();
  }

 
  /**
   * @{inheritDoc}
   */
  @Override
  public void internalReceiveCommand(String itemName, Command command) {
   
    MpdBindingProvider provider;
    String matchingPlayerCommand;
    Object params = new Object(); //nothing by default
    if  (command instanceof PercentType) {
//      we have received volume adjustment request
      matchingPlayerCommand = "PERCENT";   
      params = command;
    } else {
      matchingPlayerCommand = command.toString();
    }
   
    provider = findFirstMatchingBindingProvider(itemName, matchingPlayerCommand)
   
    if (provider == null) {     
      logger.warn("cannot find matching binding provider [itemName={}, command={}]", itemName, command);
      return;
    }
   
    String playerCommand =
      provider.getPlayerCommand(itemName, matchingPlayerCommand);
    if (StringUtils.isNotBlank(playerCommand)) {
      String playerCommandParam =
          provider.getPlayerCommandParam(itemName, matchingPlayerCommand);
      if (playerCommandParam != null) {
        params = playerCommandParam;
      }
      executePlayerCommand(playerCommand, params);
    }
   
  }

  /**
   * Find the first matching {@link MpdBindingProvider} according to
   * <code>itemName</code> and <code>command</code>.
   *
   * @param itemName
   * @param command
   *
   * @return the matching binding provider or <code>null</code> if no binding
   * provider could be found
   */
  private MpdBindingProvider findFirstMatchingBindingProvider(String itemName, String command) {
    MpdBindingProvider firstMatchingProvider = null;
    for (MpdBindingProvider provider : this.providers) {
     
      String playerCommand = provider.getPlayerCommand(itemName, command);
      if (playerCommand != null) {
        firstMatchingProvider = provider;
        break;
      }
    }
    return firstMatchingProvider;
 
 
  /**
   * Executes the given <code>playerCommandLine</code> on the MPD. The
   * <code>playerCommandLine</code> is split into it's properties
   * <code>playerId</code> and <code>playerCommand</code>.
   *
   * @param playerCommandLine the complete commandLine which gets splitted into
   * it's properties.
   */
  private void executePlayerCommand(String playerCommandLine, Object commandParams) {
    String[] commandParts = playerCommandLine.split(":");
    String playerId = commandParts[0];
    String playerCommand = commandParts[1];

    MPD daemon = findMPDInstance(playerId);
    if (daemon == null) {
      // we give that player another chance -> try to reconnect
      reconnect(playerId);
    }
   
    if (daemon != null) {
      PlayerCommandTypeMapping pCommand = null;     
      try {
        pCommand = PlayerCommandTypeMapping.fromString(playerCommand);
        MPDPlayer player = daemon.getMPDPlayer();
       
        switch (pCommand) {
          case PAUSE: player.pause(); break;
          case PLAY: player.play(); break;
          case STOP: player.stop(); break;
          case VOLUME_INCREASE: player.setVolume(player.getVolume() + VOLUME_CHANGE_SIZE); break;
          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());
            break;

        }
      }
      catch (MPDPlayerException pe) {
        logger.error("error while executing {} command: " + pe.getMessage(), pCommand);
      }
      catch (Exception e) {
        logger.warn("unknow playerCommand '{}'", playerCommand);
      }
    }
    else {
      logger.warn("didn't find player configuration instance for playerId '{}'", playerId);
    }
     
    logger.info("executed commandLine '{}' for player '{}'", playerCommand, playerId);
  }
 
  private MPD findMPDInstance(String playerId) {
    MpdPlayerConfig playerConfig = playerConfigCache.get(playerId);
    if (playerConfig != null) {
      return playerConfig.instance;
    }
    return null;
  }

  private String findPlayerId(Object object) {
    for (String playerId : playerConfigCache.keySet()) {
      MpdPlayerConfig playerConfig = playerConfigCache.get(playerId);
      if (playerConfig != null && playerConfig.monitor != null && playerConfig.monitor.equals(object)) {
        return playerId;
      }
    }
    return null;
  }
 
  /**
   * Implementation of {@link PlayerBasicChangeListener}. Posts the translated
   * type of the {@link PlayerChangeEvent} onto the internal event bus.
   * <code>PLAYER_STARTED</code>-Events are translated to <code>ON</code> whereas
   * <code>PLAYER_PAUSED</code> and <code>PLAYER_STOPPED</code>-Events are
   * translated to <code>OFF</code>.
   *
   * In this case, we use the play state change to trigger full state update, including
   * artist and song name.
   *
   * @param pbce the event which type is translated and posted onto the internal
   * event bus
   */
  public void playerBasicChange(PlayerBasicChangeEvent pbce) {
    String playerId = findPlayerId(pbce.getSource());
       
    //  trigger track name update
    determineSongChange(playerId);
   
    // trigger our play state change   
    determinePlayStateChange(playerId);
  }
 

  HashMap<String, MPDSong> songInfoCache = new HashMap<String, MPDSong>();
  HashMap<String, PlayerStatus> playerStatusCache = new HashMap<String, PlayerStatus>();
  public void trackPositionChanged(TrackPositionChangeEvent tpce) {
   
    // cache song name internally, we do not want to fire every time   
    String playerId = findPlayerId(tpce.getSource());   
    // update track name       
    determineSongChange(playerId);
   
    // also update play state   
    determinePlayStateChange(playerId);
  }
 
  private void broadcastPlayerStateChange(String playerId, PlayerCommandTypeMapping reportTo, OnOffType reportStatus) {
    String[] itemNames = getItemNamesByPlayerAndPlayerCommand(playerId, reportTo);
    for (String itemName : itemNames) {
      if (StringUtils.isNotBlank(itemName)) {
        eventPublisher.postUpdate(itemName, reportStatus);
      }
    }   
  }
 
  private void determinePlayStateChange(String playerId) {   
    MPD daemon = findMPDInstance(playerId);
    if (daemon == null) {
      // we give that player another chance -> try to reconnect
      reconnect(playerId);
    }
    if (daemon != null) {
      try {
        MPDPlayer player = daemon.getMPDPlayer();
       
        // get the song object here
        PlayerStatus ps = player.getStatus();
       
        PlayerStatus curPs = playerStatusCache.get(playerId);
        if (curPs != null) {
          if (ps != curPs) {
            logger.debug("Play state of '{}' changed", playerId);
            playerStatusCache.put(playerId, ps);
           
            PlayerCommandTypeMapping reportTo;
            OnOffType reportStatus;
            // trigger song update
            if (ps.equals(PlayerStatus.STATUS_PAUSED) || ps.equals(PlayerStatus.STATUS_STOPPED)) {
              // stopped 
              reportTo = PlayerCommandTypeMapping.STOP;
              reportStatus = OnOffType.OFF;
            } else {
              //  playing
              reportTo = PlayerCommandTypeMapping.PLAY;
              reportStatus = OnOffType.ON;
            }           
            broadcastPlayerStateChange(playerId, reportTo, reportStatus);           
          } else {
            // nothing, same state           
          }
        } else {
          playerStatusCache.put(playerId, ps);
        }               
      } catch (MPDPlayerException pe) {
        logger.error("error while updating player status: {}" + pe.getMessage(), playerId);
      } catch (Exception e) {
        logger.warn("Failed to communicate with '{}'", playerId);
      }
    } else {
      logger.warn("didn't find player configuration instance for playerId '{}'", playerId);
    }       
  }
 
  private void determineSongChange(String playerId) {
    MPD daemon = findMPDInstance(playerId);
    if (daemon == null) {
      // we give that player another chance -> try to reconnect
      reconnect(playerId);
    }
    if (daemon != null) {
      try {
        MPDPlayer player = daemon.getMPDPlayer();
       
        // get the song object here
        MPDSong curSong = player.getCurrentSong();       
       
        MPDSong curSongCache = songInfoCache.get(playerId);
        if (curSongCache != null) {
          // we have some info
          if (curSong.getId() != curSongCache.getId()) {
            songInfoCache.put(playerId, curSong);
            songChanged(playerId, curSong);
          } else {
            // nothing, same song
            // detect play state           
          }
        } else {
          // no info about player's playback state         
          songInfoCache.put(playerId, curSong);
          // action the song change&notification
          songChanged(playerId, curSong);
        }
      }
      catch (MPDPlayerException pe) {
        logger.error("error while updating player status: {}" + pe.getMessage(), playerId);
      } catch (Exception e) {
        logger.warn("Failed to communicate with '{}'", playerId);
      }
    }
    else {
      logger.warn("didn't find player configuration instance for playerId '{}'", playerId);
    }
  }
 

 
  private void songChanged(String playerId, MPDSong newSong) {
   
    logger.debug("Current song {}: {}", playerId, newSong.getTitle().toString());
   
    String[] itemNames = getItemNamesByPlayerAndPlayerCommand(playerId, PlayerCommandTypeMapping.TRACKINFO);
    //move to utilities?
    for (String itemName : itemNames) {
      if (StringUtils.isNotBlank(itemName)) {
        eventPublisher.postUpdate(itemName, new StringType(newSong.getTitle().toString()));   
        logger.debug("Updated title: {} {}", itemName, newSong.getTitle().toString());
      }
    }
   
    itemNames = getItemNamesByPlayerAndPlayerCommand(playerId, PlayerCommandTypeMapping.TRACKARTIST);
    for (String itemName : itemNames) {
      if (StringUtils.isNotBlank(itemName)) {
        eventPublisher.postUpdate(itemName, new StringType(newSong.getArtist().toString()));   
        logger.debug("Updated artist: {}, {}", itemName, newSong.getArtist().toString());
      }
    }
  }
 
 
  /**
   * More advanced state detection - allows to detect track changes etc.
   * However, the underlying MPD library does not seem to support this well
   */
  public void playerChanged(PlayerChangeEvent pce) {
       

 
   
  }
 

  /**
   * Implementation of {@link VolumeChangeListener}. Posts the volume value
   * of the {@link VolumeChangeEvent} onto the internal event bus.
   *
   * @param vce the event which volume value is posted onto the internal event bus
   */
  public void volumeChanged(VolumeChangeEvent vce) {
    String playerId = findPlayerId(vce.getSource());
    logger.debug("Volume on {} changed to {}", playerId, vce.getVolume());
    String[] itemNames = getItemNamesByPlayerAndPlayerCommand(playerId, PlayerCommandTypeMapping.VOLUME);
    for (String itemName : itemNames) {   
      if (StringUtils.isNotBlank(itemName)) {
        eventPublisher.postUpdate(itemName, new PercentType(vce.getVolume()));           
      }
    }
  }
 
 
  /**
   * Handles MPD output change events.
   *
   * @param playerId the playerId which generated the <code>event</code>
   * @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<String> itemNames = new HashSet<String>();
    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<String> itemNames = new HashSet<String>();
    for (MpdBindingProvider provider : this.providers) {
      itemNames.addAll(Arrays.asList(
        provider.getItemNamesByPlayerAndPlayerCommand(playerId, playerCommand)));
    }
    return itemNames.toArray(new String[itemNames.size()]);
  }
 
 
  @SuppressWarnings("rawtypes")
  public void updated(Dictionary config) throws ConfigurationException {
    if (config != null) {
      disconnectPlayersAndMonitors();
      cancelScheduler();
     
      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;
        }
       
        Matcher matcher = EXTRACT_PLAYER_CONFIG_PATTERN.matcher(key);
        if (!matcher.matches()) {
          logger.debug("given mpd player-config-key '" + key + "' does not follow the expected pattern '<playername>.<host|port>'");
          continue;
        }
       
        matcher.reset();
        matcher.find();
       
        String playerId = matcher.group(1);
       
        MpdPlayerConfig playerConfig = playerConfigCache.get(playerId);
        if (playerConfig == null) {
          playerConfig = new MpdPlayerConfig();
          playerConfigCache.put(playerId, playerConfig);
        }
       
        String configKey = matcher.group(2);
        String value = (String) config.get(key);

        if ("host".equals(configKey)) {
          playerConfig.host = value;
        }
        else if ("port".equals(configKey)) {
          playerConfig.port = Integer.valueOf(value);
        }
        else if ("password".equals(configKey)) {
          playerConfig.password = value;
        }
        else {
          throw new ConfigurationException(
            configKey, "the given configKey '" + configKey + "' is unknown");
        }
       
      }
     
      connectAllPlayersAndMonitors();
      scheduleReconnect();
    }
  }


  private void scheduleReconnect() {
    Scheduler sched;
    try {
      sched = StdSchedulerFactory.getDefaultScheduler();
      JobDetail job = newJob(ReconnectJob.class)
          .withIdentity("Reconnect", MPD_SCHEDULER_GROUP)
          .build();
      CronTrigger trigger = newTrigger()
          .withIdentity("Reconnect", MPD_SCHEDULER_GROUP)
          .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?"))
          .build();
 
      sched.scheduleJob(job, trigger);
      logger.debug("Scheduled a daily MPD Reconnect of all MPDs");
    } catch (SchedulerException se) {
      logger.warn("scheduling MPD Reconnect failed", se);
    }
  }
 
  /**
   * Delete all quartz scheduler jobs of the group <code>MPD</code>.
   */
  private void cancelScheduler() {
    try {
      Scheduler sched = StdSchedulerFactory.getDefaultScheduler();
      Set<JobKey> jobKeys = sched.getJobKeys(jobGroupEquals(MPD_SCHEDULER_GROUP));
      if (jobKeys.size() > 0) {
        sched.deleteJobs(new ArrayList<JobKey>(jobKeys));
        logger.debug("Found {} jobs to delete from DefaulScheduler (keys={})", jobKeys.size(), jobKeys);
      }
    } catch (SchedulerException e) {
      logger.warn("Couldn't remove job: {}", e.getMessage());
    }   
  }
 
  /**
   * Connects to all configured {@link MPD}s
   */
  private void connectAllPlayersAndMonitors() {
    logger.debug("MPD binding connecting to players");
    for (String playerId : playerConfigCache.keySet()) {
      connect(playerId);
    }
  }
 
  /**
   * Connects the player <code>playerId</code> to the given <code>host</code>
   * and <code>port</code> and registers this binding as MPD-Event listener.
   *
   * @param playerId
   * @param host
   * @param port
   */
  private void connect(final String playerId) {
    MpdPlayerConfig config = null;
    try {
       
        config = playerConfigCache.get(playerId);
        if (config != null && config.instance == null) {
         
          MPD mpd = new MPD(config.host, config.port, config.password, CONNECTION_TIMEOUT);
         
            MPDStandAloneMonitor mpdStandAloneMonitor = new MPDStandAloneMonitor(mpd, 500);
              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();
           
          config.instance = mpd;
          config.monitor = mpdStandAloneMonitor;
         
          logger.debug("Connected to player '{}' with config {}", playerId, config);
        }
       
      }
    catch(MPDConnectionException ce) {
          logger.error("Error connecting to player '" + playerId + "' with config {}", config, ce);
      }
    catch (UnknownHostException uhe) {
        logger.error("Wrong connection details for player {}", playerId, uhe);
    }
  }

  /**
   * Disconnects all available {@link MPD}s and their {@link MPDStandAloneMonitor}-Thread.
   */
  private void disconnectPlayersAndMonitors() {
    for (String playerId : playerConfigCache.keySet()) {
      disconnect(playerId);
    }
  }

  /**
   * Disconnects the player <code>playerId</code>
   *
   * @param playerId the id of the player to disconnect from
   */
  private void disconnect(String playerId) {
        try {
      MpdPlayerConfig playerConfig = playerConfigCache.get(playerId);
      MPDStandAloneMonitor monitor = playerConfig.monitor;
      if (monitor != null) {
        monitor.stop();
      }
          MPD mpd = playerConfig.instance;
          if (mpd != null) {
            mpd.close();
          }
    } catch (MPDConnectionException ce) {
      logger.warn("couldn't disconnect player {}", playerId);
    } catch (MPDResponseException re) {
      logger.warn("received response error {}", re.getLocalizedMessage());
    }
  }
 
  /**
   * Reconnects to <code>playerId</code> that means disconnect first and try
   * to connect again.
   * 
   * @param playerId the id of the player to disconnect from
   */
  private void reconnect(String playerId) {
    logger.info("reconnect player {}", playerId);
    disconnect(playerId);
    connect(playerId);
  }
 
  /**
   * Reconnects all MPDs and Monitors. Meaning disconnect first and try
   * to connect again.
   */
  private void reconnectAllPlayerAndMonitors() {
    disconnectPlayersAndMonitors();
    connectAllPlayersAndMonitors();
  }

   
  /**
   * Internal data structure which carries the connection details of one
   * MPD (there could be several)
   * 
   * @author Thomas.Eichstaedt-Engelen
   */
  static class MpdPlayerConfig {
   
    String host;
    int port;
    String password;
   
    MPD instance;
    MPDStandAloneMonitor monitor;
   
    @Override
    public String toString() {
      return "MPD [host=" + host + ", port=" + port + ", password=" + password +"]";
    }
   
  }


  public void onClick(Command type) {
  }

  public void onDoubleClick(Command type) {
  }
 
 
  /**
   * A quartz scheduler job to simply do a reconnection to the MPDs.
   */
  public class ReconnectJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
      reconnectAllPlayerAndMonitors();
    }
  }

}
TOP

Related Classes of org.openhab.binding.mpd.internal.MpdBinding$MpdPlayerConfig

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.