Home Assistant Unofficial Reference 2024.12.1
number.py
Go to the documentation of this file.
1 """Configure number in a device through MQTT topic."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 import logging
7 
8 import voluptuous as vol
9 
10 from homeassistant.components import number
12  DEFAULT_MAX_VALUE,
13  DEFAULT_MIN_VALUE,
14  DEFAULT_STEP,
15  NumberDeviceClass,
16  NumberMode,
17  RestoreNumber,
18 )
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.const import (
21  CONF_DEVICE_CLASS,
22  CONF_MODE,
23  CONF_NAME,
24  CONF_OPTIMISTIC,
25  CONF_UNIT_OF_MEASUREMENT,
26  CONF_VALUE_TEMPLATE,
27 )
28 from homeassistant.core import HomeAssistant, callback
29 from homeassistant.helpers import config_validation as cv
30 from homeassistant.helpers.entity_platform import AddEntitiesCallback
31 from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
32 from homeassistant.helpers.typing import ConfigType, VolSchemaType
33 
34 from . import subscription
35 from .config import MQTT_RW_SCHEMA
36 from .const import (
37  CONF_COMMAND_TEMPLATE,
38  CONF_COMMAND_TOPIC,
39  CONF_PAYLOAD_RESET,
40  CONF_STATE_TOPIC,
41 )
42 from .entity import MqttEntity, async_setup_entity_entry_helper
43 from .models import (
44  MqttCommandTemplate,
45  MqttValueTemplate,
46  PublishPayloadType,
47  ReceiveMessage,
48 )
49 from .schemas import MQTT_ENTITY_COMMON_SCHEMA
50 
51 _LOGGER = logging.getLogger(__name__)
52 
53 PARALLEL_UPDATES = 0
54 
55 CONF_MIN = "min"
56 CONF_MAX = "max"
57 CONF_STEP = "step"
58 
59 DEFAULT_NAME = "MQTT Number"
60 DEFAULT_PAYLOAD_RESET = "None"
61 
62 MQTT_NUMBER_ATTRIBUTES_BLOCKED = frozenset(
63  {
64  number.ATTR_MAX,
65  number.ATTR_MIN,
66  number.ATTR_STEP,
67  }
68 )
69 
70 
71 def validate_config(config: ConfigType) -> ConfigType:
72  """Validate that the configuration is valid, throws if it isn't."""
73  if config[CONF_MIN] >= config[CONF_MAX]:
74  raise vol.Invalid(f"'{CONF_MAX}' must be > '{CONF_MIN}'")
75 
76  return config
77 
78 
79 _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
80  {
81  vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
82  vol.Optional(CONF_DEVICE_CLASS): vol.Any(
83  vol.All(vol.Lower, vol.Coerce(NumberDeviceClass)), None
84  ),
85  vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): vol.Coerce(float),
86  vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): vol.Coerce(float),
87  vol.Optional(CONF_MODE, default=NumberMode.AUTO): vol.Coerce(NumberMode),
88  vol.Optional(CONF_NAME): vol.Any(cv.string, None),
89  vol.Optional(CONF_PAYLOAD_RESET, default=DEFAULT_PAYLOAD_RESET): cv.string,
90  vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All(
91  vol.Coerce(float), vol.Range(min=1e-3)
92  ),
93  vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.Any(cv.string, None),
94  vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
95  },
96 ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
97 
98 PLATFORM_SCHEMA_MODERN = vol.All(
99  _PLATFORM_SCHEMA_BASE,
100  validate_config,
101 )
102 
103 DISCOVERY_SCHEMA = vol.All(
104  _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
105  validate_config,
106 )
107 
108 
110  hass: HomeAssistant,
111  config_entry: ConfigEntry,
112  async_add_entities: AddEntitiesCallback,
113 ) -> None:
114  """Set up MQTT number through YAML and through MQTT discovery."""
116  hass,
117  config_entry,
118  MqttNumber,
119  number.DOMAIN,
120  async_add_entities,
121  DISCOVERY_SCHEMA,
122  PLATFORM_SCHEMA_MODERN,
123  )
124 
125 
127  """representation of an MQTT number."""
128 
129  _default_name = DEFAULT_NAME
130  _entity_id_format = number.ENTITY_ID_FORMAT
131  _attributes_extra_blocked = MQTT_NUMBER_ATTRIBUTES_BLOCKED
132 
133  _optimistic: bool
134  _command_template: Callable[[PublishPayloadType], PublishPayloadType]
135  _value_template: Callable[[ReceivePayloadType], ReceivePayloadType]
136 
137  @staticmethod
138  def config_schema() -> VolSchemaType:
139  """Return the config schema."""
140  return DISCOVERY_SCHEMA
141 
142  def _setup_from_config(self, config: ConfigType) -> None:
143  """(Re)Setup the entity."""
144  self._config_config_config = config
145  self._attr_assumed_state_attr_assumed_state = config[CONF_OPTIMISTIC]
146 
147  self._command_template_command_template = MqttCommandTemplate(
148  config.get(CONF_COMMAND_TEMPLATE), entity=self
149  ).async_render
150  self._value_template_value_template = MqttValueTemplate(
151  config.get(CONF_VALUE_TEMPLATE),
152  entity=self,
153  ).async_render_with_possible_json_value
154 
155  self._attr_device_class_attr_device_class = config.get(CONF_DEVICE_CLASS)
156  self._attr_mode_attr_mode = config[CONF_MODE]
157  self._attr_native_max_value_attr_native_max_value = config[CONF_MAX]
158  self._attr_native_min_value_attr_native_min_value = config[CONF_MIN]
159  self._attr_native_step_attr_native_step = config[CONF_STEP]
160  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
161 
162  @callback
163  def _message_received(self, msg: ReceiveMessage) -> None:
164  """Handle new MQTT messages."""
165  num_value: int | float | None
166  payload = str(self._value_template_value_template(msg.payload))
167  if not payload.strip():
168  _LOGGER.debug("Ignoring empty state update from '%s'", msg.topic)
169  return
170  try:
171  if payload == self._config_config_config[CONF_PAYLOAD_RESET]:
172  num_value = None
173  elif payload.isnumeric():
174  num_value = int(payload)
175  else:
176  num_value = float(payload)
177  except ValueError:
178  _LOGGER.warning("Payload '%s' is not a Number", msg.payload)
179  return
180 
181  if num_value is not None and (
182  num_value < self.min_valuemin_value or num_value > self.max_valuemax_value
183  ):
184  _LOGGER.error(
185  "Invalid value for %s: %s (range %s - %s)",
186  self.entity_identity_id,
187  num_value,
188  self.min_valuemin_value,
189  self.max_valuemax_value,
190  )
191  return
192 
193  self._attr_native_value_attr_native_value = num_value
194 
195  @callback
196  def _prepare_subscribe_topics(self) -> None:
197  """(Re)Subscribe to topics."""
198  if not self.add_subscriptionadd_subscription(
199  CONF_STATE_TOPIC, self._message_received_message_received, {"_attr_native_value"}
200  ):
201  # Force into optimistic mode.
202  self._attr_assumed_state_attr_assumed_state = True
203  return
204 
205  async def _subscribe_topics(self) -> None:
206  """(Re)Subscribe to topics."""
207  subscription.async_subscribe_topics_internal(self.hasshasshass, self._sub_state_sub_state)
208 
209  if self._attr_assumed_state_attr_assumed_state and (
210  last_number_data := await self.async_get_last_number_dataasync_get_last_number_data()
211  ):
212  self._attr_native_value_attr_native_value = last_number_data.native_value
213 
214  async def async_set_native_value(self, value: float) -> None:
215  """Update the current value."""
216  current_number = value
217 
218  if value.is_integer():
219  current_number = int(value)
220  payload = self._command_template_command_template(current_number)
221 
222  if self._attr_assumed_state_attr_assumed_state:
223  self._attr_native_value_attr_native_value = current_number
224  self.async_write_ha_stateasync_write_ha_state()
225  await self.async_publish_with_configasync_publish_with_config(self._config_config_config[CONF_COMMAND_TOPIC], payload)
None async_publish_with_config(self, str topic, PublishPayloadType payload)
Definition: entity.py:1377
bool add_subscription(self, str state_topic_config_key, Callable[[ReceiveMessage], None] msg_callback, set[str]|None tracked_attributes, bool disable_encoding=False)
Definition: entity.py:1484
None _message_received(self, ReceiveMessage msg)
Definition: number.py:163
None _setup_from_config(self, ConfigType config)
Definition: number.py:142
None async_set_native_value(self, float value)
Definition: number.py:214
NumberExtraStoredData|None async_get_last_number_data(self)
Definition: __init__.py:549
None async_setup_entity_entry_helper(HomeAssistant hass, ConfigEntry entry, type[MqttEntity]|None entity_class, str domain, AddEntitiesCallback async_add_entities, VolSchemaType discovery_schema, VolSchemaType platform_schema_modern, dict[str, type[MqttEntity]]|None schema_class_mapping=None)
Definition: entity.py:245
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: number.py:113
ConfigType validate_config(ConfigType config)
Definition: number.py:71