mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 13:47:35 +00:00
Update buienweather data before adding entities (#98455)
* Update buienweather data before adding entities * Fix tests
This commit is contained in:
parent
ffe3d7c255
commit
90413daa8a
@ -14,10 +14,10 @@ CONF_TIMEFRAME = "timeframe"
|
|||||||
SUPPORTED_COUNTRY_CODES = ["NL", "BE"]
|
SUPPORTED_COUNTRY_CODES = ["NL", "BE"]
|
||||||
DEFAULT_COUNTRY = "NL"
|
DEFAULT_COUNTRY = "NL"
|
||||||
|
|
||||||
"""Schedule next call after (minutes)."""
|
|
||||||
SCHEDULE_OK = 10
|
SCHEDULE_OK = 10
|
||||||
"""When an error occurred, new call after (minutes)."""
|
"""Schedule next call after (minutes)."""
|
||||||
SCHEDULE_NOK = 2
|
SCHEDULE_NOK = 2
|
||||||
|
"""When an error occurred, new call after (minutes)."""
|
||||||
|
|
||||||
STATE_CONDITIONS = ["clear", "cloudy", "fog", "rainy", "snowy", "lightning"]
|
STATE_CONDITIONS = ["clear", "cloudy", "fog", "rainy", "snowy", "lightning"]
|
||||||
|
|
||||||
|
@ -714,17 +714,18 @@ async def async_setup_entry(
|
|||||||
timeframe,
|
timeframe,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# create weather entities:
|
||||||
entities = [
|
entities = [
|
||||||
BrSensor(config.get(CONF_NAME, "Buienradar"), coordinates, description)
|
BrSensor(config.get(CONF_NAME, "Buienradar"), coordinates, description)
|
||||||
for description in SENSOR_TYPES
|
for description in SENSOR_TYPES
|
||||||
]
|
]
|
||||||
|
|
||||||
async_add_entities(entities)
|
# create weather data:
|
||||||
|
|
||||||
data = BrData(hass, coordinates, timeframe, entities)
|
data = BrData(hass, coordinates, timeframe, entities)
|
||||||
# schedule the first update in 1 minute from now:
|
|
||||||
await data.schedule_update(1)
|
|
||||||
hass.data[DOMAIN][entry.entry_id][Platform.SENSOR] = data
|
hass.data[DOMAIN][entry.entry_id][Platform.SENSOR] = data
|
||||||
|
await data.async_update()
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class BrSensor(SensorEntity):
|
class BrSensor(SensorEntity):
|
||||||
@ -755,7 +756,7 @@ class BrSensor(SensorEntity):
|
|||||||
@callback
|
@callback
|
||||||
def data_updated(self, data: BrData):
|
def data_updated(self, data: BrData):
|
||||||
"""Update data."""
|
"""Update data."""
|
||||||
if self.hass and self._load_data(data.data):
|
if self._load_data(data.data) and self.hass:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -27,7 +27,7 @@ from buienradar.constants import (
|
|||||||
from buienradar.urls import JSON_FEED_URL, json_precipitation_forecast_url
|
from buienradar.urls import JSON_FEED_URL, json_precipitation_forecast_url
|
||||||
|
|
||||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||||
from homeassistant.core import CALLBACK_TYPE
|
from homeassistant.core import CALLBACK_TYPE, callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
@ -77,7 +77,8 @@ class BrData:
|
|||||||
for dev in self.devices:
|
for dev in self.devices:
|
||||||
dev.data_updated(self)
|
dev.data_updated(self)
|
||||||
|
|
||||||
async def schedule_update(self, minute=1):
|
@callback
|
||||||
|
def async_schedule_update(self, minute=1):
|
||||||
"""Schedule an update after minute minutes."""
|
"""Schedule an update after minute minutes."""
|
||||||
_LOGGER.debug("Scheduling next update in %s minutes", minute)
|
_LOGGER.debug("Scheduling next update in %s minutes", minute)
|
||||||
nxt = dt_util.utcnow() + timedelta(minutes=minute)
|
nxt = dt_util.utcnow() + timedelta(minutes=minute)
|
||||||
@ -110,7 +111,7 @@ class BrData:
|
|||||||
if resp is not None:
|
if resp is not None:
|
||||||
await resp.release()
|
await resp.release()
|
||||||
|
|
||||||
async def async_update(self, *_):
|
async def _async_update(self):
|
||||||
"""Update the data from buienradar."""
|
"""Update the data from buienradar."""
|
||||||
content = await self.get_data(JSON_FEED_URL)
|
content = await self.get_data(JSON_FEED_URL)
|
||||||
|
|
||||||
@ -123,9 +124,7 @@ class BrData:
|
|||||||
content.get(MESSAGE),
|
content.get(MESSAGE),
|
||||||
content.get(STATUS_CODE),
|
content.get(STATUS_CODE),
|
||||||
)
|
)
|
||||||
# schedule new call
|
return None
|
||||||
await self.schedule_update(SCHEDULE_NOK)
|
|
||||||
return
|
|
||||||
self.load_error_count = 0
|
self.load_error_count = 0
|
||||||
|
|
||||||
# rounding coordinates prevents unnecessary redirects/calls
|
# rounding coordinates prevents unnecessary redirects/calls
|
||||||
@ -143,9 +142,7 @@ class BrData:
|
|||||||
raincontent.get(MESSAGE),
|
raincontent.get(MESSAGE),
|
||||||
raincontent.get(STATUS_CODE),
|
raincontent.get(STATUS_CODE),
|
||||||
)
|
)
|
||||||
# schedule new call
|
return None
|
||||||
await self.schedule_update(SCHEDULE_NOK)
|
|
||||||
return
|
|
||||||
self.rain_error_count = 0
|
self.rain_error_count = 0
|
||||||
|
|
||||||
result = parse_data(
|
result = parse_data(
|
||||||
@ -164,12 +161,21 @@ class BrData:
|
|||||||
"Unable to parse data from Buienradar. (Msg: %s)",
|
"Unable to parse data from Buienradar. (Msg: %s)",
|
||||||
result.get(MESSAGE),
|
result.get(MESSAGE),
|
||||||
)
|
)
|
||||||
await self.schedule_update(SCHEDULE_NOK)
|
return None
|
||||||
|
|
||||||
|
return result[DATA]
|
||||||
|
|
||||||
|
async def async_update(self, *_):
|
||||||
|
"""Update the data from buienradar and schedule the next update."""
|
||||||
|
data = await self._async_update()
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
self.async_schedule_update(SCHEDULE_NOK)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.data = result.get(DATA)
|
self.data = data
|
||||||
await self.update_devices()
|
await self.update_devices()
|
||||||
await self.schedule_update(SCHEDULE_OK)
|
self.async_schedule_update(SCHEDULE_OK)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attribution(self):
|
def attribution(self):
|
||||||
|
@ -82,6 +82,11 @@ CONDITION_CLASSES = {
|
|||||||
ATTR_CONDITION_WINDY_VARIANT: (),
|
ATTR_CONDITION_WINDY_VARIANT: (),
|
||||||
ATTR_CONDITION_EXCEPTIONAL: (),
|
ATTR_CONDITION_EXCEPTIONAL: (),
|
||||||
}
|
}
|
||||||
|
CONDITION_MAP = {
|
||||||
|
cond_code: cond_ha
|
||||||
|
for cond_ha, cond_codes in CONDITION_CLASSES.items()
|
||||||
|
for cond_code in cond_codes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -106,20 +111,10 @@ async def async_setup_entry(
|
|||||||
# create weather data:
|
# create weather data:
|
||||||
data = BrData(hass, coordinates, DEFAULT_TIMEFRAME, entities)
|
data = BrData(hass, coordinates, DEFAULT_TIMEFRAME, entities)
|
||||||
hass.data[DOMAIN][entry.entry_id][Platform.WEATHER] = data
|
hass.data[DOMAIN][entry.entry_id][Platform.WEATHER] = data
|
||||||
|
await data.async_update()
|
||||||
# create condition helper
|
|
||||||
if DATA_CONDITION not in hass.data[DOMAIN]:
|
|
||||||
cond_keys = [str(chr(x)) for x in range(97, 123)]
|
|
||||||
hass.data[DOMAIN][DATA_CONDITION] = dict.fromkeys(cond_keys)
|
|
||||||
for cond, condlst in CONDITION_CLASSES.items():
|
|
||||||
for condi in condlst:
|
|
||||||
hass.data[DOMAIN][DATA_CONDITION][condi] = cond
|
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
# schedule the first update in 1 minute from now:
|
|
||||||
await data.schedule_update(1)
|
|
||||||
|
|
||||||
|
|
||||||
class BrWeather(WeatherEntity):
|
class BrWeather(WeatherEntity):
|
||||||
"""Representation of a weather condition."""
|
"""Representation of a weather condition."""
|
||||||
@ -143,9 +138,6 @@ class BrWeather(WeatherEntity):
|
|||||||
@callback
|
@callback
|
||||||
def data_updated(self, data: BrData) -> None:
|
def data_updated(self, data: BrData) -> None:
|
||||||
"""Update data."""
|
"""Update data."""
|
||||||
if not self.hass:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._attr_attribution = data.attribution
|
self._attr_attribution = data.attribution
|
||||||
self._attr_condition = self._calc_condition(data)
|
self._attr_condition = self._calc_condition(data)
|
||||||
self._attr_forecast = self._calc_forecast(data)
|
self._attr_forecast = self._calc_forecast(data)
|
||||||
@ -158,22 +150,20 @@ class BrWeather(WeatherEntity):
|
|||||||
self._attr_native_visibility = data.visibility
|
self._attr_native_visibility = data.visibility
|
||||||
self._attr_native_wind_speed = data.wind_speed
|
self._attr_native_wind_speed = data.wind_speed
|
||||||
self._attr_wind_bearing = data.wind_bearing
|
self._attr_wind_bearing = data.wind_bearing
|
||||||
|
|
||||||
|
if not self.hass:
|
||||||
|
return
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
def _calc_condition(self, data: BrData):
|
def _calc_condition(self, data: BrData):
|
||||||
"""Return the current condition."""
|
"""Return the current condition."""
|
||||||
if (
|
if data.condition and (ccode := data.condition.get(CONDCODE)):
|
||||||
data.condition
|
return CONDITION_MAP.get(ccode)
|
||||||
and (ccode := data.condition.get(CONDCODE))
|
|
||||||
and (conditions := self.hass.data[DOMAIN].get(DATA_CONDITION))
|
|
||||||
):
|
|
||||||
return conditions.get(ccode)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _calc_forecast(self, data: BrData):
|
def _calc_forecast(self, data: BrData):
|
||||||
"""Return the forecast array."""
|
"""Return the forecast array."""
|
||||||
fcdata_out = []
|
fcdata_out = []
|
||||||
cond = self.hass.data[DOMAIN][DATA_CONDITION]
|
|
||||||
|
|
||||||
if not data.forecast:
|
if not data.forecast:
|
||||||
return None
|
return None
|
||||||
@ -181,10 +171,10 @@ class BrWeather(WeatherEntity):
|
|||||||
for data_in in data.forecast:
|
for data_in in data.forecast:
|
||||||
# remap keys from external library to
|
# remap keys from external library to
|
||||||
# keys understood by the weather component:
|
# keys understood by the weather component:
|
||||||
condcode = data_in.get(CONDITION, []).get(CONDCODE)
|
condcode = data_in.get(CONDITION, {}).get(CONDCODE)
|
||||||
data_out = {
|
data_out = {
|
||||||
ATTR_FORECAST_TIME: data_in.get(DATETIME).isoformat(),
|
ATTR_FORECAST_TIME: data_in.get(DATETIME).isoformat(),
|
||||||
ATTR_FORECAST_CONDITION: cond[condcode],
|
ATTR_FORECAST_CONDITION: CONDITION_MAP.get(condcode),
|
||||||
ATTR_FORECAST_NATIVE_TEMP_LOW: data_in.get(MIN_TEMP),
|
ATTR_FORECAST_NATIVE_TEMP_LOW: data_in.get(MIN_TEMP),
|
||||||
ATTR_FORECAST_NATIVE_TEMP: data_in.get(MAX_TEMP),
|
ATTR_FORECAST_NATIVE_TEMP: data_in.get(MAX_TEMP),
|
||||||
ATTR_FORECAST_NATIVE_PRECIPITATION: data_in.get(RAIN),
|
ATTR_FORECAST_NATIVE_PRECIPITATION: data_in.get(RAIN),
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""The tests for the Buienradar sensor platform."""
|
"""The tests for the Buienradar sensor platform."""
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
from homeassistant.components.buienradar.const import DOMAIN
|
from homeassistant.components.buienradar.const import DOMAIN
|
||||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -18,6 +20,9 @@ async def test_smoke_test_setup_component(
|
|||||||
aioclient_mock: AiohttpClientMocker, hass: HomeAssistant
|
aioclient_mock: AiohttpClientMocker, hass: HomeAssistant
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Smoke test for successfully set-up with default config."""
|
"""Smoke test for successfully set-up with default config."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://data.buienradar.nl/2.0/feed/json", status=HTTPStatus.NOT_FOUND
|
||||||
|
)
|
||||||
mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA)
|
mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA)
|
||||||
|
|
||||||
mock_entry.add_to_hass(hass)
|
mock_entry.add_to_hass(hass)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""The tests for the buienradar weather component."""
|
"""The tests for the buienradar weather component."""
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
from homeassistant.components.buienradar.const import DOMAIN
|
from homeassistant.components.buienradar.const import DOMAIN
|
||||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -13,6 +15,9 @@ async def test_smoke_test_setup_component(
|
|||||||
aioclient_mock: AiohttpClientMocker, hass: HomeAssistant
|
aioclient_mock: AiohttpClientMocker, hass: HomeAssistant
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Smoke test for successfully set-up with default config."""
|
"""Smoke test for successfully set-up with default config."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://data.buienradar.nl/2.0/feed/json", status=HTTPStatus.NOT_FOUND
|
||||||
|
)
|
||||||
mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA)
|
mock_entry = MockConfigEntry(domain=DOMAIN, unique_id="TEST_ID", data=TEST_CFG_DATA)
|
||||||
|
|
||||||
mock_entry.add_to_hass(hass)
|
mock_entry.add_to_hass(hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user