diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index 05d0d381e18..000b961bda9 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -7,7 +7,7 @@ import logging from wiffi import WiffiTcpServer from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry @@ -22,6 +22,7 @@ from homeassistant.util.dt import utcnow from .const import ( CHECK_ENTITIES_SIGNAL, CREATE_ENTITY_SIGNAL, + DEFAULT_TIMEOUT, DOMAIN, UPDATE_ENTITY_SIGNAL, ) @@ -39,6 +40,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Set up wiffi from a config entry, config_entry contains data from config entry database.""" + if not config_entry.update_listeners: + config_entry.add_update_listener(async_update_options) + # create api object api = WiffiIntegrationApi(hass) api.async_setup(config_entry) @@ -63,6 +67,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): return True +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): + """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.""" api: "WiffiIntegrationApi" = hass.data[DOMAIN][config_entry.entry_id] @@ -146,7 +155,7 @@ class WiffiIntegrationApi: class WiffiEntity(Entity): """Common functionality for all wiffi entities.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the base elements of a wiffi entity.""" self._id = generate_unique_id(device, metric) self._device_info = { @@ -162,6 +171,7 @@ class WiffiEntity(Entity): self._name = metric.description self._expiration_date = None self._value = None + self._timeout = options.get(CONF_TIMEOUT, DEFAULT_TIMEOUT) async def async_added_to_hass(self): """Entity has been added to hass.""" @@ -208,7 +218,7 @@ class WiffiEntity(Entity): Will be called by derived classes after a value update has been received. """ - self._expiration_date = utcnow() + timedelta(minutes=3) + self._expiration_date = utcnow() + timedelta(minutes=self._timeout) @callback def _update_value_callback(self, device, metric): diff --git a/homeassistant/components/wiffi/binary_sensor.py b/homeassistant/components/wiffi/binary_sensor.py index 009fc2b4a67..f6063b3c202 100644 --- a/homeassistant/components/wiffi/binary_sensor.py +++ b/homeassistant/components/wiffi/binary_sensor.py @@ -21,7 +21,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] if metric.is_bool: - entities.append(BoolEntity(device, metric)) + entities.append(BoolEntity(device, metric, config_entry.options)) async_add_entities(entities) @@ -31,9 +31,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BoolEntity(WiffiEntity, BinarySensorEntity): """Entity for wiffi metrics which have a boolean value.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the entity.""" - super().__init__(device, metric) + super().__init__(device, metric, options) self._value = metric.value self.reset_expiration_date() diff --git a/homeassistant/components/wiffi/config_flow.py b/homeassistant/components/wiffi/config_flow.py index 82dbbb040ef..f30ee8792df 100644 --- a/homeassistant/components/wiffi/config_flow.py +++ b/homeassistant/components/wiffi/config_flow.py @@ -8,10 +8,14 @@ import voluptuous as vol from wiffi import WiffiTcpServer from homeassistant import config_entries -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.core import callback -from .const import DEFAULT_PORT, DOMAIN # pylint: disable=unused-import +from .const import ( # pylint: disable=unused-import + DEFAULT_PORT, + DEFAULT_TIMEOUT, + DOMAIN, +) class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -20,6 +24,12 @@ class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Create Wiffi server setup option flow.""" + return OptionsFlowHandler(config_entry) + async def async_step_user(self, user_input=None): """Handle the start of the config flow. @@ -55,3 +65,30 @@ class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="user", data_schema=vol.Schema(data_schema), errors=errors or {} ) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Wiffi server setup option flow.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_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="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_TIMEOUT, + default=self.config_entry.options.get( + CONF_TIMEOUT, DEFAULT_TIMEOUT + ), + ): int, + } + ), + ) diff --git a/homeassistant/components/wiffi/const.py b/homeassistant/components/wiffi/const.py index 6b71c89002f..584ab9899b6 100644 --- a/homeassistant/components/wiffi/const.py +++ b/homeassistant/components/wiffi/const.py @@ -6,6 +6,9 @@ DOMAIN = "wiffi" # Default port for TCP server DEFAULT_PORT = 8189 +# Default timeout in minutes +DEFAULT_TIMEOUT = 3 + # Signal name to send create/update to platform (sensor/binary_sensor) CREATE_ENTITY_SIGNAL = "wiffi_create_entity_signal" UPDATE_ENTITY_SIGNAL = "wiffi_update_entity_signal" diff --git a/homeassistant/components/wiffi/sensor.py b/homeassistant/components/wiffi/sensor.py index cc6befaf067..f207e3be3ac 100644 --- a/homeassistant/components/wiffi/sensor.py +++ b/homeassistant/components/wiffi/sensor.py @@ -49,9 +49,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] if metric.is_number: - entities.append(NumberEntity(device, metric)) + entities.append(NumberEntity(device, metric, config_entry.options)) elif metric.is_string: - entities.append(StringEntity(device, metric)) + entities.append(StringEntity(device, metric, config_entry.options)) async_add_entities(entities) @@ -61,9 +61,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class NumberEntity(WiffiEntity): """Entity for wiffi metrics which have a number value.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the entity.""" - super().__init__(device, metric) + super().__init__(device, metric, options) self._device_class = UOM_TO_DEVICE_CLASS_MAP.get(metric.unit_of_measurement) self._unit_of_measurement = UOM_MAP.get( metric.unit_of_measurement, metric.unit_of_measurement @@ -103,9 +103,9 @@ class NumberEntity(WiffiEntity): class StringEntity(WiffiEntity): """Entity for wiffi metrics which have a string value.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the entity.""" - super().__init__(device, metric) + super().__init__(device, metric, options) self._value = metric.value self.reset_expiration_date() diff --git a/homeassistant/components/wiffi/strings.json b/homeassistant/components/wiffi/strings.json index 36f836366a5..e219b2ecae7 100644 --- a/homeassistant/components/wiffi/strings.json +++ b/homeassistant/components/wiffi/strings.json @@ -12,5 +12,14 @@ "addr_in_use": "Server port already in use.", "start_server_failed": "Start server failed." } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Timeout (minutes)" + } + } + } } } diff --git a/homeassistant/components/wiffi/translations/en.json b/homeassistant/components/wiffi/translations/en.json index bcaf0820bd5..0ac1868714d 100644 --- a/homeassistant/components/wiffi/translations/en.json +++ b/homeassistant/components/wiffi/translations/en.json @@ -12,5 +12,14 @@ "title": "Setup TCP server for WIFFI devices" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Timeout (minutes)" + } + } + } } } \ No newline at end of file diff --git a/tests/components/wiffi/test_config_flow.py b/tests/components/wiffi/test_config_flow.py index ef6ce528623..5c3e96eb959 100644 --- a/tests/components/wiffi/test_config_flow.py +++ b/tests/components/wiffi/test_config_flow.py @@ -4,15 +4,19 @@ import errno from asynctest import patch import pytest -from homeassistant import config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.components.wiffi.const import DOMAIN -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM, ) +from tests.common import MockConfigEntry + +MOCK_CONFIG = {CONF_PORT: 8765} + @pytest.fixture(name="dummy_tcp_server") def mock_dummy_tcp_server(): @@ -78,7 +82,7 @@ async def test_form(hass, dummy_tcp_server): assert result["step_id"] == config_entries.SOURCE_USER result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_PORT: 8765}, + result["flow_id"], user_input=MOCK_CONFIG, ) assert result2["type"] == RESULT_TYPE_CREATE_ENTRY @@ -90,7 +94,7 @@ async def test_form_addr_in_use(hass, addr_in_use): ) result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_PORT: 8765}, + result["flow_id"], user_input=MOCK_CONFIG, ) assert result2["type"] == RESULT_TYPE_ABORT assert result2["reason"] == "addr_in_use" @@ -103,7 +107,28 @@ async def test_form_start_server_failed(hass, start_server_failed): ) result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_PORT: 8765}, + result["flow_id"], user_input=MOCK_CONFIG, ) assert result2["type"] == RESULT_TYPE_ABORT assert result2["reason"] == "start_server_failed" + + +async def test_option_flow(hass): + """Test option flow.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG) + entry.add_to_hass(hass) + + assert not entry.options + + result = await hass.config_entries.options.async_init(entry.entry_id, data=None) + + 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_TIMEOUT: 9} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "" + assert result["data"][CONF_TIMEOUT] == 9