Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config flow for template binary sensor #99339

Merged
merged 2 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions homeassistant/components/template/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
Expand Down Expand Up @@ -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."""

Expand Down
31 changes: 31 additions & 0 deletions homeassistant/components/template/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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,
Expand All @@ -31,6 +32,7 @@
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
Expand All @@ -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(
Expand Down Expand Up @@ -197,11 +216,17 @@ async 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",
Expand All @@ -212,6 +237,11 @@ async def _validate_user_input(

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",
Expand All @@ -223,6 +253,7 @@ async def _validate_user_input(
str,
Callable[[HomeAssistant, str, dict[str, Any]], TemplateEntity],
] = {
"binary_sensor": async_create_preview_binary_sensor,
"sensor": async_create_preview_sensor,
}

Expand Down
48 changes: 48 additions & 0 deletions homeassistant/components/template/strings.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
Expand All @@ -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%]",
Expand All @@ -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",
Expand Down
55 changes: 48 additions & 7 deletions tests/components/template/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
"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')) }}",
Expand Down Expand Up @@ -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"},
{},
{},
Expand All @@ -145,6 +166,7 @@ async def test_options(
template_type,
old_state_template,
new_state_template,
template_state,
input_states,
extra_options,
options_options,
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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(
Expand All @@ -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"],
[{}, {}],
),
),
Expand All @@ -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."""
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -317,6 +347,7 @@ async def test_config_flow_preview(
@pytest.mark.parametrize(
("template_type", "state_template", "extra_user_input", "error"),
[
("binary_sensor", "{{", {}, {"state": EARLY_END_ERROR}),
("sensor", "{{", {}, {"state": EARLY_END_ERROR}),
(
"sensor",
Expand Down Expand Up @@ -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')) }}",
Expand Down