Add number platform to template integration (#54789)
This commit is contained in:
parent
8407ad01d4
commit
e9625e4b7a
4 changed files with 587 additions and 0 deletions
|
@ -4,6 +4,7 @@ import logging
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config import async_log_exception, config_without_domain
|
||||
|
@ -13,6 +14,7 @@ from homeassistant.helpers.trigger import async_validate_trigger_config
|
|||
|
||||
from . import (
|
||||
binary_sensor as binary_sensor_platform,
|
||||
number as number_platform,
|
||||
select as select_platform,
|
||||
sensor as sensor_platform,
|
||||
)
|
||||
|
@ -24,6 +26,9 @@ CONFIG_SECTION_SCHEMA = vol.Schema(
|
|||
{
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_TRIGGER): cv.TRIGGER_SCHEMA,
|
||||
vol.Optional(NUMBER_DOMAIN): vol.All(
|
||||
cv.ensure_list, [number_platform.NUMBER_SCHEMA]
|
||||
),
|
||||
vol.Optional(SENSOR_DOMAIN): vol.All(
|
||||
cv.ensure_list, [sensor_platform.SENSOR_SCHEMA]
|
||||
),
|
||||
|
|
|
@ -15,6 +15,7 @@ PLATFORMS = [
|
|||
"fan",
|
||||
"light",
|
||||
"lock",
|
||||
"number",
|
||||
"select",
|
||||
"sensor",
|
||||
"switch",
|
||||
|
|
245
homeassistant/components/template/number.py
Normal file
245
homeassistant/components/template/number.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
"""Support for numbers which integrates with other components."""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.components.number.const import (
|
||||
ATTR_MAX,
|
||||
ATTR_MIN,
|
||||
ATTR_STEP,
|
||||
ATTR_VALUE,
|
||||
DEFAULT_MAX_VALUE,
|
||||
DEFAULT_MIN_VALUE,
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
)
|
||||
from homeassistant.components.template import TriggerUpdateCoordinator
|
||||
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, CONF_UNIQUE_ID
|
||||
from homeassistant.core import Config, HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.template import Template, TemplateError
|
||||
|
||||
from .const import CONF_AVAILABILITY
|
||||
from .template_entity import TemplateEntity
|
||||
from .trigger_entity import TriggerEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_SET_VALUE = "set_value"
|
||||
|
||||
DEFAULT_NAME = "Template Number"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
|
||||
NUMBER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Required(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(ATTR_STEP): cv.template,
|
||||
vol.Optional(ATTR_MIN, default=DEFAULT_MIN_VALUE): cv.template,
|
||||
vol.Optional(ATTR_MAX, default=DEFAULT_MAX_VALUE): cv.template,
|
||||
vol.Optional(CONF_AVAILABILITY): cv.template,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def _async_create_entities(
|
||||
hass: HomeAssistant, definitions: list[dict[str, Any]], unique_id_prefix: str | None
|
||||
) -> list[TemplateNumber]:
|
||||
"""Create the Template number."""
|
||||
entities = []
|
||||
for definition in definitions:
|
||||
unique_id = definition.get(CONF_UNIQUE_ID)
|
||||
if unique_id and unique_id_prefix:
|
||||
unique_id = f"{unique_id_prefix}-{unique_id}"
|
||||
entities.append(
|
||||
TemplateNumber(
|
||||
hass,
|
||||
definition[CONF_NAME],
|
||||
definition[CONF_STATE],
|
||||
definition.get(CONF_AVAILABILITY),
|
||||
definition[CONF_SET_VALUE],
|
||||
definition[ATTR_STEP],
|
||||
definition[ATTR_MIN],
|
||||
definition[ATTR_MAX],
|
||||
definition[CONF_OPTIMISTIC],
|
||||
unique_id,
|
||||
)
|
||||
)
|
||||
return entities
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: Config,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Set up the template number."""
|
||||
if discovery_info is None:
|
||||
_LOGGER.warning(
|
||||
"Template number entities can only be configured under template:"
|
||||
)
|
||||
return
|
||||
|
||||
if "coordinator" in discovery_info:
|
||||
async_add_entities(
|
||||
TriggerNumberEntity(hass, discovery_info["coordinator"], config)
|
||||
for config in discovery_info["entities"]
|
||||
)
|
||||
return
|
||||
|
||||
async_add_entities(
|
||||
await _async_create_entities(
|
||||
hass, discovery_info["entities"], discovery_info["unique_id"]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TemplateNumber(TemplateEntity, NumberEntity):
|
||||
"""Representation of a template number."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
name_template: Template,
|
||||
value_template: Template,
|
||||
availability_template: Template | None,
|
||||
command_set_value: dict[str, Any],
|
||||
step_template: Template,
|
||||
minimum_template: Template | None,
|
||||
maximum_template: Template | None,
|
||||
optimistic: bool,
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the number."""
|
||||
super().__init__(availability_template=availability_template)
|
||||
self._attr_name = DEFAULT_NAME
|
||||
self._name_template = name_template
|
||||
name_template.hass = hass
|
||||
with contextlib.suppress(TemplateError):
|
||||
self._attr_name = name_template.async_render(parse_result=False)
|
||||
self._value_template = value_template
|
||||
domain = __name__.split(".")[-2]
|
||||
self._command_set_value = Script(
|
||||
hass, command_set_value, self._attr_name, domain
|
||||
)
|
||||
self._step_template = step_template
|
||||
self._min_value_template = minimum_template
|
||||
self._max_value_template = maximum_template
|
||||
self._attr_assumed_state = self._optimistic = optimistic
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_value = None
|
||||
self._attr_step = None
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
if self._name_template and not self._name_template.is_static:
|
||||
self.add_template_attribute("_attr_name", self._name_template, cv.string)
|
||||
self.add_template_attribute(
|
||||
"_attr_value",
|
||||
self._value_template,
|
||||
validator=vol.Coerce(float),
|
||||
none_on_template_error=True,
|
||||
)
|
||||
self.add_template_attribute(
|
||||
"_attr_step",
|
||||
self._step_template,
|
||||
validator=vol.Coerce(float),
|
||||
none_on_template_error=True,
|
||||
)
|
||||
if self._min_value_template is not None:
|
||||
self.add_template_attribute(
|
||||
"_attr_min_value",
|
||||
self._min_value_template,
|
||||
validator=vol.Coerce(float),
|
||||
none_on_template_error=True,
|
||||
)
|
||||
if self._max_value_template is not None:
|
||||
self.add_template_attribute(
|
||||
"_attr_max_value",
|
||||
self._max_value_template,
|
||||
validator=vol.Coerce(float),
|
||||
none_on_template_error=True,
|
||||
)
|
||||
await super().async_added_to_hass()
|
||||
|
||||
async def async_set_value(self, value: float) -> None:
|
||||
"""Set value of the number."""
|
||||
if self._optimistic:
|
||||
self._attr_value = value
|
||||
self.async_write_ha_state()
|
||||
await self._command_set_value.async_run(
|
||||
{ATTR_VALUE: value}, context=self._context
|
||||
)
|
||||
|
||||
|
||||
class TriggerNumberEntity(TriggerEntity, NumberEntity):
|
||||
"""Number entity based on trigger data."""
|
||||
|
||||
domain = NUMBER_DOMAIN
|
||||
extra_template_keys = (
|
||||
CONF_STATE,
|
||||
ATTR_STEP,
|
||||
ATTR_MIN,
|
||||
ATTR_MAX,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
coordinator: TriggerUpdateCoordinator,
|
||||
config: dict,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(hass, coordinator, config)
|
||||
domain = __name__.split(".")[-2]
|
||||
self._command_set_value = Script(
|
||||
hass,
|
||||
config[CONF_SET_VALUE],
|
||||
self._rendered.get(CONF_NAME, DEFAULT_NAME),
|
||||
domain,
|
||||
)
|
||||
|
||||
@property
|
||||
def value(self) -> float | None:
|
||||
"""Return the currently selected option."""
|
||||
return vol.Any(vol.Coerce(float), None)(self._rendered.get(CONF_STATE))
|
||||
|
||||
@property
|
||||
def min_value(self) -> int:
|
||||
"""Return the minimum value."""
|
||||
return vol.Any(vol.Coerce(float), None)(
|
||||
self._rendered.get(ATTR_MIN, super().min_value)
|
||||
)
|
||||
|
||||
@property
|
||||
def max_value(self) -> int:
|
||||
"""Return the maximum value."""
|
||||
return vol.Any(vol.Coerce(float), None)(
|
||||
self._rendered.get(ATTR_MAX, super().max_value)
|
||||
)
|
||||
|
||||
@property
|
||||
def step(self) -> int:
|
||||
"""Return the increment/decrement step."""
|
||||
return vol.Any(vol.Coerce(float), None)(
|
||||
self._rendered.get(ATTR_STEP, super().step)
|
||||
)
|
||||
|
||||
async def async_set_value(self, value: float) -> None:
|
||||
"""Set value of the number."""
|
||||
if self._config[CONF_OPTIMISTIC]:
|
||||
self._attr_value = value
|
||||
self.async_write_ha_state()
|
||||
await self._command_set_value.async_run(
|
||||
{ATTR_VALUE: value}, context=self._context
|
||||
)
|
336
tests/components/template/test_number.py
Normal file
336
tests/components/template/test_number.py
Normal file
|
@ -0,0 +1,336 @@
|
|||
"""The tests for the Template number platform."""
|
||||
import pytest
|
||||
|
||||
from homeassistant import setup
|
||||
from homeassistant.components.input_number import (
|
||||
ATTR_VALUE as INPUT_NUMBER_ATTR_VALUE,
|
||||
DOMAIN as INPUT_NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE as INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.components.number.const import (
|
||||
ATTR_MAX,
|
||||
ATTR_MIN,
|
||||
ATTR_STEP,
|
||||
ATTR_VALUE as NUMBER_ATTR_VALUE,
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE as NUMBER_SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_UNKNOWN
|
||||
from homeassistant.core import Context
|
||||
from homeassistant.helpers.entity_registry import async_get
|
||||
|
||||
from tests.common import (
|
||||
assert_setup_component,
|
||||
async_capture_events,
|
||||
async_mock_service,
|
||||
)
|
||||
|
||||
_TEST_NUMBER = "number.template_number"
|
||||
# Represent for number's value
|
||||
_VALUE_INPUT_NUMBER = "input_number.value"
|
||||
# Represent for number's minimum
|
||||
_MINIMUM_INPUT_NUMBER = "input_number.minimum"
|
||||
# Represent for number's maximum
|
||||
_MAXIMUM_INPUT_NUMBER = "input_number.maximum"
|
||||
# Represent for number's step
|
||||
_STEP_INPUT_NUMBER = "input_number.step"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def calls(hass):
|
||||
"""Track calls to a mock service."""
|
||||
return async_mock_service(hass, "test", "automation")
|
||||
|
||||
|
||||
async def test_missing_optional_config(hass, calls):
|
||||
"""Test: missing optional template is ok."""
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"number": {
|
||||
"state": "{{ 4 }}",
|
||||
"set_value": {"service": "script.set_value"},
|
||||
"step": "{{ 1 }}",
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
_verify(hass, 4, 1, 0.0, 100.0)
|
||||
|
||||
|
||||
async def test_missing_required_keys(hass, calls):
|
||||
"""Test: missing required fields will fail."""
|
||||
with assert_setup_component(0, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"number": {
|
||||
"set_value": {"service": "script.set_value"},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
with assert_setup_component(0, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"number": {
|
||||
"state": "{{ 4 }}",
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.async_all() == []
|
||||
|
||||
|
||||
async def test_all_optional_config(hass, calls):
|
||||
"""Test: including all optional templates is ok."""
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"number": {
|
||||
"state": "{{ 4 }}",
|
||||
"set_value": {"service": "script.set_value"},
|
||||
"min": "{{ 3 }}",
|
||||
"max": "{{ 5 }}",
|
||||
"step": "{{ 1 }}",
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
_verify(hass, 4, 1, 3, 5)
|
||||
|
||||
|
||||
async def test_templates_with_entities(hass, calls):
|
||||
"""Test tempalates with values from other entities."""
|
||||
with assert_setup_component(4, "input_number"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"input_number",
|
||||
{
|
||||
"input_number": {
|
||||
"value": {
|
||||
"min": 0.0,
|
||||
"max": 100.0,
|
||||
"name": "Value",
|
||||
"step": 1.0,
|
||||
"mode": "slider",
|
||||
},
|
||||
"step": {
|
||||
"min": 0.0,
|
||||
"max": 100.0,
|
||||
"name": "Step",
|
||||
"step": 1.0,
|
||||
"mode": "slider",
|
||||
},
|
||||
"minimum": {
|
||||
"min": 0.0,
|
||||
"max": 100.0,
|
||||
"name": "Minimum",
|
||||
"step": 1.0,
|
||||
"mode": "slider",
|
||||
},
|
||||
"maximum": {
|
||||
"min": 0.0,
|
||||
"max": 100.0,
|
||||
"name": "Maximum",
|
||||
"step": 1.0,
|
||||
"mode": "slider",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"unique_id": "b",
|
||||
"number": {
|
||||
"state": f"{{{{ states('{_VALUE_INPUT_NUMBER}') }}}}",
|
||||
"step": f"{{{{ states('{_STEP_INPUT_NUMBER}') }}}}",
|
||||
"min": f"{{{{ states('{_MINIMUM_INPUT_NUMBER}') }}}}",
|
||||
"max": f"{{{{ states('{_MAXIMUM_INPUT_NUMBER}') }}}}",
|
||||
"set_value": {
|
||||
"service": "input_number.set_value",
|
||||
"data_template": {
|
||||
"entity_id": _VALUE_INPUT_NUMBER,
|
||||
"value": "{{ value }}",
|
||||
},
|
||||
},
|
||||
"optimistic": True,
|
||||
"unique_id": "a",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
hass.states.async_set(_VALUE_INPUT_NUMBER, 4)
|
||||
hass.states.async_set(_STEP_INPUT_NUMBER, 1)
|
||||
hass.states.async_set(_MINIMUM_INPUT_NUMBER, 3)
|
||||
hass.states.async_set(_MAXIMUM_INPUT_NUMBER, 5)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
ent_reg = async_get(hass)
|
||||
entry = ent_reg.async_get(_TEST_NUMBER)
|
||||
assert entry
|
||||
assert entry.unique_id == "b-a"
|
||||
|
||||
_verify(hass, 4, 1, 3, 5)
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_NUMBER_DOMAIN,
|
||||
INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _VALUE_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 5},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
_verify(hass, 5, 1, 3, 5)
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_NUMBER_DOMAIN,
|
||||
INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _STEP_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 2},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
_verify(hass, 5, 2, 3, 5)
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_NUMBER_DOMAIN,
|
||||
INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _MINIMUM_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 2},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
_verify(hass, 5, 2, 2, 5)
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_NUMBER_DOMAIN,
|
||||
INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _MAXIMUM_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 6},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
_verify(hass, 5, 2, 2, 6)
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _TEST_NUMBER, NUMBER_ATTR_VALUE: 2},
|
||||
blocking=True,
|
||||
)
|
||||
_verify(hass, 2, 2, 2, 6)
|
||||
|
||||
|
||||
async def test_trigger_number(hass):
|
||||
"""Test trigger based template number."""
|
||||
events = async_capture_events(hass, "test_number_event")
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": [
|
||||
{"invalid": "config"},
|
||||
# Config after invalid should still be set up
|
||||
{
|
||||
"unique_id": "listening-test-event",
|
||||
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||
"number": [
|
||||
{
|
||||
"name": "Hello Name",
|
||||
"unique_id": "hello_name-id",
|
||||
"state": "{{ trigger.event.data.beers_drank }}",
|
||||
"min": "{{ trigger.event.data.min_beers }}",
|
||||
"max": "{{ trigger.event.data.max_beers }}",
|
||||
"step": "{{ trigger.event.data.step }}",
|
||||
"set_value": {"event": "test_number_event"},
|
||||
"optimistic": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("number.hello_name")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes["min"] == 0.0
|
||||
assert state.attributes["max"] == 100.0
|
||||
assert state.attributes["step"] == 1.0
|
||||
|
||||
context = Context()
|
||||
hass.bus.async_fire(
|
||||
"test_event",
|
||||
{"beers_drank": 3, "min_beers": 1.0, "max_beers": 5.0, "step": 0.5},
|
||||
context=context,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("number.hello_name")
|
||||
assert state is not None
|
||||
assert state.state == "3.0"
|
||||
assert state.attributes["min"] == 1.0
|
||||
assert state.attributes["max"] == 5.0
|
||||
assert state.attributes["step"] == 0.5
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: "number.hello_name", NUMBER_ATTR_VALUE: 2},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(events) == 1
|
||||
assert events[0].event_type == "test_number_event"
|
||||
|
||||
|
||||
def _verify(
|
||||
hass,
|
||||
expected_value,
|
||||
expected_step,
|
||||
expected_minimum,
|
||||
expected_maximum,
|
||||
):
|
||||
"""Verify number's state."""
|
||||
state = hass.states.get(_TEST_NUMBER)
|
||||
attributes = state.attributes
|
||||
assert state.state == str(float(expected_value))
|
||||
assert attributes.get(ATTR_STEP) == float(expected_step)
|
||||
assert attributes.get(ATTR_MAX) == float(expected_maximum)
|
||||
assert attributes.get(ATTR_MIN) == float(expected_minimum)
|
Loading…
Add table
Add a link
Reference in a new issue