"""Support for numbers which integrates with other components.""" from typing import TYPE_CHECKING, Any, override import voluptuous as vol from homeassistant.components.number import ( DEFAULT_MAX_VALUE, DEFAULT_MIN_VALUE, DEFAULT_STEP, DEVICE_CLASSES_SCHEMA, DOMAIN as NUMBER_DOMAIN, ENTITY_ID_FORMAT, NumberExtraStoredData, RestoreNumber, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_NAME, CONF_STATE, CONF_UNIT_OF_MEASUREMENT, ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import ( AddConfigEntryEntitiesCallback, AddEntitiesCallback, ) from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import TriggerUpdateCoordinator, validators as template_validators from .const import CONF_MAX, CONF_MIN, CONF_STEP, DOMAIN from .entity import AbstractTemplateEntity from .helpers import ( async_setup_template_entry, async_setup_template_platform, async_setup_template_preview, ) from .schemas import ( TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA, TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA, make_template_entity_common_modern_schema, ) from .template_entity import TemplateEntity from .trigger_entity import TriggerEntity CONF_SET_VALUE = "set_value" DEFAULT_NAME = "Template Number" DEFAULT_OPTIMISTIC = False SCRIPT_FIELDS = (CONF_SET_VALUE,) NUMBER_COMMON_SCHEMA = vol.Schema( { vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): cv.template, vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): cv.template, vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA, vol.Optional(CONF_STATE): cv.template, vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.template, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, } ) NUMBER_YAML_SCHEMA = NUMBER_COMMON_SCHEMA.extend( TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA ).extend(make_template_entity_common_modern_schema(NUMBER_DOMAIN, DEFAULT_NAME).schema) NUMBER_CONFIG_ENTRY_SCHEMA = NUMBER_COMMON_SCHEMA.extend( TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema ) async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the template number.""" await async_setup_template_platform( hass, NUMBER_DOMAIN, config, StateNumberEntity, TriggerNumberEntity, async_add_entities, discovery_info, script_options=SCRIPT_FIELDS, ) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Initialize config entry.""" await async_setup_template_entry( hass, config_entry, async_add_entities, StateNumberEntity, NUMBER_CONFIG_ENTRY_SCHEMA, script_options=SCRIPT_FIELDS, ) @callback def async_create_preview_number( hass: HomeAssistant, name: str, config: dict[str, Any] ) -> StateNumberEntity: """Create a preview number.""" return async_setup_template_preview( hass, name, config, StateNumberEntity, NUMBER_CONFIG_ENTRY_SCHEMA ) class AbstractTemplateNumber(AbstractTemplateEntity, RestoreNumber): """Representation of a template number features.""" _entity_id_format = ENTITY_ID_FORMAT _optimistic_entity = True _state_option = CONF_STATE _restore_state_extra_data = NumberExtraStoredData _restore_state_properties = ("_attr_native_value",) # The super init is not called because TemplateEntity # and TriggerEntity will call # AbstractTemplateEntity.__init__. This ensures that # the __init__ on AbstractTemplateEntity is not # called twice. def __init__(self, name: str, config: dict[str, Any]) -> None: # pylint: disable=super-init-not-called """Initialize the features.""" self._attr_device_class = config.get(CONF_DEVICE_CLASS) self._attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT) self._attr_native_step = DEFAULT_STEP self._attr_native_min_value = DEFAULT_MIN_VALUE self._attr_native_max_value = DEFAULT_MAX_VALUE self.setup_state_template( "_attr_native_value", template_validators.number(self, CONF_STATE), ) for option, attribute in ( (CONF_STEP, "_attr_native_step"), (CONF_MIN, "_attr_native_min_value"), (CONF_MAX, "_attr_native_max_value"), ): self.setup_template( option, attribute, template_validators.number(self, option) ) self.add_script(CONF_SET_VALUE, config[CONF_SET_VALUE], name, DOMAIN) @override async def async_set_native_value(self, value: float) -> None: """Set value of the number.""" if self._attr_assumed_state: self._attr_native_value = value self.async_write_ha_state() if set_value := self._action_scripts.get(CONF_SET_VALUE): await self.async_run_script( set_value, run_variables={"value": value}, context=self._context, ) @override def restore_extra_data(self, extra_data: NumberExtraStoredData) -> None: """Restore the extra data.""" # Do not restore native_unit_of_measurement, this is always pulled from the # number configuration. self._attr_native_max_value = ( DEFAULT_MAX_VALUE if extra_data.native_max_value is None else extra_data.native_max_value ) self._attr_native_min_value = ( DEFAULT_MIN_VALUE if extra_data.native_min_value is None else extra_data.native_min_value ) self._attr_native_step = ( DEFAULT_STEP if extra_data.native_step is None else extra_data.native_step ) self._attr_native_value = extra_data.native_value class StateNumberEntity(TemplateEntity, AbstractTemplateNumber): """Representation of a template number.""" _attr_should_poll = False def __init__( self, hass: HomeAssistant, config: ConfigType, unique_id: str | None, ) -> None: """Initialize the number.""" TemplateEntity.__init__(self, hass, config, unique_id) name = self._attr_name if TYPE_CHECKING: assert name is not None AbstractTemplateNumber.__init__(self, name, config) class TriggerNumberEntity(TriggerEntity, AbstractTemplateNumber): """Number entity based on trigger data.""" domain = NUMBER_DOMAIN def __init__( self, hass: HomeAssistant, coordinator: TriggerUpdateCoordinator, config: dict, ) -> None: """Initialize the entity.""" TriggerEntity.__init__(self, hass, coordinator, config) name = self._rendered.get(CONF_NAME, DEFAULT_NAME) AbstractTemplateNumber.__init__(self, name, config)