Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Support for IPMA weather service."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import contextlib
7 import logging
8 from typing import Literal
9 
10 from pyipma.api import IPMA_API
11 from pyipma.forecast import Forecast as IPMAForecast
12 from pyipma.location import Location
13 
15  ATTR_FORECAST_CONDITION,
16  ATTR_FORECAST_NATIVE_TEMP,
17  ATTR_FORECAST_NATIVE_TEMP_LOW,
18  ATTR_FORECAST_NATIVE_WIND_SPEED,
19  ATTR_FORECAST_PRECIPITATION_PROBABILITY,
20  ATTR_FORECAST_TIME,
21  ATTR_FORECAST_WIND_BEARING,
22  Forecast,
23  WeatherEntity,
24  WeatherEntityFeature,
25 )
26 from homeassistant.config_entries import ConfigEntry
27 from homeassistant.const import (
28  CONF_MODE,
29  UnitOfPressure,
30  UnitOfSpeed,
31  UnitOfTemperature,
32 )
33 from homeassistant.core import HomeAssistant
34 from homeassistant.helpers.entity_platform import AddEntitiesCallback
35 from homeassistant.helpers.sun import is_up
36 from homeassistant.util import Throttle
37 
38 from .const import (
39  ATTRIBUTION,
40  CONDITION_MAP,
41  DATA_API,
42  DATA_LOCATION,
43  DOMAIN,
44  MIN_TIME_BETWEEN_UPDATES,
45 )
46 from .entity import IPMADevice
47 
48 _LOGGER = logging.getLogger(__name__)
49 
50 
52  hass: HomeAssistant,
53  config_entry: ConfigEntry,
54  async_add_entities: AddEntitiesCallback,
55 ) -> None:
56  """Add a weather entity from a config_entry."""
57  api = hass.data[DOMAIN][config_entry.entry_id][DATA_API]
58  location = hass.data[DOMAIN][config_entry.entry_id][DATA_LOCATION]
59  async_add_entities([IPMAWeather(api, location, config_entry)], True)
60 
61 
63  """Representation of a weather condition."""
64 
65  _attr_attribution = ATTRIBUTION
66  _attr_name = None
67  _attr_native_pressure_unit = UnitOfPressure.HPA
68  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
69  _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
70  _attr_supported_features = (
71  WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
72  )
73 
74  def __init__(
75  self, api: IPMA_API, location: Location, config_entry: ConfigEntry
76  ) -> None:
77  """Initialise the platform with a data instance and station name."""
78  IPMADevice.__init__(self, api, location)
79  self._mode_mode = config_entry.data.get(CONF_MODE)
80  self._period_period = 1 if config_entry.data.get(CONF_MODE) == "hourly" else 24
81  self._observation_observation = None
82  self._daily_forecast_daily_forecast: list[IPMAForecast] | None = None
83  self._hourly_forecast: list[IPMAForecast] | None = None
84  if self._mode_mode is not None:
85  self._attr_unique_id_attr_unique_id = f"{self._location.station_latitude}, {self._location.station_longitude}, {self._mode}"
86  else:
87  self._attr_unique_id_attr_unique_id = (
88  f"{self._location.station_latitude}, {self._location.station_longitude}"
89  )
90 
91  @Throttle(MIN_TIME_BETWEEN_UPDATES)
92  async def async_update(self) -> None:
93  """Update Condition and Forecast."""
94  async with asyncio.timeout(10):
95  new_observation = await self._location_location.observation(self._api_api)
96 
97  if new_observation:
98  self._observation_observation = new_observation
99  else:
100  _LOGGER.warning("Could not update weather observation")
101 
102  if self._period_period == 24 or self._forecast_listeners_forecast_listeners["daily"]:
103  await self._update_forecast_update_forecast("daily", 24, True)
104  else:
105  self._daily_forecast_daily_forecast = None
106 
107  await self._update_forecast_update_forecast("hourly", 1, True)
108 
109  _LOGGER.debug(
110  "Updated location %s based on %s, current observation %s",
111  self._location_location.name,
112  self._location_location.station,
113  self._observation_observation,
114  )
115 
116  async def _update_forecast(
117  self,
118  forecast_type: Literal["daily", "hourly"],
119  period: int,
120  update_listeners: bool,
121  ) -> None:
122  """Update weather forecast."""
123  new_forecast = await self._location_location.forecast(self._api_api, period)
124  if new_forecast:
125  setattr(self, f"_{forecast_type}_forecast", new_forecast)
126  if update_listeners:
127  await self.async_update_listenersasync_update_listeners((forecast_type,))
128  else:
129  _LOGGER.warning("Could not update %s weather forecast", forecast_type)
130 
131  def _condition_conversion(self, identifier, forecast_dt):
132  """Convert from IPMA weather_type id to HA."""
133  if identifier == 1 and not is_up(self.hasshass, forecast_dt):
134  identifier = -identifier
135 
136  return CONDITION_MAP.get(identifier)
137 
138  @property
139  def condition(self):
140  """Return the current condition which is only available on the hourly forecast data."""
141  forecast = self._hourly_forecast
142 
143  if not forecast:
144  return None
145 
146  return self._condition_conversion_condition_conversion(forecast[0].weather_type.id, None)
147 
148  @property
150  """Return the current temperature."""
151  if not self._observation_observation:
152  return None
153 
154  return self._observation_observation.temperature
155 
156  @property
157  def native_pressure(self):
158  """Return the current pressure."""
159  if not self._observation_observation:
160  return None
161 
162  return self._observation_observation.pressure
163 
164  @property
165  def humidity(self):
166  """Return the name of the sensor."""
167  if not self._observation_observation:
168  return None
169 
170  return self._observation_observation.humidity
171 
172  @property
173  def native_wind_speed(self):
174  """Return the current windspeed."""
175  if not self._observation_observation:
176  return None
177 
178  return self._observation_observation.wind_intensity_km
179 
180  @property
181  def wind_bearing(self):
182  """Return the current wind bearing (degrees)."""
183  if not self._observation_observation:
184  return None
185 
186  return self._observation_observation.wind_direction
187 
188  def _forecast(self, forecast: list[IPMAForecast] | None) -> list[Forecast]:
189  """Return the forecast array."""
190  if not forecast:
191  return []
192 
193  return [
194  {
195  ATTR_FORECAST_TIME: data_in.forecast_date,
196  ATTR_FORECAST_CONDITION: self._condition_conversion_condition_conversion(
197  data_in.weather_type.id, data_in.forecast_date
198  ),
199  ATTR_FORECAST_NATIVE_TEMP_LOW: data_in.min_temperature,
200  ATTR_FORECAST_NATIVE_TEMP: data_in.max_temperature,
201  ATTR_FORECAST_PRECIPITATION_PROBABILITY: data_in.precipitation_probability,
202  ATTR_FORECAST_NATIVE_WIND_SPEED: data_in.wind_strength,
203  ATTR_FORECAST_WIND_BEARING: data_in.wind_direction,
204  }
205  for data_in in forecast
206  ]
207 
209  self,
210  forecast_type: Literal["daily", "hourly"],
211  period: int,
212  ) -> None:
213  """Try to update weather forecast."""
214  with contextlib.suppress(TimeoutError):
215  async with asyncio.timeout(10):
216  await self._update_forecast_update_forecast(forecast_type, period, False)
217 
218  async def async_forecast_daily(self) -> list[Forecast]:
219  """Return the daily forecast in native units."""
220  await self._try_update_forecast_try_update_forecast("daily", 24)
221  return self._forecast_forecast(self._daily_forecast_daily_forecast)
222 
223  async def async_forecast_hourly(self) -> list[Forecast]:
224  """Return the hourly forecast in native units."""
225  await self._try_update_forecast_try_update_forecast("hourly", 1)
226  return self._forecast_forecast(self._hourly_forecast)
None _update_forecast(self, Literal["daily", "hourly"] forecast_type, int period, bool update_listeners)
Definition: weather.py:121
None __init__(self, IPMA_API api, Location location, ConfigEntry config_entry)
Definition: weather.py:76
list[Forecast] _forecast(self, list[IPMAForecast]|None forecast)
Definition: weather.py:188
def _condition_conversion(self, identifier, forecast_dt)
Definition: weather.py:131
None _try_update_forecast(self, Literal["daily", "hourly"] forecast_type, int period)
Definition: weather.py:212
None async_update_listeners(self, Iterable[Literal["daily", "hourly", "twice_daily"]]|None forecast_types)
Definition: __init__.py:961
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:55
bool is_up(HomeAssistant hass, datetime.datetime|None utc_point_in_time=None)
Definition: sun.py:142