mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Merge branch 'pr/1643' into dev
Conflicts: requirements_all.txt
This commit is contained in:
commit
5b00919bed
@ -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
|
||||
|
@ -35,6 +35,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'
|
||||
@ -55,6 +56,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'
|
||||
@ -74,6 +76,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',
|
||||
@ -87,6 +90,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 = [
|
||||
@ -108,6 +112,7 @@ ATTR_TO_PROPERTY = [
|
||||
ATTR_APP_ID,
|
||||
ATTR_APP_NAME,
|
||||
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)
|
||||
|
||||
|
||||
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(
|
||||
@ -302,6 +317,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)
|
||||
@ -430,6 +465,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."""
|
||||
@ -475,6 +515,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):
|
||||
@ -511,6 +555,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.
|
||||
|
@ -82,6 +82,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.sabnzbd
|
||||
https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
|
||||
|
||||
|
@ -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…
x
Reference in New Issue
Block a user