Add AEMET conditional station updates (#50227)

pull/50607/head
Álvaro Fernández Rojas 2021-05-14 13:28:48 +02:00 committed by GitHub
parent 42df6750e2
commit 9247a157d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 183 additions and 68 deletions

View File

@ -23,7 +23,6 @@ omit =
homeassistant/components/adguard/sensor.py
homeassistant/components/adguard/switch.py
homeassistant/components/ads/*
homeassistant/components/aemet/abstract_aemet_sensor.py
homeassistant/components/aemet/weather_update_coordinator.py
homeassistant/components/aftership/sensor.py
homeassistant/components/agent_dvr/__init__.py

View File

@ -7,7 +7,13 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant
from .const import DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR, PLATFORMS
from .const import (
CONF_STATION_UPDATES,
DOMAIN,
ENTRY_NAME,
ENTRY_WEATHER_COORDINATOR,
PLATFORMS,
)
from .weather_update_coordinator import WeatherUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@ -19,9 +25,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
api_key = config_entry.data[CONF_API_KEY]
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
station_updates = config_entry.options.get(CONF_STATION_UPDATES, True)
aemet = AEMET(api_key)
weather_coordinator = WeatherUpdateCoordinator(hass, aemet, latitude, longitude)
weather_coordinator = WeatherUpdateCoordinator(
hass, aemet, latitude, longitude, station_updates
)
await weather_coordinator.async_config_entry_first_refresh()
@ -33,9 +42,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
config_entry.async_on_unload(config_entry.add_update_listener(async_update_options))
return True
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Update options."""
await hass.config_entries.async_reload(config_entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(

View File

@ -1,58 +0,0 @@
"""Abstraction form AEMET OpenData sensors."""
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTRIBUTION, SENSOR_DEVICE_CLASS, SENSOR_NAME, SENSOR_UNIT
from .weather_update_coordinator import WeatherUpdateCoordinator
class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
"""Abstract class for an AEMET OpenData sensor."""
def __init__(
self,
name,
unique_id,
sensor_type,
sensor_configuration,
coordinator: WeatherUpdateCoordinator,
):
"""Initialize the sensor."""
super().__init__(coordinator)
self._name = name
self._unique_id = unique_id
self._sensor_type = sensor_type
self._sensor_name = sensor_configuration[SENSOR_NAME]
self._unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
self._device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._name} {self._sensor_name}"
@property
def unique_id(self):
"""Return a unique_id for this entity."""
return self._unique_id
@property
def attribution(self):
"""Return the attribution."""
return ATTRIBUTION
@property
def device_class(self):
"""Return the device_class."""
return self._device_class
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {ATTR_ATTRIBUTION: ATTRIBUTION}

View File

@ -4,9 +4,10 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from .const import DEFAULT_NAME, DOMAIN
from .const import CONF_STATION_UPDATES, DEFAULT_NAME, DOMAIN
class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@ -47,6 +48,35 @@ class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a option flow for AEMET."""
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Handle options flow."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
data_schema = vol.Schema(
{
vol.Required(
CONF_STATION_UPDATES,
default=self.config_entry.options.get(CONF_STATION_UPDATES),
): bool,
}
)
return self.async_show_form(step_id="init", data_schema=data_schema)
async def _is_aemet_api_online(hass, api_key):
aemet = AEMET(api_key)

View File

@ -34,12 +34,12 @@ from homeassistant.const import (
)
ATTRIBUTION = "Powered by AEMET OpenData"
CONF_STATION_UPDATES = "station_updates"
PLATFORMS = ["sensor", "weather"]
DEFAULT_NAME = "AEMET"
DOMAIN = "aemet"
ENTRY_NAME = "name"
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
UPDATE_LISTENER = "update_listener"
SENSOR_NAME = "sensor_name"
SENSOR_UNIT = "sensor_unit"
SENSOR_DEVICE_CLASS = "sensor_device_class"

View File

@ -1,6 +1,10 @@
"""Support for the AEMET OpenData service."""
from .abstract_aemet_sensor import AbstractAemetSensor
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
ATTRIBUTION,
DOMAIN,
ENTRY_NAME,
ENTRY_WEATHER_COORDINATOR,
@ -10,6 +14,9 @@ from .const import (
FORECAST_MONITORED_CONDITIONS,
FORECAST_SENSOR_TYPES,
MONITORED_CONDITIONS,
SENSOR_DEVICE_CLASS,
SENSOR_NAME,
SENSOR_UNIT,
WEATHER_SENSOR_TYPES,
)
from .weather_update_coordinator import WeatherUpdateCoordinator
@ -56,6 +63,52 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async_add_entities(entities)
class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
"""Abstract class for an AEMET OpenData sensor."""
def __init__(
self,
name,
unique_id,
sensor_type,
sensor_configuration,
coordinator: WeatherUpdateCoordinator,
):
"""Initialize the sensor."""
super().__init__(coordinator)
self._name = name
self._unique_id = unique_id
self._sensor_type = sensor_type
self._sensor_name = sensor_configuration[SENSOR_NAME]
self._unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
self._device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._name} {self._sensor_name}"
@property
def unique_id(self):
"""Return a unique_id for this entity."""
return self._unique_id
@property
def device_class(self):
"""Return the device_class."""
return self._device_class
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {ATTR_ATTRIBUTION: ATTRIBUTION}
class AemetSensor(AbstractAemetSensor):
"""Implementation of an AEMET OpenData sensor."""

View File

@ -18,5 +18,14 @@
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "Gather data from AEMET weather stations"
}
}
}
}
}

View File

@ -18,5 +18,14 @@
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "Gather data from AEMET weather stations"
}
}
}
}
}

View File

@ -118,7 +118,7 @@ class TownNotFound(UpdateFailed):
class WeatherUpdateCoordinator(DataUpdateCoordinator):
"""Weather data update coordinator."""
def __init__(self, hass, aemet, latitude, longitude):
def __init__(self, hass, aemet, latitude, longitude, station_updates):
"""Initialize coordinator."""
super().__init__(
hass, _LOGGER, name=DOMAIN, update_interval=WEATHER_UPDATE_INTERVAL
@ -129,6 +129,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
self._town = None
self._latitude = latitude
self._longitude = longitude
self._station_updates = station_updates
self._data = {
"daily": None,
"hourly": None,
@ -210,7 +211,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
)
station = None
if self._get_weather_station():
if self._station_updates and self._get_weather_station():
station = self._aemet.get_conventional_observation_station_data(
self._station[AEMET_ATTR_IDEMA]
)

View File

@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
import requests_mock
from homeassistant import data_entry_flow
from homeassistant.components.aemet.const import DOMAIN
from homeassistant.components.aemet.const import CONF_STATION_UPDATES, DOMAIN
from homeassistant.config_entries import ENTRY_STATE_LOADED, SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
import homeassistant.util.dt as dt_util
@ -58,8 +58,64 @@ async def test_form(hass):
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_options(hass):
"""Test the form options."""
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
), requests_mock.mock() as _m:
aemet_requests_mock(_m)
entry = MockConfigEntry(
domain=DOMAIN, unique_id="40.30403754--3.72935236", data=CONFIG
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == "loaded"
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_STATION_UPDATES: False}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert entry.options == {
CONF_STATION_UPDATES: False,
}
await hass.async_block_till_done()
assert entry.state == "loaded"
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_STATION_UPDATES: True}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert entry.options == {
CONF_STATION_UPDATES: True,
}
await hass.async_block_till_done()
assert entry.state == "loaded"
async def test_form_duplicated_id(hass):
"""Test that the options form."""
"""Test setting up duplicated entry."""
now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00")
with patch("homeassistant.util.dt.now", return_value=now), patch(