Merge branch 'pr/1643' into dev

Conflicts:
	requirements_all.txt
This commit is contained in:
Paulus Schoutsen 2016-03-29 23:23:45 -07:00
commit 5b00919bed
10 changed files with 254 additions and 4 deletions

View File

@ -107,6 +107,7 @@ omit =
homeassistant/components/media_player/snapcast.py homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/yamaha.py homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/free_mobile.py homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/googlevoice.py homeassistant/components/notify/googlevoice.py

View File

@ -35,6 +35,7 @@ DISCOVERY_PLATFORMS = {
} }
SERVICE_PLAY_MEDIA = 'play_media' SERVICE_PLAY_MEDIA = 'play_media'
SERVICE_SELECT_SOURCE = 'select_source'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted' ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
@ -55,6 +56,7 @@ ATTR_MEDIA_PLAYLIST = 'media_playlist'
ATTR_APP_ID = 'app_id' ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name' ATTR_APP_NAME = 'app_name'
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands' ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
ATTR_INPUT_SOURCE = 'source'
MEDIA_TYPE_MUSIC = 'music' MEDIA_TYPE_MUSIC = 'music'
MEDIA_TYPE_TVSHOW = 'tvshow' MEDIA_TYPE_TVSHOW = 'tvshow'
@ -74,6 +76,7 @@ SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256 SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512 SUPPORT_PLAY_MEDIA = 512
SUPPORT_VOLUME_STEP = 1024 SUPPORT_VOLUME_STEP = 1024
SUPPORT_SELECT_SOURCE = 2048
SERVICE_TO_METHOD = { SERVICE_TO_METHOD = {
SERVICE_TURN_ON: 'turn_on', SERVICE_TURN_ON: 'turn_on',
@ -87,6 +90,7 @@ SERVICE_TO_METHOD = {
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track', SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track', SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
SERVICE_PLAY_MEDIA: 'play_media', SERVICE_PLAY_MEDIA: 'play_media',
SERVICE_SELECT_SOURCE: 'select_source',
} }
ATTR_TO_PROPERTY = [ ATTR_TO_PROPERTY = [
@ -108,6 +112,7 @@ ATTR_TO_PROPERTY = [
ATTR_APP_ID, ATTR_APP_ID,
ATTR_APP_NAME, ATTR_APP_NAME,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_SUPPORTED_MEDIA_COMMANDS,
ATTR_INPUT_SOURCE,
] ]
@ -220,6 +225,16 @@ def play_media(hass, media_type, media_id, entity_id=None):
hass.services.call(DOMAIN, SERVICE_PLAY_MEDIA, data) hass.services.call(DOMAIN, SERVICE_PLAY_MEDIA, data)
def select_source(hass, source, entity_id=None):
"""Send the media player the command to select input source."""
data = {ATTR_INPUT_SOURCE: source}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SELECT_SOURCE, data)
def setup(hass, config): def setup(hass, config):
"""Track states and offer events for media_players.""" """Track states and offer events for media_players."""
component = EntityComponent( component = EntityComponent(
@ -302,6 +317,26 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service, hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service,
descriptions.get(SERVICE_MEDIA_SEEK)) descriptions.get(SERVICE_MEDIA_SEEK))
def select_source_service(service):
"""Change input to selected source."""
input_source = service.data.get(ATTR_INPUT_SOURCE)
if input_source is None:
_LOGGER.error(
'Received call to %s without attribute %s',
service.service, ATTR_INPUT_SOURCE)
return
for player in component.extract_from_service(service):
player.select_source(input_source)
if player.should_poll:
player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_SELECT_SOURCE,
select_source_service,
descriptions.get(SERVICE_SELECT_SOURCE))
def play_media_service(service): def play_media_service(service):
"""Play specified media_id on the media player.""" """Play specified media_id on the media player."""
media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE) media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
@ -430,6 +465,11 @@ class MediaPlayerDevice(Entity):
"""Name of the current running app.""" """Name of the current running app."""
return None return None
@property
def source(self):
"""Name of the current input source."""
return None
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag media commands that are supported.""" """Flag media commands that are supported."""
@ -475,6 +515,10 @@ class MediaPlayerDevice(Entity):
"""Play a piece of media.""" """Play a piece of media."""
raise NotImplementedError() raise NotImplementedError()
def select_source(self, source):
"""Select input source."""
raise NotImplementedError()
# No need to overwrite these. # No need to overwrite these.
@property @property
def support_pause(self): def support_pause(self):
@ -511,6 +555,11 @@ class MediaPlayerDevice(Entity):
"""Boolean if play media command supported.""" """Boolean if play media command supported."""
return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA) return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA)
@property
def support_select_source(self):
"""Boolean if select source command supported."""
return bool(self.supported_media_commands & SUPPORT_SELECT_SOURCE)
def toggle(self): def toggle(self):
"""Toggle the power on the media player.""" """Toggle the power on the media player."""
if self.state in [STATE_OFF, STATE_IDLE]: if self.state in [STATE_OFF, STATE_IDLE]:

View File

@ -8,7 +8,7 @@ from homeassistant.components.media_player import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
MediaPlayerDevice) SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
@ -20,7 +20,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
'Living Room', 'eyU3bRy2x44', 'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)'), '♥♥ The Best Fireplace Video (3 hours)'),
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'), DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'),
DemoMusicPlayer(), DemoTVShowPlayer(), DemoMusicPlayer(), DemoTVShowPlayer(), DemoReceiver(),
]) ])
@ -37,6 +37,8 @@ MUSIC_PLAYER_SUPPORT = \
NETFLIX_PLAYER_SUPPORT = \ NETFLIX_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
RECEIVER_SUPPORT = SUPPORT_SELECT_SOURCE
class AbstractDemoPlayer(MediaPlayerDevice): class AbstractDemoPlayer(MediaPlayerDevice):
"""A demo media players.""" """A demo media players."""
@ -337,3 +339,29 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
if self._cur_episode < self._episode_count: if self._cur_episode < self._episode_count:
self._cur_episode += 1 self._cur_episode += 1
self.update_ha_state() self.update_ha_state()
class DemoReceiver(AbstractDemoPlayer):
"""A Demo receiver that only supports changing source input."""
# We only implement the methods that we support
# pylint: disable=abstract-method
def __init__(self):
"""Initialize the demo device."""
super().__init__('receiver')
self._source = 'dvd'
@property
def source(self):
"""Return the current input source."""
return self._source
def select_source(self, source):
"""Set the input source."""
self._source = source
self.update_ha_state()
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return RECEIVER_SUPPORT

View File

@ -0,0 +1,111 @@
"""
Support for Onkyo Receivers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.onkyo/
"""
import logging
from homeassistant.components.media_player import (
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import STATE_OFF, STATE_ON
REQUIREMENTS = ['https://github.com/danieljkemp/onkyo-eiscp/archive/'
'python3.zip#onkyo-eiscp==0.9.2']
_LOGGER = logging.getLogger(__name__)
SUPPORT_ONKYO = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Onkyo platform."""
from eiscp import eISCP
add_devices(OnkyoDevice(receiver)
for receiver in eISCP.discover())
class OnkyoDevice(MediaPlayerDevice):
"""Representation of a Onkyo device."""
# pylint: disable=too-many-public-methods, abstract-method
def __init__(self, receiver):
"""Initialize the Onkyo Receiver."""
self._receiver = receiver
self._muted = False
self._volume = 0
self._pwstate = STATE_OFF
self.update()
self._name = '{}_{}'.format(
receiver.info['model_name'], receiver.info['identifier'])
self._current_source = None
def update(self):
"""Get the latest details from the device."""
status = self._receiver.command('system-power query')
if status[1] == 'on':
self._pwstate = STATE_ON
else:
self._pwstate = STATE_OFF
return
volume_raw = self._receiver.command('volume query')
mute_raw = self._receiver.command('audio-muting query')
current_source_raw = self._receiver.command('input-selector query')
self._current_source = '_'.join('_'.join(
[i for i in current_source_raw[1]]))
self._muted = bool(mute_raw[1] == 'on')
self._volume = int(volume_raw[1], 16)/80.0
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._pwstate
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._muted
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_ONKYO
@property
def source(self):
""""Return the current input source of the device."""
return self._current_source
def turn_off(self):
"""Turn off media player."""
self._receiver.command('system-power standby')
def set_volume_level(self, volume):
"""Set volume level, input is range 0..1. Onkyo ranges from 1-80."""
self._receiver.command('volume {}'.format(int(volume*80)))
def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player."""
if mute:
self._receiver.command('audio-muting on')
else:
self._receiver.command('audio-muting off')
def turn_on(self):
"""Turn the media player on."""
self._receiver.power_on()
def select_source(self, source):
"""Set the input source."""
self._receiver.command('input-selector {}'.format(source))

View File

@ -126,3 +126,14 @@ play_media:
media_content_type: media_content_type:
description: The type of the content to play. Must be one of MUSIC, TVSHOW, VIDEO, EPISODE, CHANNEL or PLAYLIST description: The type of the content to play. Must be one of MUSIC, TVSHOW, VIDEO, EPISODE, CHANNEL or PLAYLIST
example: 'MUSIC' example: 'MUSIC'
select_source:
description: Send the media player the command to change input source.
fields:
entity_id:
description: Name(s) of entites to change source on
example: 'media_player.media_player.txnr535_0009b0d81f82'
source:
description: Name of the source to switch to. Platform dependent.
example: 'video1'

View File

@ -18,7 +18,8 @@ from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED,
ATTR_SUPPORTED_MEDIA_COMMANDS, DOMAIN, SERVICE_PLAY_MEDIA, ATTR_SUPPORTED_MEDIA_COMMANDS, DOMAIN, SERVICE_PLAY_MEDIA,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP, MediaPlayerDevice) SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, ATTR_INPUT_SOURCE,
SERVICE_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK, ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE,
@ -321,6 +322,11 @@ class UniversalMediaPlayer(MediaPlayerDevice):
"""Name of the current running app.""" """Name of the current running app."""
return self._child_attr(ATTR_APP_NAME) return self._child_attr(ATTR_APP_NAME)
@property
def current_source(self):
""""Return the current input source of the device."""
return self._child_attr(ATTR_INPUT_SOURCE)
@property @property
def supported_media_commands(self): def supported_media_commands(self):
"""Flag media commands that are supported.""" """Flag media commands that are supported."""
@ -340,6 +346,9 @@ class UniversalMediaPlayer(MediaPlayerDevice):
ATTR_MEDIA_VOLUME_MUTED in self._attrs: ATTR_MEDIA_VOLUME_MUTED in self._attrs:
flags |= SUPPORT_VOLUME_MUTE flags |= SUPPORT_VOLUME_MUTE
if SUPPORT_SELECT_SOURCE in self._cmds:
flags |= SUPPORT_SELECT_SOURCE
return flags return flags
@property @property
@ -406,6 +415,11 @@ class UniversalMediaPlayer(MediaPlayerDevice):
"""Play or pause the media player.""" """Play or pause the media player."""
self._call_service(SERVICE_MEDIA_PLAY_PAUSE) self._call_service(SERVICE_MEDIA_PLAY_PAUSE)
def select_source(self, source):
"""Set the input source."""
data = {ATTR_INPUT_SOURCE: source}
self._call_service(SERVICE_SELECT_SOURCE, data)
def update(self): def update(self):
"""Update state in HA.""" """Update state in HA."""
for child_name in self._children: for child_name in self._children:

