mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add temperature unit conversion support to NumberEntity (#73233)
* Add temperature unit conversion to number * Remove type enforcements * Lint * Fix legacy unit_of_measurement * Address review comments * Fix unit_of_measurement, improve test coverage
This commit is contained in:
parent
e3b6c7a66f
commit
3da3503673
@ -1,16 +1,20 @@
|
|||||||
"""Component to allow numeric input for platforms."""
|
"""Component to allow numeric input for platforms."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from contextlib import suppress
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
from math import ceil, floor
|
||||||
from typing import Any, final
|
from typing import Any, final
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.backports.enum import StrEnum
|
from homeassistant.backports.enum import StrEnum
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_MODE
|
from homeassistant.const import ATTR_MODE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
@ -19,6 +23,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
|||||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
from homeassistant.util import temperature as temperature_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_MAX,
|
ATTR_MAX,
|
||||||
@ -41,6 +46,13 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NumberDeviceClass(StrEnum):
|
||||||
|
"""Device class for numbers."""
|
||||||
|
|
||||||
|
# temperature (C/F)
|
||||||
|
TEMPERATURE = "temperature"
|
||||||
|
|
||||||
|
|
||||||
class NumberMode(StrEnum):
|
class NumberMode(StrEnum):
|
||||||
"""Modes for number entities."""
|
"""Modes for number entities."""
|
||||||
|
|
||||||
@ -49,6 +61,11 @@ class NumberMode(StrEnum):
|
|||||||
SLIDER = "slider"
|
SLIDER = "slider"
|
||||||
|
|
||||||
|
|
||||||
|
UNIT_CONVERSIONS: dict[str, Callable[[float, str, str], float]] = {
|
||||||
|
NumberDeviceClass.TEMPERATURE: temperature_util.convert,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up Number entities."""
|
"""Set up Number entities."""
|
||||||
component = hass.data[DOMAIN] = EntityComponent(
|
component = hass.data[DOMAIN] = EntityComponent(
|
||||||
@ -72,6 +89,14 @@ async def async_set_value(entity: NumberEntity, service_call: ServiceCall) -> No
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Value {value} for {entity.name} is outside valid range {entity.min_value} - {entity.max_value}"
|
f"Value {value} for {entity.name} is outside valid range {entity.min_value} - {entity.max_value}"
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
native_value = entity.convert_to_native_value(value)
|
||||||
|
# Clamp to the native range
|
||||||
|
native_value = min(
|
||||||
|
max(native_value, entity.native_min_value), entity.native_max_value
|
||||||
|
)
|
||||||
|
await entity.async_set_native_value(native_value)
|
||||||
|
except NotImplementedError:
|
||||||
await entity.async_set_value(value)
|
await entity.async_set_value(value)
|
||||||
|
|
||||||
|
|
||||||
@ -93,8 +118,56 @@ class NumberEntityDescription(EntityDescription):
|
|||||||
|
|
||||||
max_value: float | None = None
|
max_value: float | None = None
|
||||||
min_value: float | None = None
|
min_value: float | None = None
|
||||||
|
native_max_value: float | None = None
|
||||||
|
native_min_value: float | None = None
|
||||||
|
native_unit_of_measurement: str | None = None
|
||||||
|
native_step: float | None = None
|
||||||
step: float | None = None
|
step: float | None = None
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
"""Post initialisation processing."""
|
||||||
|
if (
|
||||||
|
self.max_value is not None
|
||||||
|
or self.min_value is not None
|
||||||
|
or self.step is not None
|
||||||
|
or self.unit_of_measurement is not None
|
||||||
|
):
|
||||||
|
caller = inspect.stack()[2]
|
||||||
|
module = inspect.getmodule(caller[0])
|
||||||
|
if module and module.__file__ and "custom_components" in module.__file__:
|
||||||
|
report_issue = "report it to the custom component author."
|
||||||
|
else:
|
||||||
|
report_issue = (
|
||||||
|
"create a bug report at "
|
||||||
|
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||||
|
)
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s is setting deprecated attributes on an instance of "
|
||||||
|
"NumberEntityDescription, this is not valid and will be unsupported "
|
||||||
|
"from Home Assistant 2022.10. Please %s",
|
||||||
|
module.__name__ if module else self.__class__.__name__,
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
self.native_unit_of_measurement = self.unit_of_measurement
|
||||||
|
|
||||||
|
|
||||||
|
def ceil_decimal(value: float, precision: float = 0) -> float:
|
||||||
|
"""Return the ceiling of f with d decimals.
|
||||||
|
|
||||||
|
This is a simple implementation which ignores floating point inexactness.
|
||||||
|
"""
|
||||||
|
factor = 10**precision
|
||||||
|
return ceil(value * factor) / factor
|
||||||
|
|
||||||
|
|
||||||
|
def floor_decimal(value: float, precision: float = 0) -> float:
|
||||||
|
"""Return the floor of f with d decimals.
|
||||||
|
|
||||||
|
This is a simple implementation which ignores floating point inexactness.
|
||||||
|
"""
|
||||||
|
factor = 10**precision
|
||||||
|
return floor(value * factor) / factor
|
||||||
|
|
||||||
|
|
||||||
class NumberEntity(Entity):
|
class NumberEntity(Entity):
|
||||||
"""Representation of a Number entity."""
|
"""Representation of a Number entity."""
|
||||||
@ -106,6 +179,12 @@ class NumberEntity(Entity):
|
|||||||
_attr_step: float
|
_attr_step: float
|
||||||
_attr_mode: NumberMode = NumberMode.AUTO
|
_attr_mode: NumberMode = NumberMode.AUTO
|
||||||
_attr_value: float
|
_attr_value: float
|
||||||
|
_attr_native_max_value: float
|
||||||
|
_attr_native_min_value: float
|
||||||
|
_attr_native_step: float
|
||||||
|
_attr_native_value: float
|
||||||
|
_attr_native_unit_of_measurement: str | None
|
||||||
|
_deprecated_number_entity_reported = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def capability_attributes(self) -> dict[str, Any]:
|
def capability_attributes(self) -> dict[str, Any]:
|
||||||
@ -117,40 +196,84 @@ class NumberEntity(Entity):
|
|||||||
ATTR_MODE: self.mode,
|
ATTR_MODE: self.mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_min_value(self) -> float:
|
||||||
|
"""Return the minimum value."""
|
||||||
|
if hasattr(self, "_attr_native_min_value"):
|
||||||
|
return self._attr_native_min_value
|
||||||
|
if (
|
||||||
|
hasattr(self, "entity_description")
|
||||||
|
and self.entity_description.native_min_value is not None
|
||||||
|
):
|
||||||
|
return self.entity_description.native_min_value
|
||||||
|
return DEFAULT_MIN_VALUE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_value(self) -> float:
|
def min_value(self) -> float:
|
||||||
"""Return the minimum value."""
|
"""Return the minimum value."""
|
||||||
if hasattr(self, "_attr_min_value"):
|
if hasattr(self, "_attr_min_value"):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self._attr_min_value
|
return self._attr_min_value
|
||||||
if (
|
if (
|
||||||
hasattr(self, "entity_description")
|
hasattr(self, "entity_description")
|
||||||
and self.entity_description.min_value is not None
|
and self.entity_description.min_value is not None
|
||||||
):
|
):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self.entity_description.min_value
|
return self.entity_description.min_value
|
||||||
return DEFAULT_MIN_VALUE
|
return self._convert_to_state_value(self.native_min_value, floor_decimal)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_max_value(self) -> float:
|
||||||
|
"""Return the maximum value."""
|
||||||
|
if hasattr(self, "_attr_native_max_value"):
|
||||||
|
return self._attr_native_max_value
|
||||||
|
if (
|
||||||
|
hasattr(self, "entity_description")
|
||||||
|
and self.entity_description.native_max_value is not None
|
||||||
|
):
|
||||||
|
return self.entity_description.native_max_value
|
||||||
|
return DEFAULT_MAX_VALUE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_value(self) -> float:
|
def max_value(self) -> float:
|
||||||
"""Return the maximum value."""
|
"""Return the maximum value."""
|
||||||
if hasattr(self, "_attr_max_value"):
|
if hasattr(self, "_attr_max_value"):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self._attr_max_value
|
return self._attr_max_value
|
||||||
if (
|
if (
|
||||||
hasattr(self, "entity_description")
|
hasattr(self, "entity_description")
|
||||||
and self.entity_description.max_value is not None
|
and self.entity_description.max_value is not None
|
||||||
):
|
):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self.entity_description.max_value
|
return self.entity_description.max_value
|
||||||
return DEFAULT_MAX_VALUE
|
return self._convert_to_state_value(self.native_max_value, ceil_decimal)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_step(self) -> float | None:
|
||||||
|
"""Return the increment/decrement step."""
|
||||||
|
if hasattr(self, "_attr_native_step"):
|
||||||
|
return self._attr_native_step
|
||||||
|
if (
|
||||||
|
hasattr(self, "entity_description")
|
||||||
|
and self.entity_description.native_step is not None
|
||||||
|
):
|
||||||
|
return self.entity_description.native_step
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def step(self) -> float:
|
def step(self) -> float:
|
||||||
"""Return the increment/decrement step."""
|
"""Return the increment/decrement step."""
|
||||||
if hasattr(self, "_attr_step"):
|
if hasattr(self, "_attr_step"):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self._attr_step
|
return self._attr_step
|
||||||
if (
|
if (
|
||||||
hasattr(self, "entity_description")
|
hasattr(self, "entity_description")
|
||||||
and self.entity_description.step is not None
|
and self.entity_description.step is not None
|
||||||
):
|
):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self.entity_description.step
|
return self.entity_description.step
|
||||||
|
if (native_step := self.native_step) is not None:
|
||||||
|
return native_step
|
||||||
step = DEFAULT_STEP
|
step = DEFAULT_STEP
|
||||||
value_range = abs(self.max_value - self.min_value)
|
value_range = abs(self.max_value - self.min_value)
|
||||||
if value_range != 0:
|
if value_range != 0:
|
||||||
@ -169,11 +292,60 @@ class NumberEntity(Entity):
|
|||||||
"""Return the entity state."""
|
"""Return the entity state."""
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
|
"""Return the unit of measurement of the entity, if any."""
|
||||||
|
if hasattr(self, "_attr_native_unit_of_measurement"):
|
||||||
|
return self._attr_native_unit_of_measurement
|
||||||
|
if hasattr(self, "entity_description"):
|
||||||
|
return self.entity_description.native_unit_of_measurement
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self) -> str | None:
|
||||||
|
"""Return the unit of measurement of the entity, after unit conversion."""
|
||||||
|
if hasattr(self, "_attr_unit_of_measurement"):
|
||||||
|
return self._attr_unit_of_measurement
|
||||||
|
if (
|
||||||
|
hasattr(self, "entity_description")
|
||||||
|
and self.entity_description.unit_of_measurement is not None
|
||||||
|
):
|
||||||
|
return self.entity_description.unit_of_measurement
|
||||||
|
|
||||||
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.device_class == NumberDeviceClass.TEMPERATURE
|
||||||
|
and native_unit_of_measurement in (TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
|
):
|
||||||
|
return self.hass.config.units.temperature_unit
|
||||||
|
|
||||||
|
return native_unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> float | None:
|
||||||
|
"""Return the value reported by the sensor."""
|
||||||
|
return self._attr_native_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> float | None:
|
def value(self) -> float | None:
|
||||||
"""Return the entity value to represent the entity state."""
|
"""Return the entity value to represent the entity state."""
|
||||||
|
if hasattr(self, "_attr_value"):
|
||||||
|
self._report_deprecated_number_entity()
|
||||||
return self._attr_value
|
return self._attr_value
|
||||||
|
|
||||||
|
if (native_value := self.native_value) is None:
|
||||||
|
return native_value
|
||||||
|
return self._convert_to_state_value(native_value, round)
|
||||||
|
|
||||||
|
def set_native_value(self, value: float) -> None:
|
||||||
|
"""Set new value."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
|
"""Set new value."""
|
||||||
|
await self.hass.async_add_executor_job(self.set_native_value, value)
|
||||||
|
|
||||||
def set_value(self, value: float) -> None:
|
def set_value(self, value: float) -> None:
|
||||||
"""Set new value."""
|
"""Set new value."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@ -181,3 +353,69 @@ class NumberEntity(Entity):
|
|||||||
async def async_set_value(self, value: float) -> None:
|
async def async_set_value(self, value: float) -> None:
|
||||||
"""Set new value."""
|
"""Set new value."""
|
||||||
await self.hass.async_add_executor_job(self.set_value, value)
|
await self.hass.async_add_executor_job(self.set_value, value)
|
||||||
|
|
||||||
|
def _convert_to_state_value(self, value: float, method: Callable) -> float:
|
||||||
|
"""Convert a value in the number's native unit to the configured unit."""
|
||||||
|
|
||||||
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
unit_of_measurement = self.unit_of_measurement
|
||||||
|
device_class = self.device_class
|
||||||
|
|
||||||
|
if (
|
||||||
|
native_unit_of_measurement != unit_of_measurement
|
||||||
|
and device_class in UNIT_CONVERSIONS
|
||||||
|
):
|
||||||
|
assert native_unit_of_measurement
|
||||||
|
assert unit_of_measurement
|
||||||
|
|
||||||
|
value_s = str(value)
|
||||||
|
prec = len(value_s) - value_s.index(".") - 1 if "." in value_s else 0
|
||||||
|
|
||||||
|
# Suppress ValueError (Could not convert value to float)
|
||||||
|
with suppress(ValueError):
|
||||||
|
value_new: float = UNIT_CONVERSIONS[device_class](
|
||||||
|
value,
|
||||||
|
native_unit_of_measurement,
|
||||||
|
unit_of_measurement,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Round to the wanted precision
|
||||||
|
value = method(value_new, prec)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def convert_to_native_value(self, value: float) -> float:
|
||||||
|
"""Convert a value to the number's native unit."""
|
||||||
|
|
||||||
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
unit_of_measurement = self.unit_of_measurement
|
||||||
|
device_class = self.device_class
|
||||||
|
|
||||||
|
if (
|
||||||
|
value is not None
|
||||||
|
and native_unit_of_measurement != unit_of_measurement
|
||||||
|
and device_class in UNIT_CONVERSIONS
|
||||||
|
):
|
||||||
|
assert native_unit_of_measurement
|
||||||
|
assert unit_of_measurement
|
||||||
|
|
||||||
|
value = UNIT_CONVERSIONS[device_class](
|
||||||
|
value,
|
||||||
|
unit_of_measurement,
|
||||||
|
native_unit_of_measurement,
|
||||||
|
)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _report_deprecated_number_entity(self) -> None:
|
||||||
|
"""Report that the number entity has not been upgraded."""
|
||||||
|
if not self._deprecated_number_entity_reported:
|
||||||
|
self._deprecated_number_entity_reported = True
|
||||||
|
report_issue = self._suggest_report_issue()
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Entity %s (%s) is using deprecated NumberEntity features which will "
|
||||||
|
"be unsupported from Home Assistant Core 2022.10, please %s",
|
||||||
|
self.entity_id,
|
||||||
|
type(self),
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
@ -4,19 +4,138 @@ from unittest.mock import MagicMock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.number import (
|
from homeassistant.components.number import (
|
||||||
|
ATTR_MAX,
|
||||||
|
ATTR_MIN,
|
||||||
ATTR_STEP,
|
ATTR_STEP,
|
||||||
ATTR_VALUE,
|
ATTR_VALUE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_SET_VALUE,
|
SERVICE_SET_VALUE,
|
||||||
|
NumberDeviceClass,
|
||||||
NumberEntity,
|
NumberEntity,
|
||||||
|
NumberEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_PLATFORM,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||||
|
|
||||||
|
|
||||||
class MockDefaultNumberEntity(NumberEntity):
|
class MockDefaultNumberEntity(NumberEntity):
|
||||||
"""Mock NumberEntity device to use in tests."""
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class falls back on defaults for min_value, max_value, step.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self):
|
||||||
|
"""Return the current value."""
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class MockNumberEntity(NumberEntity):
|
||||||
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class customizes min_value, max_value as overridden methods.
|
||||||
|
Step is calculated based on the smaller max_value and min_value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_max_value(self) -> float:
|
||||||
|
"""Return the max value."""
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_min_value(self) -> float:
|
||||||
|
"""Return the min value."""
|
||||||
|
return -0.5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_unit_of_measurement(self):
|
||||||
|
"""Return the current value."""
|
||||||
|
return "native_cats"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self):
|
||||||
|
"""Return the current value."""
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class MockNumberEntityAttr(NumberEntity):
|
||||||
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class customizes min_value, max_value by setting _attr members.
|
||||||
|
Step is calculated based on the smaller max_value and min_value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_attr_native_max_value = 1000.0
|
||||||
|
_attr_native_min_value = -1000.0
|
||||||
|
_attr_native_step = 100.0
|
||||||
|
_attr_native_unit_of_measurement = "native_dogs"
|
||||||
|
_attr_native_value = 500.0
|
||||||
|
|
||||||
|
|
||||||
|
class MockNumberEntityDescr(NumberEntity):
|
||||||
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class customizes min_value, max_value by entity description.
|
||||||
|
Step is calculated based on the smaller max_value and min_value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the clas instance."""
|
||||||
|
self.entity_description = NumberEntityDescription(
|
||||||
|
"test",
|
||||||
|
native_max_value=10.0,
|
||||||
|
native_min_value=-10.0,
|
||||||
|
native_step=2.0,
|
||||||
|
native_unit_of_measurement="native_rabbits",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self):
|
||||||
|
"""Return the current value."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class MockDefaultNumberEntityDeprecated(NumberEntity):
|
||||||
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class falls back on defaults for min_value, max_value, step.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self):
|
||||||
|
"""Return the current value."""
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class MockNumberEntityDeprecated(NumberEntity):
|
||||||
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class customizes min_value, max_value as overridden methods.
|
||||||
|
Step is calculated based on the smaller max_value and min_value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_value(self) -> float:
|
||||||
|
"""Return the max value."""
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_value(self) -> float:
|
||||||
|
"""Return the min value."""
|
||||||
|
return -0.5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the current value."""
|
||||||
|
return "cats"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
@ -24,13 +143,36 @@ class MockDefaultNumberEntity(NumberEntity):
|
|||||||
return 0.5
|
return 0.5
|
||||||
|
|
||||||
|
|
||||||
class MockNumberEntity(NumberEntity):
|
class MockNumberEntityAttrDeprecated(NumberEntity):
|
||||||
"""Mock NumberEntity device to use in tests."""
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
@property
|
This class customizes min_value, max_value by setting _attr members.
|
||||||
def max_value(self) -> float:
|
Step is calculated based on the smaller max_value and min_value.
|
||||||
"""Return the max value."""
|
"""
|
||||||
return 1.0
|
|
||||||
|
_attr_max_value = 1000.0
|
||||||
|
_attr_min_value = -1000.0
|
||||||
|
_attr_step = 100.0
|
||||||
|
_attr_unit_of_measurement = "dogs"
|
||||||
|
_attr_value = 500.0
|
||||||
|
|
||||||
|
|
||||||
|
class MockNumberEntityDescrDeprecated(NumberEntity):
|
||||||
|
"""Mock NumberEntity device to use in tests.
|
||||||
|
|
||||||
|
This class customizes min_value, max_value by entity description.
|
||||||
|
Step is calculated based on the smaller max_value and min_value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the clas instance."""
|
||||||
|
self.entity_description = NumberEntityDescription(
|
||||||
|
"test",
|
||||||
|
max_value=10.0,
|
||||||
|
min_value=-10.0,
|
||||||
|
step=2.0,
|
||||||
|
unit_of_measurement="rabbits",
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
@ -41,12 +183,97 @@ class MockNumberEntity(NumberEntity):
|
|||||||
async def test_step(hass: HomeAssistant) -> None:
|
async def test_step(hass: HomeAssistant) -> None:
|
||||||
"""Test the step calculation."""
|
"""Test the step calculation."""
|
||||||
number = MockDefaultNumberEntity()
|
number = MockDefaultNumberEntity()
|
||||||
|
number.hass = hass
|
||||||
assert number.step == 1.0
|
assert number.step == 1.0
|
||||||
|
|
||||||
number_2 = MockNumberEntity()
|
number_2 = MockNumberEntity()
|
||||||
|
number_2.hass = hass
|
||||||
assert number_2.step == 0.1
|
assert number_2.step == 0.1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_attributes(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the attributes."""
|
||||||
|
number = MockDefaultNumberEntity()
|
||||||
|
number.hass = hass
|
||||||
|
assert number.max_value == 100.0
|
||||||
|
assert number.min_value == 0.0
|
||||||
|
assert number.step == 1.0
|
||||||
|
assert number.unit_of_measurement is None
|
||||||
|
assert number.value == 0.5
|
||||||
|
|
||||||
|
number_2 = MockNumberEntity()
|
||||||
|
number_2.hass = hass
|
||||||
|
assert number_2.max_value == 0.5
|
||||||
|
assert number_2.min_value == -0.5
|
||||||
|
assert number_2.step == 0.1
|
||||||
|
assert number_2.unit_of_measurement == "native_cats"
|
||||||
|
assert number_2.value == 0.5
|
||||||
|
|
||||||
|
number_3 = MockNumberEntityAttr()
|
||||||
|
number_3.hass = hass
|
||||||
|
assert number_3.max_value == 1000.0
|
||||||
|
assert number_3.min_value == -1000.0
|
||||||
|
assert number_3.step == 100.0
|
||||||
|
assert number_3.unit_of_measurement == "native_dogs"
|
||||||
|
assert number_3.value == 500.0
|
||||||
|
|
||||||
|
number_4 = MockNumberEntityDescr()
|
||||||
|
number_4.hass = hass
|
||||||
|
assert number_4.max_value == 10.0
|
||||||
|
assert number_4.min_value == -10.0
|
||||||
|
assert number_4.step == 2.0
|
||||||
|
assert number_4.unit_of_measurement == "native_rabbits"
|
||||||
|
assert number_4.value is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_attributes_deprecated(hass: HomeAssistant, caplog) -> None:
|
||||||
|
"""Test overriding the deprecated attributes."""
|
||||||
|
number = MockDefaultNumberEntityDeprecated()
|
||||||
|
number.hass = hass
|
||||||
|
assert number.max_value == 100.0
|
||||||
|
assert number.min_value == 0.0
|
||||||
|
assert number.step == 1.0
|
||||||
|
assert number.unit_of_measurement is None
|
||||||
|
assert number.value == 0.5
|
||||||
|
|
||||||
|
number_2 = MockNumberEntityDeprecated()
|
||||||
|
number_2.hass = hass
|
||||||
|
assert number_2.max_value == 0.5
|
||||||
|
assert number_2.min_value == -0.5
|
||||||
|
assert number_2.step == 0.1
|
||||||
|
assert number_2.unit_of_measurement == "cats"
|
||||||
|
assert number_2.value == 0.5
|
||||||
|
|
||||||
|
number_3 = MockNumberEntityAttrDeprecated()
|
||||||
|
number_3.hass = hass
|
||||||
|
assert number_3.max_value == 1000.0
|
||||||
|
assert number_3.min_value == -1000.0
|
||||||
|
assert number_3.step == 100.0
|
||||||
|
assert number_3.unit_of_measurement == "dogs"
|
||||||
|
assert number_3.value == 500.0
|
||||||
|
|
||||||
|
number_4 = MockNumberEntityDescrDeprecated()
|
||||||
|
number_4.hass = hass
|
||||||
|
assert number_4.max_value == 10.0
|
||||||
|
assert number_4.min_value == -10.0
|
||||||
|
assert number_4.step == 2.0
|
||||||
|
assert number_4.unit_of_measurement == "rabbits"
|
||||||
|
assert number_4.value == 0.5
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Entity None (<class 'tests.components.number.test_init.MockNumberEntityAttrDeprecated'>) "
|
||||||
|
"is using deprecated NumberEntity features" in caplog.text
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"Entity None (<class 'tests.components.number.test_init.MockNumberEntityDescrDeprecated'>) "
|
||||||
|
"is using deprecated NumberEntity features" in caplog.text
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"tests.components.number.test_init is setting deprecated attributes on an "
|
||||||
|
"instance of NumberEntityDescription" in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_sync_set_value(hass: HomeAssistant) -> None:
|
async def test_sync_set_value(hass: HomeAssistant) -> None:
|
||||||
"""Test if async set_value calls sync set_value."""
|
"""Test if async set_value calls sync set_value."""
|
||||||
number = MockDefaultNumberEntity()
|
number = MockDefaultNumberEntity()
|
||||||
@ -59,9 +286,7 @@ async def test_sync_set_value(hass: HomeAssistant) -> None:
|
|||||||
assert number.set_value.call_args[0][0] == 42
|
assert number.set_value.call_args[0][0] == 42
|
||||||
|
|
||||||
|
|
||||||
async def test_custom_integration_and_validation(
|
async def test_set_value(hass: HomeAssistant, enable_custom_integrations: None) -> None:
|
||||||
hass: HomeAssistant, enable_custom_integrations: None
|
|
||||||
) -> None:
|
|
||||||
"""Test we can only set valid values."""
|
"""Test we can only set valid values."""
|
||||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||||
platform.init()
|
platform.init()
|
||||||
@ -79,9 +304,8 @@ async def test_custom_integration_and_validation(
|
|||||||
{ATTR_VALUE: 60.0, ATTR_ENTITY_ID: "number.test"},
|
{ATTR_VALUE: 60.0, ATTR_ENTITY_ID: "number.test"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.states.async_set("number.test", 60.0)
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("number.test")
|
state = hass.states.get("number.test")
|
||||||
assert state.state == "60.0"
|
assert state.state == "60.0"
|
||||||
|
|
||||||
@ -97,3 +321,252 @@ async def test_custom_integration_and_validation(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("number.test")
|
state = hass.states.get("number.test")
|
||||||
assert state.state == "60.0"
|
assert state.state == "60.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_deprecated_attributes(
|
||||||
|
hass: HomeAssistant, enable_custom_integrations: None
|
||||||
|
) -> None:
|
||||||
|
"""Test entity using deprecated attributes."""
|
||||||
|
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES.append(platform.LegacyMockNumberEntity())
|
||||||
|
entity = platform.ENTITIES[0]
|
||||||
|
entity._attr_name = "Test"
|
||||||
|
entity._attr_max_value = 25
|
||||||
|
entity._attr_min_value = -25
|
||||||
|
entity._attr_step = 2.5
|
||||||
|
entity._attr_value = 51.0
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("number.test")
|
||||||
|
assert state.state == "51.0"
|
||||||
|
assert state.attributes.get(ATTR_MAX) == 25.0
|
||||||
|
assert state.attributes.get(ATTR_MIN) == -25.0
|
||||||
|
assert state.attributes.get(ATTR_STEP) == 2.5
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: 0.0, ATTR_ENTITY_ID: "number.test"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("number.test")
|
||||||
|
assert state.state == "0.0"
|
||||||
|
|
||||||
|
# test ValueError trigger
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: 110.0, ATTR_ENTITY_ID: "number.test"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("number.test")
|
||||||
|
assert state.state == "0.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_deprecated_methods(
|
||||||
|
hass: HomeAssistant, enable_custom_integrations: None
|
||||||
|
) -> None:
|
||||||
|
"""Test entity using deprecated methods."""
|
||||||
|
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES.append(
|
||||||
|
platform.LegacyMockNumberEntity(
|
||||||
|
name="Test",
|
||||||
|
max_value=25.0,
|
||||||
|
min_value=-25.0,
|
||||||
|
step=2.5,
|
||||||
|
value=51.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("number.test")
|
||||||
|
assert state.state == "51.0"
|
||||||
|
assert state.attributes.get(ATTR_MAX) == 25.0
|
||||||
|
assert state.attributes.get(ATTR_MIN) == -25.0
|
||||||
|
assert state.attributes.get(ATTR_STEP) == 2.5
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: 0.0, ATTR_ENTITY_ID: "number.test"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("number.test")
|
||||||
|
assert state.state == "0.0"
|
||||||
|
|
||||||
|
# test ValueError trigger
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: 110.0, ATTR_ENTITY_ID: "number.test"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("number.test")
|
||||||
|
assert state.state == "0.0"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"unit_system, native_unit, state_unit, initial_native_value, initial_state_value, "
|
||||||
|
"updated_native_value, updated_state_value, native_max_value, state_max_value, "
|
||||||
|
"native_min_value, state_min_value, native_step, state_step",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
IMPERIAL_SYSTEM,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
50,
|
||||||
|
50,
|
||||||
|
140,
|
||||||
|
140,
|
||||||
|
-9,
|
||||||
|
-9,
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
IMPERIAL_SYSTEM,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
38,
|
||||||
|
100,
|
||||||
|
10,
|
||||||
|
50,
|
||||||
|
60,
|
||||||
|
140,
|
||||||
|
-23,
|
||||||
|
-10,
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
METRIC_SYSTEM,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
100,
|
||||||
|
38,
|
||||||
|
50,
|
||||||
|
10,
|
||||||
|
140,
|
||||||
|
60,
|
||||||
|
-9,
|
||||||
|
-23,
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
METRIC_SYSTEM,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
38,
|
||||||
|
38,
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
60,
|
||||||
|
60,
|
||||||
|
-23,
|
||||||
|
-23,
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_temperature_conversion(
|
||||||
|
hass,
|
||||||
|
enable_custom_integrations,
|
||||||
|
unit_system,
|
||||||
|
native_unit,
|
||||||
|
state_unit,
|
||||||
|
initial_native_value,
|
||||||
|
initial_state_value,
|
||||||
|
updated_native_value,
|
||||||
|
updated_state_value,
|
||||||
|
native_max_value,
|
||||||
|
state_max_value,
|
||||||
|
native_min_value,
|
||||||
|
state_min_value,
|
||||||
|
native_step,
|
||||||
|
state_step,
|
||||||
|
):
|
||||||
|
"""Test temperature conversion."""
|
||||||
|
hass.config.units = unit_system
|
||||||
|
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES.append(
|
||||||
|
platform.MockNumberEntity(
|
||||||
|
name="Test",
|
||||||
|
native_max_value=native_max_value,
|
||||||
|
native_min_value=native_min_value,
|
||||||
|
native_step=native_step,
|
||||||
|
native_unit_of_measurement=native_unit,
|
||||||
|
native_value=initial_native_value,
|
||||||
|
device_class=NumberDeviceClass.TEMPERATURE,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
entity0 = platform.ENTITIES[0]
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert float(state.state) == pytest.approx(float(initial_state_value))
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
|
||||||
|
assert state.attributes[ATTR_MAX] == state_max_value
|
||||||
|
assert state.attributes[ATTR_MIN] == state_min_value
|
||||||
|
assert state.attributes[ATTR_STEP] == state_step
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: updated_state_value, ATTR_ENTITY_ID: entity0.entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert float(state.state) == pytest.approx(float(updated_state_value))
|
||||||
|
assert entity0._values["native_value"] == updated_native_value
|
||||||
|
|
||||||
|
# Set to the minimum value
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: state_min_value, ATTR_ENTITY_ID: entity0.entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert float(state.state) == pytest.approx(float(state_min_value), rel=0.1)
|
||||||
|
|
||||||
|
# Set to the maximum value
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_VALUE,
|
||||||
|
{ATTR_VALUE: state_max_value, ATTR_ENTITY_ID: entity0.entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert float(state.state) == pytest.approx(float(state_max_value), rel=0.1)
|
||||||
|
@ -13,10 +13,55 @@ ENTITIES = []
|
|||||||
|
|
||||||
|
|
||||||
class MockNumberEntity(MockEntity, NumberEntity):
|
class MockNumberEntity(MockEntity, NumberEntity):
|
||||||
"""Mock Select class."""
|
"""Mock number class."""
|
||||||
|
|
||||||
_attr_value = 50.0
|
@property
|
||||||
_attr_step = 1.0
|
def native_max_value(self):
|
||||||
|
"""Return the native native_max_value."""
|
||||||
|
return self._handle("native_max_value")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_min_value(self):
|
||||||
|
"""Return the native native_min_value."""
|
||||||
|
return self._handle("native_min_value")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_step(self):
|
||||||
|
"""Return the native native_step."""
|
||||||
|
return self._handle("native_step")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_unit_of_measurement(self):
|
||||||
|
"""Return the native unit_of_measurement."""
|
||||||
|
return self._handle("native_unit_of_measurement")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self):
|
||||||
|
"""Return the native value of this sensor."""
|
||||||
|
return self._handle("native_value")
|
||||||
|
|
||||||
|
def set_native_value(self, value: float) -> None:
|
||||||
|
"""Change the selected option."""
|
||||||
|
self._values["native_value"] = value
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyMockNumberEntity(MockEntity, NumberEntity):
|
||||||
|
"""Mock Number class using deprecated features."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_value(self):
|
||||||
|
"""Return the native max_value."""
|
||||||
|
return self._handle("max_value")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_value(self):
|
||||||
|
"""Return the native min_value."""
|
||||||
|
return self._handle("min_value")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def step(self):
|
||||||
|
"""Return the native step."""
|
||||||
|
return self._handle("step")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
@ -25,7 +70,7 @@ class MockNumberEntity(MockEntity, NumberEntity):
|
|||||||
|
|
||||||
def set_value(self, value: float) -> None:
|
def set_value(self, value: float) -> None:
|
||||||
"""Change the selected option."""
|
"""Change the selected option."""
|
||||||
self._attr_value = value
|
self._values["value"] = value
|
||||||
|
|
||||||
|
|
||||||
def init(empty=False):
|
def init(empty=False):
|
||||||
@ -39,6 +84,7 @@ def init(empty=False):
|
|||||||
MockNumberEntity(
|
MockNumberEntity(
|
||||||
name="test",
|
name="test",
|
||||||
unique_id=UNIQUE_NUMBER,
|
unique_id=UNIQUE_NUMBER,
|
||||||
|
native_value=50.0,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user