4
4
import asyncio
5
5
from datetime import datetime , timedelta
6
6
import logging
7
- from typing import Final , TypedDict
7
+ from typing import Final
8
8
9
9
import aiohttp
10
10
import async_timeout
35
35
WeatherEntity ,
36
36
)
37
37
from homeassistant .config_entries import ConfigEntry
38
- from homeassistant .const import CONF_LATITUDE , CONF_LONGITUDE , CONF_NAME , TEMP_CELSIUS
38
+ from homeassistant .const import (
39
+ CONF_LATITUDE ,
40
+ CONF_LONGITUDE ,
41
+ CONF_NAME ,
42
+ LENGTH_KILOMETERS ,
43
+ LENGTH_MILLIMETERS ,
44
+ TEMP_CELSIUS ,
45
+ )
39
46
from homeassistant .core import HomeAssistant
40
47
from homeassistant .helpers import aiohttp_client
41
48
from homeassistant .helpers .device_registry import DeviceEntryType
72
79
ATTR_CONDITION_EXCEPTIONAL : [],
73
80
}
74
81
82
+ TIMEOUT = 10
75
83
# 5 minutes between retrying connect to API again
76
84
RETRY_TIMEOUT = 5 * 60
77
85
@@ -103,6 +111,11 @@ async def async_setup_entry(
103
111
class SmhiWeather (WeatherEntity ):
104
112
"""Representation of a weather entity."""
105
113
114
+ _attr_attribution = "Swedish weather institute (SMHI)"
115
+ _attr_temperature_unit = TEMP_CELSIUS
116
+ _attr_visibility_unit = LENGTH_KILOMETERS
117
+ _attr_precipitation_unit = LENGTH_MILLIMETERS
118
+
106
119
def __init__ (
107
120
self ,
108
121
name : str ,
@@ -112,140 +125,65 @@ def __init__(
112
125
) -> None :
113
126
"""Initialize the SMHI weather entity."""
114
127
115
- self ._name = name
116
- self ._latitude = latitude
117
- self ._longitude = longitude
128
+ self ._attr_name = name
129
+ self ._attr_unique_id = f"{ latitude } , { longitude } "
118
130
self ._forecasts : list [SmhiForecast ] | None = None
119
131
self ._fail_count = 0
120
- self ._smhi_api = Smhi (self . _longitude , self . _latitude , session = session )
132
+ self ._smhi_api = Smhi (longitude , latitude , session = session )
121
133
self ._attr_device_info = DeviceInfo (
122
134
entry_type = DeviceEntryType .SERVICE ,
123
- identifiers = {(DOMAIN , f"{ self . _latitude } , { self . _longitude } " )},
135
+ identifiers = {(DOMAIN , f"{ latitude } , { longitude } " )},
124
136
manufacturer = "SMHI" ,
125
137
model = "v2" ,
126
- name = self . _name ,
138
+ name = name ,
127
139
configuration_url = "http://opendata.smhi.se/apidocs/metfcst/parameters.html" ,
128
140
)
129
-
130
- @property
131
- def unique_id (self ) -> str :
132
- """Return a unique id."""
133
- return f"{ self ._latitude } , { self ._longitude } "
141
+ self ._attr_condition = None
142
+ self ._attr_temperature = None
134
143
135
144
@Throttle (MIN_TIME_BETWEEN_UPDATES )
136
145
async def async_update (self ) -> None :
137
146
"""Refresh the forecast data from SMHI weather API."""
138
147
try :
139
- async with async_timeout .timeout (10 ):
140
- self ._forecasts = await self .get_weather_forecast ()
148
+ async with async_timeout .timeout (TIMEOUT ):
149
+ self ._forecasts = await self ._smhi_api . async_get_forecast ()
141
150
self ._fail_count = 0
142
-
143
151
except (asyncio .TimeoutError , SmhiForecastException ):
144
152
_LOGGER .error ("Failed to connect to SMHI API, retry in 5 minutes" )
145
153
self ._fail_count += 1
146
154
if self ._fail_count < 3 :
147
155
async_call_later (self .hass , RETRY_TIMEOUT , self .retry_update )
156
+ return
157
+
158
+ if self ._forecasts :
159
+ self ._attr_temperature = self ._forecasts [0 ].temperature
160
+ self ._attr_humidity = self ._forecasts [0 ].humidity
161
+ # Convert from m/s to km/h
162
+ self ._attr_wind_speed = round (self ._forecasts [0 ].wind_speed * 18 / 5 )
163
+ self ._attr_wind_bearing = self ._forecasts [0 ].wind_direction
164
+ self ._attr_visibility = self ._forecasts [0 ].horizontal_visibility
165
+ self ._attr_pressure = self ._forecasts [0 ].pressure
166
+ self ._attr_condition = next (
167
+ (
168
+ k
169
+ for k , v in CONDITION_CLASSES .items ()
170
+ if self ._forecasts [0 ].symbol in v
171
+ ),
172
+ None ,
173
+ )
174
+ self ._attr_extra_state_attributes = {
175
+ ATTR_SMHI_CLOUDINESS : self ._forecasts [0 ].cloudiness ,
176
+ # Convert from m/s to km/h
177
+ ATTR_SMHI_WIND_GUST_SPEED : round (self ._forecasts [0 ].wind_gust * 18 / 5 ),
178
+ ATTR_SMHI_THUNDER_PROBABILITY : self ._forecasts [0 ].thunder ,
179
+ }
148
180
149
181
async def retry_update (self , _ : datetime ) -> None :
150
182
"""Retry refresh weather forecast."""
151
183
await self .async_update ( # pylint: disable=unexpected-keyword-arg
152
184
no_throttle = True
153
185
)
154
186
155
- async def get_weather_forecast (self ) -> list [SmhiForecast ]:
156
- """Return the current forecasts from SMHI API."""
157
- return await self ._smhi_api .async_get_forecast ()
158
-
159
- @property
160
- def name (self ) -> str :
161
- """Return the name of the sensor."""
162
- return self ._name
163
-
164
- @property
165
- def temperature (self ) -> int | None :
166
- """Return the temperature."""
167
- if self ._forecasts is not None :
168
- return self ._forecasts [0 ].temperature
169
- return None
170
-
171
- @property
172
- def temperature_unit (self ) -> str :
173
- """Return the unit of measurement."""
174
- return TEMP_CELSIUS
175
-
176
- @property
177
- def humidity (self ) -> int | None :
178
- """Return the humidity."""
179
- if self ._forecasts is not None :
180
- return self ._forecasts [0 ].humidity
181
- return None
182
-
183
- @property
184
- def wind_speed (self ) -> float | None :
185
- """Return the wind speed."""
186
- if self ._forecasts is not None :
187
- # Convert from m/s to km/h
188
- return round (self ._forecasts [0 ].wind_speed * 18 / 5 )
189
- return None
190
-
191
- @property
192
- def wind_gust_speed (self ) -> float | None :
193
- """Return the wind gust speed."""
194
- if self ._forecasts is not None :
195
- # Convert from m/s to km/h
196
- return round (self ._forecasts [0 ].wind_gust * 18 / 5 )
197
- return None
198
-
199
- @property
200
- def wind_bearing (self ) -> int | None :
201
- """Return the wind bearing."""
202
- if self ._forecasts is not None :
203
- return self ._forecasts [0 ].wind_direction
204
- return None
205
-
206
- @property
207
- def visibility (self ) -> float | None :
208
- """Return the visibility."""
209
- if self ._forecasts is not None :
210
- return self ._forecasts [0 ].horizontal_visibility
211
- return None
212
-
213
- @property
214
- def pressure (self ) -> int | None :
215
- """Return the pressure."""
216
- if self ._forecasts is not None :
217
- return self ._forecasts [0 ].pressure
218
- return None
219
-
220
- @property
221
- def cloudiness (self ) -> int | None :
222
- """Return the cloudiness."""
223
- if self ._forecasts is not None :
224
- return self ._forecasts [0 ].cloudiness
225
- return None
226
-
227
- @property
228
- def thunder_probability (self ) -> int | None :
229
- """Return the chance of thunder, unit Percent."""
230
- if self ._forecasts is not None :
231
- return self ._forecasts [0 ].thunder
232
- return None
233
-
234
- @property
235
- def condition (self ) -> str | None :
236
- """Return the weather condition."""
237
- if self ._forecasts is None :
238
- return None
239
- return next (
240
- (k for k , v in CONDITION_CLASSES .items () if self ._forecasts [0 ].symbol in v ),
241
- None ,
242
- )
243
-
244
- @property
245
- def attribution (self ) -> str :
246
- """Return the attribution."""
247
- return "Swedish weather institute (SMHI)"
248
-
249
187
@property
250
188
def forecast (self ) -> list [Forecast ] | None :
251
189
"""Return the forecast."""
@@ -270,23 +208,3 @@ def forecast(self) -> list[Forecast] | None:
270
208
)
271
209
272
210
return data
273
-
274
- @property
275
- def extra_state_attributes (self ) -> ExtraAttributes :
276
- """Return SMHI specific attributes."""
277
- extra_attributes : ExtraAttributes = {}
278
- if self .cloudiness is not None :
279
- extra_attributes [ATTR_SMHI_CLOUDINESS ] = self .cloudiness
280
- if self .wind_gust_speed is not None :
281
- extra_attributes [ATTR_SMHI_WIND_GUST_SPEED ] = self .wind_gust_speed
282
- if self .thunder_probability is not None :
283
- extra_attributes [ATTR_SMHI_THUNDER_PROBABILITY ] = self .thunder_probability
284
- return extra_attributes
285
-
286
-
287
- class ExtraAttributes (TypedDict , total = False ):
288
- """Represent the extra state attribute types."""
289
-
290
- cloudiness : int
291
- thunder_probability : int
292
- wind_gust_speed : float
0 commit comments