mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add a service target (#43725)
This commit is contained in:
parent
a2e1efca33
commit
dd513147a5
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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),
|
||||||
|
@ -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}})
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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})
|
||||||
|
@ -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."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user