Skip to content

Commit

Permalink
Merge fef12a5 into caf0751
Browse files Browse the repository at this point in the history
  • Loading branch information
soberstadt committed Jan 11, 2019
2 parents caf0751 + fef12a5 commit 95d3aee
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 59 deletions.
4 changes: 3 additions & 1 deletion .coveragerc
Expand Up @@ -308,6 +308,9 @@ omit =
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py

homeassistant/components/roku.py
homeassistant/components/*/roku.py

homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py

Expand Down Expand Up @@ -637,7 +640,6 @@ omit =
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/pjlink.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/russound_rio.py
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/snapcast.py
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/discovery.py
Expand Up @@ -47,6 +47,7 @@
SERVICE_FREEBOX = 'freebox'
SERVICE_IGD = 'igd'
SERVICE_DLNA_DMR = 'dlna_dmr'
SERVICE_ROKU = 'roku'

CONFIG_ENTRY_HANDLERS = {
SERVICE_DAIKIN: 'daikin',
Expand All @@ -67,6 +68,7 @@
SERVICE_HASSIO: ('hassio', None),
SERVICE_AXIS: ('axis', None),
SERVICE_APPLE_TV: ('apple_tv', None),
SERVICE_ROKU: ('roku', None),
SERVICE_WINK: ('wink', None),
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
SERVICE_SABNZBD: ('sabnzbd', None),
Expand All @@ -76,7 +78,6 @@
SERVICE_FREEBOX: ('freebox', None),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
'roku': ('media_player', 'roku'),
'yamaha': ('media_player', 'yamaha'),
'logitech_mediaserver': ('media_player', 'squeezebox'),
'directv': ('media_player', 'directv'),
Expand Down
66 changes: 10 additions & 56 deletions homeassistant/components/media_player/roku.py
@@ -1,79 +1,38 @@
"""
Support for the roku media player.
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

import voluptuous as vol
import requests.exceptions

from homeassistant.components.media_player import (
MEDIA_TYPE_MOVIE, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PLAY,
MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK, SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, STATE_HOME, STATE_IDLE, STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['python-roku==3.1.5']
DEPENDENCIES = ['roku']

KNOWN_HOSTS = []
DEFAULT_PORT = 8060

NOTIFICATION_ID = 'roku_notification'
NOTIFICATION_TITLE = 'Roku Media Player Setup'

_LOGGER = logging.getLogger(__name__)

SUPPORT_ROKU = SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK |\
SUPPORT_PLAY_MEDIA | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
})


def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the Roku platform."""
hosts = []

if discovery_info:
host = discovery_info.get('host')
if not discovery_info:
return

if host in KNOWN_HOSTS:
return

_LOGGER.debug("Discovered Roku: %s", host)
hosts.append(discovery_info.get('host'))

elif CONF_HOST in config:
hosts.append(config.get(CONF_HOST))

rokus = []
for host in hosts:
new_roku = RokuDevice(host)

try:
if new_roku.name is not None:
rokus.append(RokuDevice(host))
KNOWN_HOSTS.append(host)
else:
_LOGGER.error("Unable to initialize roku at %s", host)

except AttributeError:
_LOGGER.error("Unable to initialize roku at %s", host)
hass.components.persistent_notification.create(
'Error: Unable to initialize roku at {}<br />'
'Check its network connection or consider '
'using auto discovery.<br />'
'You will need to restart hass after fixing.'
''.format(config.get(CONF_HOST)),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)

add_entities(rokus)
host = discovery_info[CONF_HOST]
async_add_entities([RokuDevice(host)], True)


class RokuDevice(MediaPlayerDevice):
Expand All @@ -89,12 +48,8 @@ def __init__(self, host):
self.current_app = None
self._device_info = {}

self.update()

def update(self):
"""Retrieve latest state."""
import requests.exceptions

try:
self._device_info = self.roku.device_info
self.ip_address = self.roku.host
Expand All @@ -106,7 +61,6 @@ def update(self):
self.current_app = None
except (requests.exceptions.ConnectionError,
requests.exceptions.ReadTimeout):

pass

def get_source_list(self):
Expand Down
72 changes: 72 additions & 0 deletions homeassistant/components/remote/roku.py
@@ -0,0 +1,72 @@
"""
Support for the Roku remote.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/remote.roku/
"""
import requests.exceptions

from homeassistant.components import remote
from homeassistant.const import (CONF_HOST)


DEPENDENCIES = ['roku']


async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Roku remote platform."""
if not discovery_info:
return

host = discovery_info[CONF_HOST]
async_add_entities([RokuRemote(host)], True)


class RokuRemote(remote.RemoteDevice):
"""Device that sends commands to an Roku."""

def __init__(self, host):
"""Initialize the Roku device."""
from roku import Roku

self.roku = Roku(host)
self._device_info = {}

def update(self):
"""Retrieve latest state."""
try:
self._device_info = self.roku.device_info
except (requests.exceptions.ConnectionError,
requests.exceptions.ReadTimeout):
pass

@property
def name(self):
"""Return the name of the device."""
if self._device_info.userdevicename:
return self._device_info.userdevicename
return "Roku {}".format(self._device_info.sernum)

@property
def unique_id(self):
"""Return a unique ID."""
return self._device_info.sernum

@property
def is_on(self):
"""Return true if device is on."""
return True

@property
def should_poll(self):
"""No polling needed for Roku."""
return False

def send_command(self, command, **kwargs):
"""Send a command to one device."""
for single_command in command:
if not hasattr(self.roku, single_command):
continue

getattr(self.roku, single_command)()
115 changes: 115 additions & 0 deletions homeassistant/components/roku.py
@@ -0,0 +1,115 @@
"""
Support for Roku platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/roku/
"""
import logging

import voluptuous as vol

from homeassistant.components.discovery import SERVICE_ROKU
from homeassistant.const import CONF_HOST
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['python-roku==3.1.5']

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'roku'

SERVICE_SCAN = 'roku_scan'

ATTR_ROKU = 'roku'

DATA_ROKU = 'data_roku'

NOTIFICATION_ID = 'roku_notification'
NOTIFICATION_TITLE = 'Roku Setup'
NOTIFICATION_SCAN_ID = 'roku_scan_notification'
NOTIFICATION_SCAN_TITLE = 'Roku Scan'


CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string
})])
}, extra=vol.ALLOW_EXTRA)

# Currently no attributes but it might change later
ROKU_SCAN_SCHEMA = vol.Schema({})


def setup(hass, config):
"""Set up the Roku component."""
hass.data[DATA_ROKU] = {}

def service_handler(service):
"""Handle service calls."""
if service.service == SERVICE_SCAN:
scan_for_rokus(hass)

def roku_discovered(service, info):
"""Set up an Roku that was auto discovered."""
_setup_roku(hass, config, {
CONF_HOST: info['host']
})

discovery.listen(hass, SERVICE_ROKU, roku_discovered)

for conf in config.get(DOMAIN, []):
_setup_roku(hass, config, conf)

hass.services.register(
DOMAIN, SERVICE_SCAN, service_handler,
schema=ROKU_SCAN_SCHEMA)

return True


def scan_for_rokus(hass):
"""Scan for devices and present a notification of the ones found."""
from roku import Roku, RokuException
rokus = Roku.discover()

devices = []
for roku in rokus:
try:
r_info = roku.device_info
except RokuException: # skip non-roku device
continue
devices.append('Name: {0}<br />Host: {1}<br />'.format(
r_info.userdevicename if r_info.userdevicename
else "{} {}".format(r_info.modelname, r_info.sernum),
roku.host))
if not devices:
devices = ['No device(s) found']

hass.components.persistent_notification.create(
'The following devices were found:<br /><br />' +
'<br /><br />'.join(devices),
title=NOTIFICATION_SCAN_TITLE,
notification_id=NOTIFICATION_SCAN_ID)


def _setup_roku(hass, hass_config, roku_config):
"""Set up a Roku."""
from roku import Roku
host = roku_config[CONF_HOST]

if host in hass.data[DATA_ROKU]:
return

roku = Roku(host)
r_info = roku.device_info

hass.data[DATA_ROKU][host] = {
ATTR_ROKU: r_info.sernum
}

discovery.load_platform(
hass, 'media_player', DOMAIN, roku_config, hass_config)

discovery.load_platform(
hass, 'remote', DOMAIN, roku_config, hass_config)
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -1304,7 +1304,7 @@ python-qbittorrent==0.3.1
# homeassistant.components.sensor.ripple
python-ripple-api==0.0.3

# homeassistant.components.media_player.roku
# homeassistant.components.roku
python-roku==3.1.5

# homeassistant.components.sensor.sochain
Expand Down

0 comments on commit 95d3aee

Please sign in to comment.