Add onkyo receiver, and source select support
Added onkyo receiver component Added support for input source selection to media players, and do the onkyo receiver component.
This commit is contained in:
parent
79a2d40f4d
commit
86199c8277
10 changed files with 254 additions and 4 deletions
|
@ -107,6 +107,7 @@ omit =
|
|||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
homeassistant/components/media_player/onkyo.py
|
||||
homeassistant/components/media_player/yamaha.py
|
||||
homeassistant/components/notify/free_mobile.py
|
||||
homeassistant/components/notify/googlevoice.py
|
||||
|
|
|
@ -34,6 +34,7 @@ DISCOVERY_PLATFORMS = {
|
|||
}
|
||||
|
||||
SERVICE_PLAY_MEDIA = 'play_media'
|
||||
SERVICE_SELECT_SOURCE = 'select_source'
|
||||
|
||||
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
|
||||
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
|
||||
|
@ -54,6 +55,7 @@ ATTR_MEDIA_PLAYLIST = 'media_playlist'
|
|||
ATTR_APP_ID = 'app_id'
|
||||
ATTR_APP_NAME = 'app_name'
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
|
||||
ATTR_INPUT_SOURCE = 'source'
|
||||
|
||||
MEDIA_TYPE_MUSIC = 'music'
|
||||
MEDIA_TYPE_TVSHOW = 'tvshow'
|
||||
|
@ -73,6 +75,7 @@ SUPPORT_TURN_ON = 128
|
|||
SUPPORT_TURN_OFF = 256
|
||||
SUPPORT_PLAY_MEDIA = 512
|
||||
SUPPORT_VOLUME_STEP = 1024
|
||||
SUPPORT_SELECT_SOURCE = 2048
|
||||
|
||||
SERVICE_TO_METHOD = {
|
||||
SERVICE_TURN_ON: 'turn_on',
|
||||
|
@ -86,6 +89,7 @@ SERVICE_TO_METHOD = {
|
|||
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
|
||||
SERVICE_PLAY_MEDIA: 'play_media',
|
||||
SERVICE_SELECT_SOURCE: 'select_source',
|
||||
}
|
||||
|
||||
ATTR_TO_PROPERTY = [
|
||||
|
@ -107,6 +111,7 @@ ATTR_TO_PROPERTY = [
|
|||
ATTR_APP_ID,
|
||||
ATTR_APP_NAME,
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS,
|
||||
ATTR_INPUT_SOURCE,
|
||||
]
|
||||
|
||||
|
||||
|
@ -219,6 +224,16 @@ def play_media(hass, media_type, media_id, entity_id=None):
|
|||
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):
|
||||
"""Track states and offer events for media_players."""
|
||||
component = EntityComponent(
|
||||
|
@ -301,6 +316,26 @@ def setup(hass, config):
|
|||
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service,
|
||||
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):
|
||||
"""Play specified media_id on the media player."""
|
||||
media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE)
|
||||
|
@ -429,6 +464,11 @@ class MediaPlayerDevice(Entity):
|
|||
"""Name of the current running app."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Name of the current input source."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
"""Flag media commands that are supported."""
|
||||
|
@ -474,6 +514,10 @@ class MediaPlayerDevice(Entity):
|
|||
"""Play a piece of media."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def select_source(self, source):
|
||||
"""Select input source."""
|
||||
raise NotImplementedError()
|
||||
|
||||
# No need to overwrite these.
|
||||
@property
|
||||
def support_pause(self):
|
||||
|
@ -510,6 +554,11 @@ class MediaPlayerDevice(Entity):
|
|||
"""Boolean if play media command supported."""
|
||||
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):
|
||||
"""Toggle the power on the media player."""
|
||||
if self.state in [STATE_OFF, STATE_IDLE]:
|
||||
|
|
|
@ -8,7 +8,7 @@ from homeassistant.components.media_player import (
|
|||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
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
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
'Living Room', 'eyU3bRy2x44',
|
||||
'♥♥ The Best Fireplace Video (3 hours)'),
|
||||
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'),
|
||||
DemoMusicPlayer(), DemoTVShowPlayer(),
|
||||
DemoMusicPlayer(), DemoTVShowPlayer(), DemoReceiver(),
|
||||
])
|
||||
|
||||
|
||||
|
@ -37,6 +37,8 @@ MUSIC_PLAYER_SUPPORT = \
|
|||
NETFLIX_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
RECEIVER_SUPPORT = SUPPORT_SELECT_SOURCE
|
||||
|
||||
|
||||
class AbstractDemoPlayer(MediaPlayerDevice):
|
||||
"""A demo media players."""
|
||||
|
@ -337,3 +339,29 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
|
|||
if self._cur_episode < self._episode_count:
|
||||
self._cur_episode += 1
|
||||
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
|
||||
|
|
111
homeassistant/components/media_player/onkyo.py
Normal file
111
homeassistant/components/media_player/onkyo.py
Normal 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))
|
|
@ -126,3 +126,14 @@ play_media:
|
|||
media_content_type:
|
||||
description: The type of the content to play. Must be one of MUSIC, TVSHOW, VIDEO, EPISODE, CHANNEL or PLAYLIST
|
||||
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'
|
||||
|
|
|
@ -18,7 +18,8 @@ from homeassistant.components.media_player import (
|
|||
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED,
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS, DOMAIN, SERVICE_PLAY_MEDIA,
|
||||
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 (
|
||||
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK,
|
||||
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE,
|
||||
|
@ -321,6 +322,11 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||
"""Name of the current running app."""
|
||||
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
|
||||
def supported_media_commands(self):
|
||||
"""Flag media commands that are supported."""
|
||||
|
@ -340,6 +346,9 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||
ATTR_MEDIA_VOLUME_MUTED in self._attrs:
|
||||
flags |= SUPPORT_VOLUME_MUTE
|
||||
|
||||
if SUPPORT_SELECT_SOURCE in self._cmds:
|
||||
flags |= SUPPORT_SELECT_SOURCE
|
||||
|
||||
return flags
|
||||
|
||||
@property
|
||||
|
@ -406,6 +415,11 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||
"""Play or pause the media player."""
|
||||
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):
|
||||
"""Update state in HA."""
|
||||
for child_name in self._children:
|
||||
|
|
|
@ -6,7 +6,8 @@ from collections import defaultdict
|
|||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.media_player import (
|
||||
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 (
|
||||
ATTR_MESSAGE, SERVICE_NOTIFY)
|
||||
from homeassistant.components.sun import (
|
||||
|
@ -42,6 +43,7 @@ SERVICE_ATTRIBUTES = {
|
|||
SERVICE_SET_AWAY_MODE: [ATTR_AWAY_MODE],
|
||||
SERVICE_SET_FAN_MODE: [ATTR_FAN],
|
||||
SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE],
|
||||
SERVICE_SELECT_SOURCE: [ATTR_INPUT_SOURCE],
|
||||
}
|
||||
|
||||
# Update this dict when new services are added to HA.
|
||||
|
|
|
@ -81,6 +81,9 @@ https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1
|
|||
# homeassistant.components.modbus
|
||||
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.uber
|
||||
https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_rides==0.1.2-dev
|
||||
|
||||
|
|
|
@ -19,6 +19,25 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||
"""Stop everything that was started."""
|
||||
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):
|
||||
"""Test the volume service."""
|
||||
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
||||
|
|
|
@ -24,6 +24,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||
self._is_volume_muted = False
|
||||
self._media_title = None
|
||||
self._supported_media_commands = 0
|
||||
self._source = None
|
||||
|
||||
self.service_calls = {
|
||||
'turn_on': mock_service(
|
||||
|
@ -55,6 +56,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||
'media_play_pause': mock_service(
|
||||
hass, media_player.DOMAIN,
|
||||
media_player.SERVICE_MEDIA_PLAY_PAUSE),
|
||||
'select_source': mock_service(
|
||||
hass, media_player.DOMAIN,
|
||||
media_player.SERVICE_SELECT_SOURCE),
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -106,6 +110,10 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||
"""Mock pause."""
|
||||
self._state = STATE_PAUSED
|
||||
|
||||
def select_source(self, source):
|
||||
"""Set the input source."""
|
||||
self._state = source
|
||||
|
||||
|
||||
class TestMediaPlayer(unittest.TestCase):
|
||||
"""Test the media_player module."""
|
||||
|
@ -498,6 +506,10 @@ class TestMediaPlayer(unittest.TestCase):
|
|||
self.assertEqual(
|
||||
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):
|
||||
"""Test service call to command."""
|
||||
config = self.config_children_only
|
||||
|
|
Loading…
Add table
Reference in a new issue