1 """Component to allow numeric input for platforms."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from contextlib
import suppress
8 from datetime
import timedelta
10 from math
import ceil, floor
11 from typing
import TYPE_CHECKING, Any, Self, final
13 from propcache
import cached_property
14 import voluptuous
as vol
21 async_get_hass_or_none,
42 DEVICE_CLASSES_SCHEMA,
49 from .websocket_api
import async_setup
as async_setup_ws_api
51 _LOGGER = logging.getLogger(__name__)
53 DATA_COMPONENT: HassKey[EntityComponent[NumberEntity]] =
HassKey(DOMAIN)
54 ENTITY_ID_FORMAT = DOMAIN +
".{}"
55 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
56 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
71 "PLATFORM_SCHEMA_BASE",
75 "NumberEntityDescription",
76 "NumberExtraStoredData",
84 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
85 """Set up Number entities."""
86 component = hass.data[DATA_COMPONENT] = EntityComponent[NumberEntity](
87 _LOGGER, DOMAIN, hass, SCAN_INTERVAL
89 async_setup_ws_api(hass)
90 await component.async_setup(config)
92 component.async_register_entity_service(
94 {vol.Required(ATTR_VALUE): vol.Coerce(float)},
102 """Service call wrapper to set a new value."""
103 value = service_call.data[
"value"]
104 if value < entity.min_value
or value > entity.max_value:
106 translation_domain=DOMAIN,
107 translation_key=
"out_of_range",
108 translation_placeholders={
110 "entity_id": entity.entity_id,
111 "min_value":
str(entity.min_value),
112 "max_value":
str(entity.max_value),
117 native_value = entity.convert_to_native_value(value)
120 max(native_value, entity.native_min_value), entity.native_max_value
122 await entity.async_set_native_value(native_value)
123 except NotImplementedError:
124 await entity.async_set_value(value)
128 """Set up a config entry."""
133 """Unload a config entry."""
138 """A class that describes number entities."""
140 device_class: NumberDeviceClass |
None =
None
141 max_value:
None =
None
142 min_value:
None =
None
143 mode: NumberMode |
None =
None
144 native_max_value: float |
None =
None
145 native_min_value: float |
None =
None
146 native_step: float |
None =
None
147 native_unit_of_measurement: str |
None =
None
149 unit_of_measurement:
None =
None
153 """Return the ceiling of f with d decimals.
155 This is a simple implementation which ignores floating point inexactness.
157 factor = 10**precision
158 return ceil(value * factor) / factor
162 """Return the floor of f with d decimals.
164 This is a simple implementation which ignores floating point inexactness.
166 factor = 10**precision
167 return floor(value * factor) / factor
170 CACHED_PROPERTIES_WITH_ATTR_ = {
176 "native_unit_of_measurement",
182 """Representation of a Number entity."""
184 _entity_component_unrecorded_attributes = frozenset(
185 {ATTR_MIN, ATTR_MAX, ATTR_STEP, ATTR_STEP_VALIDATION, ATTR_MODE}
188 entity_description: NumberEntityDescription
189 _attr_device_class: NumberDeviceClass |
None
190 _attr_max_value:
None
191 _attr_min_value:
None
192 _attr_mode: NumberMode
193 _attr_state:
None =
None
195 _attr_unit_of_measurement:
None
197 _attr_native_max_value: float
198 _attr_native_min_value: float
199 _attr_native_step: float
200 _attr_native_unit_of_measurement: str |
None
201 _attr_native_value: float |
None =
None
202 _deprecated_number_entity_reported =
False
203 _number_option_unit_of_measurement: str |
None =
None
206 """Post initialisation processing."""
209 method
in cls.__dict__
216 "unit_of_measurement",
225 "%s::%s is overriding deprecated methods on an instance of "
226 "NumberEntity, this is not valid and will be unsupported "
227 "from Home Assistant 2022.10. Please %s"
235 """Call when the number entity is added to hass."""
243 """Return capability attributes."""
255 ATTR_MODE: self.
modemode,
259 """Return True if an unnamed entity should be named by its device class.
261 For numbers this is True if the entity has a device class.
267 """Return the class of this entity."""
268 if hasattr(self,
"_attr_device_class"):
269 return self._attr_device_class
270 if hasattr(self,
"entity_description"):
271 return self.entity_description.device_class
276 """Return the minimum value."""
277 if hasattr(self,
"_attr_native_min_value"):
278 return self._attr_native_min_value
280 hasattr(self,
"entity_description")
281 and self.entity_description.native_min_value
is not None
283 return self.entity_description.native_min_value
284 return DEFAULT_MIN_VALUE
289 """Return the minimum value."""
296 """Return the maximum value."""
297 if hasattr(self,
"_attr_native_max_value"):
298 return self._attr_native_max_value
300 hasattr(self,
"entity_description")
301 and self.entity_description.native_max_value
is not None
303 return self.entity_description.native_max_value
304 return DEFAULT_MAX_VALUE
309 """Return the maximum value."""
316 """Return the increment/decrement step."""
317 if hasattr(self,
"_attr_native_step"):
318 return self._attr_native_step
320 hasattr(self,
"entity_description")
321 and self.entity_description.native_step
is not None
323 return self.entity_description.native_step
329 """Return the increment/decrement step."""
333 """Return the increment/decrement step."""
334 if (native_step := self.
native_stepnative_step)
is not None:
337 value_range = abs(max_value - min_value)
339 while value_range <= step:
345 """Return the mode of the entity."""
346 if hasattr(self,
"_attr_mode"):
347 return self._attr_mode
349 hasattr(self,
"entity_description")
350 and self.entity_description.mode
is not None
352 return self.entity_description.mode
353 return NumberMode.AUTO
358 """Return the entity state."""
359 return self.
valuevalue
363 """Return the unit of measurement of the entity, if any."""
364 if hasattr(self,
"_attr_native_unit_of_measurement"):
365 return self._attr_native_unit_of_measurement
366 if hasattr(self,
"entity_description"):
367 return self.entity_description.native_unit_of_measurement
373 """Return the unit of measurement of the entity, after unit conversion."""
381 native_unit_of_measurement
382 in (UnitOfTemperature.CELSIUS, UnitOfTemperature.FAHRENHEIT)
385 return self.
hasshass.config.units.temperature_unit
389 := self.
platformplatform.default_language_platform_translations.get(translation_key)
391 if native_unit_of_measurement
is not None:
393 f
"Number entity {type(self)} from integration '{self.platform.platform_name}' "
394 f
"has a translation key for unit_of_measurement '{unit_of_measurement}', "
395 f
"but also has a native_unit_of_measurement '{native_unit_of_measurement}'"
397 return unit_of_measurement
399 return native_unit_of_measurement
403 """Return the value reported by the number."""
404 return self._attr_native_value
409 """Return the entity value to represent the entity state."""
410 if (native_value := self.
native_valuenative_value)
is None:
416 raise NotImplementedError
425 raise NotImplementedError
430 await self.
hasshass.async_add_executor_job(self.
set_valueset_value, value)
435 method: Callable[[float, int], float],
436 device_class: NumberDeviceClass |
None,
438 """Convert a value in the number's native unit to the configured unit."""
441 if device_class
not in UNIT_CONVERTERS:
446 if native_unit_of_measurement != unit_of_measurement:
448 assert native_unit_of_measurement
449 assert unit_of_measurement
452 prec = len(value_s) - value_s.index(
".") - 1
if "." in value_s
else 0
455 with suppress(ValueError):
456 value_new: float = UNIT_CONVERTERS[device_class].converter_factory(
457 native_unit_of_measurement,
462 return method(value_new, prec)
467 """Convert a value to the number's native unit."""
475 if native_unit_of_measurement != unit_of_measurement:
477 assert native_unit_of_measurement
478 assert unit_of_measurement
480 return UNIT_CONVERTERS[device_class].converter_factory(
482 native_unit_of_measurement,
489 """Run when the entity registry entry has been updated."""
493 (number_options := self.
registry_entryregistry_entry.options.get(DOMAIN))
494 and (custom_unit := number_options.get(CONF_UNIT_OF_MEASUREMENT))
497 in UNIT_CONVERTERS[device_class].VALID_UNITS
498 and custom_unit
in UNIT_CONVERTERS[device_class].VALID_UNITS
506 @dataclasses.dataclass
508 """Object to hold extra stored data."""
510 native_max_value: float |
None
511 native_min_value: float |
None
512 native_step: float |
None
513 native_unit_of_measurement: str |
None
514 native_value: float |
None
517 """Return a dict representation of the number data."""
518 return dataclasses.asdict(self)
521 def from_dict(cls, restored: dict[str, Any]) -> Self |
None:
522 """Initialize a stored number state from a dict."""
525 restored[
"native_max_value"],
526 restored[
"native_min_value"],
527 restored[
"native_step"],
528 restored[
"native_unit_of_measurement"],
529 restored[
"native_value"],
536 """Mixin class for restoring previous number state."""
540 """Return number specific state data to be restored."""
550 """Restore native_*."""
553 return NumberExtraStoredData.from_dict(restored_last_extra_data.as_dict())
None set_value(self, float value)
None async_registry_entry_updated(self)
NumberDeviceClass|None device_class(self)
None async_set_value(self, float value)
str|None native_unit_of_measurement(self)
dict[str, Any] capability_attributes(self)
None set_native_value(self, float value)
float|None native_step(self)
str|None unit_of_measurement(self)
float native_min_value(self)
float|None native_value(self)
float native_max_value(self)
_number_option_unit_of_measurement
float convert_to_native_value(self, float value)
float _calculate_step(self, float min_value, float max_value)
None async_set_native_value(self, float value)
None async_internal_added_to_hass(self)
float _convert_to_state_value(self, float value, Callable[[float, int], float] method, NumberDeviceClass|None device_class)
None __init_subclass__(cls, **Any kwargs)
bool _default_to_device_class_name(self)
NumberExtraStoredData|None async_get_last_number_data(self)
NumberExtraStoredData extra_restore_state_data(self)
str|None device_class(self)
str|None _unit_of_measurement_translation_key(self)
str|None unit_of_measurement(self)
None async_registry_entry_updated(self)
ExtraStoredData|None async_get_last_extra_data(self)
float floor_decimal(float value, float precision=0)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool async_setup(HomeAssistant hass, ConfigType config)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
float ceil_decimal(float value, float precision=0)
None async_set_value(NumberEntity entity, ServiceCall service_call)
HomeAssistant|None async_get_hass_or_none()
str async_suggest_report_issue(HomeAssistant|None hass, *Integration|None integration=None, str|None integration_domain=None, str|None module=None)