mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Poll HomeKit Controller locks for state after lock operation (#82058)
This commit is contained in:
parent
bbda122c99
commit
1ded3ac51e
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -22,6 +22,7 @@ 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 CALLBACK_TYPE, 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.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
@ -29,6 +30,7 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||||||
from .const import (
|
from .const import (
|
||||||
CHARACTERISTIC_PLATFORMS,
|
CHARACTERISTIC_PLATFORMS,
|
||||||
CONTROLLER,
|
CONTROLLER,
|
||||||
|
DEBOUNCE_COOLDOWN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HOMEKIT_ACCESSORY_DISPATCH,
|
HOMEKIT_ACCESSORY_DISPATCH,
|
||||||
IDENTIFIER_ACCESSORY_ID,
|
IDENTIFIER_ACCESSORY_ID,
|
||||||
@ -41,6 +43,8 @@ from .device_trigger import async_fire_triggers, async_setup_triggers_for_entry
|
|||||||
|
|
||||||
RETRY_INTERVAL = 60 # seconds
|
RETRY_INTERVAL = 60 # seconds
|
||||||
MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE = 3
|
MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE = 3
|
||||||
|
|
||||||
|
|
||||||
BLE_AVAILABILITY_CHECK_INTERVAL = 1800 # seconds
|
BLE_AVAILABILITY_CHECK_INTERVAL = 1800 # seconds
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -127,6 +131,14 @@ class HKDevice:
|
|||||||
|
|
||||||
self.watchable_characteristics: list[tuple[int, int]] = []
|
self.watchable_characteristics: list[tuple[int, int]] = []
|
||||||
|
|
||||||
|
self._debounced_update = Debouncer(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
cooldown=DEBOUNCE_COOLDOWN,
|
||||||
|
immediate=False,
|
||||||
|
function=self.async_update,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_map(self) -> Accessories:
|
def entity_map(self) -> Accessories:
|
||||||
"""Return the accessories from the pairing."""
|
"""Return the accessories from the pairing."""
|
||||||
@ -240,8 +252,11 @@ 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._polling_interval_remover = async_track_time_interval(
|
||||||
self.hass, self.async_update, self.pairing.poll_interval
|
self.hass, self.async_request_update, self.pairing.poll_interval
|
||||||
)
|
)
|
||||||
|
|
||||||
if transport == Transport.BLE:
|
if transport == Transport.BLE:
|
||||||
@ -631,6 +646,10 @@ class HKDevice:
|
|||||||
"""Update the available state of the device."""
|
"""Update the available state of the device."""
|
||||||
self.async_set_available_state(self.pairing.is_available)
|
self.async_set_available_state(self.pairing.is_available)
|
||||||
|
|
||||||
|
async def async_request_update(self, now: datetime | None = None) -> None:
|
||||||
|
"""Request an debounced update from the accessory."""
|
||||||
|
await self._debounced_update.async_call()
|
||||||
|
|
||||||
async def async_update(self, now=None):
|
async def async_update(self, now=None):
|
||||||
"""Poll state of all entities attached to this bridge/accessory."""
|
"""Poll state of all entities attached to this bridge/accessory."""
|
||||||
if not self.pollable_characteristics:
|
if not self.pollable_characteristics:
|
||||||
|
@ -107,3 +107,10 @@ STARTUP_EXCEPTIONS = (
|
|||||||
EncryptionError,
|
EncryptionError,
|
||||||
AccessoryDisconnectedError,
|
AccessoryDisconnectedError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 10 seconds was chosen because its soon enough
|
||||||
|
# for most state changes to happen but not too
|
||||||
|
# long that the BLE connection is dropped. It
|
||||||
|
# also happens to be the same value used by
|
||||||
|
# the update coordinator.
|
||||||
|
DEBOUNCE_COOLDOWN = 10 # seconds
|
||||||
|
@ -175,6 +175,10 @@ class HomeKitEntity(Entity):
|
|||||||
"""Define the homekit characteristics the entity cares about."""
|
"""Define the homekit characteristics the entity cares about."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def async_update(self) -> None:
|
||||||
|
"""Update the entity."""
|
||||||
|
await self._accessory.async_request_update()
|
||||||
|
|
||||||
|
|
||||||
class AccessoryEntity(HomeKitEntity):
|
class AccessoryEntity(HomeKitEntity):
|
||||||
"""A HomeKit entity that is related to an entire accessory rather than a specific service or characteristic."""
|
"""A HomeKit entity that is related to an entire accessory rather than a specific service or characteristic."""
|
||||||
|
@ -124,6 +124,10 @@ class HomeKitLock(HomeKitEntity, LockEntity):
|
|||||||
await self.async_put_characteristics(
|
await self.async_put_characteristics(
|
||||||
{CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]}
|
{CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]}
|
||||||
)
|
)
|
||||||
|
# Some locks need to be polled to update the current state
|
||||||
|
# after a target state change.
|
||||||
|
# https://github.com/home-assistant/core/issues/81887
|
||||||
|
await self._accessory.async_request_update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
@ -21,6 +21,7 @@ from aiohomekit.zeroconf import HomeKitService
|
|||||||
from homeassistant.components.device_automation import DeviceAutomationType
|
from homeassistant.components.device_automation import DeviceAutomationType
|
||||||
from homeassistant.components.homekit_controller.const import (
|
from homeassistant.components.homekit_controller.const import (
|
||||||
CONTROLLER,
|
CONTROLLER,
|
||||||
|
DEBOUNCE_COOLDOWN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HOMEKIT_ACCESSORY_DISPATCH,
|
HOMEKIT_ACCESSORY_DISPATCH,
|
||||||
IDENTIFIER_ACCESSORY_ID,
|
IDENTIFIER_ACCESSORY_ID,
|
||||||
@ -146,6 +147,7 @@ class Helper:
|
|||||||
# If they are enabled, then HA will pick up the changes next time
|
# If they are enabled, then HA will pick up the changes next time
|
||||||
# we yield control
|
# we yield control
|
||||||
await time_changed(self.hass, 60)
|
await time_changed(self.hass, 60)
|
||||||
|
await time_changed(self.hass, DEBOUNCE_COOLDOWN)
|
||||||
|
|
||||||
await self.hass.async_block_till_done()
|
await self.hass.async_block_till_done()
|
||||||
|
|
||||||
@ -165,6 +167,7 @@ class Helper:
|
|||||||
async def poll_and_get_state(self) -> State:
|
async def poll_and_get_state(self) -> State:
|
||||||
"""Trigger a time based poll and return the current entity state."""
|
"""Trigger a time based poll and return the current entity state."""
|
||||||
await time_changed(self.hass, 60)
|
await time_changed(self.hass, 60)
|
||||||
|
await time_changed(self.hass, DEBOUNCE_COOLDOWN)
|
||||||
|
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
assert state is not None
|
assert state is not None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user