mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Add config flow for threshold binary sensor (#68238)
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
87378016c1
commit
b5d2c6e43a
@ -1 +1,26 @@
|
|||||||
"""The threshold component."""
|
"""The threshold component."""
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Min/Max from a config entry."""
|
||||||
|
hass.config_entries.async_setup_platforms(entry, (Platform.BINARY_SENSOR,))
|
||||||
|
|
||||||
|
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Update listener, called when the config entry options are changed."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(
|
||||||
|
entry, (Platform.BINARY_SENSOR,)
|
||||||
|
)
|
||||||
|
@ -10,6 +10,7 @@ from homeassistant.components.binary_sensor import (
|
|||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
@ -19,11 +20,13 @@ from homeassistant.const import (
|
|||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.event import async_track_state_change_event
|
from homeassistant.helpers.event import async_track_state_change_event
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
|
from .const import CONF_HYSTERESIS, CONF_LOWER, CONF_UPPER
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_HYSTERESIS = "hysteresis"
|
ATTR_HYSTERESIS = "hysteresis"
|
||||||
@ -33,10 +36,6 @@ ATTR_SENSOR_VALUE = "sensor_value"
|
|||||||
ATTR_TYPE = "type"
|
ATTR_TYPE = "type"
|
||||||
ATTR_UPPER = "upper"
|
ATTR_UPPER = "upper"
|
||||||
|
|
||||||
CONF_HYSTERESIS = "hysteresis"
|
|
||||||
CONF_LOWER = "lower"
|
|
||||||
CONF_UPPER = "upper"
|
|
||||||
|
|
||||||
DEFAULT_NAME = "Threshold"
|
DEFAULT_NAME = "Threshold"
|
||||||
DEFAULT_HYSTERESIS = 0.0
|
DEFAULT_HYSTERESIS = 0.0
|
||||||
|
|
||||||
@ -61,6 +60,32 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize threshold config entry."""
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
device_class = None
|
||||||
|
entity_id = er.async_validate_entity_id(
|
||||||
|
registry, config_entry.options[CONF_ENTITY_ID]
|
||||||
|
)
|
||||||
|
hysteresis = config_entry.options[CONF_HYSTERESIS]
|
||||||
|
lower = config_entry.options[CONF_LOWER]
|
||||||
|
name = config_entry.title
|
||||||
|
unique_id = config_entry.entry_id
|
||||||
|
upper = config_entry.options[CONF_UPPER]
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
ThresholdSensor(
|
||||||
|
hass, entity_id, name, lower, upper, hysteresis, device_class, unique_id
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
@ -78,7 +103,7 @@ async def async_setup_platform(
|
|||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
ThresholdSensor(
|
ThresholdSensor(
|
||||||
hass, entity_id, name, lower, upper, hysteresis, device_class
|
hass, entity_id, name, lower, upper, hysteresis, device_class, None
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -87,9 +112,11 @@ async def async_setup_platform(
|
|||||||
class ThresholdSensor(BinarySensorEntity):
|
class ThresholdSensor(BinarySensorEntity):
|
||||||
"""Representation of a Threshold sensor."""
|
"""Representation of a Threshold sensor."""
|
||||||
|
|
||||||
def __init__(self, hass, entity_id, name, lower, upper, hysteresis, device_class):
|
def __init__(
|
||||||
|
self, hass, entity_id, name, lower, upper, hysteresis, device_class, unique_id
|
||||||
|
):
|
||||||
"""Initialize the Threshold sensor."""
|
"""Initialize the Threshold sensor."""
|
||||||
self._hass = hass
|
self._attr_unique_id = unique_id
|
||||||
self._entity_id = entity_id
|
self._entity_id = entity_id
|
||||||
self._name = name
|
self._name = name
|
||||||
self._threshold_lower = lower
|
self._threshold_lower = lower
|
||||||
@ -101,10 +128,9 @@ class ThresholdSensor(BinarySensorEntity):
|
|||||||
self._state = None
|
self._state = None
|
||||||
self.sensor_value = None
|
self.sensor_value = None
|
||||||
|
|
||||||
@callback
|
def _update_sensor_state():
|
||||||
def async_threshold_sensor_state_listener(event):
|
|
||||||
"""Handle sensor state changes."""
|
"""Handle sensor state changes."""
|
||||||
if (new_state := event.data.get("new_state")) is None:
|
if (new_state := hass.states.get(self._entity_id)) is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -118,11 +144,17 @@ class ThresholdSensor(BinarySensorEntity):
|
|||||||
_LOGGER.warning("State is not numerical")
|
_LOGGER.warning("State is not numerical")
|
||||||
|
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_threshold_sensor_state_listener(event):
|
||||||
|
"""Handle sensor state changes."""
|
||||||
|
_update_sensor_state()
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async_track_state_change_event(
|
async_track_state_change_event(
|
||||||
hass, [entity_id], async_threshold_sensor_state_listener
|
hass, [entity_id], async_threshold_sensor_state_listener
|
||||||
)
|
)
|
||||||
|
_update_sensor_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
63
homeassistant/components/threshold/config_flow.py
Normal file
63
homeassistant/components/threshold/config_flow.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""Config flow for Threshold integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME
|
||||||
|
from homeassistant.helpers import selector
|
||||||
|
from homeassistant.helpers.helper_config_entry_flow import (
|
||||||
|
HelperConfigFlowHandler,
|
||||||
|
HelperFlowError,
|
||||||
|
HelperFlowStep,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import CONF_HYSTERESIS, CONF_LOWER, CONF_UPPER, DEFAULT_HYSTERESIS, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_mode(data: Any) -> Any:
|
||||||
|
"""Validate the threshold mode, and set limits to None if not set."""
|
||||||
|
if CONF_LOWER not in data and CONF_UPPER not in data:
|
||||||
|
raise HelperFlowError("need_lower_upper")
|
||||||
|
return {CONF_LOWER: None, CONF_UPPER: None, **data}
|
||||||
|
|
||||||
|
|
||||||
|
OPTIONS_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): selector.selector(
|
||||||
|
{"number": {"mode": "box"}}
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_LOWER): selector.selector({"number": {"mode": "box"}}),
|
||||||
|
vol.Optional(CONF_UPPER): selector.selector({"number": {"mode": "box"}}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_NAME): selector.selector({"text": {}}),
|
||||||
|
vol.Required(CONF_ENTITY_ID): selector.selector(
|
||||||
|
{"entity": {"domain": "sensor"}}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(OPTIONS_SCHEMA.schema)
|
||||||
|
|
||||||
|
CONFIG_FLOW = {
|
||||||
|
"user": HelperFlowStep(CONFIG_SCHEMA, validate_user_input=_validate_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
OPTIONS_FLOW = {
|
||||||
|
"init": HelperFlowStep(OPTIONS_SCHEMA, validate_user_input=_validate_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
|
||||||
|
"""Handle a config or options flow for Threshold."""
|
||||||
|
|
||||||
|
config_flow = CONFIG_FLOW
|
||||||
|
options_flow = OPTIONS_FLOW
|
||||||
|
|
||||||
|
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
||||||
|
"""Return config entry title."""
|
||||||
|
return options[CONF_NAME]
|
9
homeassistant/components/threshold/const.py
Normal file
9
homeassistant/components/threshold/const.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"""Constants for the Threshold integration."""
|
||||||
|
|
||||||
|
DOMAIN = "threshold"
|
||||||
|
|
||||||
|
CONF_HYSTERESIS = "hysteresis"
|
||||||
|
CONF_LOWER = "lower"
|
||||||
|
CONF_UPPER = "upper"
|
||||||
|
|
||||||
|
DEFAULT_HYSTERESIS = 0.0
|
@ -4,5 +4,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/threshold",
|
"documentation": "https://www.home-assistant.io/integrations/threshold",
|
||||||
"codeowners": ["@fabaff"],
|
"codeowners": ["@fabaff"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling",
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
39
homeassistant/components/threshold/strings.json
Normal file
39
homeassistant/components/threshold/strings.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "New Threshold Sensor",
|
||||||
|
"description": "Configure when the sensor should turn on and off.\n\nOnly lower limit configured - Turn on when the input sensor's value is less than the lower limit.\nOnly upper limit configured - Turn on when the input sensor's value is greater than the upper limit.\nBoth lower and upper limit configured - Turn on when the input sensor's value is in the range [lower limit .. upper limit].",
|
||||||
|
"data": {
|
||||||
|
"entity_id": "Input sensor",
|
||||||
|
"hysteresis": "Hysteresis",
|
||||||
|
"lower": "Lower limit",
|
||||||
|
"mode": "Threshold mode",
|
||||||
|
"name": "Name",
|
||||||
|
"upper": "Upper limit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"need_lower_upper": "Lower and upper limits can't both be empty"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"description": "[%key:component::threshold::config::step::user::description%]",
|
||||||
|
"data": {
|
||||||
|
"entity_id": "[%key:component::threshold::config::step::user::data::entity_id%]",
|
||||||
|
"hysteresis": "[%key:component::threshold::config::step::user::data::hysteresis%]",
|
||||||
|
"lower": "[%key:component::threshold::config::step::user::data::lower%]",
|
||||||
|
"mode": "[%key:component::threshold::config::step::user::data::mode%]",
|
||||||
|
"name": "[%key:component::threshold::config::step::user::data::name%]",
|
||||||
|
"upper": "[%key:component::threshold::config::step::user::data::upper%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"need_lower_upper": "[%key:component::threshold::config::error::need_lower_upper%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
homeassistant/components/threshold/translations/en.json
Normal file
39
homeassistant/components/threshold/translations/en.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"need_lower_upper": "Lower and upper limits can't both be empty"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"entity_id": "Input sensor",
|
||||||
|
"hysteresis": "Hysteresis",
|
||||||
|
"lower": "Lower limit",
|
||||||
|
"mode": "Threshold mode",
|
||||||
|
"name": "Name",
|
||||||
|
"upper": "Upper limit"
|
||||||
|
},
|
||||||
|
"description": "Configure when the sensor should turn on and off.\n\nOnly lower limit configured - Turn on when the input sensor's value is less than the lower limit.\nOnly upper limit configured - Turn on when the input sensor's value is greater than the upper limit.\nBoth lower and upper limit configured - Turn on when the input sensor's value is in the range [lower limit .. upper limit].",
|
||||||
|
"title": "New Threshold Sensor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"error": {
|
||||||
|
"need_lower_upper": "Lower and upper limits can't both be empty"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"entity_id": "Input sensor",
|
||||||
|
"hysteresis": "Hysteresis",
|
||||||
|
"lower": "Lower limit",
|
||||||
|
"mode": "Threshold mode",
|
||||||
|
"name": "Name",
|
||||||
|
"upper": "Upper limit"
|
||||||
|
},
|
||||||
|
"description": "Configure when the sensor should turn on and off.\n\nOnly lower limit configured - Turn on when the input sensor's value is less than the lower limit.\nOnly upper limit configured - Turn on when the input sensor's value is greater than the upper limit.\nBoth lower and upper limit configured - Turn on when the input sensor's value is in the range [lower limit .. upper limit]."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -337,6 +337,7 @@ FLOWS = {
|
|||||||
"tasmota",
|
"tasmota",
|
||||||
"tellduslive",
|
"tellduslive",
|
||||||
"tesla_wall_connector",
|
"tesla_wall_connector",
|
||||||
|
"threshold",
|
||||||
"tibber",
|
"tibber",
|
||||||
"tile",
|
"tile",
|
||||||
"tolo",
|
"tolo",
|
||||||
|
@ -16,6 +16,10 @@ from homeassistant.data_entry_flow import FlowResult, UnknownHandler
|
|||||||
from . import entity_registry as er
|
from . import entity_registry as er
|
||||||
|
|
||||||
|
|
||||||
|
class HelperFlowError(Exception):
|
||||||
|
"""Validation failed."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HelperFlowStep:
|
class HelperFlowStep:
|
||||||
"""Define a helper config or options flow step."""
|
"""Define a helper config or options flow step."""
|
||||||
@ -24,6 +28,12 @@ class HelperFlowStep:
|
|||||||
# fails, the step will be retried. If the schema is None, no user input is requested.
|
# fails, the step will be retried. If the schema is None, no user input is requested.
|
||||||
schema: vol.Schema | None
|
schema: vol.Schema | None
|
||||||
|
|
||||||
|
# Optional function to validate user input.
|
||||||
|
# The validate_user_input function is called if the schema validates successfully.
|
||||||
|
# The validate_user_input function is passed the user input from the current step.
|
||||||
|
# The validate_user_input should raise HelperFlowError is user input is invalid.
|
||||||
|
validate_user_input: Callable[[dict[str, Any]], dict[str, Any]] = lambda x: x
|
||||||
|
|
||||||
# Optional function to identify next step.
|
# Optional function to identify next step.
|
||||||
# The next_step function is called if the schema validates successfully or if no
|
# The next_step function is called if the schema validates successfully or if no
|
||||||
# schema is defined. The next_step function is passed the union of config entry
|
# schema is defined. The next_step function is passed the union of config entry
|
||||||
@ -52,6 +62,13 @@ class HelperCommonFlowHandler:
|
|||||||
"""Handle a step."""
|
"""Handle a step."""
|
||||||
next_step_id: str = step_id
|
next_step_id: str = step_id
|
||||||
|
|
||||||
|
if user_input is not None and self._flow[next_step_id].schema is not None:
|
||||||
|
# Do extra validation of user input
|
||||||
|
try:
|
||||||
|
user_input = self._flow[next_step_id].validate_user_input(user_input)
|
||||||
|
except HelperFlowError as exc:
|
||||||
|
return self._show_next_step(next_step_id, exc, user_input)
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
# User input was validated successfully, update options
|
# User input was validated successfully, update options
|
||||||
self._options.update(user_input)
|
self._options.update(user_input)
|
||||||
@ -67,21 +84,35 @@ class HelperCommonFlowHandler:
|
|||||||
|
|
||||||
next_step_id = next_step_id_or_end_flow
|
next_step_id = next_step_id_or_end_flow
|
||||||
|
|
||||||
|
return self._show_next_step(next_step_id)
|
||||||
|
|
||||||
|
def _show_next_step(
|
||||||
|
self,
|
||||||
|
next_step_id: str,
|
||||||
|
error: HelperFlowError | None = None,
|
||||||
|
user_input: dict[str, Any] | None = None,
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Show step for next step."""
|
||||||
|
options = dict(self._options)
|
||||||
|
if user_input:
|
||||||
|
options.update(user_input)
|
||||||
if (data_schema := self._flow[next_step_id].schema) and data_schema.schema:
|
if (data_schema := self._flow[next_step_id].schema) and data_schema.schema:
|
||||||
# Copy the schema, then set suggested field values to saved options
|
# Make a copy of the schema with suggested values set to saved options
|
||||||
schema = dict(data_schema.schema)
|
schema = {}
|
||||||
for key in list(schema):
|
for key, val in data_schema.schema.items():
|
||||||
if key in self._options and isinstance(key, vol.Marker):
|
new_key = key
|
||||||
|
if key in options and isinstance(key, vol.Marker):
|
||||||
# Copy the marker to not modify the flow schema
|
# Copy the marker to not modify the flow schema
|
||||||
new_key = copy.copy(key)
|
new_key = copy.copy(key)
|
||||||
new_key.description = {"suggested_value": self._options[key]}
|
new_key.description = {"suggested_value": options[key]}
|
||||||
val = schema.pop(key)
|
schema[new_key] = val
|
||||||
schema[new_key] = val
|
|
||||||
data_schema = vol.Schema(schema)
|
data_schema = vol.Schema(schema)
|
||||||
|
|
||||||
|
errors = {"base": str(error)} if error else None
|
||||||
|
|
||||||
# Show form for next step
|
# Show form for next step
|
||||||
return self._handler.async_show_form(
|
return self._handler.async_show_form(
|
||||||
step_id=next_step_id, data_schema=data_schema
|
step_id=next_step_id, data_schema=data_schema, errors=errors
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
162
tests/components/threshold/test_config_flow.py
Normal file
162
tests/components/threshold/test_config_flow.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
"""Test the Threshold config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.threshold.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the config flow."""
|
||||||
|
input_sensor = "sensor.input"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] is None
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.threshold.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"lower": -2,
|
||||||
|
"upper": 0.0,
|
||||||
|
"name": "My threshold sensor",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "My threshold sensor"
|
||||||
|
assert result["data"] == {}
|
||||||
|
assert result["options"] == {
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"lower": -2.0,
|
||||||
|
"name": "My threshold sensor",
|
||||||
|
"upper": 0.0,
|
||||||
|
}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||||
|
assert config_entry.data == {}
|
||||||
|
assert config_entry.options == {
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"lower": -2.0,
|
||||||
|
"name": "My threshold sensor",
|
||||||
|
"upper": 0.0,
|
||||||
|
}
|
||||||
|
assert config_entry.title == "My threshold sensor"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("extra_input_data,error", (({}, "need_lower_upper"),))
|
||||||
|
async def test_fail(hass: HomeAssistant, extra_input_data, error) -> None:
|
||||||
|
"""Test not providing lower or upper limit fails."""
|
||||||
|
input_sensor = "sensor.input"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] is None
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"name": "My threshold sensor",
|
||||||
|
**extra_input_data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"base": error}
|
||||||
|
|
||||||
|
|
||||||
|
def get_suggested(schema, key):
|
||||||
|
"""Get suggested value for key in voluptuous schema."""
|
||||||
|
for k in schema.keys():
|
||||||
|
if k == key:
|
||||||
|
if k.description is None or "suggested_value" not in k.description:
|
||||||
|
return None
|
||||||
|
return k.description["suggested_value"]
|
||||||
|
# Wanted key absent from schema
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options(hass: HomeAssistant) -> None:
|
||||||
|
"""Test reconfiguring."""
|
||||||
|
input_sensor = "sensor.input"
|
||||||
|
hass.states.async_set(input_sensor, "10")
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"lower": -2.0,
|
||||||
|
"name": "My threshold",
|
||||||
|
"upper": None,
|
||||||
|
},
|
||||||
|
title="My threshold",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
schema = result["data_schema"].schema
|
||||||
|
assert get_suggested(schema, "hysteresis") == 0.0
|
||||||
|
assert get_suggested(schema, "lower") == -2.0
|
||||||
|
assert get_suggested(schema, "upper") is None
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"upper": 20.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["data"] == {
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"lower": None,
|
||||||
|
"name": "My threshold",
|
||||||
|
"upper": 20.0,
|
||||||
|
}
|
||||||
|
assert config_entry.data == {}
|
||||||
|
assert config_entry.options == {
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"lower": None,
|
||||||
|
"name": "My threshold",
|
||||||
|
"upper": 20.0,
|
||||||
|
}
|
||||||
|
assert config_entry.title == "My threshold"
|
||||||
|
|
||||||
|
# Check config entry is reloaded with new options
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check the entity was updated, no new entity was created
|
||||||
|
assert len(hass.states.async_all()) == 2
|
||||||
|
|
||||||
|
# Check the state of the entity has changed as expected
|
||||||
|
state = hass.states.get("binary_sensor.my_threshold")
|
||||||
|
assert state.state == "off"
|
||||||
|
assert state.attributes["type"] == "upper"
|
61
tests/components/threshold/test_init.py
Normal file
61
tests/components/threshold/test_init.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""Test the Min/Max integration."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.threshold.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("platform", ("binary_sensor",))
|
||||||
|
async def test_setup_and_remove_config_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
platform: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test setting up and removing a config entry."""
|
||||||
|
hass.states.async_set("sensor.input", "-10")
|
||||||
|
|
||||||
|
input_sensor = "sensor.input"
|
||||||
|
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
threshold_entity_id = f"{platform}.input_threshold"
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={
|
||||||
|
"entity_id": input_sensor,
|
||||||
|
"hysteresis": 0.0,
|
||||||
|
"lower": -2.0,
|
||||||
|
"name": "Input threshold",
|
||||||
|
"upper": None,
|
||||||
|
},
|
||||||
|
title="Input threshold",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check the entity is registered in the entity registry
|
||||||
|
assert registry.async_get(threshold_entity_id) is not None
|
||||||
|
|
||||||
|
# Check the platform is setup correctly
|
||||||
|
state = hass.states.get(threshold_entity_id)
|
||||||
|
assert state.state == "on"
|
||||||
|
assert state.attributes["entity_id"] == input_sensor
|
||||||
|
assert state.attributes["hysteresis"] == 0.0
|
||||||
|
assert state.attributes["lower"] == -2.0
|
||||||
|
assert state.attributes["position"] == "below"
|
||||||
|
assert state.attributes["sensor_value"] == -10.0
|
||||||
|
assert state.attributes["type"] == "lower"
|
||||||
|
assert state.attributes["upper"] is None
|
||||||
|
|
||||||
|
# Remove the config entry
|
||||||
|
assert await hass.config_entries.async_remove(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check the state and entity registry entry are removed
|
||||||
|
assert hass.states.get(threshold_entity_id) is None
|
||||||
|
assert registry.async_get(threshold_entity_id) is None
|
Loading…
x
Reference in New Issue
Block a user