Home Assistant Unofficial Reference 2024.12.1
air_quality.py
Go to the documentation of this file.
1 """Sensor for checking the air quality around Norway."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 import logging
7 
8 from niluclient import (
9  CO,
10  CO2,
11  NO,
12  NO2,
13  NOX,
14  OZONE,
15  PM1,
16  PM10,
17  PM25,
18  POLLUTION_INDEX,
19  SO2,
20  create_location_client,
21  create_station_client,
22  lookup_stations_in_area,
23 )
24 import voluptuous as vol
25 
27  PLATFORM_SCHEMA as AIR_QUALITY_PLATFORM_SCHEMA,
28  AirQualityEntity,
29 )
30 from homeassistant.const import (
31  CONF_LATITUDE,
32  CONF_LONGITUDE,
33  CONF_NAME,
34  CONF_SHOW_ON_MAP,
35 )
36 from homeassistant.core import HomeAssistant
38 from homeassistant.helpers.entity_platform import AddEntitiesCallback
39 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
40 from homeassistant.util import Throttle
41 
42 _LOGGER = logging.getLogger(__name__)
43 
44 ATTR_AREA = "area"
45 ATTR_POLLUTION_INDEX = "nilu_pollution_index"
46 
47 CONF_AREA = "area"
48 CONF_STATION = "stations"
49 
50 DEFAULT_NAME = "NILU"
51 
52 SCAN_INTERVAL = timedelta(minutes=30)
53 
54 CONF_ALLOWED_AREAS = [
55  "Bergen",
56  "Birkenes",
57  "Bodø",
58  "Brumunddal",
59  "Bærum",
60  "Drammen",
61  "Elverum",
62  "Fredrikstad",
63  "Gjøvik",
64  "Grenland",
65  "Halden",
66  "Hamar",
67  "Harstad",
68  "Hurdal",
69  "Karasjok",
70  "Kristiansand",
71  "Kårvatn",
72  "Lillehammer",
73  "Lillesand",
74  "Lillestrøm",
75  "Lørenskog",
76  "Mo i Rana",
77  "Moss",
78  "Narvik",
79  "Oslo",
80  "Prestebakke",
81  "Sandve",
82  "Sarpsborg",
83  "Stavanger",
84  "Sør-Varanger",
85  "Tromsø",
86  "Trondheim",
87  "Tustervatn",
88  "Zeppelinfjellet",
89  "Ålesund",
90 ]
91 
92 PLATFORM_SCHEMA = AIR_QUALITY_PLATFORM_SCHEMA.extend(
93  {
94  vol.Inclusive(
95  CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together"
96  ): cv.latitude,
97  vol.Inclusive(
98  CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together"
99  ): cv.longitude,
100  vol.Exclusive(
101  CONF_AREA,
102  "station_collection",
103  (
104  "Can only configure one specific station or "
105  "stations in a specific area pr sensor. "
106  "Please only configure station or area."
107  ),
108  ): vol.All(cv.string, vol.In(CONF_ALLOWED_AREAS)),
109  vol.Exclusive(
110  CONF_STATION,
111  "station_collection",
112  (
113  "Can only configure one specific station or "
114  "stations in a specific area pr sensor. "
115  "Please only configure station or area."
116  ),
117  ): vol.All(cv.ensure_list, [cv.string]),
118  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
119  vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
120  }
121 )
122 
123 
125  hass: HomeAssistant,
126  config: ConfigType,
127  add_entities: AddEntitiesCallback,
128  discovery_info: DiscoveryInfoType | None = None,
129 ) -> None:
130  """Set up the NILU air quality sensor."""
131  name: str = config[CONF_NAME]
132  area: str | None = config.get(CONF_AREA)
133  stations: list[str] | None = config.get(CONF_STATION)
134  show_on_map: bool = config[CONF_SHOW_ON_MAP]
135 
136  sensors = []
137 
138  if area:
139  stations = lookup_stations_in_area(area)
140  elif not stations:
141  latitude = config.get(CONF_LATITUDE, hass.config.latitude)
142  longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
143  location_client = create_location_client(latitude, longitude)
144  stations = location_client.station_names
145 
146  assert stations is not None
147  for station in stations:
148  client = NiluData(create_station_client(station))
149  client.update()
150  if client.data.sensors:
151  sensors.append(NiluSensor(client, name, show_on_map))
152  else:
153  _LOGGER.warning("%s didn't give any sensors results", station)
154 
155  add_entities(sensors, True)
156 
157 
158 class NiluData:
159  """Class for handling the data retrieval."""
160 
161  def __init__(self, api):
162  """Initialize the data object."""
163  self.apiapi = api
164 
165  @property
166  def data(self):
167  """Get data cached in client."""
168  return self.apiapi.data
169 
170  @Throttle(SCAN_INTERVAL)
171  def update(self):
172  """Get the latest data from nilu API."""
173  self.apiapi.update()
174 
175 
177  """Single nilu station air sensor."""
178 
179  _attr_attribution = "Data provided by luftkvalitet.info and nilu.no"
180 
181  def __init__(self, api_data: NiluData, name: str, show_on_map: bool) -> None:
182  """Initialize the sensor."""
183  self._api_api = api_data
184  self._name_name = f"{name} {api_data.data.name}"
185  self._max_aqi_max_aqi = None
186  self._attrs_attrs = {}
187 
188  if show_on_map:
189  self._attrs_attrs[CONF_LATITUDE] = api_data.data.latitude
190  self._attrs_attrs[CONF_LONGITUDE] = api_data.data.longitude
191 
192  @property
193  def extra_state_attributes(self) -> dict:
194  """Return other details about the sensor state."""
195  return self._attrs_attrs
196 
197  @property
198  def name(self) -> str:
199  """Return the name of the sensor."""
200  return self._name_name
201 
202  @property
203  def air_quality_index(self) -> str | None:
204  """Return the Air Quality Index (AQI)."""
205  return self._max_aqi_max_aqi
206 
207  @property
208  def carbon_monoxide(self) -> str | None:
209  """Return the CO (carbon monoxide) level."""
210  return self.get_component_stateget_component_state(CO)
211 
212  @property
213  def carbon_dioxide(self) -> str | None:
214  """Return the CO2 (carbon dioxide) level."""
215  return self.get_component_stateget_component_state(CO2)
216 
217  @property
218  def nitrogen_oxide(self) -> str | None:
219  """Return the N2O (nitrogen oxide) level."""
220  return self.get_component_stateget_component_state(NOX)
221 
222  @property
223  def nitrogen_monoxide(self) -> str | None:
224  """Return the NO (nitrogen monoxide) level."""
225  return self.get_component_stateget_component_state(NO)
226 
227  @property
228  def nitrogen_dioxide(self) -> str | None:
229  """Return the NO2 (nitrogen dioxide) level."""
230  return self.get_component_stateget_component_state(NO2)
231 
232  @property
233  def ozone(self) -> str | None:
234  """Return the O3 (ozone) level."""
235  return self.get_component_stateget_component_state(OZONE)
236 
237  @property
238  def particulate_matter_2_5(self) -> str | None:
239  """Return the particulate matter 2.5 level."""
240  return self.get_component_stateget_component_state(PM25)
241 
242  @property
243  def particulate_matter_10(self) -> str | None:
244  """Return the particulate matter 10 level."""
245  return self.get_component_stateget_component_state(PM10)
246 
247  @property
248  def particulate_matter_0_1(self) -> str | None:
249  """Return the particulate matter 0.1 level."""
250  return self.get_component_stateget_component_state(PM1)
251 
252  @property
253  def sulphur_dioxide(self) -> str | None:
254  """Return the SO2 (sulphur dioxide) level."""
255  return self.get_component_stateget_component_state(SO2)
256 
257  def get_component_state(self, component_name: str) -> str | None:
258  """Return formatted value of specified component."""
259  if component_name in self._api_api.data.sensors:
260  sensor = self._api_api.data.sensors[component_name]
261  return sensor.value
262  return None
263 
264  def update(self) -> None:
265  """Update the sensor."""
266  self._api_api.update()
267 
268  sensors = self._api_api.data.sensors.values()
269  if sensors:
270  max_index = max(s.pollution_index for s in sensors)
271  self._max_aqi_max_aqi = max_index
272  self._attrs_attrs[ATTR_POLLUTION_INDEX] = POLLUTION_INDEX[self._max_aqi_max_aqi]
273 
274  self._attrs_attrs[ATTR_AREA] = self._api_api.data.area
str|None get_component_state(self, str component_name)
Definition: air_quality.py:257
None __init__(self, NiluData api_data, str name, bool show_on_map)
Definition: air_quality.py:181
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: air_quality.py:129