Add a service target (#43725)

This commit is contained in:
Paulus Schoutsen 2020-11-28 23:33:32 +01:00 committed by GitHub
parent a2e1efca33
commit dd513147a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 55 additions and 16 deletions

View File

@ -9,11 +9,12 @@ blueprint:
entity: entity:
domain: binary_sensor domain: binary_sensor
device_class: motion device_class: motion
light_entity: light_target:
name: Light name: Light
selector: selector:
entity: target:
domain: light entity:
domain: light
# If motion is detected within the 120s delay, # If motion is detected within the 120s delay,
# we restart the script. # we restart the script.
@ -28,7 +29,7 @@ trigger:
action: action:
- service: homeassistant.turn_on - service: homeassistant.turn_on
entity_id: !placeholder light_entity target: !placeholder light_target
- wait_for_trigger: - wait_for_trigger:
platform: state platform: state
entity_id: !placeholder motion_entity entity_id: !placeholder motion_entity
@ -36,4 +37,4 @@ action:
to: "off" to: "off"
- delay: 120 - delay: 120
- service: homeassistant.turn_off - service: homeassistant.turn_off
entity_id: !placeholder light_entity target: !placeholder light_target

View File

@ -170,6 +170,7 @@ CONF_STATE = "state"
CONF_STATE_TEMPLATE = "state_template" CONF_STATE_TEMPLATE = "state_template"
CONF_STRUCTURE = "structure" CONF_STRUCTURE = "structure"
CONF_SWITCHES = "switches" CONF_SWITCHES = "switches"
CONF_TARGET = "target"
CONF_TEMPERATURE_UNIT = "temperature_unit" CONF_TEMPERATURE_UNIT = "temperature_unit"
CONF_TIMEOUT = "timeout" CONF_TIMEOUT = "timeout"
CONF_TIME_ZONE = "time_zone" CONF_TIME_ZONE = "time_zone"

View File

@ -62,6 +62,7 @@ from homeassistant.const import (
CONF_SERVICE, CONF_SERVICE,
CONF_SERVICE_TEMPLATE, CONF_SERVICE_TEMPLATE,
CONF_STATE, CONF_STATE,
CONF_TARGET,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_IMPERIAL,
CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_METRIC,
@ -881,7 +882,10 @@ PLATFORM_SCHEMA = vol.Schema(
PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
ENTITY_SERVICE_FIELDS = (ATTR_ENTITY_ID, ATTR_AREA_ID) ENTITY_SERVICE_FIELDS = {
vol.Optional(ATTR_ENTITY_ID): comp_entity_ids,
vol.Optional(ATTR_AREA_ID): vol.Any(ENTITY_MATCH_NONE, vol.All(ensure_list, [str])),
}
def make_entity_service_schema( def make_entity_service_schema(
@ -892,10 +896,7 @@ def make_entity_service_schema(
vol.Schema( vol.Schema(
{ {
**schema, **schema,
vol.Optional(ATTR_ENTITY_ID): comp_entity_ids, **ENTITY_SERVICE_FIELDS,
vol.Optional(ATTR_AREA_ID): vol.Any(
ENTITY_MATCH_NONE, vol.All(ensure_list, [str])
),
}, },
extra=extra, extra=extra,
), ),
@ -942,6 +943,7 @@ SERVICE_SCHEMA = vol.All(
vol.Optional("data"): vol.All(dict, template_complex), vol.Optional("data"): vol.All(dict, template_complex),
vol.Optional("data_template"): vol.All(dict, template_complex), vol.Optional("data_template"): vol.All(dict, template_complex),
vol.Optional(CONF_ENTITY_ID): comp_entity_ids, vol.Optional(CONF_ENTITY_ID): comp_entity_ids,
vol.Optional(CONF_TARGET): ENTITY_SERVICE_FIELDS,
} }
), ),
has_at_least_one_key(CONF_SERVICE, CONF_SERVICE_TEMPLATE), has_at_least_one_key(CONF_SERVICE, CONF_SERVICE_TEMPLATE),

View File

@ -111,3 +111,13 @@ class TimeSelector(Selector):
"""Selector of a time value.""" """Selector of a time value."""
CONFIG_SCHEMA = vol.Schema({}) CONFIG_SCHEMA = vol.Schema({})
@SELECTORS.register("target")
class TargetSelector(Selector):
"""Selector of a target value (area ID, device ID, entity ID etc).
Value should follow cv.ENTITY_SERVICE_FIELDS format.
"""
CONFIG_SCHEMA = vol.Schema({"entity": {"domain": str}})

View File

@ -24,6 +24,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
CONF_SERVICE, CONF_SERVICE,
CONF_SERVICE_TEMPLATE, CONF_SERVICE_TEMPLATE,
CONF_TARGET,
ENTITY_MATCH_ALL, ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE, ENTITY_MATCH_NONE,
) )
@ -136,6 +137,10 @@ def async_prepare_call_from_config(
domain, service = domain_service.split(".", 1) domain, service = domain_service.split(".", 1)
service_data = {} service_data = {}
if CONF_TARGET in config:
service_data.update(config[CONF_TARGET])
for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]: for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]:
if conf not in config: if conf not in config:
continue continue

View File

@ -123,7 +123,7 @@ async def test_motion_light(hass):
"use_blueprint": { "use_blueprint": {
"path": "motion_light.yaml", "path": "motion_light.yaml",
"input": { "input": {
"light_entity": "light.kitchen", "light_target": {"entity_id": "light.kitchen"},
"motion_entity": "binary_sensor.kitchen", "motion_entity": "binary_sensor.kitchen",
}, },
} }

View File

@ -117,3 +117,16 @@ def test_boolean_selector_schema(schema):
def test_time_selector_schema(schema): def test_time_selector_schema(schema):
"""Test time selector.""" """Test time selector."""
selector.validate_selector({"time": schema}) selector.validate_selector({"time": schema})
@pytest.mark.parametrize(
"schema",
(
{},
{"entity": {}},
{"entity": {"domain": "light"}},
),
)
def test_target_selector_schema(schema):
"""Test entity selector."""
selector.validate_selector({"target": schema})

View File

@ -175,18 +175,25 @@ class TestServiceHelpers(unittest.TestCase):
"entity_id": "hello.world", "entity_id": "hello.world",
"data": { "data": {
"hello": "{{ 'goodbye' }}", "hello": "{{ 'goodbye' }}",
"data": {"value": "{{ 'complex' }}", "simple": "simple"}, "effect": {"value": "{{ 'complex' }}", "simple": "simple"},
}, },
"data_template": {"list": ["{{ 'list' }}", "2"]}, "data_template": {"list": ["{{ 'list' }}", "2"]},
"target": {"area_id": "test-area-id", "entity_id": "will.be_overridden"},
} }
service.call_from_config(self.hass, config) service.call_from_config(self.hass, config)
self.hass.block_till_done() self.hass.block_till_done()
assert self.calls[0].data["hello"] == "goodbye" assert dict(self.calls[0].data) == {
assert self.calls[0].data["data"]["value"] == "complex" "hello": "goodbye",
assert self.calls[0].data["data"]["simple"] == "simple" "effect": {
assert self.calls[0].data["list"][0] == "list" "value": "complex",
"simple": "simple",
},
"list": ["list", "2"],
"entity_id": ["hello.world"],
"area_id": ["test-area-id"],
}
def test_service_template_service_call(self): def test_service_template_service_call(self):
"""Test legacy service_template call with templating.""" """Test legacy service_template call with templating."""