mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
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:
parent
92b3c0c96b
commit
d4c5a93b63
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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"
|
||||||
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
21
tests/components/risco/test_init.py
Normal file
21
tests/components/risco/test_init.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user