mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Code quality improvements smhi (#64312)
This commit is contained in:
parent
7e40707288
commit
bbb29ab455
@ -154,6 +154,7 @@ homeassistant.components.senseme.*
|
||||
homeassistant.components.shelly.*
|
||||
homeassistant.components.simplisafe.*
|
||||
homeassistant.components.slack.*
|
||||
homeassistant.components.smhi.*
|
||||
homeassistant.components.sonos.media_player
|
||||
homeassistant.components.ssdp.*
|
||||
homeassistant.components.stookalert.*
|
||||
|
@ -847,6 +847,8 @@ tests/components/smartthings/* @andrewsayre
|
||||
homeassistant/components/smarttub/* @mdz
|
||||
tests/components/smarttub/* @mdz
|
||||
homeassistant/components/smarty/* @z0mbieprocess
|
||||
homeassistant/components/smhi/* @gjohansson-ST
|
||||
tests/components/smhi/* @gjohansson-ST
|
||||
homeassistant/components/sms/* @ocalvo
|
||||
homeassistant/components/smtp/* @fabaff
|
||||
tests/components/smtp/* @fabaff
|
||||
|
@ -4,6 +4,6 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/smhi",
|
||||
"requirements": ["smhi-pkg==1.0.15"],
|
||||
"codeowners": [],
|
||||
"codeowners": ["@gjohansson-ST"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Final, TypedDict
|
||||
from typing import Final
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
@ -35,7 +35,14 @@ from homeassistant.components.weather import (
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS
|
||||
from homeassistant.const import (
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_NAME,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILLIMETERS,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
@ -72,6 +79,7 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||
ATTR_CONDITION_EXCEPTIONAL: [],
|
||||
}
|
||||
|
||||
TIMEOUT = 10
|
||||
# 5 minutes between retrying connect to API again
|
||||
RETRY_TIMEOUT = 5 * 60
|
||||
|
||||
@ -103,6 +111,11 @@ async def async_setup_entry(
|
||||
class SmhiWeather(WeatherEntity):
|
||||
"""Representation of a weather entity."""
|
||||
|
||||
_attr_attribution = "Swedish weather institute (SMHI)"
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
_attr_visibility_unit = LENGTH_KILOMETERS
|
||||
_attr_precipitation_unit = LENGTH_MILLIMETERS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
@ -112,39 +125,58 @@ class SmhiWeather(WeatherEntity):
|
||||
) -> None:
|
||||
"""Initialize the SMHI weather entity."""
|
||||
|
||||
self._name = name
|
||||
self._latitude = latitude
|
||||
self._longitude = longitude
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = f"{latitude}, {longitude}"
|
||||
self._forecasts: list[SmhiForecast] | None = None
|
||||
self._fail_count = 0
|
||||
self._smhi_api = Smhi(self._longitude, self._latitude, session=session)
|
||||
self._smhi_api = Smhi(longitude, latitude, session=session)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, f"{self._latitude}, {self._longitude}")},
|
||||
identifiers={(DOMAIN, f"{latitude}, {longitude}")},
|
||||
manufacturer="SMHI",
|
||||
model="v2",
|
||||
name=self._name,
|
||||
name=name,
|
||||
configuration_url="http://opendata.smhi.se/apidocs/metfcst/parameters.html",
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique id."""
|
||||
return f"{self._latitude}, {self._longitude}"
|
||||
self._attr_condition = None
|
||||
self._attr_temperature = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def async_update(self) -> None:
|
||||
"""Refresh the forecast data from SMHI weather API."""
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
self._forecasts = await self.get_weather_forecast()
|
||||
async with async_timeout.timeout(TIMEOUT):
|
||||
self._forecasts = await self._smhi_api.async_get_forecast()
|
||||
self._fail_count = 0
|
||||
|
||||
except (asyncio.TimeoutError, SmhiForecastException):
|
||||
_LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes")
|
||||
self._fail_count += 1
|
||||
if self._fail_count < 3:
|
||||
async_call_later(self.hass, RETRY_TIMEOUT, self.retry_update)
|
||||
return
|
||||
|
||||
if self._forecasts:
|
||||
self._attr_temperature = self._forecasts[0].temperature
|
||||
self._attr_humidity = self._forecasts[0].humidity
|
||||
# Convert from m/s to km/h
|
||||
self._attr_wind_speed = round(self._forecasts[0].wind_speed * 18 / 5)
|
||||
self._attr_wind_bearing = self._forecasts[0].wind_direction
|
||||
self._attr_visibility = self._forecasts[0].horizontal_visibility
|
||||
self._attr_pressure = self._forecasts[0].pressure
|
||||
self._attr_condition = next(
|
||||
(
|
||||
k
|
||||
for k, v in CONDITION_CLASSES.items()
|
||||
if self._forecasts[0].symbol in v
|
||||
),
|
||||
None,
|
||||
)
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_SMHI_CLOUDINESS: self._forecasts[0].cloudiness,
|
||||
# Convert from m/s to km/h
|
||||
ATTR_SMHI_WIND_GUST_SPEED: round(self._forecasts[0].wind_gust * 18 / 5),
|
||||
ATTR_SMHI_THUNDER_PROBABILITY: self._forecasts[0].thunder,
|
||||
}
|
||||
|
||||
async def retry_update(self, _: datetime) -> None:
|
||||
"""Retry refresh weather forecast."""
|
||||
@ -152,100 +184,6 @@ class SmhiWeather(WeatherEntity):
|
||||
no_throttle=True
|
||||
)
|
||||
|
||||
async def get_weather_forecast(self) -> list[SmhiForecast]:
|
||||
"""Return the current forecasts from SMHI API."""
|
||||
return await self._smhi_api.async_get_forecast()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def temperature(self) -> int | None:
|
||||
"""Return the temperature."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].temperature
|
||||
return None
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def humidity(self) -> int | None:
|
||||
"""Return the humidity."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].humidity
|
||||
return None
|
||||
|
||||
@property
|
||||
def wind_speed(self) -> float | None:
|
||||
"""Return the wind speed."""
|
||||
if self._forecasts is not None:
|
||||
# Convert from m/s to km/h
|
||||
return round(self._forecasts[0].wind_speed * 18 / 5)
|
||||
return None
|
||||
|
||||
@property
|
||||
def wind_gust_speed(self) -> float | None:
|
||||
"""Return the wind gust speed."""
|
||||
if self._forecasts is not None:
|
||||
# Convert from m/s to km/h
|
||||
return round(self._forecasts[0].wind_gust * 18 / 5)
|
||||
return None
|
||||
|
||||
@property
|
||||
def wind_bearing(self) -> int | None:
|
||||
"""Return the wind bearing."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].wind_direction
|
||||
return None
|
||||
|
||||
@property
|
||||
def visibility(self) -> float | None:
|
||||
"""Return the visibility."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].horizontal_visibility
|
||||
return None
|
||||
|
||||
@property
|
||||
def pressure(self) -> int | None:
|
||||
"""Return the pressure."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].pressure
|
||||
return None
|
||||
|
||||
@property
|
||||
def cloudiness(self) -> int | None:
|
||||
"""Return the cloudiness."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].cloudiness
|
||||
return None
|
||||
|
||||
@property
|
||||
def thunder_probability(self) -> int | None:
|
||||
"""Return the chance of thunder, unit Percent."""
|
||||
if self._forecasts is not None:
|
||||
return self._forecasts[0].thunder
|
||||
return None
|
||||
|
||||
@property
|
||||
def condition(self) -> str | None:
|
||||
"""Return the weather condition."""
|
||||
if self._forecasts is None:
|
||||
return None
|
||||
return next(
|
||||
(k for k, v in CONDITION_CLASSES.items() if self._forecasts[0].symbol in v),
|
||||
None,
|
||||
)
|
||||
|
||||
@property
|
||||
def attribution(self) -> str:
|
||||
"""Return the attribution."""
|
||||
return "Swedish weather institute (SMHI)"
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast."""
|
||||
@ -270,23 +208,3 @@ class SmhiWeather(WeatherEntity):
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> ExtraAttributes:
|
||||
"""Return SMHI specific attributes."""
|
||||
extra_attributes: ExtraAttributes = {}
|
||||
if self.cloudiness is not None:
|
||||
extra_attributes[ATTR_SMHI_CLOUDINESS] = self.cloudiness
|
||||
if self.wind_gust_speed is not None:
|
||||
extra_attributes[ATTR_SMHI_WIND_GUST_SPEED] = self.wind_gust_speed
|
||||
if self.thunder_probability is not None:
|
||||
extra_attributes[ATTR_SMHI_THUNDER_PROBABILITY] = self.thunder_probability
|
||||
return extra_attributes
|
||||
|
||||
|
||||
class ExtraAttributes(TypedDict, total=False):
|
||||
"""Represent the extra state attribute types."""
|
||||
|
||||
cloudiness: int
|
||||
thunder_probability: int
|
||||
wind_gust_speed: float
|
||||
|
11
mypy.ini
11
mypy.ini
@ -1506,6 +1506,17 @@ no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.smhi.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.sonos.media_player]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
Loading…
x
Reference in New Issue
Block a user