Add HomeKit Television functionality (#22968)
parent
8da600adff
commit
e24d56aa5b
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'}),
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue