mirror of
https://github.com/home-assistant/core.git
synced 2025-04-19 14:57:52 +00:00
Add AEMET Weather Radar images (#131386)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
43a420cf01
commit
5ef12c3993
@ -13,7 +13,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.storage import STORAGE_DIR
|
||||
|
||||
from .const import CONF_STATION_UPDATES, DOMAIN, PLATFORMS
|
||||
from .const import CONF_RADAR_UPDATES, CONF_STATION_UPDATES, DOMAIN, PLATFORMS
|
||||
from .coordinator import AemetConfigEntry, AemetData, WeatherUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -26,6 +26,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: AemetConfigEntry) -> boo
|
||||
latitude = entry.data[CONF_LATITUDE]
|
||||
longitude = entry.data[CONF_LONGITUDE]
|
||||
update_features: int = UpdateFeature.FORECAST
|
||||
if entry.options.get(CONF_RADAR_UPDATES, False):
|
||||
update_features |= UpdateFeature.RADAR
|
||||
if entry.options.get(CONF_STATION_UPDATES, True):
|
||||
update_features |= UpdateFeature.STATION
|
||||
|
||||
|
@ -17,10 +17,11 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaOptionsFlowHandler,
|
||||
)
|
||||
|
||||
from .const import CONF_STATION_UPDATES, DEFAULT_NAME, DOMAIN
|
||||
from .const import CONF_RADAR_UPDATES, CONF_STATION_UPDATES, DEFAULT_NAME, DOMAIN
|
||||
|
||||
OPTIONS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_RADAR_UPDATES, default=False): bool,
|
||||
vol.Required(CONF_STATION_UPDATES, default=True): bool,
|
||||
}
|
||||
)
|
||||
|
@ -51,8 +51,9 @@ from homeassistant.components.weather import (
|
||||
from homeassistant.const import Platform
|
||||
|
||||
ATTRIBUTION = "Powered by AEMET OpenData"
|
||||
CONF_RADAR_UPDATES = "radar_updates"
|
||||
CONF_STATION_UPDATES = "station_updates"
|
||||
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
||||
PLATFORMS = [Platform.IMAGE, Platform.SENSOR, Platform.WEATHER]
|
||||
DEFAULT_NAME = "AEMET"
|
||||
DOMAIN = "aemet"
|
||||
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aemet_opendata.const import AOD_COORDS
|
||||
from aemet_opendata.const import AOD_COORDS, AOD_IMG_BYTES
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import (
|
||||
@ -26,6 +26,7 @@ TO_REDACT_CONFIG = [
|
||||
|
||||
TO_REDACT_COORD = [
|
||||
AOD_COORDS,
|
||||
AOD_IMG_BYTES,
|
||||
]
|
||||
|
||||
|
||||
|
86
homeassistant/components/aemet/image.py
Normal file
86
homeassistant/components/aemet/image.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""Support for the AEMET OpenData images."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from aemet_opendata.const import AOD_DATETIME, AOD_IMG_BYTES, AOD_IMG_TYPE, AOD_RADAR
|
||||
from aemet_opendata.helpers import dict_nested_value
|
||||
|
||||
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
||||
from .entity import AemetEntity
|
||||
|
||||
AEMET_IMAGES: Final[tuple[ImageEntityDescription, ...]] = (
|
||||
ImageEntityDescription(
|
||||
key=AOD_RADAR,
|
||||
translation_key="weather_radar",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: AemetConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AEMET OpenData image entities based on a config entry."""
|
||||
domain_data = config_entry.runtime_data
|
||||
name = domain_data.name
|
||||
coordinator = domain_data.coordinator
|
||||
|
||||
unique_id = config_entry.unique_id
|
||||
assert unique_id is not None
|
||||
|
||||
async_add_entities(
|
||||
AemetImage(
|
||||
hass,
|
||||
name,
|
||||
coordinator,
|
||||
description,
|
||||
unique_id,
|
||||
)
|
||||
for description in AEMET_IMAGES
|
||||
if dict_nested_value(coordinator.data["lib"], [description.key]) is not None
|
||||
)
|
||||
|
||||
|
||||
class AemetImage(AemetEntity, ImageEntity):
|
||||
"""Implementation of an AEMET OpenData image."""
|
||||
|
||||
entity_description: ImageEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
name: str,
|
||||
coordinator: WeatherUpdateCoordinator,
|
||||
description: ImageEntityDescription,
|
||||
unique_id: str,
|
||||
) -> None:
|
||||
"""Initialize the image."""
|
||||
super().__init__(coordinator, name, unique_id)
|
||||
ImageEntity.__init__(self, hass)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{unique_id}-{description.key}"
|
||||
|
||||
self._async_update_attrs()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update attributes when the coordinator updates."""
|
||||
self._async_update_attrs()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update image attributes."""
|
||||
image_data = self.get_aemet_value([self.entity_description.key])
|
||||
self._cached_image = Image(
|
||||
content_type=image_data.get(AOD_IMG_TYPE),
|
||||
content=image_data.get(AOD_IMG_BYTES),
|
||||
)
|
||||
self._attr_image_last_updated = image_data.get(AOD_DATETIME)
|
@ -18,10 +18,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"image": {
|
||||
"weather_radar": {
|
||||
"name": "Weather radar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"radar_updates": "Gather data from AEMET weather radar",
|
||||
"station_updates": "Gather data from AEMET weather stations"
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
'entry_id': '7442b231f139e813fc1939281123f220',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
'radar_updates': True,
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
@ -33,6 +34,12 @@
|
||||
]),
|
||||
}),
|
||||
'lib': dict({
|
||||
'radar': dict({
|
||||
'datetime': '2021-01-09T11:34:06.448809+00:00',
|
||||
'id': 'national',
|
||||
'image-bytes': '**REDACTED**',
|
||||
'image-type': 'image/gif',
|
||||
}),
|
||||
'station': dict({
|
||||
'altitude': 667.0,
|
||||
'coordinates': '**REDACTED**',
|
||||
|
@ -6,7 +6,11 @@ from aemet_opendata.exceptions import AuthError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.aemet.const import CONF_STATION_UPDATES, DOMAIN
|
||||
from homeassistant.components.aemet.const import (
|
||||
CONF_RADAR_UPDATES,
|
||||
CONF_STATION_UPDATES,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -61,13 +65,20 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("user_input", "expected"), [({}, True), ({CONF_STATION_UPDATES: False}, False)]
|
||||
("user_input", "expected"),
|
||||
[
|
||||
({}, {CONF_RADAR_UPDATES: False, CONF_STATION_UPDATES: True}),
|
||||
(
|
||||
{CONF_RADAR_UPDATES: False, CONF_STATION_UPDATES: False},
|
||||
{CONF_RADAR_UPDATES: False, CONF_STATION_UPDATES: False},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_form_options(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
user_input: dict[str, bool],
|
||||
expected: bool,
|
||||
expected: dict[str, bool],
|
||||
) -> None:
|
||||
"""Test the form options."""
|
||||
|
||||
@ -98,7 +109,8 @@ async def test_form_options(
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert entry.options == {
|
||||
CONF_STATION_UPDATES: expected,
|
||||
CONF_RADAR_UPDATES: expected[CONF_RADAR_UPDATES],
|
||||
CONF_STATION_UPDATES: expected[CONF_STATION_UPDATES],
|
||||
}
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
22
tests/components/aemet/test_image.py
Normal file
22
tests/components/aemet/test_image.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""The image tests for the AEMET OpenData platform."""
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_aemet_create_images(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test creation of AEMET images."""
|
||||
|
||||
await hass.config.async_set_time_zone("UTC")
|
||||
freezer.move_to("2021-01-09 12:00:00+00:00")
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("image.aemet_weather_radar")
|
||||
assert state is not None
|
||||
assert state.state == "2021-01-09T11:34:06.448809+00:00"
|
@ -3,9 +3,9 @@
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from aemet_opendata.const import ATTR_DATA
|
||||
from aemet_opendata.const import ATTR_BYTES, ATTR_DATA, ATTR_TIMESTAMP, ATTR_TYPE
|
||||
|
||||
from homeassistant.components.aemet.const import DOMAIN
|
||||
from homeassistant.components.aemet.const import CONF_RADAR_UPDATES, DOMAIN
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@ -19,6 +19,14 @@ FORECAST_HOURLY_DATA_MOCK = {
|
||||
ATTR_DATA: load_json_value_fixture("aemet/town-28065-forecast-hourly-data.json"),
|
||||
}
|
||||
|
||||
RADAR_DATA_MOCK = {
|
||||
ATTR_DATA: {
|
||||
ATTR_TYPE: "image/gif",
|
||||
ATTR_BYTES: bytes([0]),
|
||||
},
|
||||
ATTR_TIMESTAMP: "2021-01-09T11:34:06.448809+00:00",
|
||||
}
|
||||
|
||||
STATION_DATA_MOCK = {
|
||||
ATTR_DATA: load_json_value_fixture("aemet/station-3195-data.json"),
|
||||
}
|
||||
@ -53,6 +61,9 @@ def mock_api_call(cmd: str, fetch_data: bool = False) -> dict[str, Any]:
|
||||
return FORECAST_DAILY_DATA_MOCK
|
||||
if cmd == "prediccion/especifica/municipio/horaria/28065":
|
||||
return FORECAST_HOURLY_DATA_MOCK
|
||||
if cmd == "red/radar/nacional":
|
||||
return RADAR_DATA_MOCK
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
@ -69,6 +80,9 @@ async def async_init_integration(hass: HomeAssistant):
|
||||
},
|
||||
entry_id="7442b231f139e813fc1939281123f220",
|
||||
unique_id="40.30403754--3.72935236",
|
||||
options={
|
||||
CONF_RADAR_UPDATES: True,
|
||||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user