From afd1e6a5ccd2ea92dae75a4c089cd0e9fec6dd27 Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Sat, 2 Apr 2016 03:51:03 -0400 Subject: [PATCH] Service validation for media_player component --- .../components/media_player/__init__.py | 86 ++++++++++--------- homeassistant/helpers/config_validation.py | 7 ++ 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 47ae1745fae..229a2a79e4d 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -7,11 +7,14 @@ https://home-assistant.io/components/media_player/ import logging import os +import voluptuous as vol + from homeassistant.components import discovery from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE, ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -78,6 +81,7 @@ SUPPORT_PLAY_MEDIA = 512 SUPPORT_VOLUME_STEP = 1024 SUPPORT_SELECT_SOURCE = 2048 +# simple services that only take entity_id(s) as optional argument SERVICE_TO_METHOD = { SERVICE_TURN_ON: 'turn_on', SERVICE_TURN_OFF: 'turn_off', @@ -89,8 +93,6 @@ SERVICE_TO_METHOD = { SERVICE_MEDIA_PAUSE: 'media_pause', 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 = [ @@ -115,6 +117,33 @@ ATTR_TO_PROPERTY = [ ATTR_INPUT_SOURCE, ] +# Service call validation schemas +MEDIA_PLAYER_SCHEMA = vol.Schema({ + ATTR_ENTITY_ID: cv.entity_ids, +}) + +MEDIA_PLAYER_MUTE_VOLUME_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_MEDIA_VOLUME_MUTED): vol.Coerce(bool), +}) + +MEDIA_PLAYER_SET_VOLUME_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float, +}) + +MEDIA_PLAYER_MEDIA_SEEK_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_MEDIA_SEEK_POSITION): + vol.All(vol.Coerce(float), vol.Range(min=0)), +}) + +MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string, + vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string, +}) + +MEDIA_PLAYER_SELECT_SOURCE_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ + vol.Required(ATTR_INPUT_SOURCE): cv.string, +}) + def is_on(hass, entity_id=None): """ @@ -258,18 +287,13 @@ def setup(hass, config): for service in SERVICE_TO_METHOD: hass.services.register(DOMAIN, service, media_player_service_handler, - descriptions.get(service)) + descriptions.get(service), + schema=MEDIA_PLAYER_SCHEMA) def volume_set_service(service): """Set specified volume on the media player.""" volume = service.data.get(ATTR_MEDIA_VOLUME_LEVEL) - if volume is None: - _LOGGER.error( - 'Received call to %s without attribute %s', - service.service, ATTR_MEDIA_VOLUME_LEVEL) - return - for player in component.extract_from_service(service): player.set_volume_level(volume) @@ -277,18 +301,13 @@ def setup(hass, config): player.update_ha_state(True) hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service, - descriptions.get(SERVICE_VOLUME_SET)) + descriptions.get(SERVICE_VOLUME_SET), + schema=MEDIA_PLAYER_SET_VOLUME_SCHEMA) def volume_mute_service(service): """Mute (true) or unmute (false) the media player.""" mute = service.data.get(ATTR_MEDIA_VOLUME_MUTED) - if mute is None: - _LOGGER.error( - 'Received call to %s without attribute %s', - service.service, ATTR_MEDIA_VOLUME_MUTED) - return - for player in component.extract_from_service(service): player.mute_volume(mute) @@ -296,18 +315,13 @@ def setup(hass, config): player.update_ha_state(True) hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, volume_mute_service, - descriptions.get(SERVICE_VOLUME_MUTE)) + descriptions.get(SERVICE_VOLUME_MUTE), + schema=MEDIA_PLAYER_MUTE_VOLUME_SCHEMA) def media_seek_service(service): """Seek to a position.""" position = service.data.get(ATTR_MEDIA_SEEK_POSITION) - if position is None: - _LOGGER.error( - 'Received call to %s without attribute %s', - service.service, ATTR_MEDIA_SEEK_POSITION) - return - for player in component.extract_from_service(service): player.media_seek(position) @@ -315,18 +329,13 @@ def setup(hass, config): player.update_ha_state(True) hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service, - descriptions.get(SERVICE_MEDIA_SEEK)) + descriptions.get(SERVICE_MEDIA_SEEK), + schema=MEDIA_PLAYER_MEDIA_SEEK_SCHEMA) 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) @@ -335,30 +344,23 @@ def setup(hass, config): hass.services.register(DOMAIN, SERVICE_SELECT_SOURCE, select_source_service, - descriptions.get(SERVICE_SELECT_SOURCE)) + descriptions.get(SERVICE_SELECT_SOURCE), + schema=MEDIA_PLAYER_SELECT_SOURCE_SCHEMA) def play_media_service(service): """Play specified media_id on the media player.""" media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE) media_id = service.data.get(ATTR_MEDIA_CONTENT_ID) - if media_type is None or media_id is None: - missing_attr = (ATTR_MEDIA_CONTENT_TYPE if media_type is None - else ATTR_MEDIA_CONTENT_ID) - _LOGGER.error( - 'Received call to %s without attribute %s', - service.service, missing_attr) - return - for player in component.extract_from_service(service): player.play_media(media_type, media_id) if player.should_poll: player.update_ha_state(True) - hass.services.register( - DOMAIN, SERVICE_PLAY_MEDIA, play_media_service, - descriptions.get(SERVICE_PLAY_MEDIA)) + hass.services.register(DOMAIN, SERVICE_PLAY_MEDIA, play_media_service, + descriptions.get(SERVICE_PLAY_MEDIA), + schema=MEDIA_PLAYER_PLAY_MEDIA_SCHEMA) return True diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 157d1e626df..3a9f1c113d2 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -47,6 +47,13 @@ def icon(value): raise vol.Invalid('Icons should start with prefix "mdi:"') +def string(value): + """Coerce value to string, except for None.""" + if value is not None: + return str(value) + raise vol.Invalid('Value should not be None') + + def temperature_unit(value): """Validate and transform temperature unit.""" if isinstance(value, str):