Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Sensors for the weatherflow integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass, field
7 from datetime import datetime
8 from enum import Enum
9 
10 from pyweatherflowudp.const import EVENT_RAPID_WIND
11 from pyweatherflowudp.device import (
12  EVENT_OBSERVATION,
13  EVENT_STATUS_UPDATE,
14  WeatherFlowDevice,
15 )
16 
18  SensorDeviceClass,
19  SensorEntity,
20  SensorEntityDescription,
21  SensorStateClass,
22 )
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.const import (
25  DEGREE,
26  LIGHT_LUX,
27  PERCENTAGE,
28  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
29  UV_INDEX,
30  EntityCategory,
31  UnitOfElectricPotential,
32  UnitOfIrradiance,
33  UnitOfLength,
34  UnitOfPrecipitationDepth,
35  UnitOfPressure,
36  UnitOfSpeed,
37  UnitOfTemperature,
38  UnitOfVolumetricFlux,
39 )
40 from homeassistant.core import HomeAssistant, callback
41 from homeassistant.helpers.device_registry import DeviceInfo
42 from homeassistant.helpers.dispatcher import async_dispatcher_connect
43 from homeassistant.helpers.entity_platform import AddEntitiesCallback
44 from homeassistant.helpers.typing import StateType
45 from homeassistant.util.unit_system import METRIC_SYSTEM
46 
47 from .const import DOMAIN, LOGGER, format_dispatch_call
48 
49 
50 def precipitation_raw_conversion_fn(raw_data: Enum):
51  """Parse parse precipitation type."""
52  if raw_data.name.lower() == "unknown":
53  return None
54  return raw_data.name.lower()
55 
56 
57 @dataclass(frozen=True, kw_only=True)
59  """Describes WeatherFlow sensor entity."""
60 
61  raw_data_conv_fn: Callable[[WeatherFlowDevice], datetime | StateType]
62 
63  event_subscriptions: list[str] = field(default_factory=lambda: [EVENT_OBSERVATION])
64  imperial_suggested_unit: str | None = None
65 
66  def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType:
67  """Return the parsed sensor value."""
68  if (raw_sensor_data := getattr(device, self.keykey)) is None:
69  return None
70  return self.raw_data_conv_fnraw_data_conv_fn(raw_sensor_data)
71 
72 
73 SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = (
75  key="air_density",
76  translation_key="air_density",
77  native_unit_of_measurement="kg/m³",
78  state_class=SensorStateClass.MEASUREMENT,
79  suggested_display_precision=5,
80  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
81  ),
83  key="air_temperature",
84  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
85  device_class=SensorDeviceClass.TEMPERATURE,
86  state_class=SensorStateClass.MEASUREMENT,
87  suggested_display_precision=1,
88  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
89  ),
91  key="dew_point_temperature",
92  translation_key="dew_point",
93  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
94  device_class=SensorDeviceClass.TEMPERATURE,
95  state_class=SensorStateClass.MEASUREMENT,
96  suggested_display_precision=1,
97  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
98  ),
100  key="feels_like_temperature",
101  translation_key="feels_like",
102  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
103  device_class=SensorDeviceClass.TEMPERATURE,
104  state_class=SensorStateClass.MEASUREMENT,
105  suggested_display_precision=1,
106  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
107  ),
109  key="wet_bulb_temperature",
110  translation_key="wet_bulb_temperature",
111  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
112  device_class=SensorDeviceClass.TEMPERATURE,
113  state_class=SensorStateClass.MEASUREMENT,
114  suggested_display_precision=1,
115  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
116  ),
118  key="battery",
119  translation_key="battery_voltage",
120  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
121  device_class=SensorDeviceClass.VOLTAGE,
122  entity_category=EntityCategory.DIAGNOSTIC,
123  state_class=SensorStateClass.MEASUREMENT,
124  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
125  ),
127  key="illuminance",
128  native_unit_of_measurement=LIGHT_LUX,
129  device_class=SensorDeviceClass.ILLUMINANCE,
130  state_class=SensorStateClass.MEASUREMENT,
131  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
132  ),
134  key="lightning_strike_average_distance",
135  state_class=SensorStateClass.MEASUREMENT,
136  device_class=SensorDeviceClass.DISTANCE,
137  native_unit_of_measurement=UnitOfLength.KILOMETERS,
138  translation_key="lightning_average_distance",
139  suggested_display_precision=2,
140  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
141  ),
143  key="lightning_strike_count",
144  translation_key="lightning_count",
145  state_class=SensorStateClass.TOTAL,
146  raw_data_conv_fn=lambda raw_data: raw_data,
147  ),
149  key="precipitation_type",
150  translation_key="precipitation_type",
151  device_class=SensorDeviceClass.ENUM,
152  options=["none", "rain", "hail", "rain_hail", "unknown"],
153  raw_data_conv_fn=precipitation_raw_conversion_fn,
154  ),
156  key="rain_accumulation_previous_minute",
157  native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
158  state_class=SensorStateClass.TOTAL,
159  device_class=SensorDeviceClass.PRECIPITATION,
160  imperial_suggested_unit=UnitOfPrecipitationDepth.INCHES,
161  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
162  ),
164  key="rain_rate",
165  state_class=SensorStateClass.MEASUREMENT,
166  device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
167  native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
168  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
169  ),
171  key="relative_humidity",
172  native_unit_of_measurement=PERCENTAGE,
173  device_class=SensorDeviceClass.HUMIDITY,
174  state_class=SensorStateClass.MEASUREMENT,
175  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
176  ),
178  key="rssi",
179  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
180  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
181  entity_category=EntityCategory.DIAGNOSTIC,
182  state_class=SensorStateClass.MEASUREMENT,
183  entity_registry_enabled_default=False,
184  event_subscriptions=[EVENT_STATUS_UPDATE],
185  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
186  ),
188  key="station_pressure",
189  translation_key="station_pressure",
190  native_unit_of_measurement=UnitOfPressure.MBAR,
191  device_class=SensorDeviceClass.PRESSURE,
192  state_class=SensorStateClass.MEASUREMENT,
193  suggested_display_precision=5,
194  imperial_suggested_unit=UnitOfPressure.INHG,
195  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
196  ),
198  key="solar_radiation",
199  native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
200  device_class=SensorDeviceClass.IRRADIANCE,
201  state_class=SensorStateClass.MEASUREMENT,
202  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
203  ),
205  key="up_since",
206  translation_key="uptime",
207  device_class=SensorDeviceClass.TIMESTAMP,
208  entity_category=EntityCategory.DIAGNOSTIC,
209  entity_registry_enabled_default=False,
210  event_subscriptions=[EVENT_STATUS_UPDATE],
211  raw_data_conv_fn=lambda raw_data: raw_data,
212  ),
214  key="uv",
215  translation_key="uv_index",
216  native_unit_of_measurement=UV_INDEX,
217  state_class=SensorStateClass.MEASUREMENT,
218  raw_data_conv_fn=lambda raw_data: raw_data,
219  ),
221  key="vapor_pressure",
222  translation_key="vapor_pressure",
223  native_unit_of_measurement=UnitOfPressure.MBAR,
224  device_class=SensorDeviceClass.PRESSURE,
225  state_class=SensorStateClass.MEASUREMENT,
226  imperial_suggested_unit=UnitOfPressure.INHG,
227  suggested_display_precision=5,
228  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
229  ),
230  ## Wind Sensors
232  key="wind_gust",
233  translation_key="wind_gust",
234  device_class=SensorDeviceClass.WIND_SPEED,
235  native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
236  state_class=SensorStateClass.MEASUREMENT,
237  suggested_display_precision=2,
238  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
239  ),
241  key="wind_lull",
242  translation_key="wind_lull",
243  device_class=SensorDeviceClass.WIND_SPEED,
244  native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
245  state_class=SensorStateClass.MEASUREMENT,
246  suggested_display_precision=2,
247  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
248  ),
250  key="wind_speed",
251  device_class=SensorDeviceClass.WIND_SPEED,
252  event_subscriptions=[EVENT_RAPID_WIND, EVENT_OBSERVATION],
253  native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
254  state_class=SensorStateClass.MEASUREMENT,
255  suggested_display_precision=2,
256  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
257  ),
259  key="wind_average",
260  translation_key="wind_speed_average",
261  device_class=SensorDeviceClass.WIND_SPEED,
262  native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
263  state_class=SensorStateClass.MEASUREMENT,
264  suggested_display_precision=2,
265  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
266  ),
268  key="wind_direction",
269  translation_key="wind_direction",
270  native_unit_of_measurement=DEGREE,
271  state_class=SensorStateClass.MEASUREMENT,
272  event_subscriptions=[EVENT_RAPID_WIND, EVENT_OBSERVATION],
273  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
274  ),
276  key="wind_direction_average",
277  translation_key="wind_direction_average",
278  native_unit_of_measurement=DEGREE,
279  state_class=SensorStateClass.MEASUREMENT,
280  raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
281  ),
282 )
283 
284 
286  hass: HomeAssistant,
287  config_entry: ConfigEntry,
288  async_add_entities: AddEntitiesCallback,
289 ) -> None:
290  """Set up WeatherFlow sensors using config entry."""
291 
292  @callback
293  def async_add_sensor(device: WeatherFlowDevice) -> None:
294  """Add WeatherFlow sensor."""
295  LOGGER.debug("Adding sensors for %s", device)
296 
297  sensors: list[WeatherFlowSensorEntity] = [
299  device=device,
300  description=description,
301  is_metric=(hass.config.units == METRIC_SYSTEM),
302  )
303  for description in SENSORS
304  if hasattr(device, description.key)
305  ]
306 
307  async_add_entities(sensors)
308 
309  config_entry.async_on_unload(
311  hass,
312  format_dispatch_call(config_entry),
313  async_add_sensor,
314  )
315  )
316 
317 
319  """Defines a WeatherFlow sensor entity."""
320 
321  entity_description: WeatherFlowSensorEntityDescription
322  _attr_should_poll = False
323  _attr_has_entity_name = True
324 
325  def __init__(
326  self,
327  device: WeatherFlowDevice,
328  description: WeatherFlowSensorEntityDescription,
329  is_metric: bool = True,
330  ) -> None:
331  """Initialize a WeatherFlow sensor entity."""
332  self.devicedevice = device
333  self.entity_descriptionentity_description = description
334 
335  self._attr_device_info_attr_device_info = DeviceInfo(
336  identifiers={(DOMAIN, device.serial_number)},
337  manufacturer="WeatherFlow",
338  model=device.model,
339  name=device.serial_number,
340  sw_version=device.firmware_revision,
341  )
342 
343  self._attr_unique_id_attr_unique_id = f"{device.serial_number}_{description.key}"
344 
345  # In the case of the USA - we may want to have a suggested US unit which differs from the internal suggested units
346  if description.imperial_suggested_unit is not None and not is_metric:
347  self._attr_suggested_unit_of_measurement_attr_suggested_unit_of_measurement = (
348  description.imperial_suggested_unit
349  )
350 
351  @property
352  def last_reset(self) -> datetime | None:
353  """Return the time when the sensor was last reset, if any."""
354  if self.entity_descriptionentity_description.state_class == SensorStateClass.TOTAL:
355  return self.devicedevice.last_report
356  return None
357 
358  def _async_update_state(self) -> None:
359  """Update entity state."""
360  value = self.entity_descriptionentity_description.get_native_value(self.devicedevice)
361  self._attr_available_attr_available = value is not None
362  self._attr_native_value_attr_native_value = value
363  self.async_write_ha_stateasync_write_ha_state()
364 
365  async def async_added_to_hass(self) -> None:
366  """Subscribe to events."""
367  self._async_update_state_async_update_state()
368  for event in self.entity_descriptionentity_description.event_subscriptions:
369  self.async_on_removeasync_on_remove(
370  self.devicedevice.on(event, lambda _: self._async_update_state_async_update_state())
371  )
datetime|StateType get_native_value(self, WeatherFlowDevice device)
Definition: sensor.py:66
None __init__(self, WeatherFlowDevice device, WeatherFlowSensorEntityDescription description, bool is_metric=True)
Definition: sensor.py:330
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
int|float|None get_native_value(data, IstaConsumptionType consumption_type, IstaValueType|None value_type=None)
Definition: util.py:93
str format_dispatch_call(ConfigEntry config_entry)
Definition: const.py:11
def precipitation_raw_conversion_fn(Enum raw_data)
Definition: sensor.py:50
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:289
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103