Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Tasmota sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime
6 from typing import Any
7 
8 from hatasmota import const as hc, sensor as tasmota_sensor, status_sensor
9 from hatasmota.entity import TasmotaEntity as HATasmotaEntity
10 from hatasmota.models import DiscoveryHashType
11 
12 from homeassistant.components import sensor
14  SensorDeviceClass,
15  SensorEntity,
16  SensorStateClass,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import (
20  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
21  CONCENTRATION_PARTS_PER_BILLION,
22  CONCENTRATION_PARTS_PER_MILLION,
23  LIGHT_LUX,
24  PERCENTAGE,
25  SIGNAL_STRENGTH_DECIBELS,
26  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
27  EntityCategory,
28  UnitOfApparentPower,
29  UnitOfElectricCurrent,
30  UnitOfElectricPotential,
31  UnitOfEnergy,
32  UnitOfFrequency,
33  UnitOfLength,
34  UnitOfMass,
35  UnitOfPower,
36  UnitOfPressure,
37  UnitOfReactivePower,
38  UnitOfSpeed,
39  UnitOfTemperature,
40 )
41 from homeassistant.core import HomeAssistant, callback
42 from homeassistant.helpers.dispatcher import async_dispatcher_connect
43 from homeassistant.helpers.entity_platform import AddEntitiesCallback
44 
45 from .const import DATA_REMOVE_DISCOVER_COMPONENT
46 from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW
47 from .entity import TasmotaAvailability, TasmotaDiscoveryUpdate
48 
49 DEVICE_CLASS = "device_class"
50 STATE_CLASS = "state_class"
51 ICON = "icon"
52 
53 # A Tasmota sensor type may be mapped to either a device class or an icon,
54 # both can only be set if the default device class icon is not appropriate
55 SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
56  hc.SENSOR_AMBIENT: {
57  DEVICE_CLASS: SensorDeviceClass.ILLUMINANCE,
58  STATE_CLASS: SensorStateClass.MEASUREMENT,
59  },
60  hc.SENSOR_BATTERY: {
61  DEVICE_CLASS: SensorDeviceClass.BATTERY,
62  STATE_CLASS: SensorStateClass.MEASUREMENT,
63  },
64  hc.SENSOR_CCT: {
65  ICON: "mdi:temperature-kelvin",
66  STATE_CLASS: SensorStateClass.MEASUREMENT,
67  },
68  hc.SENSOR_CO2: {
69  DEVICE_CLASS: SensorDeviceClass.CO2,
70  STATE_CLASS: SensorStateClass.MEASUREMENT,
71  },
72  hc.SENSOR_COLOR_BLUE: {ICON: "mdi:palette"},
73  hc.SENSOR_COLOR_GREEN: {ICON: "mdi:palette"},
74  hc.SENSOR_COLOR_RED: {ICON: "mdi:palette"},
75  hc.SENSOR_CURRENT: {
76  DEVICE_CLASS: SensorDeviceClass.CURRENT,
77  STATE_CLASS: SensorStateClass.MEASUREMENT,
78  },
79  hc.SENSOR_CURRENT_NEUTRAL: {
80  DEVICE_CLASS: SensorDeviceClass.CURRENT,
81  STATE_CLASS: SensorStateClass.MEASUREMENT,
82  },
83  hc.SENSOR_DEWPOINT: {
84  DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
85  ICON: "mdi:weather-rainy",
86  STATE_CLASS: SensorStateClass.MEASUREMENT,
87  },
88  hc.SENSOR_DISTANCE: {
89  DEVICE_CLASS: SensorDeviceClass.DISTANCE,
90  STATE_CLASS: SensorStateClass.MEASUREMENT,
91  },
92  hc.SENSOR_ECO2: {ICON: "mdi:molecule-co2"},
93  hc.SENSOR_ENERGY: {
94  DEVICE_CLASS: SensorDeviceClass.ENERGY,
95  STATE_CLASS: SensorStateClass.TOTAL,
96  },
97  hc.SENSOR_ENERGY_EXPORT_ACTIVE: {
98  DEVICE_CLASS: SensorDeviceClass.ENERGY,
99  STATE_CLASS: SensorStateClass.TOTAL,
100  },
101  hc.SENSOR_ENERGY_EXPORT_REACTIVE: {STATE_CLASS: SensorStateClass.TOTAL},
102  hc.SENSOR_ENERGY_EXPORT_TARIFF: {
103  DEVICE_CLASS: SensorDeviceClass.ENERGY,
104  STATE_CLASS: SensorStateClass.TOTAL,
105  },
106  hc.SENSOR_ENERGY_IMPORT_ACTIVE: {
107  DEVICE_CLASS: SensorDeviceClass.ENERGY,
108  STATE_CLASS: SensorStateClass.TOTAL,
109  },
110  hc.SENSOR_ENERGY_IMPORT_REACTIVE: {STATE_CLASS: SensorStateClass.TOTAL},
111  hc.SENSOR_ENERGY_IMPORT_TODAY: {
112  DEVICE_CLASS: SensorDeviceClass.ENERGY,
113  STATE_CLASS: SensorStateClass.TOTAL_INCREASING,
114  },
115  hc.SENSOR_ENERGY_IMPORT_TOTAL: {
116  DEVICE_CLASS: SensorDeviceClass.ENERGY,
117  STATE_CLASS: SensorStateClass.TOTAL,
118  },
119  hc.SENSOR_ENERGY_IMPORT_TOTAL_TARIFF: {
120  DEVICE_CLASS: SensorDeviceClass.ENERGY,
121  STATE_CLASS: SensorStateClass.TOTAL,
122  },
123  hc.SENSOR_ENERGY_IMPORT_YESTERDAY: {DEVICE_CLASS: SensorDeviceClass.ENERGY},
124  hc.SENSOR_ENERGY_TOTAL_START_TIME: {ICON: "mdi:progress-clock"},
125  hc.SENSOR_FREQUENCY: {
126  DEVICE_CLASS: SensorDeviceClass.FREQUENCY,
127  STATE_CLASS: SensorStateClass.MEASUREMENT,
128  },
129  hc.SENSOR_HUMIDITY: {
130  DEVICE_CLASS: SensorDeviceClass.HUMIDITY,
131  STATE_CLASS: SensorStateClass.MEASUREMENT,
132  },
133  hc.SENSOR_ILLUMINANCE: {
134  DEVICE_CLASS: SensorDeviceClass.ILLUMINANCE,
135  STATE_CLASS: SensorStateClass.MEASUREMENT,
136  },
137  hc.SENSOR_POWER_ACTIVE: {
138  DEVICE_CLASS: SensorDeviceClass.POWER,
139  STATE_CLASS: SensorStateClass.MEASUREMENT,
140  },
141  hc.SENSOR_POWER_APPARENT: {
142  DEVICE_CLASS: SensorDeviceClass.APPARENT_POWER,
143  STATE_CLASS: SensorStateClass.MEASUREMENT,
144  },
145  hc.SENSOR_STATUS_IP: {ICON: "mdi:ip-network"},
146  hc.SENSOR_STATUS_LINK_COUNT: {ICON: "mdi:counter"},
147  hc.SENSOR_MOISTURE: {DEVICE_CLASS: SensorDeviceClass.MOISTURE},
148  hc.SENSOR_STATUS_MQTT_COUNT: {ICON: "mdi:counter"},
149  hc.SENSOR_PB0_3: {ICON: "mdi:flask"},
150  hc.SENSOR_PB0_5: {ICON: "mdi:flask"},
151  hc.SENSOR_PB10: {ICON: "mdi:flask"},
152  hc.SENSOR_PB1: {ICON: "mdi:flask"},
153  hc.SENSOR_PB2_5: {ICON: "mdi:flask"},
154  hc.SENSOR_PB5: {ICON: "mdi:flask"},
155  hc.SENSOR_PM10: {
156  DEVICE_CLASS: SensorDeviceClass.PM10,
157  STATE_CLASS: SensorStateClass.MEASUREMENT,
158  },
159  hc.SENSOR_PM1: {
160  DEVICE_CLASS: SensorDeviceClass.PM1,
161  STATE_CLASS: SensorStateClass.MEASUREMENT,
162  },
163  hc.SENSOR_PM2_5: {
164  DEVICE_CLASS: SensorDeviceClass.PM25,
165  STATE_CLASS: SensorStateClass.MEASUREMENT,
166  },
167  hc.SENSOR_POWER_FACTOR: {
168  DEVICE_CLASS: SensorDeviceClass.POWER_FACTOR,
169  STATE_CLASS: SensorStateClass.MEASUREMENT,
170  },
171  hc.SENSOR_POWER: {
172  DEVICE_CLASS: SensorDeviceClass.POWER,
173  STATE_CLASS: SensorStateClass.MEASUREMENT,
174  },
175  hc.SENSOR_PRESSURE: {
176  DEVICE_CLASS: SensorDeviceClass.PRESSURE,
177  STATE_CLASS: SensorStateClass.MEASUREMENT,
178  },
179  hc.SENSOR_PRESSURE_AT_SEA_LEVEL: {
180  DEVICE_CLASS: SensorDeviceClass.PRESSURE,
181  STATE_CLASS: SensorStateClass.MEASUREMENT,
182  },
183  hc.SENSOR_PROXIMITY: {ICON: "mdi:ruler"},
184  hc.SENSOR_POWER_REACTIVE: {
185  DEVICE_CLASS: SensorDeviceClass.REACTIVE_POWER,
186  STATE_CLASS: SensorStateClass.MEASUREMENT,
187  },
188  hc.SENSOR_STATUS_LAST_RESTART_TIME: {DEVICE_CLASS: SensorDeviceClass.TIMESTAMP},
189  hc.SENSOR_STATUS_RESTART_REASON: {ICON: "mdi:information-outline"},
190  hc.SENSOR_STATUS_SIGNAL: {
191  DEVICE_CLASS: SensorDeviceClass.SIGNAL_STRENGTH,
192  STATE_CLASS: SensorStateClass.MEASUREMENT,
193  },
194  hc.SENSOR_STATUS_RSSI: {
195  ICON: "mdi:access-point",
196  STATE_CLASS: SensorStateClass.MEASUREMENT,
197  },
198  hc.SENSOR_STATUS_SSID: {ICON: "mdi:access-point-network"},
199  hc.SENSOR_TEMPERATURE: {
200  DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
201  STATE_CLASS: SensorStateClass.MEASUREMENT,
202  },
203  hc.SENSOR_TVOC: {ICON: "mdi:air-filter"},
204  hc.SENSOR_VOLTAGE: {
205  DEVICE_CLASS: SensorDeviceClass.VOLTAGE,
206  STATE_CLASS: SensorStateClass.MEASUREMENT,
207  },
208  hc.SENSOR_WEIGHT: {
209  DEVICE_CLASS: SensorDeviceClass.WEIGHT,
210  STATE_CLASS: SensorStateClass.MEASUREMENT,
211  },
212 }
213 
214 SENSOR_UNIT_MAP = {
215  hc.CONCENTRATION_MICROGRAMS_PER_CUBIC_METER: (
216  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
217  ),
218  hc.CONCENTRATION_PARTS_PER_BILLION: CONCENTRATION_PARTS_PER_BILLION,
219  hc.CONCENTRATION_PARTS_PER_MILLION: CONCENTRATION_PARTS_PER_MILLION,
220  hc.ELECTRICAL_CURRENT_AMPERE: UnitOfElectricCurrent.AMPERE,
221  hc.ELECTRICAL_VOLT_AMPERE: UnitOfApparentPower.VOLT_AMPERE,
222  hc.ENERGY_KILO_WATT_HOUR: UnitOfEnergy.KILO_WATT_HOUR,
223  hc.FREQUENCY_HERTZ: UnitOfFrequency.HERTZ,
224  hc.LENGTH_CENTIMETERS: UnitOfLength.CENTIMETERS,
225  hc.LIGHT_LUX: LIGHT_LUX,
226  hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS,
227  hc.PERCENTAGE: PERCENTAGE,
228  hc.POWER_WATT: UnitOfPower.WATT,
229  hc.PRESSURE_HPA: UnitOfPressure.HPA,
230  hc.REACTIVE_POWER: UnitOfReactivePower.VOLT_AMPERE_REACTIVE,
231  hc.SIGNAL_STRENGTH_DECIBELS: SIGNAL_STRENGTH_DECIBELS,
232  hc.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
233  hc.SPEED_KILOMETERS_PER_HOUR: UnitOfSpeed.KILOMETERS_PER_HOUR,
234  hc.SPEED_METERS_PER_SECOND: UnitOfSpeed.METERS_PER_SECOND,
235  hc.SPEED_MILES_PER_HOUR: UnitOfSpeed.MILES_PER_HOUR,
236  hc.TEMP_CELSIUS: UnitOfTemperature.CELSIUS,
237  hc.TEMP_FAHRENHEIT: UnitOfTemperature.FAHRENHEIT,
238  hc.TEMP_KELVIN: UnitOfTemperature.KELVIN,
239  hc.VOLT: UnitOfElectricPotential.VOLT,
240 }
241 
242 
244  hass: HomeAssistant,
245  config_entry: ConfigEntry,
246  async_add_entities: AddEntitiesCallback,
247 ) -> None:
248  """Set up Tasmota sensor dynamically through discovery."""
249 
250  @callback
251  def async_discover(
252  tasmota_entity: HATasmotaEntity, discovery_hash: DiscoveryHashType
253  ) -> None:
254  """Discover and add a Tasmota sensor."""
256  [
258  tasmota_entity=tasmota_entity, discovery_hash=discovery_hash
259  )
260  ]
261  )
262 
263  hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(sensor.DOMAIN)] = (
265  hass,
266  TASMOTA_DISCOVERY_ENTITY_NEW.format(sensor.DOMAIN),
267  async_discover,
268  )
269  )
270 
271 
273  """Representation of a Tasmota sensor."""
274 
275  _tasmota_entity: tasmota_sensor.TasmotaSensor
276 
277  def __init__(self, **kwds: Any) -> None:
278  """Initialize the Tasmota sensor."""
279  self._state_state: Any | None = None
280  self._state_timestamp_state_timestamp: datetime | None = None
281 
282  super().__init__(
283  **kwds,
284  )
285 
286  class_or_icon = SENSOR_DEVICE_CLASS_ICON_MAP.get(
287  self._tasmota_entity_tasmota_entity.quantity, {}
288  )
289  self._attr_device_class_attr_device_class = class_or_icon.get(DEVICE_CLASS)
290  self._attr_state_class_attr_state_class = class_or_icon.get(STATE_CLASS)
291  if self._tasmota_entity_tasmota_entity.quantity in status_sensor.SENSORS:
292  self._attr_entity_category_attr_entity_category = EntityCategory.DIAGNOSTIC
293  # Hide fast changing status sensors
294  if self._tasmota_entity_tasmota_entity.quantity in (
295  hc.SENSOR_STATUS_IP,
296  hc.SENSOR_STATUS_RSSI,
297  hc.SENSOR_STATUS_SIGNAL,
298  hc.SENSOR_STATUS_VERSION,
299  ):
300  self._attr_entity_registry_enabled_default_attr_entity_registry_enabled_default = False
301  self._attr_icon_attr_icon = class_or_icon.get(ICON)
302  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = SENSOR_UNIT_MAP.get(
303  self._tasmota_entity_tasmota_entity.unit, self._tasmota_entity_tasmota_entity.unit
304  )
305  if (
306  self._attr_device_class_attr_device_class is None
307  and self._attr_state_class_attr_state_class is None
308  and self._attr_native_unit_of_measurement_attr_native_unit_of_measurement is None
309  ):
310  # If the sensor has a numeric value, but we couldn't detect what it is,
311  # set state class to measurement.
312  if self._tasmota_entity_tasmota_entity.discovered_as_numeric:
313  self._attr_state_class_attr_state_class = SensorStateClass.MEASUREMENT
314 
315  async def async_added_to_hass(self) -> None:
316  """Subscribe to MQTT events."""
317  self._tasmota_entity_tasmota_entity.set_on_state_callback(self.sensor_state_updatedsensor_state_updated)
318  await super().async_added_to_hass()
319 
320  @callback
321  def sensor_state_updated(self, state: Any, **kwargs: Any) -> None:
322  """Handle state updates."""
323  if self.device_classdevice_classdevice_classdevice_class == SensorDeviceClass.TIMESTAMP:
324  self._state_timestamp_state_timestamp = state
325  else:
326  self._state_state = state
327  self.async_write_ha_stateasync_write_ha_state()
328 
329  @property
330  def native_value(self) -> datetime | str | None:
331  """Return the state of the entity."""
332  if self._state_timestamp_state_timestamp and self.device_classdevice_classdevice_classdevice_class == SensorDeviceClass.TIMESTAMP:
333  return self._state_timestamp_state_timestamp
334  return self._state_state
SensorDeviceClass|None device_class(self)
Definition: __init__.py:313
None sensor_state_updated(self, Any state, **Any kwargs)
Definition: sensor.py:321
None async_discover(DiscoveryInfo discovery_info)
Definition: sensor.py:217
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:247
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103