Add HomeKit support for media players (#14446)

This commit is contained in:
Matt Schmitt 2018-05-25 05:37:20 -04:00 committed by cdce8p
parent d53a8c0823
commit a9f19a16ee
7 changed files with 354 additions and 11 deletions

View file

@ -2,15 +2,20 @@
from unittest.mock import patch, Mock
import pytest
import voluptuous as vol
from homeassistant.core import State
from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN
from homeassistant.components.climate import (
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.components.media_player import (
SUPPORT_TURN_OFF, SUPPORT_TURN_ON)
from homeassistant.components.homekit import get_accessory, TYPES
from homeassistant.components.homekit.const import ON_OFF
from homeassistant.const import (
ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES,
ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT)
ATTR_UNIT_OF_MEASUREMENT, CONF_MODE, CONF_NAME, TEMP_CELSIUS,
TEMP_FAHRENHEIT)
def test_not_supported(caplog):
@ -24,6 +29,18 @@ def test_not_supported(caplog):
assert 'invalid aid' in caplog.records[0].msg
def test_not_supported_media_player():
"""Test if mode isn't supported and if no supported modes."""
# selected mode for entity not supported
with pytest.raises(vol.Invalid):
entity_state = State('media_player.demo', 'on')
get_accessory(None, entity_state, 2, {CONF_MODE: [ON_OFF]})
# no supported modes for entity
entity_state = State('media_player.demo', 'on')
assert get_accessory(None, entity_state, 2, {}) is None
@pytest.mark.parametrize('config, name', [
({CONF_NAME: 'Customize Name'}, 'Customize Name'),
])
@ -40,6 +57,9 @@ def test_customize_options(config, name):
('Fan', 'fan.test', 'on', {}, {}),
('Light', 'light.test', 'on', {}, {}),
('Lock', 'lock.test', 'locked', {}, {ATTR_CODE: '1234'}),
('MediaPlayer', 'media_player.test', 'on',
{ATTR_SUPPORTED_FEATURES: SUPPORT_TURN_ON | SUPPORT_TURN_OFF},
{CONF_MODE: [ON_OFF]}),
('SecuritySystem', 'alarm_control_panel.test', 'armed', {},
{ATTR_CODE: '1234'}),
('Thermostat', 'climate.test', 'auto', {}, {}),

View file

@ -0,0 +1,106 @@
"""Test different accessory types: Media Players."""
from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_MUTED, DOMAIN)
from homeassistant.components.homekit.type_media_players import MediaPlayer
from homeassistant.components.homekit.const import (
ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_MODE, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_VOLUME_MUTE, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED,
STATE_PLAYING)
from tests.common import async_mock_service
async def test_media_player_set_state(hass):
"""Test if accessory and HA are updated accordingly."""
config = {CONF_MODE: [ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE]}
entity_id = 'media_player.test'
hass.states.async_set(entity_id, None, {ATTR_SUPPORTED_FEATURES: 20873,
ATTR_MEDIA_VOLUME_MUTED: False})
await hass.async_block_till_done()
acc = MediaPlayer(hass, 'MediaPlayer', entity_id, 2, config)
await hass.async_add_job(acc.run)
assert acc.aid == 2
assert acc.category == 8 # Switch
assert acc.chars[ON_OFF].value == 0
assert acc.chars[PLAY_PAUSE].value == 0
assert acc.chars[PLAY_STOP].value == 0
assert acc.chars[TOGGLE_MUTE].value == 0
hass.states.async_set(entity_id, STATE_ON, {ATTR_MEDIA_VOLUME_MUTED: True})
await hass.async_block_till_done()
assert acc.chars[ON_OFF].value == 1
assert acc.chars[TOGGLE_MUTE].value == 1
hass.states.async_set(entity_id, STATE_OFF)
await hass.async_block_till_done()
assert acc.chars[ON_OFF].value == 0
hass.states.async_set(entity_id, STATE_PLAYING)
await hass.async_block_till_done()
assert acc.chars[PLAY_PAUSE].value == 1
assert acc.chars[PLAY_STOP].value == 1
hass.states.async_set(entity_id, STATE_PAUSED)
await hass.async_block_till_done()
assert acc.chars[PLAY_PAUSE].value == 0
hass.states.async_set(entity_id, STATE_IDLE)
await hass.async_block_till_done()
assert acc.chars[PLAY_STOP].value == 0
# Set from HomeKit
call_turn_on = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
call_turn_off = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
call_media_play = async_mock_service(hass, DOMAIN, SERVICE_MEDIA_PLAY)
call_media_pause = async_mock_service(hass, DOMAIN, SERVICE_MEDIA_PAUSE)
call_media_stop = async_mock_service(hass, DOMAIN, SERVICE_MEDIA_STOP)
call_toggle_mute = async_mock_service(hass, DOMAIN, SERVICE_VOLUME_MUTE)
await hass.async_add_job(acc.chars[ON_OFF].client_update_value, True)
await hass.async_block_till_done()
assert call_turn_on
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
await hass.async_add_job(acc.chars[ON_OFF].client_update_value, False)
await hass.async_block_till_done()
assert call_turn_off
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
await hass.async_add_job(acc.chars[PLAY_PAUSE].client_update_value, True)
await hass.async_block_till_done()
assert call_media_play
assert call_media_play[0].data[ATTR_ENTITY_ID] == entity_id
await hass.async_add_job(acc.chars[PLAY_PAUSE].client_update_value, False)
await hass.async_block_till_done()
assert call_media_pause
assert call_media_pause[0].data[ATTR_ENTITY_ID] == entity_id
await hass.async_add_job(acc.chars[PLAY_STOP].client_update_value, True)
await hass.async_block_till_done()
assert call_media_play
assert call_media_play[1].data[ATTR_ENTITY_ID] == entity_id
await hass.async_add_job(acc.chars[PLAY_STOP].client_update_value, False)
await hass.async_block_till_done()
assert call_media_stop
assert call_media_stop[0].data[ATTR_ENTITY_ID] == entity_id
await hass.async_add_job(acc.chars[TOGGLE_MUTE].client_update_value, True)
await hass.async_block_till_done()
assert call_toggle_mute
assert call_toggle_mute[0].data[ATTR_ENTITY_ID] == entity_id
assert call_toggle_mute[0].data[ATTR_MEDIA_VOLUME_MUTED] is True
await hass.async_add_job(acc.chars[TOGGLE_MUTE].client_update_value, False)
await hass.async_block_till_done()
assert call_toggle_mute
assert call_toggle_mute[1].data[ATTR_ENTITY_ID] == entity_id
assert call_toggle_mute[1].data[ATTR_MEDIA_VOLUME_MUTED] is False

