diff --git a/homeassistant/components/aemet/weather.py b/homeassistant/components/aemet/weather.py index aba5a2781d0..f9b0f7ef6ca 100644 --- a/homeassistant/components/aemet/weather.py +++ b/homeassistant/components/aemet/weather.py @@ -1,4 +1,6 @@ """Support for the AEMET OpenData service.""" +from typing import cast + from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_NATIVE_PRECIPITATION, @@ -8,7 +10,10 @@ from homeassistant.components.weather import ( ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, + DOMAIN as WEATHER_DOMAIN, + Forecast, WeatherEntity, + WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -17,7 +22,8 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -79,10 +85,28 @@ async def async_setup_entry( weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR] entities = [] - for mode in FORECAST_MODES: - name = f"{domain_data[ENTRY_NAME]} {mode}" - unique_id = f"{config_entry.unique_id} {mode}" - entities.append(AemetWeather(name, unique_id, weather_coordinator, mode)) + entity_registry = er.async_get(hass) + + # Add daily + hourly entity for legacy config entries, only add daily for new + # config entries. This can be removed in HA Core 2024.3 + if entity_registry.async_get_entity_id( + WEATHER_DOMAIN, + DOMAIN, + f"{config_entry.unique_id} {FORECAST_MODE_HOURLY}", + ): + for mode in FORECAST_MODES: + name = f"{domain_data[ENTRY_NAME]} {mode}" + unique_id = f"{config_entry.unique_id} {mode}" + entities.append(AemetWeather(name, unique_id, weather_coordinator, mode)) + else: + entities.append( + AemetWeather( + domain_data[ENTRY_NAME], + config_entry.unique_id, + weather_coordinator, + FORECAST_MODE_DAILY, + ) + ) async_add_entities(entities, False) @@ -95,6 +119,9 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity): _attr_native_pressure_unit = UnitOfPressure.HPA _attr_native_temperature_unit = UnitOfTemperature.CELSIUS _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR + _attr_supported_features = ( + WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY + ) def __init__( self, @@ -112,20 +139,44 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity): self._attr_name = name self._attr_unique_id = unique_id + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + super()._handle_coordinator_update() + assert self.platform.config_entry + self.platform.config_entry.async_create_task( + self.hass, self.async_update_listeners(("daily", "hourly")) + ) + @property def condition(self): """Return the current condition.""" return self.coordinator.data[ATTR_API_CONDITION] - @property - def forecast(self): + def _forecast(self, forecast_mode: str) -> list[Forecast]: """Return the forecast array.""" - forecasts = self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]] - forecast_map = FORECAST_MAP[self._forecast_mode] - return [ - {ha_key: forecast[api_key] for api_key, ha_key in forecast_map.items()} - for forecast in forecasts - ] + forecasts = self.coordinator.data[FORECAST_MODE_ATTR_API[forecast_mode]] + forecast_map = FORECAST_MAP[forecast_mode] + return cast( + list[Forecast], + [ + {ha_key: forecast[api_key] for api_key, ha_key in forecast_map.items()} + for forecast in forecasts + ], + ) + + @property + def forecast(self) -> list[Forecast]: + """Return the forecast array.""" + return self._forecast(self._forecast_mode) + + async def async_forecast_daily(self) -> list[Forecast]: + """Return the daily forecast in native units.""" + return self._forecast(FORECAST_MODE_DAILY) + + async def async_forecast_hourly(self) -> list[Forecast]: + """Return the hourly forecast in native units.""" + return self._forecast(FORECAST_MODE_HOURLY) @property def humidity(self): diff --git a/tests/components/aemet/snapshots/test_weather.ambr b/tests/components/aemet/snapshots/test_weather.ambr new file mode 100644 index 00000000000..e9c922f041e --- /dev/null +++ b/tests/components/aemet/snapshots/test_weather.ambr @@ -0,0 +1,1238 @@ +# serializer version: 1 +# name: test_forecast_service + dict({ + 'forecast': list([ + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T00:00:00+00:00', + 'precipitation_probability': 30, + 'temperature': 4.0, + 'templow': -4.0, + 'wind_bearing': 45.0, + 'wind_speed': 20.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-11T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 3.0, + 'templow': -7.0, + 'wind_bearing': 0.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-12T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'templow': -13.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'sunny', + 'datetime': '2021-01-13T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 6.0, + 'templow': -11.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-14T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 6.0, + 'templow': -7.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-15T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 5.0, + 'templow': -4.0, + 'wind_bearing': None, + }), + ]), + }) +# --- +# name: test_forecast_service.1 + dict({ + 'forecast': list([ + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T12:00:00+00:00', + 'precipitation': 3.6, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 90.0, + 'wind_speed': 15.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T13:00:00+00:00', + 'precipitation': 2.7, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 135.0, + 'wind_speed': 15.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T14:00:00+00:00', + 'precipitation': 0.6, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 135.0, + 'wind_speed': 14.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T15:00:00+00:00', + 'precipitation': 0.8, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T16:00:00+00:00', + 'precipitation': 1.4, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T17:00:00+00:00', + 'precipitation': 1.2, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 9.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T18:00:00+00:00', + 'precipitation': 0.4, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 7.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T19:00:00+00:00', + 'precipitation': 0.3, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T20:00:00+00:00', + 'precipitation': 0.1, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-09T21:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-09T22:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-09T23:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T01:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T02:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 0.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T03:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 0.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T04:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T05:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 0.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T06:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 0.0, + 'wind_speed': 9.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T07:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 13.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T08:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T09:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T10:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T11:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 2.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T12:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 22.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T13:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 20.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T14:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T15:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 4.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T16:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T17:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 2.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T18:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T19:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T20:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T21:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T22:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T23:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-11T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-11T01:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T02:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 12.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T03:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T04:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -3.0, + 'wind_bearing': 45.0, + 'wind_speed': 11.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T05:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -4.0, + 'wind_bearing': 45.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T06:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -4.0, + 'wind_bearing': 0.0, + 'wind_speed': 10.0, + }), + ]), + }) +# --- +# name: test_forecast_subscription[daily] + list([ + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T00:00:00+00:00', + 'precipitation_probability': 30, + 'temperature': 4.0, + 'templow': -4.0, + 'wind_bearing': 45.0, + 'wind_speed': 20.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-11T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 3.0, + 'templow': -7.0, + 'wind_bearing': 0.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-12T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'templow': -13.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'sunny', + 'datetime': '2021-01-13T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 6.0, + 'templow': -11.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-14T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 6.0, + 'templow': -7.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-15T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 5.0, + 'templow': -4.0, + 'wind_bearing': None, + }), + ]) +# --- +# name: test_forecast_subscription[daily].1 + list([ + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T00:00:00+00:00', + 'precipitation_probability': 30, + 'temperature': 4.0, + 'templow': -4.0, + 'wind_bearing': 45.0, + 'wind_speed': 20.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-11T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 3.0, + 'templow': -7.0, + 'wind_bearing': 0.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-12T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'templow': -13.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'sunny', + 'datetime': '2021-01-13T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 6.0, + 'templow': -11.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-14T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 6.0, + 'templow': -7.0, + 'wind_bearing': None, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-15T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 5.0, + 'templow': -4.0, + 'wind_bearing': None, + }), + ]) +# --- +# name: test_forecast_subscription[hourly] + list([ + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T12:00:00+00:00', + 'precipitation': 3.6, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 90.0, + 'wind_speed': 15.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T13:00:00+00:00', + 'precipitation': 2.7, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 135.0, + 'wind_speed': 15.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T14:00:00+00:00', + 'precipitation': 0.6, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 135.0, + 'wind_speed': 14.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T15:00:00+00:00', + 'precipitation': 0.8, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T16:00:00+00:00', + 'precipitation': 1.4, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T17:00:00+00:00', + 'precipitation': 1.2, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 9.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T18:00:00+00:00', + 'precipitation': 0.4, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 7.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T19:00:00+00:00', + 'precipitation': 0.3, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T20:00:00+00:00', + 'precipitation': 0.1, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-09T21:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-09T22:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-09T23:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T01:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T02:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 0.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T03:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 0.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T04:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T05:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 0.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T06:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 0.0, + 'wind_speed': 9.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T07:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 13.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T08:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T09:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T10:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T11:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 2.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T12:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 22.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T13:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 20.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T14:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T15:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 4.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T16:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T17:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 2.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T18:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T19:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T20:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T21:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T22:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T23:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-11T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-11T01:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T02:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 12.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T03:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T04:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -3.0, + 'wind_bearing': 45.0, + 'wind_speed': 11.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T05:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -4.0, + 'wind_bearing': 45.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T06:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -4.0, + 'wind_bearing': 0.0, + 'wind_speed': 10.0, + }), + ]) +# --- +# name: test_forecast_subscription[hourly].1 + list([ + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T12:00:00+00:00', + 'precipitation': 3.6, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 90.0, + 'wind_speed': 15.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T13:00:00+00:00', + 'precipitation': 2.7, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 135.0, + 'wind_speed': 15.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T14:00:00+00:00', + 'precipitation': 0.6, + 'precipitation_probability': 100, + 'temperature': 0.0, + 'wind_bearing': 135.0, + 'wind_speed': 14.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T15:00:00+00:00', + 'precipitation': 0.8, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T16:00:00+00:00', + 'precipitation': 1.4, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T17:00:00+00:00', + 'precipitation': 1.2, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 9.0, + }), + dict({ + 'condition': 'snowy', + 'datetime': '2021-01-09T18:00:00+00:00', + 'precipitation': 0.4, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 7.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T19:00:00+00:00', + 'precipitation': 0.3, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'rainy', + 'datetime': '2021-01-09T20:00:00+00:00', + 'precipitation': 0.1, + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 135.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-09T21:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-09T22:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-09T23:00:00+00:00', + 'precipitation_probability': 100, + 'temperature': 1.0, + 'wind_bearing': 90.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T01:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T02:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 0.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'fog', + 'datetime': '2021-01-10T03:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': 0.0, + 'wind_bearing': 0.0, + 'wind_speed': 6.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T04:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 8.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T05:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 0.0, + 'wind_speed': 5.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T06:00:00+00:00', + 'precipitation_probability': 10, + 'temperature': -1.0, + 'wind_bearing': 0.0, + 'wind_speed': 9.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T07:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 13.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T08:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T09:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T10:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T11:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 2.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T12:00:00+00:00', + 'precipitation_probability': 15, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 22.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T13:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 20.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T14:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T15:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 4.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T16:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 3.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T17:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 2.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T18:00:00+00:00', + 'precipitation_probability': 5, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-10T19:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T20:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T21:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 1.0, + 'wind_bearing': 45.0, + 'wind_speed': 17.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T22:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-10T23:00:00+00:00', + 'precipitation_probability': None, + 'temperature': 0.0, + 'wind_bearing': 45.0, + 'wind_speed': 21.0, + }), + dict({ + 'condition': 'cloudy', + 'datetime': '2021-01-11T00:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 19.0, + }), + dict({ + 'condition': 'partlycloudy', + 'datetime': '2021-01-11T01:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -1.0, + 'wind_bearing': 45.0, + 'wind_speed': 16.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T02:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 12.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T03:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -2.0, + 'wind_bearing': 45.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T04:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -3.0, + 'wind_bearing': 45.0, + 'wind_speed': 11.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T05:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -4.0, + 'wind_bearing': 45.0, + 'wind_speed': 10.0, + }), + dict({ + 'condition': 'clear-night', + 'datetime': '2021-01-11T06:00:00+00:00', + 'precipitation_probability': None, + 'temperature': -4.0, + 'wind_bearing': 0.0, + 'wind_speed': 10.0, + }), + ]) +# --- diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index 30b11876e74..c64e824e18d 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -1,7 +1,16 @@ """The sensor tests for the AEMET OpenData platform.""" +import datetime from unittest.mock import patch -from homeassistant.components.aemet.const import ATTRIBUTION +from freezegun.api import FrozenDateTimeFactory +import pytest +import requests_mock +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.aemet.const import ATTRIBUTION, DOMAIN +from homeassistant.components.aemet.weather_update_coordinator import ( + WEATHER_UPDATE_INTERVAL, +) from homeassistant.components.weather import ( ATTR_CONDITION_PARTLYCLOUDY, ATTR_CONDITION_SNOWY, @@ -19,17 +28,65 @@ from homeassistant.components.weather import ( ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, + DOMAIN as WEATHER_DOMAIN, + SERVICE_GET_FORECAST, ) from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util -from .util import async_init_integration +from .util import aemet_requests_mock, async_init_integration + +from tests.typing import WebSocketGenerator async def test_aemet_weather(hass: HomeAssistant) -> None: """Test states of the weather.""" + hass.config.set_time_zone("UTC") + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ): + await async_init_integration(hass) + + state = hass.states.get("weather.aemet") + assert state + assert state.state == ATTR_CONDITION_SNOWY + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 99.0 + assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1004.4 # 100440.0 Pa -> hPa + assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == -0.7 + assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 90.0 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 15.0 # 4.17 m/s -> km/h + forecast = state.attributes.get(ATTR_FORECAST)[0] + assert forecast.get(ATTR_FORECAST_CONDITION) == ATTR_CONDITION_PARTLYCLOUDY + assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None + assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 30 + assert forecast.get(ATTR_FORECAST_TEMP) == 4 + assert forecast.get(ATTR_FORECAST_TEMP_LOW) == -4 + assert ( + forecast.get(ATTR_FORECAST_TIME) + == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() + ) + assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0 + assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20.0 # 5.56 m/s -> km/h + + state = hass.states.get("weather.aemet_hourly") + assert state is None + + +async def test_aemet_weather_legacy(hass: HomeAssistant) -> None: + """Test states of the weather.""" + + registry = er.async_get(hass) + registry.async_get_or_create( + WEATHER_DOMAIN, + DOMAIN, + "None hourly", + ) + hass.config.set_time_zone("UTC") now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") with patch("homeassistant.util.dt.now", return_value=now), patch( @@ -61,3 +118,87 @@ async def test_aemet_weather(hass: HomeAssistant) -> None: state = hass.states.get("weather.aemet_hourly") assert state is None + + +async def test_forecast_service( + hass: HomeAssistant, + snapshot: SnapshotAssertion, +) -> None: + """Test multiple forecast.""" + hass.config.set_time_zone("UTC") + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ): + await async_init_integration(hass) + + response = await hass.services.async_call( + WEATHER_DOMAIN, + SERVICE_GET_FORECAST, + { + "entity_id": "weather.aemet", + "type": "daily", + }, + blocking=True, + return_response=True, + ) + assert response == snapshot + + response = await hass.services.async_call( + WEATHER_DOMAIN, + SERVICE_GET_FORECAST, + { + "entity_id": "weather.aemet", + "type": "hourly", + }, + blocking=True, + return_response=True, + ) + assert response == snapshot + + +@pytest.mark.parametrize("forecast_type", ["daily", "hourly"]) +async def test_forecast_subscription( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, + forecast_type: str, +) -> None: + """Test multiple forecast.""" + client = await hass_ws_client(hass) + + hass.config.set_time_zone("UTC") + freezer.move_to("2021-01-09 12:00:00+00:00") + await async_init_integration(hass) + + await client.send_json_auto_id( + { + "type": "weather/subscribe_forecast", + "forecast_type": forecast_type, + "entity_id": "weather.aemet", + } + ) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] is None + subscription_id = msg["id"] + + msg = await client.receive_json() + assert msg["id"] == subscription_id + assert msg["type"] == "event" + forecast1 = msg["event"]["forecast"] + + assert forecast1 == snapshot + + with requests_mock.mock() as _m: + aemet_requests_mock(_m) + freezer.tick(WEATHER_UPDATE_INTERVAL + datetime.timedelta(seconds=1)) + await hass.async_block_till_done() + msg = await client.receive_json() + + assert msg["id"] == subscription_id + assert msg["type"] == "event" + forecast2 = msg["event"]["forecast"] + + assert forecast2 == snapshot