From 88d13f0ac96af181a5e2c1bf12de2e56f23f03cc Mon Sep 17 00:00:00 2001
From: Brent <bah2830@users.noreply.github.com>
Date: Sun, 15 May 2016 15:00:31 -0500
Subject: [PATCH] Added support for the roku media player (#2046)

---
 .coveragerc                                   |   1 +
 homeassistant/components/discovery.py         |   4 +-
 .../components/media_player/__init__.py       |   1 +
 homeassistant/components/media_player/roku.py | 187 ++++++++++++++++++
 requirements_all.txt                          |   5 +-
 5 files changed, 196 insertions(+), 2 deletions(-)
 create mode 100644 homeassistant/components/media_player/roku.py

diff --git a/.coveragerc b/.coveragerc
index c6af4c298b9..7a3978d04cc 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -122,6 +122,7 @@ omit =
     homeassistant/components/media_player/panasonic_viera.py
     homeassistant/components/media_player/pioneer.py
     homeassistant/components/media_player/plex.py
+    homeassistant/components/media_player/roku.py
     homeassistant/components/media_player/samsungtv.py
     homeassistant/components/media_player/snapcast.py
     homeassistant/components/media_player/sonos.py
diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py
index 01211398f72..fac94aacf69 100644
--- a/homeassistant/components/discovery.py
+++ b/homeassistant/components/discovery.py
@@ -15,7 +15,7 @@ from homeassistant.const import (
     EVENT_PLATFORM_DISCOVERED)
 
 DOMAIN = "discovery"
-REQUIREMENTS = ['netdisco==0.6.6']
+REQUIREMENTS = ['netdisco==0.6.7']
 
 SCAN_INTERVAL = 300  # seconds
 
@@ -29,6 +29,7 @@ SERVICE_SONOS = 'sonos'
 SERVICE_PLEX = 'plex_mediaserver'
 SERVICE_SQUEEZEBOX = 'logitech_mediaserver'
 SERVICE_PANASONIC_VIERA = 'panasonic_viera'
+SERVICE_ROKU = 'roku'
 
 SERVICE_HANDLERS = {
     SERVICE_WEMO: "wemo",
@@ -39,6 +40,7 @@ SERVICE_HANDLERS = {
     SERVICE_PLEX: 'media_player',
     SERVICE_SQUEEZEBOX: 'media_player',
     SERVICE_PANASONIC_VIERA: 'media_player',
+    SERVICE_ROKU: 'media_player',
 }
 
 
diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py
index ff8eb8113b9..072a145129b 100644
--- a/homeassistant/components/media_player/__init__.py
+++ b/homeassistant/components/media_player/__init__.py
@@ -36,6 +36,7 @@ DISCOVERY_PLATFORMS = {
     discovery.SERVICE_PLEX: 'plex',
     discovery.SERVICE_SQUEEZEBOX: 'squeezebox',
     discovery.SERVICE_PANASONIC_VIERA: 'panasonic_viera',
+    discovery.SERVICE_ROKU: 'roku',
 }
 
 SERVICE_PLAY_MEDIA = 'play_media'
diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/media_player/roku.py
new file mode 100644
index 00000000000..3a196fe38d4
--- /dev/null
+++ b/homeassistant/components/media_player/roku.py
@@ -0,0 +1,187 @@
+"""
+Support for the roku media player.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/media_player.roku/
+"""
+
+import logging
+
+from homeassistant.components.media_player import (
+    MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA,
+    SUPPORT_PREVIOUS_TRACK, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+    SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
+
+from homeassistant.const import (
+    CONF_HOST, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN, STATE_HOME)
+
+REQUIREMENTS = [
+    'https://github.com/bah2830/python-roku/archive/3.1.1.zip'
+    '#python-roku==3.1.1']
+
+KNOWN_HOSTS = []
+DEFAULT_PORT = 8060
+
+_LOGGER = logging.getLogger(__name__)
+
+SUPPORT_ROKU = SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK |\
+    SUPPORT_PLAY_MEDIA | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
+    SUPPORT_SELECT_SOURCE
+
+
+# pylint: disable=abstract-method
+def setup_platform(hass, config, add_devices, discovery_info=None):
+    """Setup the Roku platform."""
+    hosts = []
+
+    if discovery_info and discovery_info in KNOWN_HOSTS:
+        return
+
+    if discovery_info is not None:
+        _LOGGER.debug('Discovered Roku: %s', discovery_info[0])
+        hosts.append(discovery_info[0])
+
+    elif CONF_HOST in config:
+        hosts.append(config[CONF_HOST])
+
+    rokus = []
+    for host in hosts:
+        rokus.append(RokuDevice(host))
+        KNOWN_HOSTS.append(host)
+
+    add_devices(rokus)
+
+
+class RokuDevice(MediaPlayerDevice):
+    """Representation of a Roku device on the network."""
+
+    # pylint: disable=abstract-method
+    # pylint: disable=too-many-public-methods
+    def __init__(self, host):
+        """Initialize the Roku device."""
+        from roku import Roku
+
+        self.roku = Roku(host)
+        self.update()
+
+    def update(self):
+        """Retrieve latest state."""
+        self.roku_name = "roku_" + self.roku.device_info.sernum
+        self.ip_address = self.roku.host
+        self.channels = self.get_source_list()
+
+        if self.roku.current_app is not None:
+            self.current_app = self.roku.current_app
+        else:
+            self.current_app = None
+
+    def get_source_list(self):
+        """Get the list of applications to be used as sources."""
+        return ["Home"] + sorted(channel.name for channel in self.roku.apps)
+
+    @property
+    def should_poll(self):
+        """Device should be polled."""
+        return True
+
+    @property
+    def name(self):
+        """Return the name of the device."""
+        return self.roku_name
+
+    @property
+    def state(self):
+        """Return the state of the device."""
+        if self.current_app.name in ["Power Saver", "Default screensaver"]:
+            return STATE_IDLE
+        elif self.current_app.name == "Roku":
+            return STATE_HOME
+        elif self.current_app.name is not None:
+            return STATE_PLAYING
+
+        return STATE_UNKNOWN
+
+    @property
+    def supported_media_commands(self):
+        """Flag of media commands that are supported."""
+        return SUPPORT_ROKU
+
+    @property
+    def media_content_type(self):
+        """Content type of current playing media."""
+        if self.current_app is None:
+            return None
+        elif self.current_app.name == "Power Saver":
+            return None
+        elif self.current_app.name == "Roku":
+            return None
+        else:
+            return MEDIA_TYPE_VIDEO
+
+    @property
+    def media_image_url(self):
+        """Image url of current playing media."""
+        if self.current_app is None:
+            return None
+        elif self.current_app.name == "Roku":
+            return None
+        elif self.current_app.name == "Power Saver":
+            return None
+        elif self.current_app.id is None:
+            return None
+
+        return 'http://{0}:{1}/query/icon/{2}'.format(self.ip_address,
+                                                      DEFAULT_PORT,
+                                                      self.current_app.id)
+
+    @property
+    def app_name(self):
+        """Name of the current running app."""
+        return self.current_app.name
+
+    @property
+    def app_id(self):
+        """Return the ID of the current running app."""
+        return self.current_app.id
+
+    @property
+    def source(self):
+        """Return the current input source."""
+        return self.current_app.name
+
+    @property
+    def source_list(self):
+        """List of available input sources."""
+        return self.channels
+
+    def media_play_pause(self):
+        """Send play/pause command."""
+        self.roku.play()
+
+    def media_previous_track(self):
+        """Send previous track command."""
+        self.roku.reverse()
+
+    def media_next_track(self):
+        """Send next track command."""
+        self.roku.forward()
+
+    def mute_volume(self, mute):
+        """Mute the volume."""
+        self.roku.volume_mute()
+
+    def volume_up(self):
+        """Volume up media player."""
+        self.roku.volume_up()
+
+    def volume_down(self):
+        """Volume down media player."""
+        self.roku.volume_down()
+
+    def select_source(self, source):
+        """Select input source."""
+        if source == "Home":
+            self.roku.home()
+        else:
+            channel = self.roku[source]
+            channel.launch()
diff --git a/requirements_all.txt b/requirements_all.txt
index 60461a5eddf..90d88e0bcae 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -97,6 +97,9 @@ https://github.com/TheRealLink/pythinkingcleaner/archive/v0.0.2.zip#pythinkingcl
 # homeassistant.components.alarm_control_panel.alarmdotcom
 https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1
 
+# homeassistant.components.media_player.roku
+https://github.com/bah2830/python-roku/archive/3.1.1.zip#python-roku==3.1.1
+
 # homeassistant.components.modbus
 https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0
 
@@ -156,7 +159,7 @@ messagebird==1.2.0
 mficlient==0.3.0
 
 # homeassistant.components.discovery
-netdisco==0.6.6
+netdisco==0.6.7
 
 # homeassistant.components.sensor.neurio_energy
 neurio==0.2.10
-- 
GitLab