Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for AirVisual air quality sensors."""
2 
3 from __future__ import annotations
4 
6  SensorDeviceClass,
7  SensorEntity,
8  SensorEntityDescription,
9  SensorStateClass,
10 )
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import (
13  ATTR_LATITUDE,
14  ATTR_LONGITUDE,
15  ATTR_STATE,
16  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
17  CONCENTRATION_PARTS_PER_BILLION,
18  CONCENTRATION_PARTS_PER_MILLION,
19  CONF_COUNTRY,
20  CONF_LATITUDE,
21  CONF_LONGITUDE,
22  CONF_SHOW_ON_MAP,
23  CONF_STATE,
24 )
25 from homeassistant.core import HomeAssistant, callback
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
28 
29 from . import AirVisualConfigEntry
30 from .const import CONF_CITY
31 from .entity import AirVisualEntity
32 
33 ATTR_CITY = "city"
34 ATTR_COUNTRY = "country"
35 ATTR_POLLUTANT_SYMBOL = "pollutant_symbol"
36 ATTR_POLLUTANT_UNIT = "pollutant_unit"
37 ATTR_REGION = "region"
38 
39 SENSOR_KIND_AQI = "air_quality_index"
40 SENSOR_KIND_LEVEL = "air_pollution_level"
41 SENSOR_KIND_POLLUTANT = "main_pollutant"
42 
43 GEOGRAPHY_SENSOR_DESCRIPTIONS = (
45  key=SENSOR_KIND_LEVEL,
46  name="Air pollution level",
47  device_class=SensorDeviceClass.ENUM,
48  options=[
49  "good",
50  "moderate",
51  "unhealthy",
52  "unhealthy_sensitive",
53  "very_unhealthy",
54  "hazardous",
55  ],
56  translation_key="pollutant_level",
57  ),
59  key=SENSOR_KIND_AQI,
60  name="Air quality index",
61  device_class=SensorDeviceClass.AQI,
62  state_class=SensorStateClass.MEASUREMENT,
63  ),
65  key=SENSOR_KIND_POLLUTANT,
66  name="Main pollutant",
67  device_class=SensorDeviceClass.ENUM,
68  options=["co", "n2", "o3", "p1", "p2", "s2"],
69  translation_key="pollutant_label",
70  ),
71 )
72 GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
73 
74 
75 STATE_POLLUTANT_LABEL_CO = "co"
76 STATE_POLLUTANT_LABEL_N2 = "n2"
77 STATE_POLLUTANT_LABEL_O3 = "o3"
78 STATE_POLLUTANT_LABEL_P1 = "p1"
79 STATE_POLLUTANT_LABEL_P2 = "p2"
80 STATE_POLLUTANT_LABEL_S2 = "s2"
81 
82 STATE_POLLUTANT_LEVEL_GOOD = "good"
83 STATE_POLLUTANT_LEVEL_MODERATE = "moderate"
84 STATE_POLLUTANT_LEVEL_UNHEALTHY_SENSITIVE = "unhealthy_sensitive"
85 STATE_POLLUTANT_LEVEL_UNHEALTHY = "unhealthy"
86 STATE_POLLUTANT_LEVEL_VERY_UNHEALTHY = "very_unhealthy"
87 STATE_POLLUTANT_LEVEL_HAZARDOUS = "hazardous"
88 
89 POLLUTANT_LEVELS = {
90  (0, 50): (STATE_POLLUTANT_LEVEL_GOOD, "mdi:emoticon-excited"),
91  (51, 100): (STATE_POLLUTANT_LEVEL_MODERATE, "mdi:emoticon-happy"),
92  (101, 150): (STATE_POLLUTANT_LEVEL_UNHEALTHY_SENSITIVE, "mdi:emoticon-neutral"),
93  (151, 200): (STATE_POLLUTANT_LEVEL_UNHEALTHY, "mdi:emoticon-sad"),
94  (201, 300): (STATE_POLLUTANT_LEVEL_VERY_UNHEALTHY, "mdi:emoticon-dead"),
95  (301, 1000): (STATE_POLLUTANT_LEVEL_HAZARDOUS, "mdi:biohazard"),
96 }
97 
98 POLLUTANT_UNITS = {
99  "co": CONCENTRATION_PARTS_PER_MILLION,
100  "n2": CONCENTRATION_PARTS_PER_BILLION,
101  "o3": CONCENTRATION_PARTS_PER_BILLION,
102  "p1": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
103  "p2": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
104  "s2": CONCENTRATION_PARTS_PER_BILLION,
105 }
106 
107 
109  hass: HomeAssistant,
110  entry: AirVisualConfigEntry,
111  async_add_entities: AddEntitiesCallback,
112 ) -> None:
113  """Set up AirVisual sensors based on a config entry."""
114  coordinator = entry.runtime_data
116  AirVisualGeographySensor(coordinator, entry, description, locale)
117  for locale in GEOGRAPHY_SENSOR_LOCALES
118  for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
119  )
120 
121 
123  """Define an AirVisual sensor related to geography data via the Cloud API."""
124 
125  def __init__(
126  self,
127  coordinator: DataUpdateCoordinator,
128  entry: ConfigEntry,
129  description: SensorEntityDescription,
130  locale: str,
131  ) -> None:
132  """Initialize."""
133  super().__init__(coordinator, entry, description)
134 
135  self._attr_extra_state_attributes_attr_extra_state_attributes.update(
136  {
137  ATTR_CITY: entry.data.get(CONF_CITY),
138  ATTR_STATE: entry.data.get(CONF_STATE),
139  ATTR_COUNTRY: entry.data.get(CONF_COUNTRY),
140  }
141  )
142  self._attr_name_attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {description.name}"
143  self._attr_unique_id_attr_unique_id = f"{entry.unique_id}_{locale}_{description.key}"
144  self._locale_locale = locale
145 
146  @property
147  def available(self) -> bool:
148  """Return if entity is available."""
149  return super().available and self.coordinator.data["current"]["pollution"]
150 
151  @callback
152  def update_from_latest_data(self) -> None:
153  """Update the entity from the latest data."""
154  try:
155  data = self.coordinator.data["current"]["pollution"]
156  except KeyError:
157  return
158 
159  if self.entity_descriptionentity_description.key == SENSOR_KIND_LEVEL:
160  aqi = data[f"aqi{self._locale}"]
161  [(self._attr_native_value_attr_native_value, self._attr_icon)] = [
162  (name, icon)
163  for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
164  if floor <= aqi <= ceiling
165  ]
166  elif self.entity_descriptionentity_description.key == SENSOR_KIND_AQI:
167  self._attr_native_value_attr_native_value = data[f"aqi{self._locale}"]
168  elif self.entity_descriptionentity_description.key == SENSOR_KIND_POLLUTANT:
169  symbol = data[f"main{self._locale}"]
170  self._attr_native_value_attr_native_value = symbol
171  self._attr_extra_state_attributes_attr_extra_state_attributes.update(
172  {
173  ATTR_POLLUTANT_SYMBOL: symbol,
174  ATTR_POLLUTANT_UNIT: POLLUTANT_UNITS[symbol],
175  }
176  )
177 
178  # Displaying the geography on the map relies upon putting the latitude/longitude
179  # in the entity attributes with "latitude" and "longitude" as the keys.
180  # Conversely, we can hide the location on the map by using other keys, like
181  # "lati" and "long".
182  #
183  # We use any coordinates in the config entry and, in the case of a geography by
184  # name, we fall back to the latitude longitude provided in the coordinator data:
185  latitude = self._entry_entry.data.get(
186  CONF_LATITUDE,
187  self.coordinator.data["location"]["coordinates"][1],
188  )
189  longitude = self._entry_entry.data.get(
190  CONF_LONGITUDE,
191  self.coordinator.data["location"]["coordinates"][0],
192  )
193 
194  if self._entry_entry.options[CONF_SHOW_ON_MAP]:
195  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_LATITUDE] = latitude
196  self._attr_extra_state_attributes_attr_extra_state_attributes[ATTR_LONGITUDE] = longitude
197  self._attr_extra_state_attributes_attr_extra_state_attributes.pop("lati", None)
198  self._attr_extra_state_attributes_attr_extra_state_attributes.pop("long", None)
199  else:
200  self._attr_extra_state_attributes_attr_extra_state_attributes["lati"] = latitude
201  self._attr_extra_state_attributes_attr_extra_state_attributes["long"] = longitude
202  self._attr_extra_state_attributes_attr_extra_state_attributes.pop(ATTR_LATITUDE, None)
203  self._attr_extra_state_attributes_attr_extra_state_attributes.pop(ATTR_LONGITUDE, None)
None __init__(self, DataUpdateCoordinator coordinator, ConfigEntry entry, SensorEntityDescription description, str locale)
Definition: sensor.py:131
None async_setup_entry(HomeAssistant hass, AirVisualConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:112
IssData update(pyiss.ISS iss)
Definition: __init__.py:33