Add flash light device actions (#33689)

* Add flash light device actions

* Single action with extra fields

* Change extra_fields to dictionary
This commit is contained in:
Robert Chmielowiec 2020-04-13 19:30:20 +02:00 committed by GitHub
parent 2239f2f732
commit 7bd6f5413d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 145 additions and 24 deletions

View File

@ -87,6 +87,7 @@ 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_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 = 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)) VALID_BRIGHTNESS_STEP_PCT = vol.All(vol.Coerce(float), vol.Clamp(min=-100, max=100))
VALID_FLASH = vol.In([FLASH_SHORT, FLASH_LONG])
LIGHT_TURN_ON_SCHEMA = { LIGHT_TURN_ON_SCHEMA = {
vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string, vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
@ -116,7 +117,7 @@ LIGHT_TURN_ON_SCHEMA = {
), ),
vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=0)), vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=0)),
ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)),
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_FLASH: VALID_FLASH,
ATTR_EFFECT: cv.string, ATTR_EFFECT: cv.string,
} }
@ -252,10 +253,7 @@ async def async_setup(hass, config):
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
{ {ATTR_TRANSITION: VALID_TRANSITION, ATTR_FLASH: VALID_FLASH},
ATTR_TRANSITION: VALID_TRANSITION,
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
},
"async_turn_off", "async_turn_off",
) )

View File

@ -4,6 +4,13 @@ from typing import List
import voluptuous as vol import voluptuous as vol
from homeassistant.components.device_automation import toggle_entity from homeassistant.components.device_automation import toggle_entity
from homeassistant.components.light import (
ATTR_FLASH,
FLASH_SHORT,
SUPPORT_FLASH,
VALID_BRIGHTNESS_PCT,
VALID_FLASH,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
@ -19,6 +26,7 @@ from . import ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS_STEP_PCT, DOMAIN, SUPPORT_BRI
TYPE_BRIGHTNESS_INCREASE = "brightness_increase" TYPE_BRIGHTNESS_INCREASE = "brightness_increase"
TYPE_BRIGHTNESS_DECREASE = "brightness_decrease" TYPE_BRIGHTNESS_DECREASE = "brightness_decrease"
TYPE_FLASH = "flash"
ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{ {
@ -26,11 +34,10 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
vol.Required(CONF_DOMAIN): DOMAIN, vol.Required(CONF_DOMAIN): DOMAIN,
vol.Required(CONF_TYPE): vol.In( vol.Required(CONF_TYPE): vol.In(
toggle_entity.DEVICE_ACTION_TYPES toggle_entity.DEVICE_ACTION_TYPES
+ [TYPE_BRIGHTNESS_INCREASE, TYPE_BRIGHTNESS_DECREASE] + [TYPE_BRIGHTNESS_INCREASE, TYPE_BRIGHTNESS_DECREASE, TYPE_FLASH]
),
vol.Optional(ATTR_BRIGHTNESS_PCT): vol.All(
vol.Coerce(int), vol.Range(min=0, max=100)
), ),
vol.Optional(ATTR_BRIGHTNESS_PCT): VALID_BRIGHTNESS_PCT,
vol.Optional(ATTR_FLASH): VALID_FLASH,
} }
) )
@ -60,6 +67,12 @@ async def async_call_action_from_config(
elif ATTR_BRIGHTNESS_PCT in config: elif ATTR_BRIGHTNESS_PCT in config:
data[ATTR_BRIGHTNESS_PCT] = config[ATTR_BRIGHTNESS_PCT] data[ATTR_BRIGHTNESS_PCT] = config[ATTR_BRIGHTNESS_PCT]
if config[CONF_TYPE] == TYPE_FLASH:
if ATTR_FLASH in config:
data[ATTR_FLASH] = config[ATTR_FLASH]
else:
data[ATTR_FLASH] = FLASH_SHORT
await hass.services.async_call( await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=context DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=context
) )
@ -100,6 +113,18 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
) )
) )
if supported_features & SUPPORT_FLASH:
actions.extend(
(
{
CONF_TYPE: TYPE_FLASH,
"device_id": device_id,
"entity_id": entry.entity_id,
"domain": DOMAIN,
},
)
)
return actions return actions
@ -119,15 +144,12 @@ async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> di
elif entry: elif entry:
supported_features = entry.supported_features supported_features = entry.supported_features
if not supported_features & SUPPORT_BRIGHTNESS: extra_fields = {}
return {}
return { if supported_features & SUPPORT_BRIGHTNESS:
"extra_fields": vol.Schema( extra_fields[vol.Optional(ATTR_BRIGHTNESS_PCT)] = VALID_BRIGHTNESS_PCT
{
vol.Optional(ATTR_BRIGHTNESS_PCT): vol.All( if supported_features & SUPPORT_FLASH:
vol.Coerce(int), vol.Range(min=0, max=100) extra_fields[vol.Optional(ATTR_FLASH)] = VALID_FLASH
)
} return {"extra_fields": vol.Schema(extra_fields)} if extra_fields else {}
)
}

View File

