From 34ace2e1cddfa47017e1e6b33de87a4e2942bb75 Mon Sep 17 00:00:00 2001 From: RDFurman Date: Thu, 24 Mar 2022 19:17:36 -0600 Subject: [PATCH] Honeywell away temps (#54704) --- .../components/honeywell/__init__.py | 33 +++++++++++- homeassistant/components/honeywell/climate.py | 4 +- .../components/honeywell/config_flow.py | 48 ++++++++++++++++- homeassistant/components/honeywell/const.py | 2 + .../components/honeywell/strings.json | 12 +++++ .../components/honeywell/translations/en.json | 12 +++++ .../components/honeywell/test_config_flow.py | 51 ++++++++++++++++++- tests/components/honeywell/test_init.py | 33 ++++++++++++ 8 files changed, 188 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py index bafd4c470db..d141822e2ea 100644 --- a/homeassistant/components/honeywell/__init__.py +++ b/homeassistant/components/honeywell/__init__.py @@ -6,19 +6,48 @@ import somecomfort from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.util import Throttle -from .const import _LOGGER, CONF_DEV_ID, CONF_LOC_ID, DOMAIN +from .const import ( + _LOGGER, + CONF_COOL_AWAY_TEMPERATURE, + CONF_DEV_ID, + CONF_HEAT_AWAY_TEMPERATURE, + CONF_LOC_ID, + DOMAIN, +) UPDATE_LOOP_SLEEP_TIME = 5 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) PLATFORMS = [Platform.CLIMATE] +MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE} + + +@callback +def _async_migrate_data_to_options( + hass: HomeAssistant, config_entry: ConfigEntry +) -> None: + if not MIGRATE_OPTIONS_KEYS.intersection(config_entry.data): + return + hass.config_entries.async_update_entry( + config_entry, + data={ + k: v for k, v in config_entry.data.items() if k not in MIGRATE_OPTIONS_KEYS + }, + options={ + **config_entry.options, + **{k: config_entry.data.get(k) for k in MIGRATE_OPTIONS_KEYS}, + }, + ) + async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: """Set up the Honeywell thermostat.""" + _async_migrate_data_to_options(hass, config) + username = config.data[CONF_USERNAME] password = config.data[CONF_PASSWORD] diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index f0e18953402..f8c07de7184 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -81,8 +81,8 @@ PARALLEL_UPDATES = 1 async def async_setup_entry(hass, config, async_add_entities, discovery_info=None): """Set up the Honeywell thermostat.""" - cool_away_temp = config.data.get(CONF_COOL_AWAY_TEMPERATURE) - heat_away_temp = config.data.get(CONF_HEAT_AWAY_TEMPERATURE) + cool_away_temp = config.options.get(CONF_COOL_AWAY_TEMPERATURE) + heat_away_temp = config.options.get(CONF_HEAT_AWAY_TEMPERATURE) data = hass.data[DOMAIN][config.entry_id] diff --git a/homeassistant/components/honeywell/config_flow.py b/homeassistant/components/honeywell/config_flow.py index 505a49f062b..e6fdd9b54bd 100644 --- a/homeassistant/components/honeywell/config_flow.py +++ b/homeassistant/components/honeywell/config_flow.py @@ -3,9 +3,16 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback from . import get_somecomfort_client -from .const import DOMAIN +from .const import ( + CONF_COOL_AWAY_TEMPERATURE, + CONF_HEAT_AWAY_TEMPERATURE, + DEFAULT_COOL_AWAY_TEMPERATURE, + DEFAULT_HEAT_AWAY_TEMPERATURE, + DOMAIN, +) class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -42,3 +49,42 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return client is not None + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Options callback for Honeywell.""" + return HoneywellOptionsFlowHandler(config_entry) + + +class HoneywellOptionsFlowHandler(config_entries.OptionsFlow): + """Config flow options for Honeywell.""" + + def __init__(self, entry: config_entries.ConfigEntry) -> None: + """Initialize Honeywell options flow.""" + self.config_entry = entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title=DOMAIN, data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + CONF_COOL_AWAY_TEMPERATURE, + default=self.config_entry.options.get( + CONF_COOL_AWAY_TEMPERATURE, DEFAULT_COOL_AWAY_TEMPERATURE + ), + ): int, + vol.Required( + CONF_HEAT_AWAY_TEMPERATURE, + default=self.config_entry.options.get( + CONF_HEAT_AWAY_TEMPERATURE, DEFAULT_HEAT_AWAY_TEMPERATURE + ), + ): int, + } + ), + ) diff --git a/homeassistant/components/honeywell/const.py b/homeassistant/components/honeywell/const.py index 2dce56046a3..08e7c3fc14e 100644 --- a/homeassistant/components/honeywell/const.py +++ b/homeassistant/components/honeywell/const.py @@ -5,6 +5,8 @@ DOMAIN = "honeywell" CONF_COOL_AWAY_TEMPERATURE = "away_cool_temperature" CONF_HEAT_AWAY_TEMPERATURE = "away_heat_temperature" +DEFAULT_COOL_AWAY_TEMPERATURE = 88 +DEFAULT_HEAT_AWAY_TEMPERATURE = 61 CONF_DEV_ID = "thermostat" CONF_LOC_ID = "location" diff --git a/homeassistant/components/honeywell/strings.json b/homeassistant/components/honeywell/strings.json index ce76b571996..1e20c0b1e81 100644 --- a/homeassistant/components/honeywell/strings.json +++ b/homeassistant/components/honeywell/strings.json @@ -13,5 +13,17 @@ "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" } + }, + "options": { + "step": { + "init": { + "title": "Honeywell Options", + "description": "Additional Honeywell config options. Temperatures are set in Fahrenheit.", + "data": { + "away_cool_temperature": "Away cool temperature", + "away_heat_temperature": "Away heat temperature" + } + } + } } } diff --git a/homeassistant/components/honeywell/translations/en.json b/homeassistant/components/honeywell/translations/en.json index 454093c5b3e..168d3a5b93d 100644 --- a/homeassistant/components/honeywell/translations/en.json +++ b/homeassistant/components/honeywell/translations/en.json @@ -13,5 +13,17 @@ "title": "Honeywell Total Connect Comfort (US)" } } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Away cool temperature", + "away_heat_temperature": "Away heat temperature" + }, + "description": "Additional Honeywell config options. Temperatures are set in Fahrenheit.", + "title": "Honeywell Options" + } + } } } \ No newline at end of file diff --git a/tests/components/honeywell/test_config_flow.py b/tests/components/honeywell/test_config_flow.py index 47897cf246d..b93e359c1ac 100644 --- a/tests/components/honeywell/test_config_flow.py +++ b/tests/components/honeywell/test_config_flow.py @@ -4,10 +4,16 @@ from unittest.mock import patch import somecomfort from homeassistant import data_entry_flow -from homeassistant.components.honeywell.const import DOMAIN -from homeassistant.config_entries import SOURCE_USER +from homeassistant.components.honeywell.const import ( + CONF_COOL_AWAY_TEMPERATURE, + CONF_HEAT_AWAY_TEMPERATURE, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_USER, ConfigEntryState from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry + FAKE_CONFIG = { "username": "fake", "password": "user", @@ -49,3 +55,44 @@ async def test_create_entry(hass: HomeAssistant) -> None: ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == FAKE_CONFIG + + +@patch("homeassistant.components.honeywell.UPDATE_LOOP_SLEEP_TIME", 0) +async def test_show_option_form( + hass: HomeAssistant, config_entry: MockConfigEntry, location +) -> None: + """Test that the option form is shown.""" + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + + 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" + + +@patch("homeassistant.components.honeywell.UPDATE_LOOP_SLEEP_TIME", 0) +async def test_create_option_entry( + hass: HomeAssistant, config_entry: MockConfigEntry, location +) -> None: + """Test that the config entry is created.""" + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + + options_form = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + options_form["flow_id"], + user_input={CONF_COOL_AWAY_TEMPERATURE: 1, CONF_HEAT_AWAY_TEMPERATURE: 2}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == { + CONF_COOL_AWAY_TEMPERATURE: 1, + CONF_HEAT_AWAY_TEMPERATURE: 2, + } diff --git a/tests/components/honeywell/test_init.py b/tests/components/honeywell/test_init.py index 49917aae151..83be3a05873 100644 --- a/tests/components/honeywell/test_init.py +++ b/tests/components/honeywell/test_init.py @@ -4,11 +4,19 @@ from unittest.mock import create_autospec, patch import somecomfort +from homeassistant.components.honeywell.const import ( + CONF_COOL_AWAY_TEMPERATURE, + CONF_HEAT_AWAY_TEMPERATURE, + DOMAIN, +) from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE} + @patch("homeassistant.components.honeywell.UPDATE_LOOP_SLEEP_TIME", 0) async def test_setup_entry(hass: HomeAssistant, config_entry: MockConfigEntry): @@ -48,3 +56,28 @@ async def test_setup_multiple_thermostats_with_same_deviceid( assert config_entry.state is ConfigEntryState.LOADED assert hass.states.async_entity_ids_count() == 1 assert "Platform honeywell does not generate unique IDs" not in caplog.text + + +async def test_away_temps_migration(hass: HomeAssistant) -> None: + """Test away temps migrate to config options.""" + legacy_config = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_USERNAME: "fake", + CONF_PASSWORD: "user", + CONF_COOL_AWAY_TEMPERATURE: 1, + CONF_HEAT_AWAY_TEMPERATURE: 2, + }, + options={}, + ) + + with patch( + "homeassistant.components.honeywell.somecomfort.SomeComfort", + ): + legacy_config.add_to_hass(hass) + await hass.config_entries.async_setup(legacy_config.entry_id) + await hass.async_block_till_done() + assert legacy_config.options == { + CONF_COOL_AWAY_TEMPERATURE: 1, + CONF_HEAT_AWAY_TEMPERATURE: 2, + }