View File

@ -6,7 +6,8 @@ from collections import defaultdict
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_SEEK_POSITION,
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, SERVICE_PLAY_MEDIA) ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOURCE, ATTR_INPUT_SOURCE)
from homeassistant.components.notify import ( from homeassistant.components.notify import (
ATTR_MESSAGE, SERVICE_NOTIFY) ATTR_MESSAGE, SERVICE_NOTIFY)
from homeassistant.components.sun import ( from homeassistant.components.sun import (
@ -42,6 +43,7 @@ SERVICE_ATTRIBUTES = {
SERVICE_SET_AWAY_MODE: [ATTR_AWAY_MODE], SERVICE_SET_AWAY_MODE: [ATTR_AWAY_MODE],
SERVICE_SET_FAN_MODE: [ATTR_FAN], SERVICE_SET_FAN_MODE: [ATTR_FAN],
SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE], SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE],
SERVICE_SELECT_SOURCE: [ATTR_INPUT_SOURCE],
} }
# Update this dict when new services are added to HA. # Update this dict when new services are added to HA.

View File

@ -82,6 +82,9 @@ https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1
# homeassistant.components.modbus # homeassistant.components.modbus
https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0 https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0
# homeassistant.components.media_player.onkyo
https://github.com/danieljkemp/onkyo-eiscp/archive/python3.zip#onkyo-eiscp==0.9.2
# homeassistant.components.sensor.sabnzbd # homeassistant.components.sensor.sabnzbd
https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1

