Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for the Environment Canada weather 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 
10  SensorDeviceClass,
11  SensorEntity,
12  SensorEntityDescription,
13  SensorStateClass,
14 )
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import (
17  ATTR_LOCATION,
18  DEGREE,
19  PERCENTAGE,
20  UV_INDEX,
21  UnitOfLength,
22  UnitOfPressure,
23  UnitOfSpeed,
24  UnitOfTemperature,
25 )
26 from homeassistant.core import HomeAssistant
27 from homeassistant.helpers.entity_platform import AddEntitiesCallback
28 from homeassistant.helpers.update_coordinator import CoordinatorEntity
29 
30 from . import device_info
31 from .const import ATTR_STATION, DOMAIN
32 
33 ATTR_TIME = "alert time"
34 
35 
36 @dataclass(frozen=True, kw_only=True)
38  """Describes Environment Canada sensor entity."""
39 
40  value_fn: Callable[[Any], Any]
41  transform: Callable[[Any], Any] | None = None
42 
43 
44 SENSOR_TYPES: tuple[ECSensorEntityDescription, ...] = (
46  key="condition",
47  translation_key="condition",
48  value_fn=lambda data: data.conditions.get("condition", {}).get("value"),
49  ),
51  key="dewpoint",
52  translation_key="dewpoint",
53  device_class=SensorDeviceClass.TEMPERATURE,
54  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
55  state_class=SensorStateClass.MEASUREMENT,
56  value_fn=lambda data: data.conditions.get("dewpoint", {}).get("value"),
57  ),
59  key="high_temp",
60  translation_key="high_temp",
61  device_class=SensorDeviceClass.TEMPERATURE,
62  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
63  state_class=SensorStateClass.MEASUREMENT,
64  value_fn=lambda data: data.conditions.get("high_temp", {}).get("value"),
65  ),
67  key="humidex",
68  translation_key="humidex",
69  device_class=SensorDeviceClass.TEMPERATURE,
70  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
71  state_class=SensorStateClass.MEASUREMENT,
72  value_fn=lambda data: data.conditions.get("humidex", {}).get("value"),
73  ),
75  key="humidity",
76  device_class=SensorDeviceClass.HUMIDITY,
77  native_unit_of_measurement=PERCENTAGE,
78  state_class=SensorStateClass.MEASUREMENT,
79  value_fn=lambda data: data.conditions.get("humidity", {}).get("value"),
80  ),
82  key="icon_code",
83  translation_key="icon_code",
84  name="Icon code",
85  value_fn=lambda data: data.conditions.get("icon_code", {}).get("value"),
86  ),
88  key="low_temp",
89  translation_key="low_temp",
90  name="Low temperature",
91  device_class=SensorDeviceClass.TEMPERATURE,
92  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
93  state_class=SensorStateClass.MEASUREMENT,
94  value_fn=lambda data: data.conditions.get("low_temp", {}).get("value"),
95  ),
97  key="normal_high",
98  translation_key="normal_high",
99  device_class=SensorDeviceClass.TEMPERATURE,
100  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
101  value_fn=lambda data: data.conditions.get("normal_high", {}).get("value"),
102  ),
104  key="normal_low",
105  translation_key="normal_low",
106  device_class=SensorDeviceClass.TEMPERATURE,
107  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
108  value_fn=lambda data: data.conditions.get("normal_low", {}).get("value"),
109  ),
111  key="pop",
112  translation_key="pop",
113  native_unit_of_measurement=PERCENTAGE,
114  value_fn=lambda data: data.conditions.get("pop", {}).get("value"),
115  ),
117  key="pressure",
118  translation_key="pressure",
119  device_class=SensorDeviceClass.PRESSURE,
120  native_unit_of_measurement=UnitOfPressure.KPA,
121  state_class=SensorStateClass.MEASUREMENT,
122  value_fn=lambda data: data.conditions.get("pressure", {}).get("value"),
123  ),
125  key="temperature",
126  device_class=SensorDeviceClass.TEMPERATURE,
127  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
128  state_class=SensorStateClass.MEASUREMENT,
129  value_fn=lambda data: data.conditions.get("temperature", {}).get("value"),
130  ),
132  key="tendency",
133  translation_key="tendency",
134  value_fn=lambda data: data.conditions.get("tendency", {}).get("value"),
135  transform=lambda val: str(val).capitalize(),
136  ),
138  key="text_summary",
139  translation_key="text_summary",
140  value_fn=lambda data: data.conditions.get("text_summary", {}).get("value"),
141  transform=lambda val: val[:255],
142  ),
144  key="timestamp",
145  translation_key="timestamp",
146  device_class=SensorDeviceClass.TIMESTAMP,
147  value_fn=lambda data: data.metadata.get("timestamp"),
148  ),
150  key="uv_index",
151  translation_key="uv_index",
152  native_unit_of_measurement=UV_INDEX,
153  state_class=SensorStateClass.MEASUREMENT,
154  value_fn=lambda data: data.conditions.get("uv_index", {}).get("value"),
155  ),
157  key="visibility",
158  translation_key="visibility",
159  native_unit_of_measurement=UnitOfLength.KILOMETERS,
160  device_class=SensorDeviceClass.DISTANCE,
161  state_class=SensorStateClass.MEASUREMENT,
162  value_fn=lambda data: data.conditions.get("visibility", {}).get("value"),
163  ),
165  key="wind_bearing",
166  translation_key="wind_bearing",
167  native_unit_of_measurement=DEGREE,
168  value_fn=lambda data: data.conditions.get("wind_bearing", {}).get("value"),
169  ),
171  key="wind_chill",
172  translation_key="wind_chill",
173  device_class=SensorDeviceClass.TEMPERATURE,
174  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
175  state_class=SensorStateClass.MEASUREMENT,
176  value_fn=lambda data: data.conditions.get("wind_chill", {}).get("value"),
177  ),
179  key="wind_dir",
180  translation_key="wind_dir",
181  value_fn=lambda data: data.conditions.get("wind_dir", {}).get("value"),
182  ),
184  key="wind_gust",
185  translation_key="wind_gust",
186  native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
187  device_class=SensorDeviceClass.WIND_SPEED,
188  state_class=SensorStateClass.MEASUREMENT,
189  value_fn=lambda data: data.conditions.get("wind_gust", {}).get("value"),
190  ),
192  key="wind_speed",
193  native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
194  device_class=SensorDeviceClass.WIND_SPEED,
195  state_class=SensorStateClass.MEASUREMENT,
196  value_fn=lambda data: data.conditions.get("wind_speed", {}).get("value"),
197  ),
198 )
199 
200 
201 def _get_aqhi_value(data):
202  if (aqhi := data.current) is not None:
203  return aqhi
204  if data.forecasts and (hourly := data.forecasts.get("hourly")) is not None:
205  if values := list(hourly.values()):
206  return values[0]
207  return None
208 
209 
211  key="aqhi",
212  translation_key="aqhi",
213  device_class=SensorDeviceClass.AQI,
214  state_class=SensorStateClass.MEASUREMENT,
215  value_fn=_get_aqhi_value,
216 )
217 
218 ALERT_TYPES: tuple[ECSensorEntityDescription, ...] = (
220  key="advisories",
221  translation_key="advisories",
222  value_fn=lambda data: data.alerts.get("advisories", {}).get("value"),
223  transform=len,
224  ),
226  key="endings",
227  translation_key="endings",
228  value_fn=lambda data: data.alerts.get("endings", {}).get("value"),
229  transform=len,
230  ),
232  key="statements",
233  translation_key="statements",
234  value_fn=lambda data: data.alerts.get("statements", {}).get("value"),
235  transform=len,
236  ),
238  key="warnings",
239  translation_key="warnings",
240  value_fn=lambda data: data.alerts.get("warnings", {}).get("value"),
241  transform=len,
242  ),
244  key="watches",
245  translation_key="watches",
246  value_fn=lambda data: data.alerts.get("watches", {}).get("value"),
247  transform=len,
248  ),
249 )
250 
251 
253  hass: HomeAssistant,
254  config_entry: ConfigEntry,
255  async_add_entities: AddEntitiesCallback,
256 ) -> None:
257  """Add a weather entity from a config_entry."""
258  coordinator = hass.data[DOMAIN][config_entry.entry_id]["weather_coordinator"]
259  sensors: list[ECBaseSensor] = [ECSensor(coordinator, desc) for desc in SENSOR_TYPES]
260  sensors.extend([ECAlertSensor(coordinator, desc) for desc in ALERT_TYPES])
261  aqhi_coordinator = hass.data[DOMAIN][config_entry.entry_id]["aqhi_coordinator"]
262  sensors.append(ECSensor(aqhi_coordinator, AQHI_SENSOR))
263  async_add_entities(sensors)
264 
265 
267  """Environment Canada sensor base."""
268 
269  entity_description: ECSensorEntityDescription
270  _attr_has_entity_name = True
271 
272  def __init__(self, coordinator, description):
273  """Initialize the base sensor."""
274  super().__init__(coordinator)
275  self.entity_descriptionentity_description = description
276  self._ec_data_ec_data = coordinator.ec_data
277  self._attr_attribution_attr_attribution = self._ec_data_ec_data.metadata["attribution"]
278  self._attr_unique_id_attr_unique_id = f"{coordinator.config_entry.title}-{description.key}"
279  self._attr_device_info_attr_device_info = device_info(coordinator.config_entry)
280 
281  @property
282  def native_value(self):
283  """Return the native value of the sensor."""
284  value = self.entity_descriptionentity_description.value_fn(self._ec_data_ec_data)
285  if value is not None and self.entity_descriptionentity_description.transform:
286  value = self.entity_descriptionentity_description.transform(value)
287  return value
288 
289 
291  """Environment Canada sensor for conditions."""
292 
293  def __init__(self, coordinator, description):
294  """Initialize the sensor."""
295  super().__init__(coordinator, description)
296  self._attr_extra_state_attributes_attr_extra_state_attributes = {
297  ATTR_LOCATION: self._ec_data_ec_data.metadata.get("location"),
298  ATTR_STATION: self._ec_data_ec_data.metadata.get("station"),
299  }
300 
301 
303  """Environment Canada sensor for alerts."""
304 
305  @property
307  """Return the extra state attributes."""
308  value = self.entity_descriptionentity_description.value_fn(self._ec_data_ec_data)
309  if not value:
310  return None
311 
312  extra_state_attrs = {
313  ATTR_LOCATION: self._ec_data_ec_data.metadata.get("location"),
314  ATTR_STATION: self._ec_data_ec_data.metadata.get("station"),
315  }
316  for index, alert in enumerate(value, start=1):
317  extra_state_attrs[f"alert_{index}"] = alert.get("title")
318  extra_state_attrs[f"alert_time_{index}"] = alert.get("date")
319 
320  return extra_state_attrs
def __init__(self, coordinator, description)
Definition: sensor.py:293
DeviceInfo|None device_info(self)
Definition: entity.py:798
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:256