From 6f4dd7b057b492379404c92b8f96fee20fdc1c74 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Mon, 28 May 2018 16:26:33 +0200 Subject: [PATCH] Improve Homekit media_player options (#14637) * Optimize imports * Optimize name * Optimize config schema * Rename mode to feature * Replace mode with feature_list --- homeassistant/components/homekit/__init__.py | 26 ++--- homeassistant/components/homekit/const.py | 14 ++- .../components/homekit/type_media_players.py | 96 ++++++++-------- homeassistant/components/homekit/util.py | 107 +++++++++++------- .../homekit/test_get_accessories.py | 32 +++--- .../homekit/test_type_media_players.py | 55 +++++---- tests/components/homekit/test_util.py | 34 +++--- 7 files changed, 201 insertions(+), 163 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index f011a56a77b..a79fbf85400 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -9,12 +9,11 @@ from zlib import adler32 import voluptuous as vol -from homeassistant.components.cover import ( - SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION) +import homeassistant.components.cover as cover from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - CONF_IP_ADDRESS, CONF_MODE, CONF_NAME, CONF_PORT, - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT) import homeassistant.helpers.config_validation as cv @@ -22,11 +21,11 @@ from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry from .const import ( - CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FILTER, DEFAULT_AUTO_START, - DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE, - SERVICE_HOMEKIT_START) + CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, CONF_FILTER, + DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, + DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START) from .util import ( - show_setup_message, validate_entity_config, validate_media_player_modes) + show_setup_message, validate_entity_config, validate_media_player_features) TYPES = Registry() _LOGGER = logging.getLogger(__name__) @@ -110,11 +109,11 @@ def get_accessory(hass, state, aid, config): device_class = state.attributes.get(ATTR_DEVICE_CLASS) if device_class == 'garage' and \ - features & (SUPPORT_OPEN | SUPPORT_CLOSE): + features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): a_type = 'GarageDoorOpener' - elif features & SUPPORT_SET_POSITION: + elif features & cover.SUPPORT_SET_POSITION: a_type = 'WindowCovering' - elif features & (SUPPORT_OPEN | SUPPORT_CLOSE): + elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): a_type = 'WindowCoveringBasic' elif state.domain == 'fan': @@ -127,8 +126,9 @@ def get_accessory(hass, state, aid, config): a_type = 'Lock' elif state.domain == 'media_player': - validate_media_player_modes(state, config) - if config.get(CONF_MODE): + feature_list = config.get(CONF_FEATURE_LIST) + if feature_list and \ + validate_media_player_features(state, feature_list): a_type = 'MediaPlayer' elif state.domain == 'sensor': diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index f59ee5488ec..6d49c806e0f 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -8,12 +8,20 @@ HOMEKIT_NOTIFY_ID = 4663548 # #### Config #### CONF_AUTO_START = 'auto_start' CONF_ENTITY_CONFIG = 'entity_config' +CONF_FEATURE = 'feature' +CONF_FEATURE_LIST = 'feature_list' CONF_FILTER = 'filter' # #### Config Defaults #### DEFAULT_AUTO_START = True DEFAULT_PORT = 51827 +# #### Features #### +FEATURE_ON_OFF = 'on_off' +FEATURE_PLAY_PAUSE = 'play_pause' +FEATURE_PLAY_STOP = 'play_stop' +FEATURE_TOGGLE_MUTE = 'toggle_mute' + # #### HomeKit Component Services #### SERVICE_HOMEKIT_START = 'start' @@ -23,12 +31,6 @@ BRIDGE_NAME = 'Home Assistant Bridge' BRIDGE_SERIAL_NUMBER = 'homekit.bridge' MANUFACTURER = 'Home Assistant' -# #### Media Player Modes #### -ON_OFF = 'on_off' -PLAY_PAUSE = 'play_pause' -PLAY_STOP = 'play_stop' -TOGGLE_MUTE = 'toggle_mute' - # #### Services #### SERV_ACCESSORY_INFO = 'AccessoryInformation' SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor' diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 563cd0cb25c..ec41b9fd618 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -4,7 +4,7 @@ import logging from pyhap.const import CATEGORY_SWITCH from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_MODE, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, + 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) from homeassistant.components.media_player import ( @@ -13,15 +13,15 @@ from homeassistant.components.media_player import ( from . import TYPES from .accessories import HomeAccessory from .const import ( - CHAR_NAME, CHAR_ON, ON_OFF, PLAY_PAUSE, PLAY_STOP, SERV_SWITCH, - TOGGLE_MUTE) + CHAR_NAME, CHAR_ON, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, + FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, SERV_SWITCH) _LOGGER = logging.getLogger(__name__) -MODE_FRIENDLY_NAME = {ON_OFF: 'Power', - PLAY_PAUSE: 'Play/Pause', - PLAY_STOP: 'Play/Stop', - TOGGLE_MUTE: 'Mute'} +MODE_FRIENDLY_NAME = {FEATURE_ON_OFF: 'Power', + FEATURE_PLAY_PAUSE: 'Play/Pause', + FEATURE_PLAY_STOP: 'Play/Stop', + FEATURE_TOGGLE_MUTE: 'Mute'} @TYPES.register('MediaPlayer') @@ -31,38 +31,38 @@ class MediaPlayer(HomeAccessory): def __init__(self, *args): """Initialize a Switch accessory object.""" super().__init__(*args, category=CATEGORY_SWITCH) - self._flag = {ON_OFF: False, PLAY_PAUSE: False, - PLAY_STOP: False, TOGGLE_MUTE: False} - self.chars = {ON_OFF: None, PLAY_PAUSE: None, - PLAY_STOP: None, TOGGLE_MUTE: None} - modes = self.config[CONF_MODE] + self._flag = {FEATURE_ON_OFF: False, FEATURE_PLAY_PAUSE: False, + FEATURE_PLAY_STOP: False, FEATURE_TOGGLE_MUTE: False} + self.chars = {FEATURE_ON_OFF: None, FEATURE_PLAY_PAUSE: None, + FEATURE_PLAY_STOP: None, FEATURE_TOGGLE_MUTE: None} + feature_list = self.config[CONF_FEATURE_LIST] - if ON_OFF in modes: + if FEATURE_ON_OFF in feature_list: + name = self.generate_service_name(FEATURE_ON_OFF) serv_on_off = self.add_preload_service(SERV_SWITCH, CHAR_NAME) - serv_on_off.configure_char( - CHAR_NAME, value=self.generate_service_name(ON_OFF)) - self.chars[ON_OFF] = serv_on_off.configure_char( + serv_on_off.configure_char(CHAR_NAME, value=name) + self.chars[FEATURE_ON_OFF] = serv_on_off.configure_char( CHAR_ON, value=False, setter_callback=self.set_on_off) - if PLAY_PAUSE in modes: + if FEATURE_PLAY_PAUSE in feature_list: + name = self.generate_service_name(FEATURE_PLAY_PAUSE) serv_play_pause = self.add_preload_service(SERV_SWITCH, CHAR_NAME) - serv_play_pause.configure_char( - CHAR_NAME, value=self.generate_service_name(PLAY_PAUSE)) - self.chars[PLAY_PAUSE] = serv_play_pause.configure_char( + serv_play_pause.configure_char(CHAR_NAME, value=name) + self.chars[FEATURE_PLAY_PAUSE] = serv_play_pause.configure_char( CHAR_ON, value=False, setter_callback=self.set_play_pause) - if PLAY_STOP in modes: + if FEATURE_PLAY_STOP in feature_list: + name = self.generate_service_name(FEATURE_PLAY_STOP) serv_play_stop = self.add_preload_service(SERV_SWITCH, CHAR_NAME) - serv_play_stop.configure_char( - CHAR_NAME, value=self.generate_service_name(PLAY_STOP)) - self.chars[PLAY_STOP] = serv_play_stop.configure_char( + serv_play_stop.configure_char(CHAR_NAME, value=name) + self.chars[FEATURE_PLAY_STOP] = serv_play_stop.configure_char( CHAR_ON, value=False, setter_callback=self.set_play_stop) - if TOGGLE_MUTE in modes: + if FEATURE_TOGGLE_MUTE in feature_list: + name = self.generate_service_name(FEATURE_TOGGLE_MUTE) serv_toggle_mute = self.add_preload_service(SERV_SWITCH, CHAR_NAME) - serv_toggle_mute.configure_char( - CHAR_NAME, value=self.generate_service_name(TOGGLE_MUTE)) - self.chars[TOGGLE_MUTE] = serv_toggle_mute.configure_char( + serv_toggle_mute.configure_char(CHAR_NAME, value=name) + self.chars[FEATURE_TOGGLE_MUTE] = serv_toggle_mute.configure_char( CHAR_ON, value=False, setter_callback=self.set_toggle_mute) def generate_service_name(self, mode): @@ -73,7 +73,7 @@ class MediaPlayer(HomeAccessory): """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[ON_OFF] = True + self._flag[FEATURE_ON_OFF] = True service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} self.hass.services.call(DOMAIN, service, params) @@ -82,7 +82,7 @@ class MediaPlayer(HomeAccessory): """Move switch state to value if call came from HomeKit.""" _LOGGER.debug('%s: Set switch state for "play_pause" to %s', self.entity_id, value) - self._flag[PLAY_PAUSE] = True + self._flag[FEATURE_PLAY_PAUSE] = True service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} self.hass.services.call(DOMAIN, service, params) @@ -91,7 +91,7 @@ class MediaPlayer(HomeAccessory): """Move switch state to value if call came from HomeKit.""" _LOGGER.debug('%s: Set switch state for "play_stop" to %s', self.entity_id, value) - self._flag[PLAY_STOP] = True + self._flag[FEATURE_PLAY_STOP] = True service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_STOP params = {ATTR_ENTITY_ID: self.entity_id} self.hass.services.call(DOMAIN, service, params) @@ -100,7 +100,7 @@ class MediaPlayer(HomeAccessory): """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[TOGGLE_MUTE] = True + self._flag[FEATURE_TOGGLE_MUTE] = True params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_MUTED: value} self.hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, params) @@ -109,34 +109,34 @@ class MediaPlayer(HomeAccessory): """Update switch state after state changed.""" current_state = new_state.state - if self.chars[ON_OFF]: + if self.chars[FEATURE_ON_OFF]: hk_state = current_state not in (STATE_OFF, STATE_UNKNOWN, 'None') - if not self._flag[ON_OFF]: + if not self._flag[FEATURE_ON_OFF]: _LOGGER.debug('%s: Set current state for "on_off" to %s', self.entity_id, hk_state) - self.chars[ON_OFF].set_value(hk_state) - self._flag[ON_OFF] = False + self.chars[FEATURE_ON_OFF].set_value(hk_state) + self._flag[FEATURE_ON_OFF] = False - if self.chars[PLAY_PAUSE]: + if self.chars[FEATURE_PLAY_PAUSE]: hk_state = current_state == STATE_PLAYING - if not self._flag[PLAY_PAUSE]: + if not self._flag[FEATURE_PLAY_PAUSE]: _LOGGER.debug('%s: Set current state for "play_pause" to %s', self.entity_id, hk_state) - self.chars[PLAY_PAUSE].set_value(hk_state) - self._flag[PLAY_PAUSE] = False + self.chars[FEATURE_PLAY_PAUSE].set_value(hk_state) + self._flag[FEATURE_PLAY_PAUSE] = False - if self.chars[PLAY_STOP]: + if self.chars[FEATURE_PLAY_STOP]: hk_state = current_state == STATE_PLAYING - if not self._flag[PLAY_STOP]: + if not self._flag[FEATURE_PLAY_STOP]: _LOGGER.debug('%s: Set current state for "play_stop" to %s', self.entity_id, hk_state) - self.chars[PLAY_STOP].set_value(hk_state) - self._flag[PLAY_STOP] = False + self.chars[FEATURE_PLAY_STOP].set_value(hk_state) + self._flag[FEATURE_PLAY_STOP] = False - if self.chars[TOGGLE_MUTE]: + if self.chars[FEATURE_TOGGLE_MUTE]: current_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) - if not self._flag[TOGGLE_MUTE]: + if not self._flag[FEATURE_TOGGLE_MUTE]: _LOGGER.debug('%s: Set current state for "toggle_mute" to %s', self.entity_id, current_state) - self.chars[TOGGLE_MUTE].set_value(current_state) - self._flag[TOGGLE_MUTE] = False + self.chars[FEATURE_TOGGLE_MUTE].set_value(current_state) + self._flag[FEATURE_TOGGLE_MUTE] = False diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 57ce562ce21..50095844757 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -3,20 +3,37 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_STOP, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE) +import homeassistant.components.media_player as media_player from homeassistant.core import split_entity_id from homeassistant.const import ( - ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_MODE, CONF_NAME, TEMP_CELSIUS) + ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, TEMP_CELSIUS) import homeassistant.helpers.config_validation as cv import homeassistant.util.temperature as temp_util from .const import ( - HOMEKIT_NOTIFY_ID, ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE) + CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF, + FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE) _LOGGER = logging.getLogger(__name__) -MEDIA_PLAYER_MODES = (ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE) + +BASIC_INFO_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME): cv.string, +}) + +FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({ + vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list, +}) + + +CODE_SCHEMA = BASIC_INFO_SCHEMA.extend({ + vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string), +}) + +MEDIA_PLAYER_SCHEMA = vol.Schema({ + vol.Required(CONF_FEATURE): vol.All( + cv.string, vol.In((FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, + FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE))), +}) def validate_entity_config(values): @@ -24,57 +41,59 @@ def validate_entity_config(values): entities = {} for entity_id, config in values.items(): entity = cv.entity_id(entity_id) - params = {} - if not isinstance(config, dict): - raise vol.Invalid('The configuration for "{}" must be ' - ' a dictionary.'.format(entity)) - - for key in (CONF_NAME, ): - value = config.get(key, -1) - if value != -1: - params[key] = cv.string(value) - domain, _ = split_entity_id(entity) + if not isinstance(config, dict): + raise vol.Invalid('The configuration for {} must be ' + ' a dictionary.'.format(entity)) + if domain in ('alarm_control_panel', 'lock'): - code = config.get(ATTR_CODE) - params[ATTR_CODE] = cv.string(code) if code else None + config = CODE_SCHEMA(config) - if domain == 'media_player': - mode = config.get(CONF_MODE) - params[CONF_MODE] = cv.ensure_list(mode) - for key in params[CONF_MODE]: - if key not in MEDIA_PLAYER_MODES: - raise vol.Invalid( - 'Invalid mode: "{}", valid modes are: "{}".' - .format(key, MEDIA_PLAYER_MODES)) + elif domain == media_player.DOMAIN: + config = FEATURE_SCHEMA(config) + feature_list = {} + for feature in config[CONF_FEATURE_LIST]: + params = MEDIA_PLAYER_SCHEMA(feature) + key = params.pop(CONF_FEATURE) + if key in feature_list: + raise vol.Invalid('A feature can be added only once for {}' + .format(entity)) + feature_list[key] = params + config[CONF_FEATURE_LIST] = feature_list - entities[entity] = params + else: + config = BASIC_INFO_SCHEMA(config) + + entities[entity] = config return entities -def validate_media_player_modes(state, config): - """Validate modes for media playeres.""" +def validate_media_player_features(state, feature_list): + """Validate features for media players.""" features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported_modes = [] - if features & (SUPPORT_TURN_ON | SUPPORT_TURN_OFF): - supported_modes.append(ON_OFF) - if features & (SUPPORT_PLAY | SUPPORT_PAUSE): - supported_modes.append(PLAY_PAUSE) - if features & (SUPPORT_PLAY | SUPPORT_STOP): - supported_modes.append(PLAY_STOP) - if features & SUPPORT_VOLUME_MUTE: - supported_modes.append(TOGGLE_MUTE) + if features & (media_player.SUPPORT_TURN_ON | + media_player.SUPPORT_TURN_OFF): + supported_modes.append(FEATURE_ON_OFF) + if features & (media_player.SUPPORT_PLAY | media_player.SUPPORT_PAUSE): + supported_modes.append(FEATURE_PLAY_PAUSE) + if features & (media_player.SUPPORT_PLAY | media_player.SUPPORT_STOP): + supported_modes.append(FEATURE_PLAY_STOP) + if features & media_player.SUPPORT_VOLUME_MUTE: + supported_modes.append(FEATURE_TOGGLE_MUTE) - if not config.get(CONF_MODE): - config[CONF_MODE] = supported_modes - return + error_list = [] + for feature in feature_list: + if feature not in supported_modes: + error_list.append(feature) - for mode in config[CONF_MODE]: - if mode not in supported_modes: - raise vol.Invalid('"{}" does not support mode: "{}".' - .format(state.entity_id, mode)) + if error_list: + _LOGGER.error("%s does not support features: %s", + state.entity_id, error_list) + return False + return True def show_setup_message(hass, pincode): diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 11b2d737a70..46e5f8b1174 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -2,20 +2,17 @@ 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) +import homeassistant.components.cover as cover +import homeassistant.components.climate as climate +import homeassistant.components.media_player as media_player from homeassistant.components.homekit import get_accessory, TYPES -from homeassistant.components.homekit.const import ON_OFF +from homeassistant.components.homekit.const import ( + CONF_FEATURE_LIST, FEATURE_ON_OFF) from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, - ATTR_UNIT_OF_MEASUREMENT, CONF_MODE, CONF_NAME, TEMP_CELSIUS, - TEMP_FAHRENHEIT) + ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT) def test_not_supported(caplog): @@ -32,9 +29,9 @@ def test_not_supported(caplog): 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]}) + config = {CONF_FEATURE_LIST: {FEATURE_ON_OFF: None}} + entity_state = State('media_player.demo', 'on') + get_accessory(None, entity_state, 2, config) is None # no supported modes for entity entity_state = State('media_player.demo', 'on') @@ -58,14 +55,15 @@ def test_customize_options(config, name): ('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]}), + {ATTR_SUPPORTED_FEATURES: media_player.SUPPORT_TURN_ON | + media_player.SUPPORT_TURN_OFF}, {CONF_FEATURE_LIST: + {FEATURE_ON_OFF: None}}), ('SecuritySystem', 'alarm_control_panel.test', 'armed', {}, {ATTR_CODE: '1234'}), ('Thermostat', 'climate.test', 'auto', {}, {}), ('Thermostat', 'climate.test', 'auto', - {ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_LOW | - SUPPORT_TARGET_TEMPERATURE_HIGH}, {}), + {ATTR_SUPPORTED_FEATURES: climate.SUPPORT_TARGET_TEMPERATURE_LOW | + climate.SUPPORT_TARGET_TEMPERATURE_HIGH}, {}), ]) def test_types(type_name, entity_id, state, attrs, config): """Test if types are associated correctly.""" @@ -82,7 +80,7 @@ def test_types(type_name, entity_id, state, attrs, config): @pytest.mark.parametrize('type_name, entity_id, state, attrs', [ ('GarageDoorOpener', 'cover.garage_door', 'open', {ATTR_DEVICE_CLASS: 'garage', - ATTR_SUPPORTED_FEATURES: SUPPORT_OPEN | SUPPORT_CLOSE}), + ATTR_SUPPORTED_FEATURES: cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE}), ('WindowCovering', 'cover.set_position', 'open', {ATTR_SUPPORTED_FEATURES: 4}), ('WindowCoveringBasic', 'cover.open_window', 'open', diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 03135b1418e..d89f9740ea6 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -4,9 +4,10 @@ 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) + CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, + FEATURE_TOGGLE_MUTE) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_MODE, SERVICE_MEDIA_PAUSE, + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, 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) @@ -16,7 +17,9 @@ 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]} + config = {CONF_FEATURE_LIST: { + FEATURE_ON_OFF: None, FEATURE_PLAY_PAUSE: None, + FEATURE_PLAY_STOP: None, FEATURE_TOGGLE_MUTE: None}} entity_id = 'media_player.test' hass.states.async_set(entity_id, None, {ATTR_SUPPORTED_FEATURES: 20873, @@ -28,32 +31,32 @@ async def test_media_player_set_state(hass): 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 + assert acc.chars[FEATURE_ON_OFF].value == 0 + assert acc.chars[FEATURE_PLAY_PAUSE].value == 0 + assert acc.chars[FEATURE_PLAY_STOP].value == 0 + assert acc.chars[FEATURE_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 + assert acc.chars[FEATURE_ON_OFF].value == 1 + assert acc.chars[FEATURE_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 + assert acc.chars[FEATURE_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 + assert acc.chars[FEATURE_PLAY_PAUSE].value == 1 + assert acc.chars[FEATURE_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 + assert acc.chars[FEATURE_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 + assert acc.chars[FEATURE_PLAY_STOP].value == 0 # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) @@ -63,43 +66,51 @@ async def test_media_player_set_state(hass): 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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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_add_job(acc.chars[FEATURE_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 diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 56a625e02d7..0bc1eb96841 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -4,17 +4,18 @@ import voluptuous as vol from homeassistant.core import State from homeassistant.components.homekit.const import ( - HOMEKIT_NOTIFY_ID, ON_OFF, PLAY_PAUSE, PLAY_STOP, TOGGLE_MUTE) + CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF, + FEATURE_PLAY_PAUSE) 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, - validate_media_player_modes) + validate_media_player_features) 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, ATTR_SUPPORTED_FEATURES, CONF_MODE, CONF_NAME, STATE_UNKNOWN, + ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT) from tests.common import async_mock_service @@ -25,7 +26,11 @@ def test_validate_entity_config(): configs = [{'invalid_entity_id': {}}, {'demo.test': 1}, {'demo.test': 'test'}, {'demo.test': [1, 2]}, {'demo.test': None}, {'demo.test': {CONF_NAME: None}}, - {'media_player.test': {CONF_MODE: 'invalid_mode'}}] + {'media_player.test': {CONF_FEATURE_LIST: [ + {CONF_FEATURE: 'invalid_feature'}]}}, + {'media_player.test': {CONF_FEATURE_LIST: [ + {CONF_FEATURE: FEATURE_ON_OFF}, + {CONF_FEATURE: FEATURE_ON_OFF}]}}, ] for conf in configs: with pytest.raises(vol.Invalid): @@ -45,23 +50,26 @@ def test_validate_entity_config(): {'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]}} + {'media_player.demo': {CONF_FEATURE_LIST: {}}} + config = {CONF_FEATURE_LIST: [{CONF_FEATURE: FEATURE_ON_OFF}, + {CONF_FEATURE: FEATURE_PLAY_PAUSE}]} + assert vec({'media_player.demo': config}) == \ + {'media_player.demo': {CONF_FEATURE_LIST: + {FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}}} -def test_validate_media_player_modes(): +def test_validate_media_player_features(): """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]} + assert validate_media_player_features(entity_state, config) is True + + config = {FEATURE_ON_OFF: None} + assert validate_media_player_features(entity_state, config) is True entity_state = State('media_player.demo', 'on') - config = {CONF_MODE: [ON_OFF]} - with pytest.raises(vol.Invalid): - validate_media_player_modes(entity_state, config) + assert validate_media_player_features(entity_state, config) is False def test_convert_to_float():