@ -5,7 +5,8 @@
"brightness_increase": "Increase {entity_name} brightness", "brightness_increase": "Increase {entity_name} brightness",
"toggle": "Toggle {entity_name}", "toggle": "Toggle {entity_name}",
"turn_on": "Turn on {entity_name}", "turn_on": "Turn on {entity_name}",
"turn_off": "Turn off {entity_name}" "turn_off": "Turn off {entity_name}",
"flash": "Flash {entity_name}"
}, },
"condition_type": { "condition_type": {
"is_on": "{entity_name} is on", "is_on": "{entity_name} is on",

View File

@ -2,7 +2,13 @@
import pytest import pytest
import homeassistant.components.automation as automation import homeassistant.components.automation as automation
from homeassistant.components.light import DOMAIN, SUPPORT_BRIGHTNESS from homeassistant.components.light import (
DOMAIN,
FLASH_LONG,
FLASH_SHORT,
SUPPORT_BRIGHTNESS,
SUPPORT_FLASH,
)
from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON
from homeassistant.helpers import device_registry from homeassistant.helpers import device_registry
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -48,7 +54,7 @@ async def test_get_actions(hass, device_reg, entity_reg):
"test", "test",
"5678", "5678",
device_id=device_entry.id, device_id=device_entry.id,
supported_features=SUPPORT_BRIGHTNESS, supported_features=SUPPORT_BRIGHTNESS | SUPPORT_FLASH,
) )
expected_actions = [ expected_actions = [
{ {
@ -81,6 +87,12 @@ async def test_get_actions(hass, device_reg, entity_reg):
"device_id": device_entry.id, "device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678", "entity_id": f"{DOMAIN}.test_5678",
}, },
{
"domain": DOMAIN,
"type": "flash",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
] ]
actions = await async_get_device_automations(hass, "action", device_entry.id) actions = await async_get_device_automations(hass, "action", device_entry.id)
assert actions == expected_actions assert actions == expected_actions
@ -128,7 +140,7 @@ async def test_get_action_capabilities_brightness(hass, device_reg, entity_reg):
{ {
"name": "brightness_pct", "name": "brightness_pct",
"optional": True, "optional": True,
"type": "integer", "type": "float",
"valueMax": 100, "valueMax": 100,
"valueMin": 0, "valueMin": 0,
} }
@ -146,6 +158,45 @@ async def test_get_action_capabilities_brightness(hass, device_reg, entity_reg):
assert capabilities == {"extra_fields": []} assert capabilities == {"extra_fields": []}
async def test_get_action_capabilities_flash(hass, device_reg, entity_reg):
"""Test we get the expected capabilities from a light action."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(
DOMAIN,
"test",
"5678",
device_id=device_entry.id,
supported_features=SUPPORT_FLASH,
)
expected_capabilities = {
"extra_fields": [
{
"name": "flash",
"optional": True,
"type": "select",
"options": [("short", "short"), ("long", "long")],
}
]
}
actions = await async_get_device_automations(hass, "action", device_entry.id)
assert len(actions) == 4
for action in actions:
capabilities = await async_get_device_automation_capabilities(
hass, "action", action
)
if action["type"] == "turn_on":
assert capabilities == expected_capabilities
else:
assert capabilities == {"extra_fields": []}
async def test_action(hass, calls): async def test_action(hass, calls):
"""Test for turn_on and turn_off actions.""" """Test for turn_on and turn_off actions."""
platform = getattr(hass.components, f"test.{DOMAIN}") platform = getattr(hass.components, f"test.{DOMAIN}")
@ -187,6 +238,25 @@ async def test_action(hass, calls):
"type": "toggle", "type": "toggle",
}, },
}, },
{
"trigger": {"platform": "event", "event_type": "test_flash_short"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "flash",
},
},
{
"trigger": {"platform": "event", "event_type": "test_flash_long"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "flash",
"flash": "long",
},
},
{ {
"trigger": { "trigger": {
"platform": "event", "platform": "event",
@ -252,6 +322,22 @@ async def test_action(hass, calls):
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_toggle")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_flash_short")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_toggle")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_flash_long")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
turn_on_calls = async_mock_service(hass, DOMAIN, "turn_on") turn_on_calls = async_mock_service(hass, DOMAIN, "turn_on")
hass.bus.async_fire("test_brightness_increase") hass.bus.async_fire("test_brightness_increase")
@ -281,3 +367,17 @@ async def test_action(hass, calls):
assert len(turn_on_calls) == 4 assert len(turn_on_calls) == 4
assert turn_on_calls[3].data["entity_id"] == ent1.entity_id assert turn_on_calls[3].data["entity_id"] == ent1.entity_id
assert "brightness_pct" not in turn_on_calls[3].data assert "brightness_pct" not in turn_on_calls[3].data
hass.bus.async_fire("test_flash_short")
await hass.async_block_till_done()
assert len(turn_on_calls) == 5
assert turn_on_calls[4].data["entity_id"] == ent1.entity_id
assert turn_on_calls[4].data["flash"] == FLASH_SHORT
hass.bus.async_fire("test_flash_long")
await hass.async_block_till_done()
assert len(turn_on_calls) == 6
assert turn_on_calls[5].data["entity_id"] == ent1.entity_id
assert turn_on_calls[5].data["flash"] == FLASH_LONG