From 24d017f9744341d42b8fa837e157846c0bb03707 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 24 Aug 2021 14:37:50 -0600 Subject: [PATCH] Add ability to configure OpenUV "protection window" UV indices (#54562) --- homeassistant/components/openuv/__init__.py | 15 ++++- .../components/openuv/config_flow.py | 55 ++++++++++++++++++- homeassistant/components/openuv/const.py | 6 ++ homeassistant/components/openuv/strings.json | 11 ++++ .../components/openuv/translations/en.json | 11 ++++ tests/components/openuv/test_config_flow.py | 33 ++++++++++- 6 files changed, 126 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index bcdd0b2ba40..d931049bb31 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -28,10 +28,14 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.service import verify_domain_control from .const import ( + CONF_FROM_WINDOW, + CONF_TO_WINDOW, DATA_CLIENT, DATA_LISTENER, DATA_PROTECTION_WINDOW, DATA_UV, + DEFAULT_FROM_WINDOW, + DEFAULT_TO_WINDOW, DOMAIN, LOGGER, ) @@ -55,13 +59,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b try: websession = aiohttp_client.async_get_clientsession(hass) openuv = OpenUV( + config_entry, Client( config_entry.data[CONF_API_KEY], config_entry.data.get(CONF_LATITUDE, hass.config.latitude), config_entry.data.get(CONF_LONGITUDE, hass.config.longitude), altitude=config_entry.data.get(CONF_ELEVATION, hass.config.elevation), session=websession, - ) + ), ) await openuv.async_update() hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = openuv @@ -134,15 +139,19 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> class OpenUV: """Define a generic OpenUV object.""" - def __init__(self, client: Client) -> None: + def __init__(self, config_entry: ConfigEntry, client: Client) -> None: """Initialize.""" + self._config_entry = config_entry self.client = client self.data: dict[str, Any] = {} async def async_update_protection_data(self) -> None: """Update binary sensor (protection window) data.""" + low = self._config_entry.options.get(CONF_FROM_WINDOW, DEFAULT_FROM_WINDOW) + high = self._config_entry.options.get(CONF_TO_WINDOW, DEFAULT_TO_WINDOW) + try: - resp = await self.client.uv_protection_window() + resp = await self.client.uv_protection_window(low=low, high=high) self.data[DATA_PROTECTION_WINDOW] = resp["result"] except OpenUvError as err: LOGGER.error("Error during protection data update: %s", err) diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index d8652ae09c5..62db9b2cf8c 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -8,16 +8,24 @@ from pyopenuv.errors import OpenUvError import voluptuous as vol from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_API_KEY, CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, ) +from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv -from .const import DOMAIN +from .const import ( + CONF_FROM_WINDOW, + CONF_TO_WINDOW, + DEFAULT_FROM_WINDOW, + DEFAULT_TO_WINDOW, + DOMAIN, +) class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -51,6 +59,12 @@ class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors if errors else {}, ) + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> OpenUvOptionsFlowHandler: + """Define the config flow to handle options.""" + return OpenUvOptionsFlowHandler(config_entry) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -75,3 +89,42 @@ class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._show_form({CONF_API_KEY: "invalid_api_key"}) return self.async_create_entry(title=identifier, data=user_input) + + +class OpenUvOptionsFlowHandler(config_entries.OptionsFlow): + """Handle a OpenUV options flow.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize.""" + self.config_entry = config_entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_FROM_WINDOW, + description={ + "suggested_value": self.config_entry.options.get( + CONF_FROM_WINDOW, DEFAULT_FROM_WINDOW + ) + }, + ): vol.Coerce(float), + vol.Optional( + CONF_TO_WINDOW, + description={ + "suggested_value": self.config_entry.options.get( + CONF_FROM_WINDOW, DEFAULT_TO_WINDOW + ) + }, + ): vol.Coerce(float), + } + ), + ) diff --git a/homeassistant/components/openuv/const.py b/homeassistant/components/openuv/const.py index 683e349eb50..3b117fe37aa 100644 --- a/homeassistant/components/openuv/const.py +++ b/homeassistant/components/openuv/const.py @@ -4,11 +4,17 @@ import logging DOMAIN = "openuv" LOGGER = logging.getLogger(__package__) +CONF_FROM_WINDOW = "from_window" +CONF_TO_WINDOW = "to_window" + DATA_CLIENT = "data_client" DATA_LISTENER = "data_listener" DATA_PROTECTION_WINDOW = "protection_window" DATA_UV = "uv" +DEFAULT_FROM_WINDOW = 3.5 +DEFAULT_TO_WINDOW = 3.5 + TYPE_CURRENT_OZONE_LEVEL = "current_ozone_level" TYPE_CURRENT_UV_INDEX = "current_uv_index" TYPE_CURRENT_UV_LEVEL = "current_uv_level" diff --git a/homeassistant/components/openuv/strings.json b/homeassistant/components/openuv/strings.json index f865aa1e621..cd9ec36d93a 100644 --- a/homeassistant/components/openuv/strings.json +++ b/homeassistant/components/openuv/strings.json @@ -17,5 +17,16 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" } + }, + "options": { + "step": { + "init": { + "title": "Configure OpenUV", + "data": { + "from_window": "Starting UV index for the protection window", + "to_window": "Ending UV index for the protection window" + } + } + } } } diff --git a/homeassistant/components/openuv/translations/en.json b/homeassistant/components/openuv/translations/en.json index 6021c7a2030..92ca71cd46f 100644 --- a/homeassistant/components/openuv/translations/en.json +++ b/homeassistant/components/openuv/translations/en.json @@ -17,5 +17,16 @@ "title": "Fill in your information" } } + }, + "options": { + "step": { + "init": { + "data": { + "from_window": "Starting UV index for the protection window", + "to_window": "Ending UV index for the protection window" + }, + "title": "Configure OpenUV" + } + } } } \ No newline at end of file diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index 3feeb2638b4..8afd9803d7c 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import patch from pyopenuv.errors import InvalidApiKeyError from homeassistant import data_entry_flow -from homeassistant.components.openuv import DOMAIN +from homeassistant.components.openuv import CONF_FROM_WINDOW, CONF_TO_WINDOW, DOMAIN from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( CONF_API_KEY, @@ -57,6 +57,37 @@ async def test_invalid_api_key(hass): assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} +async def test_options_flow(hass): + """Test config flow options.""" + conf = { + CONF_API_KEY: "12345abcde", + CONF_ELEVATION: 59.1234, + CONF_LATITUDE: 39.128712, + CONF_LONGITUDE: -104.9812612, + } + + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id="abcde12345", + data=conf, + ) + config_entry.add_to_hass(hass) + + with patch("homeassistant.components.openuv.async_setup_entry", return_value=True): + await hass.config_entries.async_setup(config_entry.entry_id) + result = await hass.config_entries.options.async_init(config_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_FROM_WINDOW: 3.5, CONF_TO_WINDOW: 2.0} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == {CONF_FROM_WINDOW: 3.5, CONF_TO_WINDOW: 2.0} + + async def test_step_user(hass): """Test that the user step works.""" conf = {