Service validation for light.turn_on/.turn_off/.toggle

This commit is contained in:
Jan Harkes 2016-03-31 18:24:06 -04:00
parent 5bd58351c7
commit 017f47dd2c
3 changed files with 45 additions and 73 deletions

View file

@ -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

View file

@ -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))

View file

@ -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."""