1
mirror of https://github.com/home-assistant/core synced 2024-10-07 10:13:38 +02:00

Add brightness_step to light.turn_on (#31452)

* Clean up light turn on service

* Add brightness_step to turn_on schema

* Fix import

* Fix imports 2

* Fix RFLink test
This commit is contained in:
Paulus Schoutsen 2020-02-04 16:13:29 -08:00 committed by GitHub
parent e970177eeb
commit c85a7934ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 73 deletions

View File

@ -1,5 +1,4 @@
"""Provides functionality to interact with lights."""
import asyncio
import csv
from datetime import timedelta
import logging
@ -8,15 +7,12 @@ from typing import Dict, Optional, Tuple
import voluptuous as vol
from homeassistant.auth.permissions.const import POLICY_CONTROL
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_ON,
)
from homeassistant.exceptions import Unauthorized, UnknownUser
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
@ -61,6 +57,8 @@ ATTR_WHITE_VALUE = "white_value"
# Brightness of the light, 0..255 or percentage
ATTR_BRIGHTNESS = "brightness"
ATTR_BRIGHTNESS_PCT = "brightness_pct"
ATTR_BRIGHTNESS_STEP = "brightness_step"
ATTR_BRIGHTNESS_STEP_PCT = "brightness_step_pct"
# String representing a profile (built-in ones or external defined).
ATTR_PROFILE = "profile"
@ -87,12 +85,16 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))
VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100))
VALID_BRIGHTNESS_STEP = vol.All(vol.Coerce(int), vol.Clamp(min=-255, max=255))
VALID_BRIGHTNESS_STEP_PCT = vol.All(vol.Coerce(float), vol.Clamp(min=-100, max=100))
LIGHT_TURN_ON_SCHEMA = {
vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
ATTR_TRANSITION: VALID_TRANSITION,
ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT,
vol.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT,
vol.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP,
vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT,
vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string,
vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(
vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)
@ -169,7 +171,7 @@ def preprocess_turn_off(params):
"""Process data for turning light off if brightness is 0."""
if ATTR_BRIGHTNESS in params and params[ATTR_BRIGHTNESS] == 0:
# Zero brightness: Light will be turned off
params = {k: v for k, v in params.items() if k in [ATTR_TRANSITION, ATTR_FLASH]}
params = {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)}
return (True, params) # Light should be turned off
return (False, None) # Light should be turned on
@ -187,70 +189,65 @@ async def async_setup(hass, config):
if not profiles_valid:
return False
async def async_handle_light_on_service(service):
"""Handle a turn light on service call."""
# Get the validated data
params = service.data.copy()
def preprocess_data(data):
"""Preprocess the service data."""
base = {}
# Convert the entity ids to valid light ids
target_lights = await component.async_extract_from_service(service)
params.pop(ATTR_ENTITY_ID, None)
for entity_field in cv.ENTITY_SERVICE_FIELDS:
if entity_field in data:
base[entity_field] = data.pop(entity_field)
if service.context.user_id:
user = await hass.auth.async_get_user(service.context.user_id)
if user is None:
raise UnknownUser(context=service.context)
preprocess_turn_on_alternatives(data)
turn_lights_off, off_params = preprocess_turn_off(data)
entity_perms = user.permissions.check_entity
base["params"] = data
base["turn_lights_off"] = turn_lights_off
base["off_params"] = off_params
for light in target_lights:
if not entity_perms(light, POLICY_CONTROL):
raise Unauthorized(
context=service.context,
entity_id=light,
permission=POLICY_CONTROL,
)
return base
preprocess_turn_on_alternatives(params)
turn_lights_off, off_params = preprocess_turn_off(params)
async def async_handle_light_on_service(light, call):
"""Handle turning a light on.
poll_lights = []
change_tasks = []
for light in target_lights:
light.async_set_context(service.context)
If brightness is set to 0, this service will turn the light off.
"""
params = call.data["params"]
turn_light_off = call.data["turn_lights_off"]
off_params = call.data["off_params"]
if not params:
default_profile = Profiles.get_default(light.entity_id)
if default_profile is not None:
params = {ATTR_PROFILE: default_profile}
preprocess_turn_on_alternatives(params)
turn_light_off, off_params = preprocess_turn_off(params)
elif ATTR_BRIGHTNESS_STEP in params or ATTR_BRIGHTNESS_STEP_PCT in params:
brightness = light.brightness if light.is_on else 0
params = params.copy()
if ATTR_BRIGHTNESS_STEP in params:
brightness += params.pop(ATTR_BRIGHTNESS_STEP)
pars = params
off_pars = off_params
turn_light_off = turn_lights_off
if not pars:
pars = params.copy()
pars[ATTR_PROFILE] = Profiles.get_default(light.entity_id)
preprocess_turn_on_alternatives(pars)
turn_light_off, off_pars = preprocess_turn_off(pars)
if turn_light_off:
task = light.async_request_call(light.async_turn_off(**off_pars))
else:
task = light.async_request_call(light.async_turn_on(**pars))
brightness += int(params.pop(ATTR_BRIGHTNESS_STEP_PCT) / 100 * 255)
change_tasks.append(task)
params[ATTR_BRIGHTNESS] = max(0, min(255, brightness))
turn_light_off, off_params = preprocess_turn_off(params)
if light.should_poll:
poll_lights.append(light)
if change_tasks:
await asyncio.wait(change_tasks)
if poll_lights:
await asyncio.wait(
[light.async_update_ha_state(True) for light in poll_lights]
)
if turn_light_off:
await light.async_turn_off(**off_params)
else:
await light.async_turn_on(**params)
# Listen for light on and light off service calls.
hass.services.async_register(
DOMAIN,
component.async_register_entity_service(
SERVICE_TURN_ON,
vol.All(cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA), preprocess_data),
async_handle_light_on_service,
schema=cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA),
)
component.async_register_entity_service(

View File

@ -1,6 +1,7 @@
"""Intents for the light integration."""
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv
@ -8,7 +9,6 @@ import homeassistant.util.color as color_util
from . import (
ATTR_BRIGHTNESS_PCT,
ATTR_ENTITY_ID,
ATTR_RGB_COLOR,
DOMAIN,
SERVICE_TURN_ON,

View File

@ -36,6 +36,12 @@ turn_on:
brightness_pct:
description: Number between 0..100 indicating percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness and 100 is the maximum brightness supported by the light.
example: 47
brightness_step:
description: Change brightness by an amount. Should be between -255..255.
example: -25.5
brightness_step_pct:
description: Change brightness by a percentage. Should be between -100..100.
example: -10
profile:
description: Name of a light profile to use.
example: relax

View File

@ -19,7 +19,6 @@ import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_ENTITY_ID,
ATTR_HS_COLOR,
PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS,
@ -27,7 +26,7 @@ from homeassistant.components.light import (
SUPPORT_COLOR_TEMP,
Light,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.util import color, dt

View File

@ -462,3 +462,37 @@ async def test_light_turn_on_auth(hass, hass_admin_user):
True,
core.Context(user_id=hass_admin_user.id),
)
async def test_light_brightness_step(hass):
"""Test that light context works."""
platform = getattr(hass.components, "test.light")
platform.init()
entity = platform.ENTITIES[0]
entity.supported_features = light.SUPPORT_BRIGHTNESS
entity.brightness = 100
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
state = hass.states.get(entity.entity_id)
assert state is not None
assert state.attributes["brightness"] == 100
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": entity.entity_id, "brightness_step": -10},
True,
)
_, data = entity.last_call("turn_on")
assert data["brightness"] == 90, data
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": entity.entity_id, "brightness_step_pct": 10},
True,
)
_, data = entity.last_call("turn_on")
assert data["brightness"] == 125, data

