mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Set up konnected entities even if panel isn't immediately reachable (#38879)
This commit is contained in:
parent
472b12bef5
commit
a6236886e4
@ -63,7 +63,6 @@ from .const import (
|
|||||||
ZONE_TO_PIN,
|
ZONE_TO_PIN,
|
||||||
ZONES,
|
ZONES,
|
||||||
)
|
)
|
||||||
from .errors import CannotConnect
|
|
||||||
from .handlers import HANDLERS
|
from .handlers import HANDLERS
|
||||||
from .panel import AlarmPanel
|
from .panel import AlarmPanel
|
||||||
|
|
||||||
@ -258,11 +257,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
# creates a panel data store in hass.data[DOMAIN][CONF_DEVICES]
|
# creates a panel data store in hass.data[DOMAIN][CONF_DEVICES]
|
||||||
await client.async_save_data()
|
await client.async_save_data()
|
||||||
|
|
||||||
try:
|
# if the cfg entry was created we know we could connect to the panel at some point
|
||||||
await client.async_connect()
|
# async_connect will handle retries until it establishes a connection
|
||||||
except CannotConnect:
|
await client.async_connect()
|
||||||
# this will trigger a retry in the future
|
|
||||||
raise config_entries.ConfigEntryNotReady
|
|
||||||
|
|
||||||
for component in PLATFORMS:
|
for component in PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
@ -355,6 +352,11 @@ class KonnectedView(HomeAssistantView):
|
|||||||
"unregistered device", status_code=HTTP_BAD_REQUEST
|
"unregistered device", status_code=HTTP_BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
|
||||||
|
panel = device.get("panel")
|
||||||
|
if panel is not None:
|
||||||
|
# connect if we haven't already
|
||||||
|
hass.async_create_task(panel.async_connect())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
zone_num = str(payload.get(CONF_ZONE) or PIN_TO_ZONE[payload[CONF_PIN]])
|
zone_num = str(payload.get(CONF_ZONE) or PIN_TO_ZONE[payload[CONF_PIN]])
|
||||||
payload[CONF_ZONE] = zone_num
|
payload[CONF_ZONE] = zone_num
|
||||||
@ -390,6 +392,11 @@ class KonnectedView(HomeAssistantView):
|
|||||||
f"Device {device_id} not configured", status_code=HTTP_NOT_FOUND
|
f"Device {device_id} not configured", status_code=HTTP_NOT_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
|
panel = device.get("panel")
|
||||||
|
if panel is not None:
|
||||||
|
# connect if we haven't already
|
||||||
|
hass.async_create_task(panel.async_connect())
|
||||||
|
|
||||||
# Our data model is based on zone ids but we convert from/to pin ids
|
# Our data model is based on zone ids but we convert from/to pin ids
|
||||||
# based on whether they are specified in the request
|
# based on whether they are specified in the request
|
||||||
try:
|
try:
|
||||||
|
@ -73,6 +73,9 @@ class AlarmPanel:
|
|||||||
self.client = None
|
self.client = None
|
||||||
self.status = None
|
self.status = None
|
||||||
self.api_version = KONN_API_VERSIONS[KONN_MODEL]
|
self.api_version = KONN_API_VERSIONS[KONN_MODEL]
|
||||||
|
self.connected = False
|
||||||
|
self.connect_attempts = 0
|
||||||
|
self.cancel_connect_retry = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_id(self):
|
def device_id(self):
|
||||||
@ -84,6 +87,11 @@ class AlarmPanel:
|
|||||||
"""Return the configuration stored in `hass.data` for this device."""
|
"""Return the configuration stored in `hass.data` for this device."""
|
||||||
return self.hass.data[DOMAIN][CONF_DEVICES].get(self.device_id)
|
return self.hass.data[DOMAIN][CONF_DEVICES].get(self.device_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return whether the device is available."""
|
||||||
|
return self.connected
|
||||||
|
|
||||||
def format_zone(self, zone, other_items=None):
|
def format_zone(self, zone, other_items=None):
|
||||||
"""Get zone or pin based dict based on the client type."""
|
"""Get zone or pin based dict based on the client type."""
|
||||||
payload = {
|
payload = {
|
||||||
@ -94,8 +102,15 @@ class AlarmPanel:
|
|||||||
payload.update(other_items or {})
|
payload.update(other_items or {})
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
async def async_connect(self):
|
async def async_connect(self, now=None):
|
||||||
"""Connect to and setup a Konnected device."""
|
"""Connect to and setup a Konnected device."""
|
||||||
|
if self.connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.cancel_connect_retry:
|
||||||
|
# cancel any pending connect attempt and try now
|
||||||
|
self.cancel_connect_retry()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.client = konnected.Client(
|
self.client = konnected.Client(
|
||||||
host=self.host,
|
host=self.host,
|
||||||
@ -118,8 +133,16 @@ class AlarmPanel:
|
|||||||
|
|
||||||
except self.client.ClientError as err:
|
except self.client.ClientError as err:
|
||||||
_LOGGER.warning("Exception trying to connect to panel: %s", err)
|
_LOGGER.warning("Exception trying to connect to panel: %s", err)
|
||||||
raise CannotConnect
|
|
||||||
|
|
||||||
|
# retry in a bit, never more than ~3 min
|
||||||
|
self.connect_attempts += 1
|
||||||
|
self.cancel_connect_retry = self.hass.helpers.event.async_call_later(
|
||||||
|
2 ** min(self.connect_attempts, 5) * 5, self.async_connect
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.connect_attempts = 0
|
||||||
|
self.connected = True
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Set up Konnected device %s. Open http://%s:%s in a "
|
"Set up Konnected device %s. Open http://%s:%s in a "
|
||||||
"web browser to view device status",
|
"web browser to view device status",
|
||||||
@ -129,7 +152,6 @@ class AlarmPanel:
|
|||||||
)
|
)
|
||||||
|
|
||||||
device_registry = await dr.async_get_registry(self.hass)
|
device_registry = await dr.async_get_registry(self.hass)
|
||||||
|
|
||||||
device_registry.async_get_or_create(
|
device_registry.async_get_or_create(
|
||||||
config_entry_id=self.config_entry.entry_id,
|
config_entry_id=self.config_entry.entry_id,
|
||||||
connections={(dr.CONNECTION_NETWORK_MAC, self.status.get("mac"))},
|
connections={(dr.CONNECTION_NETWORK_MAC, self.status.get("mac"))},
|
||||||
|
@ -81,6 +81,11 @@ class KonnectedSwitch(ToggleEntity):
|
|||||||
"identifiers": {(KONNECTED_DOMAIN, self._device_id)},
|
"identifiers": {(KONNECTED_DOMAIN, self._device_id)},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return whether the panel is available."""
|
||||||
|
return self.panel.available
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Send a command to turn on the switch."""
|
"""Send a command to turn on the switch."""
|
||||||
resp = await self.panel.update_switch(
|
resp = await self.panel.update_switch(
|
||||||
|
@ -411,12 +411,14 @@ async def test_api(hass, aiohttp_client, mock_panel):
|
|||||||
"id": "112233445566",
|
"id": "112233445566",
|
||||||
"model": "Konnected Pro",
|
"model": "Konnected Pro",
|
||||||
"access_token": "abcdefgh",
|
"access_token": "abcdefgh",
|
||||||
|
"api_host": "http://192.168.86.32:8123",
|
||||||
"default_options": config_flow.OPTIONS_SCHEMA({config_flow.CONF_IO: {}}),
|
"default_options": config_flow.OPTIONS_SCHEMA({config_flow.CONF_IO: {}}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
device_options = config_flow.OPTIONS_SCHEMA(
|
device_options = config_flow.OPTIONS_SCHEMA(
|
||||||
{
|
{
|
||||||
|
"api_host": "http://192.168.86.32:8123",
|
||||||
"io": {
|
"io": {
|
||||||
"1": "Binary Sensor",
|
"1": "Binary Sensor",
|
||||||
"2": "Binary Sensor",
|
"2": "Binary Sensor",
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
"""Test Konnected setup process."""
|
"""Test Konnected setup process."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.konnected import config_flow, panel
|
from homeassistant.components.konnected import config_flow, panel
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import utcnow
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_panel")
|
@pytest.fixture(name="mock_panel")
|
||||||
@ -551,3 +554,118 @@ async def test_default_options(hass, mock_panel):
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connect_retry(hass, mock_panel):
|
||||||
|
"""Test that we create a Konnected Panel and save the data."""
|
||||||
|
device_config = config_flow.CONFIG_ENTRY_SCHEMA(
|
||||||
|
{
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 1234,
|
||||||
|
"id": "112233445566",
|
||||||
|
"model": "Konnected Pro",
|
||||||
|
"access_token": "11223344556677889900",
|
||||||
|
"default_options": config_flow.OPTIONS_SCHEMA(
|
||||||
|
{
|
||||||
|
"io": {
|
||||||
|
"1": "Binary Sensor",
|
||||||
|
"2": "Binary Sensor",
|
||||||
|
"3": "Binary Sensor",
|
||||||
|
"4": "Digital Sensor",
|
||||||
|
"5": "Digital Sensor",
|
||||||
|
"6": "Switchable Output",
|
||||||
|
"out": "Switchable Output",
|
||||||
|
},
|
||||||
|
"binary_sensors": [
|
||||||
|
{"zone": "1", "type": "door"},
|
||||||
|
{
|
||||||
|
"zone": "2",
|
||||||
|
"type": "window",
|
||||||
|
"name": "winder",
|
||||||
|
"inverse": True,
|
||||||
|
},
|
||||||
|
{"zone": "3", "type": "door"},
|
||||||
|
],
|
||||||
|
"sensors": [
|
||||||
|
{"zone": "4", "type": "dht"},
|
||||||
|
{"zone": "5", "type": "ds18b20", "name": "temper"},
|
||||||
|
],
|
||||||
|
"switches": [
|
||||||
|
{
|
||||||
|
"zone": "out",
|
||||||
|
"name": "switcher",
|
||||||
|
"activation": "low",
|
||||||
|
"momentary": 50,
|
||||||
|
"pause": 100,
|
||||||
|
"repeat": 4,
|
||||||
|
},
|
||||||
|
{"zone": "6"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain="konnected",
|
||||||
|
title="Konnected Alarm Panel",
|
||||||
|
data=device_config,
|
||||||
|
options={},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
# fail first 2 attempts, and succeed the third
|
||||||
|
mock_panel.get_status.side_effect = [
|
||||||
|
mock_panel.ClientError,
|
||||||
|
mock_panel.ClientError,
|
||||||
|
{
|
||||||
|
"hwVersion": "2.3.0",
|
||||||
|
"swVersion": "2.3.1",
|
||||||
|
"heap": 10000,
|
||||||
|
"uptime": 12222,
|
||||||
|
"ip": "192.168.1.90",
|
||||||
|
"port": 9123,
|
||||||
|
"sensors": [],
|
||||||
|
"actuators": [],
|
||||||
|
"dht_sensors": [],
|
||||||
|
"ds18b20_sensors": [],
|
||||||
|
"mac": "11:22:33:44:55:66",
|
||||||
|
"model": "Konnected Pro",
|
||||||
|
"settings": {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# setup the integration and inspect panel behavior
|
||||||
|
assert (
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
panel.DOMAIN,
|
||||||
|
{
|
||||||
|
panel.DOMAIN: {
|
||||||
|
panel.CONF_ACCESS_TOKEN: "arandomstringvalue",
|
||||||
|
panel.CONF_API_HOST: "http://192.168.1.1:8123",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
# confirm switch is unavailable after initial attempt
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("switch.konnected_445566_actuator_6").state == "unavailable"
|
||||||
|
|
||||||
|
# confirm switch is unavailable after second attempt
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=11))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.helpers.entity_component.async_update_entity(
|
||||||
|
"switch.konnected_445566_actuator_6"
|
||||||
|
)
|
||||||
|
assert hass.states.get("switch.konnected_445566_actuator_6").state == "unavailable"
|
||||||
|
|
||||||
|
# confirm switch is available after third attempt
|
||||||
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=21))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.helpers.entity_component.async_update_entity(
|
||||||
|
"switch.konnected_445566_actuator_6"
|
||||||
|
)
|
||||||
|
assert hass.states.get("switch.konnected_445566_actuator_6").state == "off"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user