Add config flow for template binary sensor (#99339)
This commit is contained in:
parent
a5dcc25aab
commit
501d5db375
4 changed files with 151 additions and 7 deletions
|
@ -17,6 +17,7 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
|
@ -194,6 +195,29 @@ async def async_setup_platform(
|
|||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize config entry."""
|
||||
_options = dict(config_entry.options)
|
||||
_options.pop("template_type")
|
||||
validated_config = BINARY_SENSOR_SCHEMA(_options)
|
||||
async_add_entities(
|
||||
[BinarySensorTemplate(hass, validated_config, config_entry.entry_id)]
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_preview_binary_sensor(
|
||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||
) -> BinarySensorTemplate:
|
||||
"""Create a preview sensor."""
|
||||
validated_config = BINARY_SENSOR_SCHEMA(config | {CONF_NAME: name})
|
||||
return BinarySensorTemplate(hass, validated_config, None)
|
||||
|
||||
|
||||
class BinarySensorTemplate(TemplateEntity, BinarySensorEntity, RestoreEntity):
|
||||
"""A virtual binary sensor that triggers from another sensor."""
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import Any, cast
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.sensor import (
|
||||
CONF_STATE_CLASS,
|
||||
DEVICE_CLASS_STATE_CLASSES,
|
||||
|
@ -31,6 +32,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
|||
SchemaFlowMenuStep,
|
||||
)
|
||||
|
||||
from .binary_sensor import async_create_preview_binary_sensor
|
||||
from .const import DOMAIN
|
||||
from .sensor import async_create_preview_sensor
|
||||
from .template_entity import TemplateEntity
|
||||
|
@ -42,6 +44,23 @@ def generate_schema(domain: str) -> dict[vol.Marker, Any]:
|
|||
"""Generate schema."""
|
||||
schema: dict[vol.Marker, Any] = {}
|
||||
|
||||
if domain == Platform.BINARY_SENSOR:
|
||||
schema = {
|
||||
vol.Optional(CONF_DEVICE_CLASS): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=[
|
||||
NONE_SENTINEL,
|
||||
*sorted(
|
||||
[cls.value for cls in BinarySensorDeviceClass],
|
||||
key=str.casefold,
|
||||
),
|
||||
],
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
translation_key="binary_sensor_device_class",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if domain == Platform.SENSOR:
|
||||
schema = {
|
||||
vol.Optional(
|
||||
|
@ -197,11 +216,17 @@ def validate_user_input(
|
|||
|
||||
|
||||
TEMPLATE_TYPES = [
|
||||
"binary_sensor",
|
||||
"sensor",
|
||||
]
|
||||
|
||||
CONFIG_FLOW = {
|
||||
"user": SchemaFlowMenuStep(TEMPLATE_TYPES),
|
||||
Platform.BINARY_SENSOR: SchemaFlowFormStep(
|
||||
config_schema(Platform.BINARY_SENSOR),
|
||||
preview="template",
|
||||
validate_user_input=validate_user_input(Platform.BINARY_SENSOR),
|
||||
),
|
||||
Platform.SENSOR: SchemaFlowFormStep(
|
||||
config_schema(Platform.SENSOR),
|
||||
preview="template",
|
||||
|
@ -212,6 +237,11 @@ CONFIG_FLOW = {
|
|||
|
||||
OPTIONS_FLOW = {
|
||||
"init": SchemaFlowFormStep(next_step=choose_options_step),
|
||||
Platform.BINARY_SENSOR: SchemaFlowFormStep(
|
||||
options_schema(Platform.BINARY_SENSOR),
|
||||
preview="template",
|
||||
validate_user_input=validate_user_input(Platform.BINARY_SENSOR),
|
||||
),
|
||||
Platform.SENSOR: SchemaFlowFormStep(
|
||||
options_schema(Platform.SENSOR),
|
||||
preview="template",
|
||||
|
@ -223,6 +253,7 @@ CREATE_PREVIEW_ENTITY: dict[
|
|||
str,
|
||||
Callable[[HomeAssistant, str, dict[str, Any]], TemplateEntity],
|
||||
] = {
|
||||
"binary_sensor": async_create_preview_binary_sensor,
|
||||
"sensor": async_create_preview_sensor,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"binary_sensor": {
|
||||
"data": {
|
||||
"device_class": "[%key:component::template::config::step::sensor::data::device_class%]",
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"state_template": "[%key:component::template::config::step::sensor::data::state_template%]"
|
||||
},
|
||||
"title": "Template binary sensor"
|
||||
},
|
||||
"sensor": {
|
||||
"data": {
|
||||
"device_class": "Device class",
|
||||
|
@ -14,6 +22,7 @@
|
|||
"user": {
|
||||
"description": "This helper allow you to create helper entities that define their state using a template.",
|
||||
"menu_options": {
|
||||
"binary_sensor": "Template a binary sensor",
|
||||
"sensor": "Template a sensor"
|
||||
},
|
||||
"title": "Template helper"
|
||||
|
@ -22,6 +31,13 @@
|
|||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"binary_sensor": {
|
||||
"data": {
|
||||
"device_class": "[%key:component::template::config::step::sensor::data::device_class%]",
|
||||
"state_template": "[%key:component::template::config::step::sensor::data::state_template%]"
|
||||
},
|
||||
"title": "[%key:component::template::config::step::binary_sensor::title%]"
|
||||
},
|
||||
"sensor": {
|
||||
"data": {
|
||||
"device_class": "[%key:component::template::config::step::sensor::data::device_class%]",
|
||||
|
@ -34,6 +50,38 @@
|
|||
}
|
||||
},
|
||||
"selector": {
|
||||
"binary_sensor_device_class": {
|
||||
"options": {
|
||||
"none": "[%key:component::template::selector::sensor_device_class::options::none%]",
|
||||
"battery": "[%key:component::binary_sensor::entity_component::battery::name%]",
|
||||
"battery_charging": "[%key:component::binary_sensor::entity_component::battery_charging::name%]",
|
||||
"carbon_monoxide": "[%key:component::binary_sensor::entity_component::carbon_monoxide::name%]",
|
||||
"cold": "[%key:component::binary_sensor::entity_component::cold::name%]",
|
||||
"connectivity": "[%key:component::binary_sensor::entity_component::connectivity::name%]",
|
||||
"door": "[%key:component::binary_sensor::entity_component::door::name%]",
|
||||
"garage_door": "[%key:component::binary_sensor::entity_component::garage_door::name%]",
|
||||
"gas": "[%key:component::binary_sensor::entity_component::gas::name%]",
|
||||
"heat": "[%key:component::binary_sensor::entity_component::heat::name%]",
|
||||
"light": "[%key:component::binary_sensor::entity_component::light::name%]",
|
||||
"lock": "[%key:component::binary_sensor::entity_component::lock::name%]",
|
||||
"moisture": "[%key:component::binary_sensor::entity_component::moisture::name%]",
|
||||
"motion": "[%key:component::binary_sensor::entity_component::motion::name%]",
|
||||
"moving": "[%key:component::binary_sensor::entity_component::moving::name%]",
|
||||
"occupancy": "[%key:component::binary_sensor::entity_component::occupancy::name%]",
|
||||
"opening": "[%key:component::binary_sensor::entity_component::opening::name%]",
|
||||
"plug": "[%key:component::binary_sensor::entity_component::plug::name%]",
|
||||
"power": "[%key:component::binary_sensor::entity_component::power::name%]",
|
||||
"presence": "[%key:component::binary_sensor::entity_component::presence::name%]",
|
||||
"problem": "[%key:component::binary_sensor::entity_component::problem::name%]",
|
||||
"running": "[%key:component::binary_sensor::entity_component::running::name%]",
|
||||
"safety": "[%key:component::binary_sensor::entity_component::safety::name%]",
|
||||
"smoke": "[%key:component::binary_sensor::entity_component::smoke::name%]",
|
||||
"sound": "[%key:component::binary_sensor::entity_component::sound::name%]",
|
||||
"update": "[%key:component::binary_sensor::entity_component::update::name%]",
|
||||
"vibration": "[%key:component::binary_sensor::entity_component::vibration::name%]",
|
||||
"window": "[%key:component::binary_sensor::entity_component::window::name%]"
|
||||
}
|
||||
},
|
||||
"sensor_device_class": {
|
||||
"options": {
|
||||
"none": "No device class",
|
||||
|
|
|
@ -25,6 +25,16 @@ from tests.typing import WebSocketGenerator
|
|||
"extra_attrs",
|
||||
),
|
||||
(
|
||||
(
|
||||
"binary_sensor",
|
||||
"{{ states('binary_sensor.one') == 'on' or states('binary_sensor.two') == 'on' }}",
|
||||
"on",
|
||||
{"one": "on", "two": "off"},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
|
@ -125,15 +135,26 @@ def get_suggested(schema, key):
|
|||
"template_type",
|
||||
"old_state_template",
|
||||
"new_state_template",
|
||||
"template_state",
|
||||
"input_states",
|
||||
"extra_options",
|
||||
"options_options",
|
||||
),
|
||||
(
|
||||
(
|
||||
"binary_sensor",
|
||||
"{{ states('binary_sensor.one') == 'on' or states('binary_sensor.two') == 'on' }}",
|
||||
"{{ states('binary_sensor.one') == 'on' and states('binary_sensor.two') == 'on' }}",
|
||||
["on", "off"],
|
||||
{"one": "on", "two": "off"},
|
||||
{},
|
||||
{},
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
"{{ float(states('sensor.one')) - float(states('sensor.two')) }}",
|
||||
["50.0", "10.0"],
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
{},
|
||||
{},
|
||||
|
@ -145,6 +166,7 @@ async def test_options(
|
|||
template_type,
|
||||
old_state_template,
|
||||
new_state_template,
|
||||
template_state,
|
||||
input_states,
|
||||
extra_options,
|
||||
options_options,
|
||||
|
@ -174,7 +196,7 @@ async def test_options(
|
|||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(f"{template_type}.my_template")
|
||||
assert state.state == "50.0"
|
||||
assert state.state == template_state[0]
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
|
||||
|
@ -207,7 +229,7 @@ async def test_options(
|
|||
# Check config entry is reloaded with new options
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(f"{template_type}.my_template")
|
||||
assert state.state == "10.0"
|
||||
assert state.state == template_state[1]
|
||||
|
||||
# Check we don't get suggestions from another entry
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -233,16 +255,24 @@ async def test_options(
|
|||
"state_template",
|
||||
"extra_user_input",
|
||||
"input_states",
|
||||
"template_state",
|
||||
"template_states",
|
||||
"extra_attributes",
|
||||
),
|
||||
(
|
||||
(
|
||||
"binary_sensor",
|
||||
"{{ states.binary_sensor.one.state == 'on' or states.binary_sensor.two.state == 'on' }}",
|
||||
{},
|
||||
{"one": "on", "two": "off"},
|
||||
["off", "on"],
|
||||
[{}, {}],
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
{},
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
"50.0",
|
||||
["unavailable", "50.0"],
|
||||
[{}, {}],
|
||||
),
|
||||
),
|
||||
|
@ -254,7 +284,7 @@ async def test_config_flow_preview(
|
|||
state_template: str,
|
||||
extra_user_input: dict[str, Any],
|
||||
input_states: list[str],
|
||||
template_state: str,
|
||||
template_states: str,
|
||||
extra_attributes: list[dict[str, Any]],
|
||||
) -> None:
|
||||
"""Test the config flow preview."""
|
||||
|
@ -293,7 +323,7 @@ async def test_config_flow_preview(
|
|||
msg = await client.receive_json()
|
||||
assert msg["event"] == {
|
||||
"attributes": {"friendly_name": "My template"} | extra_attributes[0],
|
||||
"state": "unavailable",
|
||||
"state": template_states[0],
|
||||
}
|
||||
|
||||
for input_entity in input_entities:
|
||||
|
@ -306,7 +336,7 @@ async def test_config_flow_preview(
|
|||
"attributes": {"friendly_name": "My template"}
|
||||
| extra_attributes[0]
|
||||
| extra_attributes[1],
|
||||
"state": template_state,
|
||||
"state": template_states[1],
|
||||
}
|
||||
assert len(hass.states.async_all()) == 2
|
||||
|
||||
|
@ -317,6 +347,7 @@ EARLY_END_ERROR = "invalid template (TemplateSyntaxError: unexpected 'end of tem
|
|||
@pytest.mark.parametrize(
|
||||
("template_type", "state_template", "extra_user_input", "error"),
|
||||
[
|
||||
("binary_sensor", "{{", {}, {"state": EARLY_END_ERROR}),
|
||||
("sensor", "{{", {}, {"state": EARLY_END_ERROR}),
|
||||
(
|
||||
"sensor",
|
||||
|
@ -453,6 +484,16 @@ async def test_config_flow_preview_bad_state(
|
|||
"extra_attributes",
|
||||
),
|
||||
[
|
||||
(
|
||||
"binary_sensor",
|
||||
"{{ states('binary_sensor.one') == 'on' or states('binary_sensor.two') == 'on' }}",
|
||||
"{{ states('binary_sensor.one') == 'on' and states('binary_sensor.two') == 'on' }}",
|
||||
{},
|
||||
{},
|
||||
{"one": "on", "two": "off"},
|
||||
"off",
|
||||
{},
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue