From 489340160b68923767fc4e60cd21a76310f1d8ee Mon Sep 17 00:00:00 2001 From: mvn23 Date: Fri, 18 Oct 2019 02:36:34 +0200 Subject: [PATCH] Add opentherm_gw options flow. (#27316) --- .../opentherm_gw/.translations/en.json | 11 ++++ .../components/opentherm_gw/__init__.py | 9 ++++ .../components/opentherm_gw/climate.py | 19 +++++-- .../components/opentherm_gw/config_flow.py | 53 +++++++++++++++++- .../components/opentherm_gw/strings.json | 15 ++++-- .../opentherm_gw/test_config_flow.py | 54 +++++++++++++++++-- 6 files changed, 149 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/opentherm_gw/.translations/en.json b/homeassistant/components/opentherm_gw/.translations/en.json index 65d7d9e92bb..4aba4ed047a 100644 --- a/homeassistant/components/opentherm_gw/.translations/en.json +++ b/homeassistant/components/opentherm_gw/.translations/en.json @@ -19,5 +19,16 @@ } }, "title": "OpenTherm Gateway" + }, + "options": { + "step": { + "init": { + "description": "Options for the OpenTherm Gateway", + "data": { + "floor_temperature": "Floor Temperature", + "precision": "Precision" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index ba6de4c0bea..643f80ae8f9 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -75,6 +75,12 @@ CONFIG_SCHEMA = vol.Schema( ) +async def options_updated(hass, entry): + """Handle options update.""" + gateway = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][entry.data[CONF_ID]] + async_dispatcher_send(hass, gateway.options_update_signal, entry) + + async def async_setup_entry(hass, config_entry): """Set up the OpenTherm Gateway component.""" if DATA_OPENTHERM_GW not in hass.data: @@ -83,6 +89,8 @@ async def async_setup_entry(hass, config_entry): gateway = OpenThermGatewayDevice(hass, config_entry) hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] = gateway + config_entry.add_update_listener(options_updated) + # Schedule directly on the loop to avoid blocking HA startup. hass.loop.create_task(gateway.connect_and_subscribe()) @@ -348,6 +356,7 @@ class OpenThermGatewayDevice: self.climate_config = config_entry.options self.status = {} self.update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_update" + self.options_update_signal = f"{DATA_OPENTHERM_GW}_{self.gw_id}_options_update" self.gateway = pyotgw.pyotgw() async def connect_and_subscribe(self): diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 19763121e89..44f143d64da 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -39,7 +39,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ents = [] ents.append( OpenThermClimate( - hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] + hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]], + config_entry.options, ) ) @@ -49,12 +50,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class OpenThermClimate(ClimateDevice): """Representation of a climate device.""" - def __init__(self, gw_dev): + def __init__(self, gw_dev, options): """Initialize the device.""" self._gateway = gw_dev self.friendly_name = gw_dev.name - self.floor_temp = gw_dev.climate_config.get(CONF_FLOOR_TEMP) - self.temp_precision = gw_dev.climate_config.get(CONF_PRECISION) + self.floor_temp = options[CONF_FLOOR_TEMP] + self.temp_precision = options.get(CONF_PRECISION) self._current_operation = None self._current_temperature = None self._hvac_mode = HVAC_MODE_HEAT @@ -65,12 +66,22 @@ class OpenThermClimate(ClimateDevice): self._away_state_a = False self._away_state_b = False + @callback + def update_options(self, entry): + """Update climate entity options.""" + self.floor_temp = entry.options[CONF_FLOOR_TEMP] + self.temp_precision = entry.options.get(CONF_PRECISION) + self.async_schedule_update_ha_state() + async def async_added_to_hass(self): """Connect to the OpenTherm Gateway device.""" _LOGGER.debug("Added OpenTherm Gateway climate device %s", self.friendly_name) async_dispatcher_connect( self.hass, self._gateway.update_signal, self.receive_report ) + async_dispatcher_connect( + self.hass, self._gateway.options_update_signal, self.update_options + ) @callback def receive_report(self, status): diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index e1b68f1ae49..2d7a65bbd84 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -6,11 +6,20 @@ import pyotgw import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME +from homeassistant.const import ( + CONF_DEVICE, + CONF_ID, + CONF_NAME, + PRECISION_HALVES, + PRECISION_TENTHS, + PRECISION_WHOLE, +) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from . import DOMAIN +from .const import CONF_FLOOR_TEMP, CONF_PRECISION class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -19,6 +28,12 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OpenThermGwOptionsFlow(config_entry) + async def async_step_init(self, info=None): """Handle config flow initiation.""" if info: @@ -89,3 +104,39 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry( title=name, data={CONF_ID: gw_id, CONF_DEVICE: device, CONF_NAME: name} ) + + +class OpenThermGwOptionsFlow(config_entries.OptionsFlow): + """Handle opentherm_gw options.""" + + def __init__(self, config_entry): + """Initialize the options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the opentherm_gw options.""" + if user_input is not None: + if user_input.get(CONF_PRECISION) == 0: + user_input[CONF_PRECISION] = 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_PRECISION, + default=self.config_entry.options.get(CONF_PRECISION, 0), + ): vol.All( + vol.Coerce(float), + vol.In( + [0, PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), + ), + vol.Optional( + CONF_FLOOR_TEMP, + default=self.config_entry.options.get(CONF_FLOOR_TEMP, False), + ): bool, + } + ), + ) diff --git a/homeassistant/components/opentherm_gw/strings.json b/homeassistant/components/opentherm_gw/strings.json index a62a4625049..1c246432fb1 100644 --- a/homeassistant/components/opentherm_gw/strings.json +++ b/homeassistant/components/opentherm_gw/strings.json @@ -7,9 +7,7 @@ "data": { "name": "Name", "device": "Path or URL", - "id": "ID", - "precision": "Climate temperature precision", - "floor_temperature": "Floor climate temperature" + "id": "ID" } } }, @@ -19,5 +17,16 @@ "serial_error": "Error connecting to device", "timeout": "Connection attempt timed out" } + }, + "options": { + "step": { + "init": { + "description": "Options for the OpenTherm Gateway", + "data": { + "floor_temperature": "Floor Temperature", + "precision": "Precision" + } + } + } } } \ No newline at end of file diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index da80e2f9fbb..89f2783cf71 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -3,12 +3,16 @@ import asyncio from serial import SerialException from unittest.mock import patch -from homeassistant import config_entries, setup -from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME -from homeassistant.components.opentherm_gw.const import DOMAIN +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES +from homeassistant.components.opentherm_gw.const import ( + DOMAIN, + CONF_FLOOR_TEMP, + CONF_PRECISION, +) from pyotgw import OTGW_ABOUT -from tests.common import mock_coro +from tests.common import mock_coro, MockConfigEntry async def test_form_user(hass): @@ -161,3 +165,45 @@ async def test_form_connection_error(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "serial_error"} assert len(mock_connect.mock_calls) == 1 + + +async def test_options_form(hass): + """Test the options form.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Mock Gateway", + data={ + CONF_NAME: "Mock Gateway", + CONF_DEVICE: "/dev/null", + CONF_ID: "mock_gateway", + }, + options={}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.flow.async_init( + entry.entry_id, context={"source": "test"}, data=None + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.flow.async_configure( + result["flow_id"], + user_input={CONF_FLOOR_TEMP: True, CONF_PRECISION: PRECISION_HALVES}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_FLOOR_TEMP] is True + + result = await hass.config_entries.options.flow.async_init( + entry.entry_id, context={"source": "test"}, data=None + ) + + result = await hass.config_entries.options.flow.async_configure( + result["flow_id"], user_input={CONF_PRECISION: 0} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_PRECISION] is None + assert result["data"][CONF_FLOOR_TEMP] is True