View File

@ -298,18 +298,16 @@ async def test_signal_repetitions_cancelling(hass, monkeypatch):
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: DOMAIN + ".test"}
)
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: DOMAIN + ".test"}
)
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: DOMAIN + ".test"}, blocking=True
)
await hass.async_block_till_done()
assert protocol.send_command_ack.call_args_list[0][0][1] == "off"
assert protocol.send_command_ack.call_args_list[1][0][1] == "on"
assert protocol.send_command_ack.call_args_list[2][0][1] == "on"
assert protocol.send_command_ack.call_args_list[3][0][1] == "on"
assert [call[0][1] for call in protocol.send_command_ack.call_args_list] == [
"off",
"on",
"on",
"on",
]
async def test_type_toggle(hass, monkeypatch):

View File

@ -3,6 +3,7 @@ Provide a mock light platform.
Call init before using it in your tests to ensure clean test data.
"""
from homeassistant.components.light import Light
from homeassistant.const import STATE_OFF, STATE_ON
from tests.common import MockToggleEntity
@ -18,9 +19,9 @@ def init(empty=False):
[]
if empty
else [
MockToggleEntity("Ceiling", STATE_ON),
MockToggleEntity("Ceiling", STATE_OFF),
MockToggleEntity(None, STATE_OFF),
MockLight("Ceiling", STATE_ON),
MockLight("Ceiling", STATE_OFF),
MockLight(None, STATE_OFF),
]
)
@ -30,3 +31,10 @@ async def async_setup_platform(
):
"""Return mock entities."""
async_add_entities_callback(ENTITIES)
class MockLight(MockToggleEntity, Light):
"""Mock light class."""
brightness = None
supported_features = 0