Add config option to set timeout for wiffi devices (#35694)

* add config option to set timeout for wiffi devices

Wiffi devices allow to configure the update period (=
full_loop_minutes). The integration shall respect the configured update
period and therefore need a configuration for the timeout, too.

* Move timeout from config flow to option flow

* add test for option flow
pull/30413/head
Steffen Zimmermann 2020-05-17 10:31:28 +02:00 committed by GitHub
parent d02bb70f0c
commit 51eebb3906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 112 additions and 19 deletions

View File

@ -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):

View File

@ -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()

View File

@ -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,
}
),
)

View File

@ -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"

View File

@ -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()

View File

@ -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)"
}
}
}
}
}

View File

@ -12,5 +12,14 @@
"title": "Setup TCP server for WIFFI devices"
}
}
},
"options": {
"step": {
"init": {
"data": {
"timeout": "Timeout (minutes)"
}
}
}
}
}

View File

@ -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