Add HomeKit Television functionality (#22968)

pull/23705/head
Austin Drummond 2019-05-05 11:51:48 -04:00 committed by cdce8p
parent 8da600adff
commit e24d56aa5b
5 changed files with 447 additions and 18 deletions

View File

@ -6,6 +6,7 @@ from zlib import adler32
import voluptuous as vol
from homeassistant.components import cover
from homeassistant.components.media_player import DEVICE_CLASS_TV
from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, CONF_TYPE, DEVICE_CLASS_HUMIDITY,
@ -99,7 +100,7 @@ async def async_setup(hass, config):
def get_accessory(hass, driver, state, aid, config):
"""Take state and return an accessory object if supported."""
if not aid:
_LOGGER.warning('The entitiy "%s" is not supported, since it '
_LOGGER.warning('The entity "%s" is not supported, since it '
'generates an invalid aid, please change it.',
state.entity_id)
return None
@ -138,10 +139,15 @@ def get_accessory(hass, driver, state, aid, config):
a_type = 'Lock'
elif state.domain == 'media_player':
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
feature_list = config.get(CONF_FEATURE_LIST)
if feature_list and \
validate_media_player_features(state, feature_list):
a_type = 'MediaPlayer'
if device_class == DEVICE_CLASS_TV:
a_type = 'TelevisionMediaPlayer'
else:
if feature_list and \
validate_media_player_features(state, feature_list):
a_type = 'MediaPlayer'
elif state.domain == 'sensor':
device_class = state.attributes.get(ATTR_DEVICE_CLASS)

View File

@ -61,6 +61,7 @@ SERV_CONTACT_SENSOR = 'ContactSensor'
SERV_FANV2 = 'Fanv2'
SERV_GARAGE_DOOR_OPENER = 'GarageDoorOpener'
SERV_HUMIDITY_SENSOR = 'HumiditySensor'
SERV_INPUT_SOURCE = 'InputSource'
SERV_LEAK_SENSOR = 'LeakSensor'
SERV_LIGHT_SENSOR = 'LightSensor'
SERV_LIGHTBULB = 'Lightbulb'
@ -71,6 +72,8 @@ SERV_OUTLET = 'Outlet'
SERV_SECURITY_SYSTEM = 'SecuritySystem'
SERV_SMOKE_SENSOR = 'SmokeSensor'
SERV_SWITCH = 'Switch'
SERV_TELEVISION = 'Television'
SERV_TELEVISION_SPEAKER = 'TelevisionSpeaker'
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
SERV_THERMOSTAT = 'Thermostat'
SERV_VALVE = 'Valve'
@ -78,6 +81,7 @@ SERV_WINDOW_COVERING = 'WindowCovering'
# #### Characteristics ####
CHAR_ACTIVE = 'Active'
CHAR_ACTIVE_IDENTIFIER = 'ActiveIdentifier'
CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity'
CHAR_AIR_QUALITY = 'AirQuality'
CHAR_BATTERY_LEVEL = 'BatteryLevel'
@ -90,6 +94,7 @@ CHAR_CARBON_MONOXIDE_LEVEL = 'CarbonMonoxideLevel'
CHAR_CARBON_MONOXIDE_PEAK_LEVEL = 'CarbonMonoxidePeakLevel'
CHAR_CHARGING_STATE = 'ChargingState'
CHAR_COLOR_TEMPERATURE = 'ColorTemperature'
CHAR_CONFIGURED_NAME = 'ConfiguredName'
CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState'
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = 'CurrentAmbientLightLevel'
@ -99,10 +104,14 @@ CHAR_CURRENT_POSITION = 'CurrentPosition'
CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity'
CHAR_CURRENT_SECURITY_STATE = 'SecuritySystemCurrentState'
CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
CHAR_CURRENT_VISIBILITY_STATE = 'CurrentVisibilityState'
CHAR_FIRMWARE_REVISION = 'FirmwareRevision'
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
CHAR_HUE = 'Hue'
CHAR_IDENTIFIER = 'Identifier'
CHAR_IN_USE = 'InUse'
CHAR_INPUT_SOURCE_TYPE = 'InputSourceType'
CHAR_IS_CONFIGURED = 'IsConfigured'
CHAR_LEAK_DETECTED = 'LeakDetected'
CHAR_LOCK_CURRENT_STATE = 'LockCurrentState'
CHAR_LOCK_TARGET_STATE = 'LockTargetState'
@ -110,15 +119,18 @@ CHAR_LINK_QUALITY = 'LinkQuality'
CHAR_MANUFACTURER = 'Manufacturer'
CHAR_MODEL = 'Model'
CHAR_MOTION_DETECTED = 'MotionDetected'
CHAR_MUTE = 'Mute'
CHAR_NAME = 'Name'
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
CHAR_ON = 'On'
CHAR_OUTLET_IN_USE = 'OutletInUse'
CHAR_POSITION_STATE = 'PositionState'
CHAR_REMOTE_KEY = 'RemoteKey'
CHAR_ROTATION_DIRECTION = 'RotationDirection'
CHAR_ROTATION_SPEED = 'RotationSpeed'
CHAR_SATURATION = 'Saturation'
CHAR_SERIAL_NUMBER = 'SerialNumber'
CHAR_SLEEP_DISCOVER_MODE = 'SleepDiscoveryMode'
CHAR_SMOKE_DETECTED = 'SmokeDetected'
CHAR_STATUS_LOW_BATTERY = 'StatusLowBattery'
CHAR_SWING_MODE = 'SwingMode'
@ -129,6 +141,10 @@ CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
CHAR_TARGET_TEMPERATURE = 'TargetTemperature'
CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits'
CHAR_VALVE_TYPE = 'ValveType'
CHAR_VOLUME = 'Volume'
CHAR_VOLUME_SELECTOR = 'VolumeSelector'
CHAR_VOLUME_CONTROL_TYPE = 'VolumeControlType'
# #### Properties ####
PROP_MAX_VALUE = 'maxValue'

View File

@ -1,23 +1,49 @@
"""Class to hold all media player accessories."""
import logging
from pyhap.const import CATEGORY_SWITCH
from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION
from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_MUTED, DOMAIN)
ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_VOLUME_LEVEL, SERVICE_SELECT_SOURCE, DOMAIN, SUPPORT_PAUSE,
SUPPORT_PLAY, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
SUPPORT_SELECT_SOURCE)
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE,
STATE_OFF, STATE_PLAYING, STATE_UNKNOWN)
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_STOP,
SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, STATE_OFF, STATE_PLAYING,
STATE_PAUSED, STATE_UNKNOWN)
from . import TYPES
from .accessories import HomeAccessory
from .const import (
CHAR_NAME, CHAR_ON, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, SERV_SWITCH)
CHAR_ACTIVE, CHAR_ACTIVE_IDENTIFIER, CHAR_CONFIGURED_NAME,
CHAR_CURRENT_VISIBILITY_STATE, CHAR_IDENTIFIER, CHAR_INPUT_SOURCE_TYPE,
CHAR_IS_CONFIGURED, CHAR_NAME, CHAR_SLEEP_DISCOVER_MODE, CHAR_MUTE,
CHAR_ON, CHAR_REMOTE_KEY, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR,
CHAR_VOLUME, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, SERV_SWITCH, SERV_TELEVISION,
SERV_TELEVISION_SPEAKER, SERV_INPUT_SOURCE)
_LOGGER = logging.getLogger(__name__)
MEDIA_PLAYER_KEYS = {
# 0: "Rewind",
# 1: "FastForward",
# 2: "NextTrack",
# 3: "PreviousTrack",
# 4: "ArrowUp",
# 5: "ArrowDown",
# 6: "ArrowLeft",
# 7: "ArrowRight",
# 8: "Select",
# 9: "Back",
# 10: "Exit",
11: SERVICE_MEDIA_PLAY_PAUSE,
# 15: "Information",
}
MODE_FRIENDLY_NAME = {
FEATURE_ON_OFF: 'Power',
FEATURE_PLAY_PAUSE: 'Play/Pause',
@ -142,3 +168,185 @@ class MediaPlayer(HomeAccessory):
self.entity_id, current_state)
self.chars[FEATURE_TOGGLE_MUTE].set_value(current_state)
self._flag[FEATURE_TOGGLE_MUTE] = False
@TYPES.register('TelevisionMediaPlayer')
class TelevisionMediaPlayer(HomeAccessory):
"""Generate a Television Media Player accessory."""
def __init__(self, *args):
"""Initialize a Switch accessory object."""
super().__init__(*args, category=CATEGORY_TELEVISION)
self._flag = {CHAR_ACTIVE: False, CHAR_ACTIVE_IDENTIFIER: False,
CHAR_MUTE: False}
self.support_select_source = False
self.sources = []
# Add additional characteristics if volume or input selection supported
self.chars_tv = []
self.chars_speaker = []
features = self.hass.states.get(self.entity_id) \
.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & (SUPPORT_PLAY | SUPPORT_PAUSE):
self.chars_tv.append(CHAR_REMOTE_KEY)
if features & SUPPORT_VOLUME_MUTE or features & SUPPORT_VOLUME_STEP:
self.chars_speaker.extend((CHAR_NAME, CHAR_ACTIVE,
CHAR_VOLUME_CONTROL_TYPE,
CHAR_VOLUME_SELECTOR))
if features & SUPPORT_VOLUME_SET:
self.chars_speaker.append(CHAR_VOLUME)
if features & SUPPORT_SELECT_SOURCE:
self.support_select_source = True
serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv)
self.set_primary_service(serv_tv)
serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name)
serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True)
self.char_active = serv_tv.configure_char(
CHAR_ACTIVE, setter_callback=self.set_on_off)
if CHAR_REMOTE_KEY in self.chars_tv:
self.char_remote_key = serv_tv.configure_char(
CHAR_REMOTE_KEY, setter_callback=self.set_remote_key)
if CHAR_VOLUME_SELECTOR in self.chars_speaker:
serv_speaker = self.add_preload_service(
SERV_TELEVISION_SPEAKER, self.chars_speaker)
serv_tv.add_linked_service(serv_speaker)
name = '{} {}'.format(self.display_name, 'Volume')
serv_speaker.configure_char(CHAR_NAME, value=name)
serv_speaker.configure_char(CHAR_ACTIVE, value=1)
self.char_mute = serv_speaker.configure_char(
CHAR_MUTE, value=False, setter_callback=self.set_mute)
volume_control_type = 1 if CHAR_VOLUME in self.chars_speaker else 2
serv_speaker.configure_char(CHAR_VOLUME_CONTROL_TYPE,
value=volume_control_type)
self.char_volume_selector = serv_speaker.configure_char(
CHAR_VOLUME_SELECTOR, setter_callback=self.set_volume_step)
if CHAR_VOLUME in self.chars_speaker:
self.char_volume = serv_speaker.configure_char(
CHAR_VOLUME, setter_callback=self.set_volume)
if self.support_select_source:
self.sources = self.hass.states.get(self.entity_id) \
.attributes.get(ATTR_INPUT_SOURCE_LIST, [])
self.char_input_source = serv_tv.configure_char(
CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source)
for index, source in enumerate(self.sources):
serv_input = self.add_preload_service(
SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME])
serv_tv.add_linked_service(serv_input)
serv_input.configure_char(
CHAR_CONFIGURED_NAME, value=source)
serv_input.configure_char(CHAR_NAME, value=source)
serv_input.configure_char(CHAR_IDENTIFIER, value=index)
serv_input.configure_char(CHAR_IS_CONFIGURED, value=True)
input_type = 3 if "hdmi" in source.lower() else 0
serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE,
value=input_type)
serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE,
value=False)
_LOGGER.debug('%s: Added source %s.', self.entity_id, source)
def set_on_off(self, value):
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug('%s: Set switch state for "on_off" to %s',
self.entity_id, value)
self._flag[CHAR_ACTIVE] = True
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
params = {ATTR_ENTITY_ID: self.entity_id}
self.call_service(DOMAIN, service, params)
def set_mute(self, value):
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug('%s: Set switch state for "toggle_mute" to %s',
self.entity_id, value)
self._flag[CHAR_MUTE] = True
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_MEDIA_VOLUME_MUTED: value}
self.call_service(DOMAIN, SERVICE_VOLUME_MUTE, params)
def set_volume(self, value):
"""Send volume step value if call came from HomeKit."""
_LOGGER.debug('%s: Set volume to %s', self.entity_id, value)
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_MEDIA_VOLUME_LEVEL: value}
self.call_service(DOMAIN, SERVICE_VOLUME_SET, params)
def set_volume_step(self, value):
"""Send volume step value if call came from HomeKit."""
_LOGGER.debug('%s: Step volume by %s',
self.entity_id, value)
service = SERVICE_VOLUME_DOWN if value else SERVICE_VOLUME_UP
params = {ATTR_ENTITY_ID: self.entity_id}
self.call_service(DOMAIN, service, params)
def set_input_source(self, value):
"""Send input set value if call came from HomeKit."""
_LOGGER.debug('%s: Set current input to %s',
self.entity_id, value)
source = self.sources[value]
self._flag[CHAR_ACTIVE_IDENTIFIER] = True
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_INPUT_SOURCE: source}
self.call_service(DOMAIN, SERVICE_SELECT_SOURCE, params)
def set_remote_key(self, value):
"""Send remote key value if call came from HomeKit."""
_LOGGER.debug('%s: Set remote key to %s', self.entity_id, value)
service = MEDIA_PLAYER_KEYS.get(value)
if service:
# Handle Play Pause
if service == SERVICE_MEDIA_PLAY_PAUSE:
state = self.hass.states.get(self.entity_id).state
if state in (STATE_PLAYING, STATE_PAUSED):
service = SERVICE_MEDIA_PLAY if state == STATE_PAUSED \
else SERVICE_MEDIA_PAUSE
params = {ATTR_ENTITY_ID: self.entity_id}
self.call_service(DOMAIN, service, params)
def update_state(self, new_state):
"""Update Television state after state changed."""
current_state = new_state.state
# Power state television
hk_state = current_state not in (STATE_OFF, STATE_UNKNOWN)
if not self._flag[CHAR_ACTIVE]:
_LOGGER.debug('%s: Set current active state to %s',
self.entity_id, hk_state)
self.char_active.set_value(hk_state)
self._flag[CHAR_ACTIVE] = False
# Set mute state
if CHAR_VOLUME_SELECTOR in self.chars_speaker:
current_mute_state = new_state.attributes.get(
ATTR_MEDIA_VOLUME_MUTED)
if not self._flag[CHAR_MUTE]:
_LOGGER.debug('%s: Set current mute state to %s',
self.entity_id, current_mute_state)
self.char_mute.set_value(current_mute_state)
self._flag[CHAR_MUTE] = False
# Set active input
if self.support_select_source:
source_name = new_state.attributes.get(ATTR_INPUT_SOURCE)
if self.sources and not self._flag[CHAR_ACTIVE_IDENTIFIER]:
_LOGGER.debug('%s: Set current input to %s', self.entity_id,
source_name)
if source_name in self.sources:
index = self.sources.index(source_name)
self.char_input_source.set_value(index)
else:
_LOGGER.warning('%s: Sources out of sync. '
'Restart HomeAssistant', self.entity_id)
self.char_input_source.set_value(0)
self._flag[CHAR_ACTIVE_IDENTIFIER] = False

