Restore HomeKit Controller BLE GSN at startup (#83206)
This commit is contained in:
parent
de77132a5a
commit
1577f6ea50
8 changed files with 50 additions and 37 deletions
|
@ -84,7 +84,7 @@ async def async_setup_entry(
|
||||||
entity.old_unique_id, entity.unique_id, Platform.BUTTON
|
entity.old_unique_id, entity.unique_id, Platform.BUTTON
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
conn.add_char_factory(async_add_characteristic)
|
conn.add_char_factory(async_add_characteristic)
|
||||||
|
|
|
@ -583,6 +583,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
accessories_state.config_num,
|
accessories_state.config_num,
|
||||||
accessories_state.accessories.serialize(),
|
accessories_state.accessories.serialize(),
|
||||||
serialize_broadcast_key(accessories_state.broadcast_key),
|
serialize_broadcast_key(accessories_state.broadcast_key),
|
||||||
|
accessories_state.state_num,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.async_create_entry(title=name, data=pairing_data)
|
return self.async_create_entry(title=name, data=pairing_data)
|
||||||
|
|
|
@ -20,7 +20,7 @@ from aiohomekit.model.services import Service
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_VIA_DEVICE, EVENT_HOMEASSISTANT_STARTED
|
from homeassistant.const import ATTR_VIA_DEVICE, EVENT_HOMEASSISTANT_STARTED
|
||||||
from homeassistant.core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback
|
from homeassistant.core import CoreState, Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.debounce import Debouncer
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
@ -116,11 +116,6 @@ class HKDevice:
|
||||||
|
|
||||||
self.pollable_characteristics: list[tuple[int, int]] = []
|
self.pollable_characteristics: list[tuple[int, int]] = []
|
||||||
|
|
||||||
# If this is set polling is active and can be disabled by calling
|
|
||||||
# this method.
|
|
||||||
self._polling_interval_remover: CALLBACK_TYPE | None = None
|
|
||||||
self._ble_available_interval_remover: CALLBACK_TYPE | None = None
|
|
||||||
|
|
||||||
# Never allow concurrent polling of the same accessory or bridge
|
# Never allow concurrent polling of the same accessory or bridge
|
||||||
self._polling_lock = asyncio.Lock()
|
self._polling_lock = asyncio.Lock()
|
||||||
self._polling_lock_warned = False
|
self._polling_lock_warned = False
|
||||||
|
@ -185,8 +180,8 @@ class HKDevice:
|
||||||
self.available = available
|
self.available = available
|
||||||
async_dispatcher_send(self.hass, self.signal_state_updated)
|
async_dispatcher_send(self.hass, self.signal_state_updated)
|
||||||
|
|
||||||
async def _async_retry_populate_ble_accessory_state(self, event: Event) -> None:
|
async def _async_populate_ble_accessory_state(self, event: Event) -> None:
|
||||||
"""Try again to populate the BLE accessory state.
|
"""Populate the BLE accessory state without blocking startup.
|
||||||
|
|
||||||
If the accessory was asleep at startup we need to retry
|
If the accessory was asleep at startup we need to retry
|
||||||
since we continued on to allow startup to proceed.
|
since we continued on to allow startup to proceed.
|
||||||
|
@ -194,6 +189,7 @@ class HKDevice:
|
||||||
If this fails the state may be inconsistent, but will
|
If this fails the state may be inconsistent, but will
|
||||||
get corrected as soon as the accessory advertises again.
|
get corrected as soon as the accessory advertises again.
|
||||||
"""
|
"""
|
||||||
|
self._async_start_polling()
|
||||||
try:
|
try:
|
||||||
await self.pairing.async_populate_accessories_state(force_update=True)
|
await self.pairing.async_populate_accessories_state(force_update=True)
|
||||||
except STARTUP_EXCEPTIONS as ex:
|
except STARTUP_EXCEPTIONS as ex:
|
||||||
|
@ -221,20 +217,28 @@ class HKDevice:
|
||||||
# so we only poll those chars but that is not possible
|
# so we only poll those chars but that is not possible
|
||||||
# yet.
|
# yet.
|
||||||
attempts = None if self.hass.state == CoreState.running else 1
|
attempts = None if self.hass.state == CoreState.running else 1
|
||||||
try:
|
if (
|
||||||
await self.pairing.async_populate_accessories_state(
|
transport == Transport.BLE
|
||||||
force_update=True, attempts=attempts
|
and pairing.accessories
|
||||||
)
|
and pairing.accessories.has_aid(1)
|
||||||
except AccessoryNotFoundError:
|
):
|
||||||
if transport != Transport.BLE or not pairing.accessories:
|
# The GSN gets restored and a catch up poll will be
|
||||||
# BLE devices may sleep and we can't force a connection
|
# triggered via disconnected events automatically
|
||||||
raise
|
# if we are out of sync. To be sure we are in sync;
|
||||||
|
# If for some reason the BLE connection failed
|
||||||
|
# previously we force an update after startup
|
||||||
|
# is complete.
|
||||||
entry.async_on_unload(
|
entry.async_on_unload(
|
||||||
self.hass.bus.async_listen(
|
self.hass.bus.async_listen(
|
||||||
EVENT_HOMEASSISTANT_STARTED,
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
self._async_retry_populate_ble_accessory_state,
|
self._async_populate_ble_accessory_state,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
await self.pairing.async_populate_accessories_state(
|
||||||
|
force_update=True, attempts=attempts
|
||||||
|
)
|
||||||
|
self._async_start_polling()
|
||||||
|
|
||||||
entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events))
|
entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events))
|
||||||
entry.async_on_unload(
|
entry.async_on_unload(
|
||||||
|
@ -252,27 +256,34 @@ class HKDevice:
|
||||||
|
|
||||||
self.async_set_available_state(self.pairing.is_available)
|
self.async_set_available_state(self.pairing.is_available)
|
||||||
|
|
||||||
# We use async_request_update to avoid multiple updates
|
|
||||||
# at the same time which would generate a spurious warning
|
|
||||||
# in the log about concurrent polling.
|
|
||||||
self._polling_interval_remover = async_track_time_interval(
|
|
||||||
self.hass, self.async_request_update, self.pairing.poll_interval
|
|
||||||
)
|
|
||||||
|
|
||||||
if transport == Transport.BLE:
|
if transport == Transport.BLE:
|
||||||
# If we are using BLE, we need to periodically check of the
|
# If we are using BLE, we need to periodically check of the
|
||||||
# BLE device is available since we won't get callbacks
|
# BLE device is available since we won't get callbacks
|
||||||
# when it goes away since we HomeKit supports disconnected
|
# when it goes away since we HomeKit supports disconnected
|
||||||
# notifications and we cannot treat a disconnect as unavailability.
|
# notifications and we cannot treat a disconnect as unavailability.
|
||||||
self._ble_available_interval_remover = async_track_time_interval(
|
entry.async_on_unload(
|
||||||
self.hass,
|
async_track_time_interval(
|
||||||
self.async_update_available_state,
|
self.hass,
|
||||||
timedelta(seconds=BLE_AVAILABILITY_CHECK_INTERVAL),
|
self.async_update_available_state,
|
||||||
|
timedelta(seconds=BLE_AVAILABILITY_CHECK_INTERVAL),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# BLE devices always get an RSSI sensor as well
|
# BLE devices always get an RSSI sensor as well
|
||||||
if "sensor" not in self.platforms:
|
if "sensor" not in self.platforms:
|
||||||
await self.async_load_platform("sensor")
|
await self.async_load_platform("sensor")
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_start_polling(self) -> None:
|
||||||
|
"""Start polling for updates."""
|
||||||
|
# We use async_request_update to avoid multiple updates
|
||||||
|
# at the same time which would generate a spurious warning
|
||||||
|
# in the log about concurrent polling.
|
||||||
|
self.config_entry.async_on_unload(
|
||||||
|
async_track_time_interval(
|
||||||
|
self.hass, self.async_request_update, self.pairing.poll_interval
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_add_new_entities(self) -> None:
|
async def async_add_new_entities(self) -> None:
|
||||||
"""Add new entities to Home Assistant."""
|
"""Add new entities to Home Assistant."""
|
||||||
await self.async_load_platforms()
|
await self.async_load_platforms()
|
||||||
|
@ -529,9 +540,6 @@ class HKDevice:
|
||||||
|
|
||||||
async def async_unload(self) -> None:
|
async def async_unload(self) -> None:
|
||||||
"""Stop interacting with device and prepare for removal from hass."""
|
"""Stop interacting with device and prepare for removal from hass."""
|
||||||
if self._polling_interval_remover:
|
|
||||||
self._polling_interval_remover()
|
|
||||||
|
|
||||||
await self.pairing.shutdown()
|
await self.pairing.shutdown()
|
||||||
|
|
||||||
await self.hass.config_entries.async_unload_platforms(
|
await self.hass.config_entries.async_unload_platforms(
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "HomeKit Controller",
|
"name": "HomeKit Controller",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"requirements": ["aiohomekit==2.3.6"],
|
"requirements": ["aiohomekit==2.4.0"],
|
||||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."],
|
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."],
|
||||||
"bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }],
|
"bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }],
|
||||||
"dependencies": ["bluetooth", "zeroconf"],
|
"dependencies": ["bluetooth", "zeroconf"],
|
||||||
|
|
|
@ -78,7 +78,7 @@ async def async_setup_entry(
|
||||||
entity.old_unique_id, entity.unique_id, Platform.NUMBER
|
entity.old_unique_id, entity.unique_id, Platform.NUMBER
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
conn.add_char_factory(async_add_characteristic)
|
conn.add_char_factory(async_add_characteristic)
|
||||||
|
|
|
@ -61,11 +61,15 @@ class EntityMapStorage:
|
||||||
config_num: int,
|
config_num: int,
|
||||||
accessories: list[Any],
|
accessories: list[Any],
|
||||||
broadcast_key: str | None = None,
|
broadcast_key: str | None = None,
|
||||||
|
state_num: int | None = None,
|
||||||
) -> Pairing:
|
) -> Pairing:
|
||||||
"""Create a new pairing cache."""
|
"""Create a new pairing cache."""
|
||||||
_LOGGER.debug("Creating or updating entity map for %s", homekit_id)
|
_LOGGER.debug("Creating or updating entity map for %s", homekit_id)
|
||||||
data = Pairing(
|
data = Pairing(
|
||||||
config_num=config_num, accessories=accessories, broadcast_key=broadcast_key
|
config_num=config_num,
|
||||||
|
accessories=accessories,
|
||||||
|
broadcast_key=broadcast_key,
|
||||||
|
state_num=state_num,
|
||||||
)
|
)
|
||||||
self.storage_data[homekit_id] = data
|
self.storage_data[homekit_id] = data
|
||||||
self._async_schedule_save()
|
self._async_schedule_save()
|
||||||
|
|
|
@ -174,7 +174,7 @@ aioguardian==2022.07.0
|
||||||
aioharmony==0.2.9
|
aioharmony==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==2.3.6
|
aiohomekit==2.4.0
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
|
|
|
@ -158,7 +158,7 @@ aioguardian==2022.07.0
|
||||||
aioharmony==0.2.9
|
aioharmony==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==2.3.6
|
aiohomekit==2.4.0
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue