diff --git a/.coveragerc b/.coveragerc
index aabeda5ad818b54242d3fc4a86d24b906c77cbf9..d7792e56d8a1b9c548a572f8fcef3a502e67d540 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -129,6 +129,7 @@ omit =
     homeassistant/components/media_player/mpd.py
     homeassistant/components/media_player/onkyo.py
     homeassistant/components/media_player/panasonic_viera.py
+    homeassistant/components/media_player/pandora.py
     homeassistant/components/media_player/pioneer.py
     homeassistant/components/media_player/plex.py
     homeassistant/components/media_player/roku.py
diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py
new file mode 100644
index 0000000000000000000000000000000000000000..629a2ed2ba99c5317f23816d4dae153ad13bd6a8
--- /dev/null
+++ b/homeassistant/components/media_player/pandora.py
@@ -0,0 +1,337 @@
+"""
+Component for controlling Pandora stations through the pianobar client.
+
+For more details about this platform, please refer to the documentation
+https://home-assistant.io/components/media_player.pandora/
+"""
+
+import logging
+import re
+import os
+import signal
+from datetime import timedelta
+import shutil
+
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
+from homeassistant.components.media_player import (
+    SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
+    SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+    SUPPORT_SELECT_SOURCE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PLAY_PAUSE,
+    SERVICE_MEDIA_PLAY, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
+    MediaPlayerDevice)
+from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
+                                 STATE_IDLE)
+from homeassistant import util
+
+REQUIREMENTS = ['pexpect==4.0.1']
+_LOGGER = logging.getLogger(__name__)
+
+# SUPPORT_VOLUME_SET is close to available but we need volume up/down
+# controls in the GUI.
+PANDORA_SUPPORT = \
+    SUPPORT_PAUSE | \
+    SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_NEXT_TRACK | \
+    SUPPORT_SELECT_SOURCE
+
+CMD_MAP = {SERVICE_MEDIA_NEXT_TRACK: 'n',
+           SERVICE_MEDIA_PLAY_PAUSE: 'p',
+           SERVICE_MEDIA_PLAY: 'p',
+           SERVICE_VOLUME_UP: ')',
+           SERVICE_VOLUME_DOWN: '('}
+MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=2)
+CURRENT_SONG_PATTERN = re.compile(r'"(.*?)"\s+by\s+"(.*?)"\son\s+"(.*?)"',
+                                  re.MULTILINE)
+STATION_PATTERN = re.compile(r'Station\s"(.+?)"', re.MULTILINE)
+
+
+# pylint: disable=unused-argument
+def setup_platform(hass, config, add_devices, discovery_info=None):
+    """Setup the media player pandora platform."""
+    if not _pianobar_exists():
+        return False
+    pandora = PandoraMediaPlayer('Pandora')
+
+    # make sure we end the pandora subprocess on exit in case user doesn't
+    # power it down.
+    def _stop_pianobar(_event):
+        pandora.turn_off()
+
+    hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _stop_pianobar)
+    add_devices([pandora])
+
+
+# pylint: disable=too-many-instance-attributes
+class PandoraMediaPlayer(MediaPlayerDevice):
+    """A media player that uses the Pianobar interface to Pandora."""
+
+    # pylint: disable=abstract-method
+    def __init__(self, name):
+        """Initialize the demo device."""
+        MediaPlayerDevice.__init__(self)
+        self._name = name
+        self._player_state = STATE_OFF
+        self._station = ''
+        self._media_title = ''
+        self._media_artist = ''
+        self._media_album = ''
+        self._stations = []
+        self._time_remaining = 0
+        self._media_duration = 0
+        self._pianobar = None
+
+    @property
+    def should_poll(self):
+        """Should be polled for current state."""
+        return True
+
+    @property
+    def name(self):
+        """Return the name of the media player."""
+        return self._name
+
+    @property
+    def state(self):
+        """Return the state of the player."""
+        return self._player_state
+
+    def turn_on(self):
+        """Turn the media player on."""
+        import pexpect
+        self._pianobar = pexpect.spawn('pianobar')
+        _LOGGER.info('Started pianobar subprocess')
+        mode = self._pianobar.expect(['Receiving new playlist',
+                                      'Select station:'])
+        if mode == 1:
+            # station list was presented. dismiss it.
+            self._pianobar.sendcontrol('m')
+
+        self._update_stations()
+        self.update_playing_status()
+
+        self._player_state = STATE_IDLE
+        self.update_ha_state()
+
+    def turn_off(self):
+        """Turn the media player off."""
+        import pexpect
+        if self._pianobar is None:
+            _LOGGER.info('Pianobar subprocess already stopped')
+            return
+        self._pianobar.send('q')
+        try:
+            _LOGGER.info('Stopped Pianobar subprocess')
+        except pexpect.exceptions.TIMEOUT:
+            # kill the process group
+            os.killpg(os.getpgid(self._pianobar.pid), signal.SIGTERM)
+            _LOGGER.info('Killed Pianobar subprocess')
+        self._pianobar = None
+        self._player_state = STATE_OFF
+        self.update_ha_state()
+
+    def media_play(self):
+        """Send play command."""
+        self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
+        self._player_state = STATE_PLAYING
+        self.update_ha_state()
+
+    def media_pause(self):
+        """Send pause command."""
+        self._send_pianobar_command(SERVICE_MEDIA_PLAY_PAUSE)
+        self._player_state = STATE_PAUSED
+        self.update_ha_state()
+
+    def media_next_track(self):
+        """Go to next track."""
+        self._send_pianobar_command(SERVICE_MEDIA_NEXT_TRACK)
+        self.update_ha_state()
+
+    @property
+    def supported_media_commands(self):
+        """Show what this supports."""
+        return PANDORA_SUPPORT
+
+    @property
+    def source(self):
+        """Name of the current input source."""
+        return self._station
+
+    @property
+    def source_list(self):
+        """List of available input sources."""
+        return self._stations
+
+    @property
+    def media_title(self):
+        """Title of current playing media."""
+        self.update_playing_status()
+        return self._media_title
+
+    @property
+    def media_artist(self):
+        """Artist of current playing media, music track only."""
+        return self._media_artist
+
+    @property
+    def media_album_name(self):
+        """Album name of current playing media, music track only."""
+        return self._media_album
+
+    @property
+    def media_duration(self):
+        """Duration of current playing media in seconds."""
+        return self._media_duration
+
+    def select_source(self, source):
+        """Choose a different Pandora station and play it."""
+        try:
+            station_index = self._stations.index(source)
+        except ValueError:
+            _LOGGER.warning('Station `%s` is not in list', source)
+            return
+        _LOGGER.info('Setting station %s, %d', source, station_index)
+        self._pianobar.send('s')
+        self._pianobar.expect('Select station')
+        self._pianobar.sendline('{}'.format(station_index))
+        self._pianobar.expect('\r\n')
+        self._player_state = STATE_PLAYING
+
+    def update_playing_status(self):
+        """Query pianobar for info about current media_title, station."""
+        response = self._query_for_playing_status()
+        if not response:
+            return
+        self._update_current_station(response)
+        self._update_current_song(response)
+        self._update_song_position()
+
+    def _query_for_playing_status(self):
+        """Query system for info about current track."""
+        import pexpect
+        self._clear_buffer()
+        self._pianobar.send('i')
+        try:
+            match_idx = self._pianobar.expect([br'(\d\d):(\d\d)/(\d\d):(\d\d)',
+                                               'No song playing',
+                                               'Select station',
+                                               'Receiving new playlist'])
+        except pexpect.exceptions.EOF:
+            _LOGGER.info('Pianobar process already exited.')
+            return None
+
+        self._log_match()
+        if match_idx == 1:
+            # idle.
+            response = None
+        elif match_idx == 2:
+            # stuck on a station selection dialog. Clear it.
+            _LOGGER.warning('On unexpected station list page.')
+            self._pianobar.sendcontrol('m')  # press enter
+            self._pianobar.sendcontrol('m')  # do it again b/c an 'i' got in
+            response = self.update_playing_status()
+        elif match_idx == 3:
+            _LOGGER.debug('Received new playlist list.')
+            response = self.update_playing_status()
+        else:
+            response = self._pianobar.before.decode('utf-8')
+        return response
+
+    def _update_current_station(self, response):
+        """Update current station."""
+        station_match = re.search(STATION_PATTERN, response)
+        if station_match:
+            self._station = station_match.group(1)
+            _LOGGER.debug('Got station as: %s', self._station)
+        else:
+            _LOGGER.warning('No station match. ')
+
+    def _update_current_song(self, response):
+        """Update info about current song."""
+        song_match = re.search(CURRENT_SONG_PATTERN, response)
+        if song_match:
+            (self._media_title, self._media_artist,
+             self._media_album) = song_match.groups()
+            _LOGGER.debug('Got song as: %s', self._media_title)
+        else:
+            _LOGGER.warning('No song match.')
+
+    @util.Throttle(MIN_TIME_BETWEEN_UPDATES)
+    def _update_song_position(self):
+        """
+        Get the song position and duration.
+
+        It's hard to predict whether or not the music will start during init
+        so we have to detect state by checking the ticker.
+
+        """
+        (cur_minutes, cur_seconds,
+         total_minutes, total_seconds) = self._pianobar.match.groups()
+        time_remaining = int(cur_minutes) * 60 + int(cur_seconds)
+        self._media_duration = int(total_minutes) * 60 + int(total_seconds)
+
+        if time_remaining != self._time_remaining:
+            _LOGGER.debug('PLAYING')
+            self._player_state = STATE_PLAYING
+        elif self._player_state == STATE_PLAYING:
+            _LOGGER.debug('PAUSED')
+            self._player_state = STATE_PAUSED
+        self._time_remaining = time_remaining
+
+    def _log_match(self):
+        """Log grabbed values from console."""
+        _LOGGER.debug('Before: %s\nMatch: %s\nAfter: %s',
+                      repr(self._pianobar.before),
+                      repr(self._pianobar.match),
+                      repr(self._pianobar.after))
+
+    def _send_pianobar_command(self, service_cmd):
+        """Send a command to Pianobar."""
+        command = CMD_MAP.get(service_cmd)
+        if command is None:
+            _LOGGER.info('Command %s not supported yet', service_cmd)
+        self._pianobar.sendline(command)
+
+    def _update_stations(self):
+        """List defined Pandora stations."""
+        self._pianobar.send('s')
+        self._pianobar.expect('Select station:')
+        station_lines = self._pianobar.before.decode('utf-8')
+        _LOGGER.debug('Getting stations: %s', station_lines)
+        self._stations = []
+        for line in station_lines.split('\r\n'):
+            match = re.search(r'\d+\).....(.+)', line)
+            if match:
+                station = match.group(1).strip()
+                _LOGGER.debug('Found station %s', station)
+                self._stations.append(station)
+            else:
+                _LOGGER.debug('No station match on `%s`', line)
+        self._pianobar.sendcontrol('m')  # press enter with blank line
+        self._pianobar.sendcontrol('m')  # do it twice in case an 'i' got in
+
+    def _clear_buffer(self):
+        """
+        Clear buffer from pexpect.
+
+        This is necessary because there are a bunch of 00:00 in the buffer
+
+        """
+        import pexpect
+        try:
+            while not self._pianobar.expect('.+', timeout=0.1):
+                pass
+        except pexpect.exceptions.TIMEOUT:
+            pass
+
+
+def _pianobar_exists():
+    """Verify that Pianobar is properly installed."""
+    pianobar_exe = shutil.which('pianobar')
+    if pianobar_exe:
+        return True
+    else:
+        _LOGGER.warning('The Pandora component depends on the Pianobar '
+                        'client, which cannot be found. Please install '
+                        'using instructions at'
+                        'https://home-assistant.io'
+                        '/components/media_player.pandora/')
+        return False
diff --git a/requirements_all.txt b/requirements_all.txt
index a2aec136dbb405ef878148fee738d05d1e6f1ad9..d112e3944b60e23e90143a379d15aedfa0897379 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -195,6 +195,7 @@ panasonic_viera==0.2
 
 # homeassistant.components.device_tracker.aruba
 # homeassistant.components.device_tracker.asuswrt
+# homeassistant.components.media_player.pandora
 pexpect==4.0.1
 
 # homeassistant.components.light.hue