Include HKC BLE MAC in device info when available (#141900)

* Include HKC BLE MAC in device info when available

* update tests

* cover

* dry

* dry

* dry
This commit is contained in:
J. Nick Koston 2025-04-13 22:14:48 -10:00 committed by GitHub
parent 1aa996d5f0
commit 6f02550ac3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 106 additions and 43 deletions

View File

@ -9,10 +9,11 @@ from functools import partial
import logging import logging
from operator import attrgetter from operator import attrgetter
from types import MappingProxyType from types import MappingProxyType
from typing import Any from typing import Any, cast
from aiohomekit import Controller from aiohomekit import Controller
from aiohomekit.controller import TransportType from aiohomekit.controller import TransportType
from aiohomekit.controller.ble.discovery import BleDiscovery
from aiohomekit.exceptions import ( from aiohomekit.exceptions import (
AccessoryDisconnectedError, AccessoryDisconnectedError,
AccessoryNotFoundError, AccessoryNotFoundError,
@ -372,6 +373,16 @@ class HKDevice:
if not self.unreliable_serial_numbers: if not self.unreliable_serial_numbers:
identifiers.add((IDENTIFIER_SERIAL_NUMBER, accessory.serial_number)) identifiers.add((IDENTIFIER_SERIAL_NUMBER, accessory.serial_number))
connections: set[tuple[str, str]] = set()
if self.pairing.transport == Transport.BLE and (
discovery := self.pairing.controller.discoveries.get(
normalize_hkid(self.unique_id)
)
):
connections = {
(dr.CONNECTION_BLUETOOTH, cast(BleDiscovery, discovery).device.address),
}
device_info = DeviceInfo( device_info = DeviceInfo(
identifiers={ identifiers={
( (
@ -379,6 +390,7 @@ class HKDevice:
f"{self.unique_id}:aid:{accessory.aid}", f"{self.unique_id}:aid:{accessory.aid}",
) )
}, },
connections=connections,
name=accessory.name, name=accessory.name,
manufacturer=accessory.manufacturer, manufacturer=accessory.manufacturer,
model=accessory.model, model=accessory.model,

View File

@ -4,7 +4,9 @@ from collections.abc import Callable, Generator
import datetime import datetime
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from aiohomekit.testing import FakeController from aiohomekit.model import Transport
from aiohomekit.testing import FakeController, FakeDiscovery, FakePairing
from bleak.backends.device import BLEDevice
from freezegun import freeze_time from freezegun import freeze_time
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
@ -57,3 +59,31 @@ def get_next_aid() -> Generator[Callable[[], int]]:
return id_counter return id_counter
return _get_id return _get_id
@pytest.fixture
def fake_ble_discovery() -> Generator[None]:
"""Fake BLE discovery."""
class FakeBLEDiscovery(FakeDiscovery):
device = BLEDevice(
address="AA:BB:CC:DD:EE:FF", name="TestDevice", rssi=-50, details=()
)
with patch("aiohomekit.testing.FakeDiscovery", FakeBLEDiscovery):
yield
@pytest.fixture
def fake_ble_pairing() -> Generator[None]:
"""Fake BLE pairing."""
class FakeBLEPairing(FakePairing):
"""Fake BLE pairing."""
@property
def transport(self):
return Transport.BLE
with patch("aiohomekit.testing.FakePairing", FakeBLEPairing):
yield

View File

@ -174,6 +174,7 @@ async def test_offline_device_raises(
assert hass.states.get("light.testdevice").state == STATE_OFF assert hass.states.get("light.testdevice").state == STATE_OFF
@pytest.mark.usefixtures("fake_ble_discovery")
async def test_ble_device_only_checks_is_available( async def test_ble_device_only_checks_is_available(
hass: HomeAssistant, get_next_aid: Callable[[], int], controller hass: HomeAssistant, get_next_aid: Callable[[], int], controller
) -> None: ) -> None:
@ -242,6 +243,34 @@ async def test_ble_device_only_checks_is_available(
assert hass.states.get("light.testdevice").state == STATE_OFF assert hass.states.get("light.testdevice").state == STATE_OFF
@pytest.mark.usefixtures("fake_ble_discovery", "fake_ble_pairing")
async def test_ble_device_populates_connections(
hass: HomeAssistant, get_next_aid: Callable[[], int], controller
) -> None:
"""Test a BLE device populates connections in the device registry."""
aid = get_next_aid()
accessory = Accessory.create_with_info(
aid, "TestDevice", "example.com", "Test", "0001", "0.1"
)
create_alive_service(accessory)
await async_setup_component(hass, DOMAIN, {})
config_entry, _ = await setup_test_accessories_with_controller(
hass, [accessory], controller
)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
dev_reg = dr.async_get(hass)
assert (
dev_reg.async_get_device(
identifiers={}, connections={("bluetooth", "AA:BB:CC:DD:EE:FF")}
)
is not None
)
@pytest.mark.parametrize("example", FIXTURES, ids=lambda val: str(val.stem)) @pytest.mark.parametrize("example", FIXTURES, ids=lambda val: str(val.stem))
async def test_snapshots( async def test_snapshots(
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -1,14 +1,12 @@
"""Basic checks for HomeKit sensor.""" """Basic checks for HomeKit sensor."""
from collections.abc import Callable from collections.abc import Callable
from unittest.mock import patch
from aiohomekit.model import Accessory, Transport from aiohomekit.model import Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus
from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.protocol.statuscodes import HapStatusCode from aiohomekit.protocol.statuscodes import HapStatusCode
from aiohomekit.testing import FakePairing
import pytest import pytest
from homeassistant.components.homekit_controller.sensor import ( from homeassistant.components.homekit_controller.sensor import (
@ -406,21 +404,18 @@ def test_thread_status_to_str() -> None:
assert thread_status_to_str(ThreadStatus.DISABLED) == "disabled" assert thread_status_to_str(ThreadStatus.DISABLED) == "disabled"
@pytest.mark.usefixtures("enable_bluetooth", "entity_registry_enabled_by_default") @pytest.mark.usefixtures(
"enable_bluetooth",
"entity_registry_enabled_by_default",
"fake_ble_discovery",
"fake_ble_pairing",
)
async def test_rssi_sensor( async def test_rssi_sensor(
hass: HomeAssistant, get_next_aid: Callable[[], int] hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None: ) -> None:
"""Test an rssi sensor.""" """Test an rssi sensor."""
inject_bluetooth_service_info(hass, TEST_DEVICE_SERVICE_INFO) inject_bluetooth_service_info(hass, TEST_DEVICE_SERVICE_INFO)
class FakeBLEPairing(FakePairing):
"""Fake BLE pairing."""
@property
def transport(self):
return Transport.BLE
with patch("aiohomekit.testing.FakePairing", FakeBLEPairing):
# Any accessory will do for this test, but we need at least # Any accessory will do for this test, but we need at least
# one or the rssi sensor will not be created # one or the rssi sensor will not be created
await setup_test_component( await setup_test_component(
@ -433,7 +428,12 @@ async def test_rssi_sensor(
assert hass.states.get("sensor.testdevice_signal_strength").state == "-56" assert hass.states.get("sensor.testdevice_signal_strength").state == "-56"
@pytest.mark.usefixtures("enable_bluetooth", "entity_registry_enabled_by_default") @pytest.mark.usefixtures(
"enable_bluetooth",
"entity_registry_enabled_by_default",
"fake_ble_discovery",
"fake_ble_pairing",
)
async def test_migrate_rssi_sensor_unique_id( async def test_migrate_rssi_sensor_unique_id(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
@ -449,14 +449,6 @@ async def test_migrate_rssi_sensor_unique_id(
inject_bluetooth_service_info(hass, TEST_DEVICE_SERVICE_INFO) inject_bluetooth_service_info(hass, TEST_DEVICE_SERVICE_INFO)
class FakeBLEPairing(FakePairing):
"""Fake BLE pairing."""
@property
def transport(self):
return Transport.BLE
with patch("aiohomekit.testing.FakePairing", FakeBLEPairing):
# Any accessory will do for this test, but we need at least # Any accessory will do for this test, but we need at least
# one or the rssi sensor will not be created # one or the rssi sensor will not be created
await setup_test_component( await setup_test_component(