View File

@ -59,10 +59,6 @@ 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: media_player_c.SUPPORT_TURN_ON |
media_player_c.SUPPORT_TURN_OFF}, {CONF_FEATURE_LIST:
{FEATURE_ON_OFF: None}}),
('SecuritySystem', 'alarm_control_panel.test', 'armed_away', {},
{ATTR_CODE: '1234'}),
('Thermostat', 'climate.test', 'auto', {}, {}),
@ -101,6 +97,26 @@ def test_type_covers(type_name, entity_id, state, attrs):
assert mock_type.called
@pytest.mark.parametrize('type_name, entity_id, state, attrs, config', [
('MediaPlayer', 'media_player.test', 'on',
{ATTR_SUPPORTED_FEATURES: media_player_c.SUPPORT_TURN_ON |
media_player_c.SUPPORT_TURN_OFF}, {CONF_FEATURE_LIST:
{FEATURE_ON_OFF: None}}),
('TelevisionMediaPlayer', 'media_player.tv', 'on',
{ATTR_DEVICE_CLASS: 'tv'}, {}),
])
def test_type_media_player(type_name, entity_id, state, attrs, config):
"""Test if media_player types are associated correctly."""
mock_type = Mock()
with patch.dict(TYPES, {type_name: mock_type}):
entity_state = State(entity_id, state, attrs)
get_accessory(None, None, entity_state, 2, config)
assert mock_type.called
if config:
assert mock_type.call_args[0][-1] == config
@pytest.mark.parametrize('type_name, entity_id, state, attrs', [
('BinarySensor', 'binary_sensor.opening', 'on',
{ATTR_DEVICE_CLASS: 'opening'}),

View File

@ -3,12 +3,15 @@
from homeassistant.components.homekit.const import (
ATTR_VALUE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE)
from homeassistant.components.homekit.type_media_players import MediaPlayer
from homeassistant.components.media_player import DEVICE_CLASS_TV
from homeassistant.components.homekit.type_media_players import (
MediaPlayer, TelevisionMediaPlayer)
from homeassistant.components.media_player.const import (
ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED, DOMAIN)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_IDLE, STATE_OFF, STATE_ON,
STATE_PAUSED, STATE_PLAYING)
ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_IDLE,
STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING)
from tests.common import async_mock_service
@ -129,3 +132,183 @@ async def test_media_player_set_state(hass, hk_driver, events):
assert call_toggle_mute[1].data[ATTR_MEDIA_VOLUME_MUTED] is False
assert len(events) == 8
assert events[-1].data[ATTR_VALUE] is None
async def test_media_player_television(hass, hk_driver, events, caplog):
"""Test if television accessory and HA are updated accordingly."""
entity_id = 'media_player.television'
# Supports 'select_source', 'volume_step', 'turn_on', 'turn_off',
# 'volume_mute', 'volume_set', 'pause'
hass.states.async_set(entity_id, None, {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TV, ATTR_SUPPORTED_FEATURES: 3469,
ATTR_MEDIA_VOLUME_MUTED: False, ATTR_INPUT_SOURCE_LIST: [
'HDMI 1', 'HDMI 2', 'HDMI 3', 'HDMI 4']})
await hass.async_block_till_done()
acc = TelevisionMediaPlayer(hass, hk_driver, 'MediaPlayer', entity_id, 2,
None)
await hass.async_add_job(acc.run)
assert acc.aid == 2
assert acc.category == 31 # Television
assert acc.char_active.value == 0
assert acc.char_remote_key.value == 0
assert acc.char_input_source.value == 0
assert acc.char_mute.value is False
hass.states.async_set(entity_id, STATE_ON, {ATTR_MEDIA_VOLUME_MUTED: True})
await hass.async_block_till_done()
assert acc.char_active.value == 1
assert acc.char_mute.value is True
hass.states.async_set(entity_id, STATE_OFF)
await hass.async_block_till_done()
assert acc.char_active.value == 0
hass.states.async_set(entity_id, STATE_ON, {ATTR_INPUT_SOURCE: 'HDMI 2'})
await hass.async_block_till_done()
assert acc.char_input_source.value == 1
hass.states.async_set(entity_id, STATE_ON, {ATTR_INPUT_SOURCE: 'HDMI 3'})
await hass.async_block_till_done()
assert acc.char_input_source.value == 2
hass.states.async_set(entity_id, STATE_ON, {ATTR_INPUT_SOURCE: 'HDMI 5'})
await hass.async_block_till_done()
assert acc.char_input_source.value == 0
assert caplog.records[-2].levelname == 'WARNING'
# Set from HomeKit
call_turn_on = async_mock_service(hass, DOMAIN, 'turn_on')
call_turn_off = async_mock_service(hass, DOMAIN, 'turn_off')
call_media_play = async_mock_service(hass, DOMAIN, 'media_play')
call_media_pause = async_mock_service(hass, DOMAIN, 'media_pause')
call_media_play_pause = async_mock_service(hass, DOMAIN,
'media_play_pause')
call_toggle_mute = async_mock_service(hass, DOMAIN, 'volume_mute')
call_select_source = async_mock_service(hass, DOMAIN, 'select_source')
call_volume_up = async_mock_service(hass, DOMAIN, 'volume_up')
call_volume_down = async_mock_service(hass, DOMAIN, 'volume_down')
call_volume_set = async_mock_service(hass, DOMAIN, 'volume_set')
await hass.async_add_job(acc.char_active.client_update_value, 1)
await hass.async_block_till_done()
assert call_turn_on
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_active.client_update_value, 0)
await hass.async_block_till_done()
assert call_turn_off
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 2
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_remote_key.client_update_value, 11)
await hass.async_block_till_done()
assert call_media_play_pause
assert call_media_play_pause[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 3
assert events[-1].data[ATTR_VALUE] is None
hass.states.async_set(entity_id, STATE_PLAYING)
await hass.async_block_till_done()
await hass.async_add_job(acc.char_remote_key.client_update_value, 11)
await hass.async_block_till_done()
assert call_media_pause
assert call_media_pause[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 4
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_remote_key.client_update_value, 10)
await hass.async_block_till_done()
assert len(events) == 4
assert events[-1].data[ATTR_VALUE] is None
hass.states.async_set(entity_id, STATE_PAUSED)
await hass.async_block_till_done()
await hass.async_add_job(acc.char_remote_key.client_update_value, 11)
await hass.async_block_till_done()
assert call_media_play
assert call_media_play[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 5
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_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
assert len(events) == 6
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_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
assert len(events) == 7
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_input_source.client_update_value, 1)
await hass.async_block_till_done()
assert call_select_source
assert call_select_source[0].data[ATTR_ENTITY_ID] == entity_id
assert call_select_source[0].data[ATTR_INPUT_SOURCE] == 'HDMI 2'
assert len(events) == 8
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_volume_selector.client_update_value, 0)
await hass.async_block_till_done()
assert call_volume_up
assert call_volume_up[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 9
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_volume_selector.client_update_value, 1)
await hass.async_block_till_done()
assert call_volume_down
assert call_volume_down[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 10
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_job(acc.char_volume.client_update_value, 20)
await hass.async_block_till_done()
assert call_volume_set[0]
assert call_volume_set[0].data[ATTR_ENTITY_ID] == entity_id
assert call_volume_set[0].data[ATTR_MEDIA_VOLUME_LEVEL] == 20
assert len(events) == 11
assert events[-1].data[ATTR_VALUE] is None
async def test_media_player_television_basic(hass, hk_driver, events, caplog):
"""Test if basic television accessory and HA are updated accordingly."""
entity_id = 'media_player.television'
# Supports turn_on', 'turn_off'
hass.states.async_set(entity_id, None, {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TV, ATTR_SUPPORTED_FEATURES: 384})
await hass.async_block_till_done()
acc = TelevisionMediaPlayer(hass, hk_driver, 'MediaPlayer', entity_id, 2,
None)
await hass.async_add_job(acc.run)
assert acc.chars_tv == []
assert acc.chars_speaker == []
assert acc.support_select_source is False
hass.states.async_set(entity_id, STATE_ON, {ATTR_MEDIA_VOLUME_MUTED: True})
await hass.async_block_till_done()
assert acc.char_active.value == 1
hass.states.async_set(entity_id, STATE_OFF)
await hass.async_block_till_done()
assert acc.char_active.value == 0
hass.states.async_set(entity_id, STATE_ON, {ATTR_INPUT_SOURCE: 'HDMI 3'})
await hass.async_block_till_done()
assert acc.char_active.value == 1
assert 'Error' not in caplog.messages[-1]