View file

@ -2,16 +2,20 @@
import pytest
import voluptuous as vol
from homeassistant.components.homekit.const import HOMEKIT_NOTIFY_ID
from homeassistant.core import State
from homeassistant.components.homekit.const import (
HOMEKIT_NOTIFY_ID, ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE)
from homeassistant.components.homekit.util import (
convert_to_float, density_to_air_quality, dismiss_setup_message,
show_setup_message, temperature_to_homekit, temperature_to_states)
show_setup_message, temperature_to_homekit, temperature_to_states,
validate_media_player_modes)
from homeassistant.components.homekit.util import validate_entity_config \
as vec
from homeassistant.components.persistent_notification import (
ATTR_MESSAGE, ATTR_NOTIFICATION_ID, DOMAIN)
from homeassistant.const import (
ATTR_CODE, CONF_NAME, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT)
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_MODE, CONF_NAME, STATE_UNKNOWN,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
from tests.common import async_mock_service
@ -20,7 +24,8 @@ def test_validate_entity_config():
"""Test validate entities."""
configs = [{'invalid_entity_id': {}}, {'demo.test': 1},
{'demo.test': 'test'}, {'demo.test': [1, 2]},
{'demo.test': None}, {'demo.test': {CONF_NAME: None}}]
{'demo.test': None}, {'demo.test': {CONF_NAME: None}},
{'media_player.test': {CONF_MODE: 'invalid_mode'}}]
for conf in configs:
with pytest.raises(vol.Invalid):
@ -39,6 +44,25 @@ def test_validate_entity_config():
assert vec({'lock.demo': {ATTR_CODE: '1234'}}) == \
{'lock.demo': {ATTR_CODE: '1234'}}
assert vec({'media_player.demo': {}}) == \
{'media_player.demo': {CONF_MODE: []}}
assert vec({'media_player.demo': {CONF_MODE: [ON_OFF]}}) == \
{'media_player.demo': {CONF_MODE: [ON_OFF]}}
def test_validate_media_player_modes():
"""Test validate modes for media players."""
config = {}
attrs = {ATTR_SUPPORTED_FEATURES: 20873}
entity_state = State('media_player.demo', 'on', attrs)
validate_media_player_modes(entity_state, config)
assert config == {CONF_MODE: [ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE]}
entity_state = State('media_player.demo', 'on')
config = {CONF_MODE: [ON_OFF]}
with pytest.raises(vol.Invalid):
validate_media_player_modes(entity_state, config)
def test_convert_to_float():
"""Test convert_to_float method."""