diff --git a/.coveragerc b/.coveragerc index 6be3ac8cef6..75a9622d284 100644 --- a/.coveragerc +++ b/.coveragerc @@ -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 diff --git a/homeassistant/components/aemet/__init__.py b/homeassistant/components/aemet/__init__.py index a4a0526062d..879f59fa2fc 100644 --- a/homeassistant/components/aemet/__init__.py +++ b/homeassistant/components/aemet/__init__.py @@ -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( diff --git a/homeassistant/components/aemet/abstract_aemet_sensor.py b/homeassistant/components/aemet/abstract_aemet_sensor.py deleted file mode 100644 index 8847a5d094d..00000000000 --- a/homeassistant/components/aemet/abstract_aemet_sensor.py +++ /dev/null @@ -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} diff --git a/homeassistant/components/aemet/config_flow.py b/homeassistant/components/aemet/config_flow.py index f40725c6182..6c97ca98cb8 100644 --- a/homeassistant/components/aemet/config_flow.py +++ b/homeassistant/components/aemet/config_flow.py @@ -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) diff --git a/homeassistant/components/aemet/const.py b/homeassistant/components/aemet/const.py index 390ccb86003..0927f64dd2a 100644 --- a/homeassistant/components/aemet/const.py +++ b/homeassistant/components/aemet/const.py @@ -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" diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index 6f43d66e011..de7b06347c3 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -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.""" diff --git a/homeassistant/components/aemet/strings.json b/homeassistant/components/aemet/strings.json index a25a503bade..360f7c680ea 100644 --- a/homeassistant/components/aemet/strings.json +++ b/homeassistant/components/aemet/strings.json @@ -18,5 +18,14 @@ "title": "AEMET OpenData" } } + }, + "options": { + "step": { + "init": { + "data": { + "station_updates": "Gather data from AEMET weather stations" + } + } + } } } diff --git a/homeassistant/components/aemet/translations/en.json b/homeassistant/components/aemet/translations/en.json index 60e7f5f2ec2..3888ccdafc0 100644 --- a/homeassistant/components/aemet/translations/en.json +++ b/homeassistant/components/aemet/translations/en.json @@ -18,5 +18,14 @@ "title": "AEMET OpenData" } } + }, + "options": { + "step": { + "init": { + "data": { + "station_updates": "Gather data from AEMET weather stations" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 7aab23488b5..8259baf9984 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -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] ) diff --git a/tests/components/aemet/test_config_flow.py b/tests/components/aemet/test_config_flow.py index be01ac9de07..36713a02903 100644 --- a/tests/components/aemet/test_config_flow.py +++ b/tests/components/aemet/test_config_flow.py @@ -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(