View File

@ -19,6 +19,25 @@ class TestDemoMediaPlayer(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_source_select(self):
"""Test the input source service."""
entity_id = 'media_player.receiver'
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
state = self.hass.states.get(entity_id)
assert 'dvd' == state.attributes.get('source')
mp.select_source(self.hass, None, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 'dvd' == state.attributes.get('source')
mp.select_source(self.hass, 'xbox', entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 'xbox' == state.attributes.get('source')
def test_volume_services(self): def test_volume_services(self):
"""Test the volume service.""" """Test the volume service."""
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})

View File

@ -24,6 +24,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
self._is_volume_muted = False self._is_volume_muted = False
self._media_title = None self._media_title = None
self._supported_media_commands = 0 self._supported_media_commands = 0
self._source = None
self.service_calls = { self.service_calls = {
'turn_on': mock_service( 'turn_on': mock_service(
@ -55,6 +56,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
'media_play_pause': mock_service( 'media_play_pause': mock_service(
hass, media_player.DOMAIN, hass, media_player.DOMAIN,
media_player.SERVICE_MEDIA_PLAY_PAUSE), media_player.SERVICE_MEDIA_PLAY_PAUSE),
'select_source': mock_service(
hass, media_player.DOMAIN,
media_player.SERVICE_SELECT_SOURCE),
} }
@property @property
@ -106,6 +110,10 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
"""Mock pause.""" """Mock pause."""
self._state = STATE_PAUSED self._state = STATE_PAUSED
def select_source(self, source):
"""Set the input source."""
self._state = source
class TestMediaPlayer(unittest.TestCase): class TestMediaPlayer(unittest.TestCase):
"""Test the media_player module.""" """Test the media_player module."""
@ -498,6 +506,10 @@ class TestMediaPlayer(unittest.TestCase):
self.assertEqual( self.assertEqual(
1, len(self.mock_mp_2.service_calls['media_play_pause'])) 1, len(self.mock_mp_2.service_calls['media_play_pause']))
ump.select_source('dvd')
self.assertEqual(
1, len(self.mock_mp_2.service_calls['select_source']))
def test_service_call_to_command(self): def test_service_call_to_command(self):
"""Test service call to command.""" """Test service call to command."""
config = self.config_children_only config = self.config_children_only