Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for the AirNow sensor service."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any
8 
9 from dateutil import parser
10 
12  SensorDeviceClass,
13  SensorEntity,
14  SensorEntityDescription,
15  SensorStateClass,
16 )
17 from homeassistant.const import (
18  ATTR_TIME,
19  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
20  CONCENTRATION_PARTS_PER_MILLION,
21 )
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 from homeassistant.helpers.typing import StateType
26 from homeassistant.helpers.update_coordinator import CoordinatorEntity
27 
28 from . import AirNowConfigEntry, AirNowDataUpdateCoordinator
29 from .const import (
30  ATTR_API_AQI,
31  ATTR_API_AQI_DESCRIPTION,
32  ATTR_API_AQI_LEVEL,
33  ATTR_API_O3,
34  ATTR_API_PM10,
35  ATTR_API_PM25,
36  ATTR_API_REPORT_DATE,
37  ATTR_API_REPORT_HOUR,
38  ATTR_API_REPORT_TZ,
39  ATTR_API_STATION,
40  ATTR_API_STATION_LATITUDE,
41  ATTR_API_STATION_LONGITUDE,
42  DEFAULT_NAME,
43  DOMAIN,
44  US_TZ_OFFSETS,
45 )
46 
47 ATTRIBUTION = "Data provided by AirNow"
48 
49 PARALLEL_UPDATES = 1
50 
51 ATTR_DESCR = "description"
52 ATTR_LEVEL = "level"
53 ATTR_STATION = "reporting_station"
54 
55 
56 @dataclass(frozen=True, kw_only=True)
58  """Describes Airnow sensor entity."""
59 
60  value_fn: Callable[[Any], StateType]
61  extra_state_attributes_fn: Callable[[Any], dict[str, str]] | None
62 
63 
64 def station_extra_attrs(data: dict[str, Any]) -> dict[str, Any]:
65  """Process extra attributes for station location (if available)."""
66  if ATTR_API_STATION in data:
67  return {
68  "lat": data.get(ATTR_API_STATION_LATITUDE),
69  "long": data.get(ATTR_API_STATION_LONGITUDE),
70  }
71  return {}
72 
73 
74 def aqi_extra_attrs(data: dict[str, Any]) -> dict[str, Any]:
75  """Process extra attributes for main AQI sensor."""
76  return {
77  ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION],
78  ATTR_LEVEL: data[ATTR_API_AQI_LEVEL],
79  ATTR_TIME: parser.parse(
80  f"{data[ATTR_API_REPORT_DATE]} {data[ATTR_API_REPORT_HOUR]}:00 {data[ATTR_API_REPORT_TZ]}",
81  tzinfos=US_TZ_OFFSETS,
82  ).isoformat(),
83  }
84 
85 
86 SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
88  key=ATTR_API_AQI,
89  translation_key="aqi",
90  state_class=SensorStateClass.MEASUREMENT,
91  device_class=SensorDeviceClass.AQI,
92  value_fn=lambda data: data.get(ATTR_API_AQI),
93  extra_state_attributes_fn=aqi_extra_attrs,
94  ),
96  key=ATTR_API_PM10,
97  translation_key="pm10",
98  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
99  state_class=SensorStateClass.MEASUREMENT,
100  device_class=SensorDeviceClass.PM10,
101  value_fn=lambda data: data.get(ATTR_API_PM10),
102  extra_state_attributes_fn=None,
103  ),
105  key=ATTR_API_PM25,
106  translation_key="pm25",
107  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
108  state_class=SensorStateClass.MEASUREMENT,
109  device_class=SensorDeviceClass.PM25,
110  value_fn=lambda data: data.get(ATTR_API_PM25),
111  extra_state_attributes_fn=None,
112  ),
114  key=ATTR_API_O3,
115  translation_key="o3",
116  native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
117  state_class=SensorStateClass.MEASUREMENT,
118  value_fn=lambda data: data.get(ATTR_API_O3),
119  extra_state_attributes_fn=None,
120  ),
122  key=ATTR_API_STATION,
123  translation_key="station",
124  value_fn=lambda data: data.get(ATTR_API_STATION),
125  extra_state_attributes_fn=station_extra_attrs,
126  ),
127 )
128 
129 
131  hass: HomeAssistant,
132  config_entry: AirNowConfigEntry,
133  async_add_entities: AddEntitiesCallback,
134 ) -> None:
135  """Set up AirNow sensor entities based on a config entry."""
136  coordinator = config_entry.runtime_data
137 
138  entities = [AirNowSensor(coordinator, description) for description in SENSOR_TYPES]
139 
140  async_add_entities(entities, False)
141 
142 
143 class AirNowSensor(CoordinatorEntity[AirNowDataUpdateCoordinator], SensorEntity):
144  """Define an AirNow sensor."""
145 
146  _attr_attribution = ATTRIBUTION
147  _attr_has_entity_name = True
148 
149  entity_description: AirNowEntityDescription
150 
151  def __init__(
152  self,
153  coordinator: AirNowDataUpdateCoordinator,
154  description: AirNowEntityDescription,
155  ) -> None:
156  """Initialize."""
157  super().__init__(coordinator)
158 
159  _device_id = f"{coordinator.latitude}-{coordinator.longitude}"
160 
161  self.entity_descriptionentity_description = description
162  self._attr_unique_id_attr_unique_id = f"{_device_id}-{description.key.lower()}"
163  self._attr_device_info_attr_device_info = DeviceInfo(
164  entry_type=DeviceEntryType.SERVICE,
165  identifiers={(DOMAIN, _device_id)},
166  manufacturer=DEFAULT_NAME,
167  name=DEFAULT_NAME,
168  )
169 
170  @property
171  def native_value(self) -> StateType:
172  """Return the state."""
173  return self.entity_descriptionentity_description.value_fn(self.coordinator.data)
174 
175  @property
176  def extra_state_attributes(self) -> dict[str, str] | None:
177  """Return the state attributes."""
178  if self.entity_descriptionentity_description.extra_state_attributes_fn:
179  return self.entity_descriptionentity_description.extra_state_attributes_fn(
180  self.coordinator.data
181  )
182  return None
None __init__(self, AirNowDataUpdateCoordinator coordinator, AirNowEntityDescription description)
Definition: sensor.py:155
dict[str, str]|None extra_state_attributes(self)
Definition: sensor.py:176
dict[str, Any] station_extra_attrs(dict[str, Any] data)
Definition: sensor.py:64
None async_setup_entry(HomeAssistant hass, AirNowConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:134
dict[str, Any] aqi_extra_attrs(dict[str, Any] data)
Definition: sensor.py:74