Cleanup BLE-only controls when migrating HomeKit BLE device to Thread (#110334)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Jc2k 2024-02-27 22:02:27 +00:00 committed by GitHub
parent c890c1aeee
commit cf849664ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 39 additions and 84 deletions

View File

@ -5,6 +5,7 @@ characteristics that don't map to a Home Assistant feature.
""" """
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
@ -32,6 +33,7 @@ _LOGGER = logging.getLogger(__name__)
class HomeKitButtonEntityDescription(ButtonEntityDescription): class HomeKitButtonEntityDescription(ButtonEntityDescription):
"""Describes Homekit button.""" """Describes Homekit button."""
probe: Callable[[Characteristic], bool] | None = None
write_value: int | str | None = None write_value: int | str | None = None
@ -71,13 +73,19 @@ async def async_setup_entry(
@callback @callback
def async_add_characteristic(char: Characteristic) -> bool: def async_add_characteristic(char: Characteristic) -> bool:
entities: list[HomeKitButton | HomeKitEcobeeClearHoldButton] = [] entities: list[CharacteristicEntity] = []
info = {"aid": char.service.accessory.aid, "iid": char.service.iid} info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
if description := BUTTON_ENTITIES.get(char.type): if description := BUTTON_ENTITIES.get(char.type):
entities.append(HomeKitButton(conn, info, char, description)) entities.append(HomeKitButton(conn, info, char, description))
elif entity_type := BUTTON_ENTITY_CLASSES.get(char.type): elif entity_type := BUTTON_ENTITY_CLASSES.get(char.type):
entities.append(entity_type(conn, info, char)) entities.append(entity_type(conn, info, char))
elif char.type == CharacteristicsTypes.THREAD_CONTROL_POINT:
if not conn.is_unprovisioned_thread_device:
return False
entities.append(
HomeKitProvisionPreferredThreadCredentials(conn, info, char)
)
else: else:
return False return False
@ -92,7 +100,11 @@ async def async_setup_entry(
conn.add_char_factory(async_add_characteristic) conn.add_char_factory(async_add_characteristic)
class HomeKitButton(CharacteristicEntity, ButtonEntity): class BaseHomeKitButton(CharacteristicEntity, ButtonEntity):
"""Base class for all HomeKit buttons."""
class HomeKitButton(BaseHomeKitButton):
"""Representation of a Button control on a homekit accessory.""" """Representation of a Button control on a homekit accessory."""
entity_description: HomeKitButtonEntityDescription entity_description: HomeKitButtonEntityDescription
@ -126,7 +138,7 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity):
await self.async_put_characteristics({key: val}) await self.async_put_characteristics({key: val})
class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity): class HomeKitEcobeeClearHoldButton(BaseHomeKitButton):
"""Representation of a Button control for Ecobee clear hold request.""" """Representation of a Button control for Ecobee clear hold request."""
def get_characteristic_types(self) -> list[str]: def get_characteristic_types(self) -> list[str]:
@ -155,7 +167,7 @@ class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity):
await self.async_put_characteristics({key: val}) await self.async_put_characteristics({key: val})
class HomeKitProvisionPreferredThreadCredentials(CharacteristicEntity, ButtonEntity): class HomeKitProvisionPreferredThreadCredentials(BaseHomeKitButton):
"""A button users can press to migrate their HomeKit BLE device to Thread.""" """A button users can press to migrate their HomeKit BLE device to Thread."""
_attr_entity_category = EntityCategory.CONFIG _attr_entity_category = EntityCategory.CONFIG
@ -179,5 +191,4 @@ class HomeKitProvisionPreferredThreadCredentials(CharacteristicEntity, ButtonEnt
BUTTON_ENTITY_CLASSES: dict[str, type] = { BUTTON_ENTITY_CLASSES: dict[str, type] = {
CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD: HomeKitEcobeeClearHoldButton, CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD: HomeKitEcobeeClearHoldButton,
CharacteristicsTypes.THREAD_CONTROL_POINT: HomeKitProvisionPreferredThreadCredentials,
} }

View File

@ -18,7 +18,7 @@ from aiohomekit.exceptions import (
EncryptionError, EncryptionError,
) )
from aiohomekit.model import Accessories, Accessory, Transport from aiohomekit.model import Accessories, Accessory, Transport
from aiohomekit.model.characteristics import Characteristic from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.thread.dataset_store import async_get_preferred_dataset from homeassistant.components.thread.dataset_store import async_get_preferred_dataset
@ -544,6 +544,10 @@ class HKDevice:
current_unique_id.add((accessory.aid, service.iid, None)) current_unique_id.add((accessory.aid, service.iid, None))
for char in service.characteristics: for char in service.characteristics:
if self.pairing.transport != Transport.BLE:
if char.type == CharacteristicsTypes.THREAD_CONTROL_POINT:
continue
current_unique_id.add( current_unique_id.add(
( (
accessory.aid, accessory.aid,

View File

@ -70,44 +70,6 @@
'state': 'unknown', 'state': 'unknown',
}), }),
}), }),
dict({
'entry': dict({
'aliases': list([
]),
'area_id': None,
'capabilities': None,
'config_entry_id': 'TestData',
'device_class': None,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.airversa_ap2_1808_provision_preferred_thread_credentials',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'labels': list([
]),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Airversa AP2 1808 Provision Preferred Thread Credentials',
'platform': 'homekit_controller',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_112_119',
'unit_of_measurement': None,
}),
'state': dict({
'attributes': dict({
'friendly_name': 'Airversa AP2 1808 Provision Preferred Thread Credentials',
}),
'entity_id': 'button.airversa_ap2_1808_provision_preferred_thread_credentials',
'state': 'unknown',
}),
}),
dict({ dict({
'entry': dict({ 'entry': dict({
'aliases': list([ 'aliases': list([
@ -14200,44 +14162,6 @@
'state': 'unknown', 'state': 'unknown',
}), }),
}), }),
dict({
'entry': dict({
'aliases': list([
]),
'area_id': None,
'capabilities': None,
'config_entry_id': 'TestData',
'device_class': None,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.nanoleaf_strip_3b32_provision_preferred_thread_credentials',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'labels': list([
]),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Nanoleaf Strip 3B32 Provision Preferred Thread Credentials',
'platform': 'homekit_controller',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_31_119',
'unit_of_measurement': None,
}),
'state': dict({
'attributes': dict({
'friendly_name': 'Nanoleaf Strip 3B32 Provision Preferred Thread Credentials',
}),
'entity_id': 'button.nanoleaf_strip_3b32_provision_preferred_thread_credentials',
'state': 'unknown',
}),
}),
dict({ dict({
'entry': dict({ 'entry': dict({
'aliases': list([ 'aliases': list([

View File

@ -14,7 +14,7 @@ from homeassistant.components.homekit_controller.const import (
from homeassistant.components.thread import async_add_dataset, dataset_store from homeassistant.components.thread import async_add_dataset, dataset_store
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr, entity_registry as er
from .common import setup_accessories_from_file, setup_platform, setup_test_accessories from .common import setup_accessories_from_file, setup_platform, setup_test_accessories
@ -216,7 +216,9 @@ async def test_thread_provision_no_creds(hass: HomeAssistant) -> None:
) )
async def test_thread_provision(hass: HomeAssistant) -> None: async def test_thread_provision(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test that a when a thread provision works the config entry is updated.""" """Test that a when a thread provision works the config entry is updated."""
await async_add_dataset( await async_add_dataset(
hass, hass,
@ -256,6 +258,13 @@ async def test_thread_provision(hass: HomeAssistant) -> None:
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(
"button.nanoleaf_strip_3b32_provision_preferred_thread_credentials"
)
assert entity_registry.async_get(
"button.nanoleaf_strip_3b32_provision_preferred_thread_credentials"
)
await hass.services.async_call( await hass.services.async_call(
"button", "button",
"press", "press",
@ -267,6 +276,13 @@ async def test_thread_provision(hass: HomeAssistant) -> None:
assert config_entry.data["Connection"] == "CoAP" assert config_entry.data["Connection"] == "CoAP"
assert not hass.states.get(
"button.nanoleaf_strip_3b32_provision_preferred_thread_credentials"
)
assert not entity_registry.async_get(
"button.nanoleaf_strip_3b32_provision_preferred_thread_credentials"
)
async def test_thread_provision_migration_failed(hass: HomeAssistant) -> None: async def test_thread_provision_migration_failed(hass: HomeAssistant) -> None:
"""Test that when a device 'migrates' but doesn't show up in CoAP, we remain in BLE mode.""" """Test that when a device 'migrates' but doesn't show up in CoAP, we remain in BLE mode."""