Restore HomeKit Controller BLE GSN at startup (#83206)

This commit is contained in:
J. Nick Koston 2022-12-04 07:01:37 -10:00 committed by GitHub
parent de77132a5a
commit 1577f6ea50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 50 additions and 37 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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