From 017f47dd2cf144df8fa6955483c6cdbc38e3f362 Mon Sep 17 00:00:00 2001 From: Jan Harkes Date: Thu, 31 Mar 2016 18:24:06 -0400 Subject: [PATCH] Service validation for light.turn_on/.turn_off/.toggle --- homeassistant/components/light/__init__.py | 108 ++++++++------------- homeassistant/helpers/config_validation.py | 2 + tests/components/light/test_init.py | 8 +- 3 files changed, 45 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c7014fd05f6..28784d13218 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -8,6 +8,8 @@ import logging import os import csv +import voluptuous as vol + from homeassistant.components import ( group, discovery, wemo, wink, isy994, zwave, insteon_hub, mysensors, tellstick, vera) @@ -18,7 +20,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -import homeassistant.util as util +import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util @@ -77,6 +79,31 @@ PROP_TO_ATTR = { 'xy_color': ATTR_XY_COLOR, } +# Service call validation schemas +VALID_TRANSITION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900)) + +LIGHT_TURN_ON_SCHEMA = vol.Schema({ + ATTR_ENTITY_ID: cv.entity_ids, + ATTR_PROFILE: str, + ATTR_TRANSITION: VALID_TRANSITION, + ATTR_BRIGHTNESS: vol.All(int, vol.Range(min=0, max=255)), + ATTR_RGB_COLOR: vol.ExactSequence((cv.byte, cv.byte, cv.byte)), + ATTR_XY_COLOR: vol.ExactSequence((cv.small_float, cv.small_float)), + ATTR_COLOR_TEMP: vol.All(int, vol.Range(min=154, max=500)), + ATTR_FLASH: [FLASH_SHORT, FLASH_LONG], + ATTR_EFFECT: [EFFECT_COLORLOOP, EFFECT_RANDOM, EFFECT_WHITE], +}) + +LIGHT_TURN_OFF_SCHEMA = vol.Schema({ + ATTR_ENTITY_ID: cv.entity_ids, + ATTR_TRANSITION: VALID_TRANSITION, +}) + +LIGHT_TOGGLE_SCHEMA = vol.Schema({ + ATTR_ENTITY_ID: cv.entity_ids, + ATTR_TRANSITION: VALID_TRANSITION, +}) + _LOGGER = logging.getLogger(__name__) @@ -169,18 +196,12 @@ def setup(hass, config): def handle_light_service(service): """Hande a turn light on or off service call.""" - # Get and validate data - dat = service.data + # Get the validated data + params = service.data.copy() # Convert the entity ids to valid light ids target_lights = component.extract_from_service(service) - - params = {} - - transition = util.convert(dat.get(ATTR_TRANSITION), int) - - if transition is not None: - params[ATTR_TRANSITION] = transition + params.pop(ATTR_ENTITY_ID, None) service_fun = None if service.service == SERVICE_TURN_OFF: @@ -198,63 +219,11 @@ def setup(hass, config): return # Processing extra data for turn light on request. - - # We process the profile first so that we get the desired - # behavior that extra service data attributes overwrite - # profile values. - profile = profiles.get(dat.get(ATTR_PROFILE)) + profile = profiles.get(params.pop(ATTR_PROFILE, None)) if profile: - *params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile - - if ATTR_BRIGHTNESS in dat: - # We pass in the old value as the default parameter if parsing - # of the new one goes wrong. - params[ATTR_BRIGHTNESS] = util.convert( - dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS)) - - if ATTR_XY_COLOR in dat: - try: - # xy_color should be a list containing 2 floats. - xycolor = dat.get(ATTR_XY_COLOR) - - # Without this check, a xycolor with value '99' would work. - if not isinstance(xycolor, str): - params[ATTR_XY_COLOR] = [float(val) for val in xycolor] - - except (TypeError, ValueError): - # TypeError if xy_color is not iterable - # ValueError if value could not be converted to float - pass - - if ATTR_COLOR_TEMP in dat: - # color_temp should be an int of mireds value - colortemp = dat.get(ATTR_COLOR_TEMP) - - # Without this check, a ctcolor with value '99' would work - # These values are based on Philips Hue, may need ajustment later - if isinstance(colortemp, int) and 154 <= colortemp <= 500: - params[ATTR_COLOR_TEMP] = colortemp - - if ATTR_RGB_COLOR in dat: - try: - # rgb_color should be a list containing 3 ints - rgb_color = dat.get(ATTR_RGB_COLOR) - - if len(rgb_color) == 3: - params[ATTR_RGB_COLOR] = [int(val) for val in rgb_color] - - except (TypeError, ValueError): - # TypeError if rgb_color is not iterable - # ValueError if not all values can be converted to int - pass - - if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG): - params[ATTR_FLASH] = dat[ATTR_FLASH] - - if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE, - EFFECT_RANDOM): - params[ATTR_EFFECT] = dat[ATTR_EFFECT] + params.setdefault(ATTR_XY_COLOR, list(profile[:2])) + params.setdefault(ATTR_BRIGHTNESS, profile[2]) for light in target_lights: light.turn_on(**params) @@ -267,13 +236,16 @@ def setup(hass, config): descriptions = load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml')) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service, - descriptions.get(SERVICE_TURN_ON)) + descriptions.get(SERVICE_TURN_ON), + schema=LIGHT_TURN_ON_SCHEMA) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service, - descriptions.get(SERVICE_TURN_OFF)) + descriptions.get(SERVICE_TURN_OFF), + schema=LIGHT_TURN_OFF_SCHEMA) hass.services.register(DOMAIN, SERVICE_TOGGLE, handle_light_service, - descriptions.get(SERVICE_TOGGLE)) + descriptions.get(SERVICE_TOGGLE), + schema=LIGHT_TOGGLE_SCHEMA) return True diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 1b83b2ee879..8a32b680cf3 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -12,6 +12,8 @@ PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): str, }, extra=vol.ALLOW_EXTRA) +byte = vol.All(int, vol.Range(min=0, max=255)) +small_float = vol.All(float, vol.Range(min=0, max=1)) latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90)) longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180)) diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 7df35d9f3f8..0d75a9b811a 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -212,6 +212,7 @@ class TestLight(unittest.TestCase): data) # Test shitty data + light.turn_on(self.hass) light.turn_on(self.hass, dev1.entity_id, profile="nonexisting") light.turn_on(self.hass, dev2.entity_id, xy_color=["bla-di-bla", 5]) light.turn_on(self.hass, dev3.entity_id, rgb_color=[255, None, 2]) @@ -227,7 +228,7 @@ class TestLight(unittest.TestCase): method, data = dev3.last_call('turn_on') self.assertEqual({}, data) - # faulty attributes should not overwrite profile data + # faulty attributes will not trigger a service call light.turn_on( self.hass, dev1.entity_id, profile=prof_name, brightness='bright', rgb_color='yellowish') @@ -235,10 +236,7 @@ class TestLight(unittest.TestCase): self.hass.pool.block_till_done() method, data = dev1.last_call('turn_on') - self.assertEqual( - {light.ATTR_BRIGHTNESS: prof_bri, - light.ATTR_XY_COLOR: [prof_x, prof_y]}, - data) + self.assertEqual({}, data) def test_broken_light_profiles(self): """Test light profiles."""