1 """Support for the Swedish weather institute weather service."""
3 from __future__
import annotations
6 from collections.abc
import Mapping
7 from datetime
import datetime, timedelta
9 from typing
import Any, Final
13 from smhi.smhi_lib
import SmhiForecast, SmhiForecastException
16 ATTR_CONDITION_CLEAR_NIGHT,
17 ATTR_CONDITION_CLOUDY,
18 ATTR_CONDITION_EXCEPTIONAL,
21 ATTR_CONDITION_LIGHTNING,
22 ATTR_CONDITION_LIGHTNING_RAINY,
23 ATTR_CONDITION_PARTLYCLOUDY,
24 ATTR_CONDITION_POURING,
27 ATTR_CONDITION_SNOWY_RAINY,
30 ATTR_CONDITION_WINDY_VARIANT,
31 ATTR_FORECAST_CLOUD_COVERAGE,
32 ATTR_FORECAST_CONDITION,
33 ATTR_FORECAST_HUMIDITY,
34 ATTR_FORECAST_NATIVE_PRECIPITATION,
35 ATTR_FORECAST_NATIVE_PRESSURE,
36 ATTR_FORECAST_NATIVE_TEMP,
37 ATTR_FORECAST_NATIVE_TEMP_LOW,
38 ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
39 ATTR_FORECAST_NATIVE_WIND_SPEED,
41 ATTR_FORECAST_WIND_BEARING,
53 UnitOfPrecipitationDepth,
65 from .const
import ATTR_SMHI_THUNDER_PROBABILITY, DOMAIN, ENTITY_ID_SENSOR_FORMAT
67 _LOGGER = logging.getLogger(__name__)
70 CONDITION_CLASSES: Final[dict[str, list[int]]] = {
71 ATTR_CONDITION_CLOUDY: [5, 6],
72 ATTR_CONDITION_FOG: [7],
73 ATTR_CONDITION_HAIL: [],
74 ATTR_CONDITION_LIGHTNING: [21],
75 ATTR_CONDITION_LIGHTNING_RAINY: [11],
76 ATTR_CONDITION_PARTLYCLOUDY: [3, 4],
77 ATTR_CONDITION_POURING: [10, 20],
78 ATTR_CONDITION_RAINY: [8, 9, 18, 19],
79 ATTR_CONDITION_SNOWY: [15, 16, 17, 25, 26, 27],
80 ATTR_CONDITION_SNOWY_RAINY: [12, 13, 14, 22, 23, 24],
81 ATTR_CONDITION_SUNNY: [1, 2],
82 ATTR_CONDITION_WINDY: [],
83 ATTR_CONDITION_WINDY_VARIANT: [],
84 ATTR_CONDITION_EXCEPTIONAL: [],
88 for cond_ha, cond_codes
in CONDITION_CLASSES.items()
89 for cond_code
in cond_codes
94 RETRY_TIMEOUT = 5 * 60
101 config_entry: ConfigEntry,
102 async_add_entities: AddEntitiesCallback,
104 """Add a weather entity from map location."""
105 location = config_entry.data
106 name =
slugify(location[CONF_NAME])
108 session = aiohttp_client.async_get_clientsession(hass)
112 location[CONF_LOCATION][CONF_LATITUDE],
113 location[CONF_LOCATION][CONF_LONGITUDE],
116 entity.entity_id = ENTITY_ID_SENSOR_FORMAT.format(name)
122 """Representation of a weather entity."""
124 _attr_attribution =
"Swedish weather institute (SMHI)"
125 _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
126 _attr_native_visibility_unit = UnitOfLength.KILOMETERS
127 _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
128 _attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
129 _attr_native_pressure_unit = UnitOfPressure.HPA
131 _attr_has_entity_name =
True
133 _attr_supported_features = (
134 WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
142 session: aiohttp.ClientSession,
144 """Initialize the SMHI weather entity."""
149 self.
_smhi_api_smhi_api = Smhi(longitude, latitude, session=session)
151 entry_type=DeviceEntryType.SERVICE,
152 identifiers={(DOMAIN, f
"{latitude}, {longitude}")},
156 configuration_url=
"http://opendata.smhi.se/apidocs/metfcst/parameters.html",
161 """Return additional attributes."""
164 ATTR_SMHI_THUNDER_PROBABILITY: self.
_forecast_daily_forecast_daily[0].thunder,
168 @Throttle(MIN_TIME_BETWEEN_UPDATES)
170 """Refresh the forecast data from SMHI weather API."""
172 async
with asyncio.timeout(TIMEOUT):
176 except (TimeoutError, SmhiForecastException):
177 _LOGGER.error(
"Failed to connect to SMHI API, retry in 5 minutes")
193 if self.
_attr_condition_attr_condition == ATTR_CONDITION_SUNNY
and not sun.is_up(
200 """Retry refresh weather forecast."""
204 self, forecast_data: list[SmhiForecast] |
None
205 ) -> list[Forecast] |
None:
206 """Get forecast data."""
207 if forecast_data
is None or len(forecast_data) < 3:
210 data: list[Forecast] = []
212 for forecast
in forecast_data[1:]:
213 condition = CONDITION_MAP.get(forecast.symbol)
214 if condition == ATTR_CONDITION_SUNNY
and not sun.is_up(
215 self.
hasshass, forecast.valid_time.replace(tzinfo=dt_util.UTC)
217 condition = ATTR_CONDITION_CLEAR_NIGHT
221 ATTR_FORECAST_TIME: forecast.valid_time.isoformat(),
222 ATTR_FORECAST_NATIVE_TEMP: forecast.temperature_max,
223 ATTR_FORECAST_NATIVE_TEMP_LOW: forecast.temperature_min,
224 ATTR_FORECAST_NATIVE_PRECIPITATION: forecast.total_precipitation,
225 ATTR_FORECAST_CONDITION: condition,
226 ATTR_FORECAST_NATIVE_PRESSURE: forecast.pressure,
227 ATTR_FORECAST_WIND_BEARING: forecast.wind_direction,
228 ATTR_FORECAST_NATIVE_WIND_SPEED: forecast.wind_speed,
229 ATTR_FORECAST_HUMIDITY: forecast.humidity,
230 ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: forecast.wind_gust,
231 ATTR_FORECAST_CLOUD_COVERAGE: forecast.cloudiness,
238 """Service to retrieve the daily forecast."""
242 """Service to retrieve the hourly forecast."""
list[Forecast]|None async_forecast_hourly(self)
_attr_native_wind_gust_speed
list[Forecast]|None _get_forecast_data(self, list[SmhiForecast]|None forecast_data)
list[Forecast]|None async_forecast_daily(self)
None __init__(self, str name, str latitude, str longitude, aiohttp.ClientSession session)
None retry_update(self, datetime _)
Mapping[str, Any]|None extra_state_attributes(self)
None async_update_listeners(self, Iterable[Literal["daily", "hourly", "twice_daily"]]|None forecast_types)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)