Add Risco communication delay (#101349)

* Add optional communication delay in case a panel responds slow.

* Add migration test

* Connect by increasing the communication delay

* Update init to use option instead of config

* Updated strings

* Fix migration and tests

* Review changes

* Update connect strategy

* Update tests

* Changes after review

* Change typing from object to Any.

* Add test to validate communication delay update.

* Move test to separate file

* Change filename.
This commit is contained in:
FredericMa 2023-11-13 12:24:20 +01:00 committed by GitHub
parent 92b3c0c96b
commit d4c5a93b63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 95 additions and 18 deletions

View File

@ -35,10 +35,12 @@ from homeassistant.helpers.storage import Store
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ( from .const import (
CONF_COMMUNICATION_DELAY,
DATA_COORDINATOR, DATA_COORDINATOR,
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
DOMAIN, DOMAIN,
EVENTS_COORDINATOR, EVENTS_COORDINATOR,
MAX_COMMUNICATION_DELAY,
TYPE_LOCAL, TYPE_LOCAL,
) )
@ -81,15 +83,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data = entry.data data = entry.data
risco = RiscoLocal(data[CONF_HOST], data[CONF_PORT], data[CONF_PIN]) comm_delay = initial_delay = data.get(CONF_COMMUNICATION_DELAY, 0)
try: while True:
await risco.connect() risco = RiscoLocal(
except CannotConnectError as error: data[CONF_HOST],
raise ConfigEntryNotReady() from error data[CONF_PORT],
except UnauthorizedError: data[CONF_PIN],
_LOGGER.exception("Failed to login to Risco cloud") communication_delay=comm_delay,
return False )
try:
await risco.connect()
except CannotConnectError as error:
if comm_delay >= MAX_COMMUNICATION_DELAY:
raise ConfigEntryNotReady() from error
comm_delay += 1
except UnauthorizedError:
_LOGGER.exception("Failed to login to Risco cloud")
return False
else:
break
if comm_delay > initial_delay:
new_data = data.copy()
new_data[CONF_COMMUNICATION_DELAY] = comm_delay
hass.config_entries.async_update_entry(entry, data=new_data)
async def _error(error: Exception) -> None: async def _error(error: Exception) -> None:
_LOGGER.error("Error in Risco library: %s", error) _LOGGER.error("Error in Risco library: %s", error)

View File

@ -28,10 +28,12 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ( from .const import (
CONF_CODE_ARM_REQUIRED, CONF_CODE_ARM_REQUIRED,
CONF_CODE_DISARM_REQUIRED, CONF_CODE_DISARM_REQUIRED,
CONF_COMMUNICATION_DELAY,
CONF_HA_STATES_TO_RISCO, CONF_HA_STATES_TO_RISCO,
CONF_RISCO_STATES_TO_HA, CONF_RISCO_STATES_TO_HA,
DEFAULT_OPTIONS, DEFAULT_OPTIONS,
DOMAIN, DOMAIN,
MAX_COMMUNICATION_DELAY,
RISCO_STATES, RISCO_STATES,
TYPE_LOCAL, TYPE_LOCAL,
) )
@ -78,16 +80,31 @@ async def validate_cloud_input(hass: core.HomeAssistant, data) -> dict[str, str]
async def validate_local_input( async def validate_local_input(
hass: core.HomeAssistant, data: Mapping[str, str] hass: core.HomeAssistant, data: Mapping[str, str]
) -> dict[str, str]: ) -> dict[str, Any]:
"""Validate the user input allows us to connect to a local panel. """Validate the user input allows us to connect to a local panel.
Data has the keys from LOCAL_SCHEMA with values provided by the user. Data has the keys from LOCAL_SCHEMA with values provided by the user.
""" """
risco = RiscoLocal(data[CONF_HOST], data[CONF_PORT], data[CONF_PIN]) comm_delay = 0
await risco.connect() while True:
risco = RiscoLocal(
data[CONF_HOST],
data[CONF_PORT],
data[CONF_PIN],
communication_delay=comm_delay,
)
try:
await risco.connect()
except CannotConnectError as e:
if comm_delay >= MAX_COMMUNICATION_DELAY:
raise e
comm_delay += 1
else:
break
site_id = risco.id site_id = risco.id
await risco.disconnect() await risco.disconnect()
return {"title": site_id} return {"title": site_id, "comm_delay": comm_delay}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@ -170,7 +187,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
return self.async_create_entry( return self.async_create_entry(
title=info["title"], data={**user_input, **{CONF_TYPE: TYPE_LOCAL}} title=info["title"],
data={
**user_input,
**{CONF_TYPE: TYPE_LOCAL},
**{CONF_COMMUNICATION_DELAY: info["comm_delay"]},
},
) )
return self.async_show_form( return self.async_show_form(

View File

@ -17,10 +17,13 @@ DEFAULT_SCAN_INTERVAL = 30
TYPE_LOCAL = "local" TYPE_LOCAL = "local"
MAX_COMMUNICATION_DELAY = 3
CONF_CODE_ARM_REQUIRED = "code_arm_required" CONF_CODE_ARM_REQUIRED = "code_arm_required"
CONF_CODE_DISARM_REQUIRED = "code_disarm_required" CONF_CODE_DISARM_REQUIRED = "code_disarm_required"
CONF_RISCO_STATES_TO_HA = "risco_states_to_ha" CONF_RISCO_STATES_TO_HA = "risco_states_to_ha"
CONF_HA_STATES_TO_RISCO = "ha_states_to_risco" CONF_HA_STATES_TO_RISCO = "ha_states_to_risco"
CONF_COMMUNICATION_DELAY = "communication_delay"
RISCO_GROUPS = ["A", "B", "C", "D"] RISCO_GROUPS = ["A", "B", "C", "D"]
RISCO_ARM = "arm" RISCO_ARM = "arm"

View File

@ -7,5 +7,5 @@
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["pyrisco"], "loggers": ["pyrisco"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["pyrisco==0.5.7"] "requirements": ["pyrisco==0.5.8"]
} }

View File

@ -2001,7 +2001,7 @@ pyrecswitch==1.0.2
pyrepetierng==0.1.0 pyrepetierng==0.1.0
# homeassistant.components.risco # homeassistant.components.risco
pyrisco==0.5.7 pyrisco==0.5.8
# homeassistant.components.rituals_perfume_genie # homeassistant.components.rituals_perfume_genie
pyrituals==0.0.6 pyrituals==0.0.6

View File

@ -1508,7 +1508,7 @@ pyqwikswitch==0.93
pyrainbird==4.0.0 pyrainbird==4.0.0
# homeassistant.components.risco # homeassistant.components.risco
pyrisco==0.5.7 pyrisco==0.5.8
# homeassistant.components.rituals_perfume_genie # homeassistant.components.rituals_perfume_genie
pyrituals==0.0.6 pyrituals==0.0.6

View File

@ -171,6 +171,16 @@ def connect_with_error(exception):
yield yield
@pytest.fixture
def connect_with_single_error(exception):
"""Fixture to simulate error on connect."""
with patch(
"homeassistant.components.risco.RiscoLocal.connect",
side_effect=[exception, None],
):
yield
@pytest.fixture @pytest.fixture
async def setup_risco_local(hass, local_config_entry): async def setup_risco_local(hass, local_config_entry):
"""Set up a local Risco integration for testing.""" """Set up a local Risco integration for testing."""

View File

@ -9,7 +9,7 @@ from homeassistant.components.risco.config_flow import (
CannotConnectError, CannotConnectError,
UnauthorizedError, UnauthorizedError,
) )
from homeassistant.components.risco.const import DOMAIN from homeassistant.components.risco.const import CONF_COMMUNICATION_DELAY, DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -246,7 +246,10 @@ async def test_local_form(hass: HomeAssistant) -> None:
) )
await hass.async_block_till_done() await hass.async_block_till_done()
expected_data = {**TEST_LOCAL_DATA, **{"type": "local"}} expected_data = {
**TEST_LOCAL_DATA,
**{"type": "local", CONF_COMMUNICATION_DELAY: 0},
}
assert result3["type"] == FlowResultType.CREATE_ENTRY assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == TEST_SITE_NAME assert result3["title"] == TEST_SITE_NAME
assert result3["data"] == expected_data assert result3["data"] == expected_data

View File

@ -0,0 +1,21 @@
"""Tests for the Risco initialization."""
import pytest
from homeassistant.components.risco import CannotConnectError
from homeassistant.components.risco.const import CONF_COMMUNICATION_DELAY
from homeassistant.core import HomeAssistant
@pytest.mark.parametrize("exception", [CannotConnectError])
async def test_single_error_on_connect(
hass: HomeAssistant, connect_with_single_error, local_config_entry
) -> None:
"""Test single error on connect to validate communication delay update from 0 (default) to 1."""
expected_data = {
**local_config_entry.data,
**{"type": "local", CONF_COMMUNICATION_DELAY: 1},
}
await hass.config_entries.async_setup(local_config_entry.entry_id)
await hass.async_block_till_done()
assert local_config_entry.data == expected_data