Add a homekit.unpair service to forcefully remove pairings (#53303)
- Sometimes homekit will go unresponsive because a pairing for a specific device is missing. To avoid deleting the config entry and recreating it, which can be a painful process if there are many bridged entities, the homekit.unpair service allows forceful removal of the pairings so the accessory can be paired again.
This commit is contained in:
parent
80c535f02e
commit
009f34bfed
4 changed files with 193 additions and 3 deletions
|
@ -23,6 +23,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
|||
from homeassistant.const import (
|
||||
ATTR_BATTERY_CHARGING,
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_DEVICE_ID,
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_NAME,
|
||||
|
@ -34,11 +35,12 @@ from homeassistant.const import (
|
|||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import CoreState, HomeAssistant, callback
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
||||
from homeassistant.helpers import device_registry, entity_registry
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entityfilter import BASE_FILTER_SCHEMA, FILTER_SCHEMA
|
||||
from homeassistant.helpers.reload import async_integration_yaml_config
|
||||
from homeassistant.helpers.service import async_extract_referenced_entity_ids
|
||||
from homeassistant.loader import IntegrationNotFound, async_get_integration
|
||||
|
||||
from . import ( # noqa: F401
|
||||
|
@ -93,6 +95,7 @@ from .const import (
|
|||
MANUFACTURER,
|
||||
SERVICE_HOMEKIT_RESET_ACCESSORY,
|
||||
SERVICE_HOMEKIT_START,
|
||||
SERVICE_HOMEKIT_UNPAIR,
|
||||
SHUTDOWN_TIMEOUT,
|
||||
)
|
||||
from .util import (
|
||||
|
@ -170,6 +173,12 @@ RESET_ACCESSORY_SERVICE_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
UNPAIR_SERVICE_SCHEMA = vol.All(
|
||||
vol.Schema(cv.ENTITY_SERVICE_FIELDS),
|
||||
cv.has_at_least_one_key(ATTR_DEVICE_ID),
|
||||
)
|
||||
|
||||
|
||||
def _async_get_entries_by_name(current_entries):
|
||||
"""Return a dict of the entries by name."""
|
||||
|
||||
|
@ -356,7 +365,7 @@ def _async_register_events_and_services(hass: HomeAssistant):
|
|||
hass.http.register_view(HomeKitPairingQRView)
|
||||
|
||||
async def async_handle_homekit_reset_accessory(service):
|
||||
"""Handle start HomeKit service call."""
|
||||
"""Handle reset accessory HomeKit service call."""
|
||||
for entry_id in hass.data[DOMAIN]:
|
||||
if HOMEKIT not in hass.data[DOMAIN][entry_id]:
|
||||
continue
|
||||
|
@ -378,6 +387,44 @@ def _async_register_events_and_services(hass: HomeAssistant):
|
|||
schema=RESET_ACCESSORY_SERVICE_SCHEMA,
|
||||
)
|
||||
|
||||
async def async_handle_homekit_unpair(service):
|
||||
"""Handle unpair HomeKit service call."""
|
||||
referenced = await async_extract_referenced_entity_ids(hass, service)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
for device_id in referenced.referenced_devices:
|
||||
dev_reg_ent = dev_reg.async_get(device_id)
|
||||
if not dev_reg_ent:
|
||||
raise HomeAssistantError(f"No device found for device id: {device_id}")
|
||||
macs = [
|
||||
cval
|
||||
for ctype, cval in dev_reg_ent.connections
|
||||
if ctype == device_registry.CONNECTION_NETWORK_MAC
|
||||
]
|
||||
domain_data = hass.data[DOMAIN]
|
||||
matching_instances = [
|
||||
domain_data[entry_id][HOMEKIT]
|
||||
for entry_id in domain_data
|
||||
if HOMEKIT in domain_data[entry_id]
|
||||
and domain_data[entry_id][HOMEKIT].driver
|
||||
and device_registry.format_mac(
|
||||
domain_data[entry_id][HOMEKIT].driver.state.mac
|
||||
)
|
||||
in macs
|
||||
]
|
||||
if not matching_instances:
|
||||
raise HomeAssistantError(
|
||||
f"No homekit accessory found for device id: {device_id}"
|
||||
)
|
||||
for homekit in matching_instances:
|
||||
homekit.async_unpair()
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_HOMEKIT_UNPAIR,
|
||||
async_handle_homekit_unpair,
|
||||
schema=UNPAIR_SERVICE_SCHEMA,
|
||||
)
|
||||
|
||||
async def async_handle_homekit_service_start(service):
|
||||
"""Handle start HomeKit service call."""
|
||||
tasks = []
|
||||
|
@ -639,7 +686,11 @@ class HomeKit:
|
|||
|
||||
if self.driver.state.paired:
|
||||
return
|
||||
self._async_show_setup_message()
|
||||
|
||||
@callback
|
||||
def _async_show_setup_message(self):
|
||||
"""Show the pairing setup message."""
|
||||
show_setup_message(
|
||||
self.hass,
|
||||
self._entry_id,
|
||||
|
@ -648,6 +699,16 @@ class HomeKit:
|
|||
self.driver.accessory.xhm_uri(),
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_unpair(self):
|
||||
"""Remove all pairings for an accessory so it can be repaired."""
|
||||
state = self.driver.state
|
||||
for client_uuid in list(state.paired_clients):
|
||||
state.remove_paired_client(client_uuid)
|
||||
self.driver.async_persist()
|
||||
self.driver.async_update_advertisement()
|
||||
self._async_show_setup_message()
|
||||
|
||||
@callback
|
||||
def _async_register_bridge(self):
|
||||
"""Register the bridge as a device so homekit_controller and exclude it from discovery."""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue