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
from operator import attrgetter
from types import MappingProxyType
from typing import Any
from typing import Any, cast
from aiohomekit import Controller
from aiohomekit.controller import TransportType
from aiohomekit.controller.ble.discovery import BleDiscovery
from aiohomekit.exceptions import (
AccessoryDisconnectedError,
AccessoryNotFoundError,
@ -372,6 +373,16 @@ class HKDevice:
if not self.unreliable_serial_numbers:
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(
identifiers={
(
@ -379,6 +390,7 @@ class HKDevice:
f"{self.unique_id}:aid:{accessory.aid}",
)
},
connections=connections,
name=accessory.name,
manufacturer=accessory.manufacturer,
model=accessory.model,

View File

@ -4,7 +4,9 @@ from collections.abc import Callable, Generator
import datetime
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.api import FrozenDateTimeFactory
import pytest
@ -57,3 +59,31 @@ def get_next_aid() -> Generator[Callable[[], int]]:
return id_counter
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
@pytest.mark.usefixtures("fake_ble_discovery")
async def test_ble_device_only_checks_is_available(
hass: HomeAssistant, get_next_aid: Callable[[], int], controller
) -> None:
@ -242,6 +243,34 @@ async def test_ble_device_only_checks_is_available(
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))
async def test_snapshots(
hass: HomeAssistant,

View File

@ -1,14 +1,12 @@
"""Basic checks for HomeKit sensor."""
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.const import ThreadNodeCapabilities, ThreadStatus
from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.protocol.statuscodes import HapStatusCode
from aiohomekit.testing import FakePairing
import pytest
from homeassistant.components.homekit_controller.sensor import (
@ -406,34 +404,36 @@ def test_thread_status_to_str() -> None:
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(
hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
"""Test an rssi sensor."""
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
# one or the rssi sensor will not be created
await setup_test_component(
hass,
get_next_aid(),
create_battery_level_sensor,
suffix="battery",
connection="BLE",
)
assert hass.states.get("sensor.testdevice_signal_strength").state == "-56"
# Any accessory will do for this test, but we need at least
# one or the rssi sensor will not be created
await setup_test_component(
hass,
get_next_aid(),
create_battery_level_sensor,
suffix="battery",
connection="BLE",
)
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(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
@ -449,24 +449,16 @@ async def test_migrate_rssi_sensor_unique_id(
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
# one or the rssi sensor will not be created
await setup_test_component(
hass,
get_next_aid(),
create_battery_level_sensor,
suffix="battery",
connection="BLE",
)
assert hass.states.get("sensor.renamed_rssi").state == "-56"
# Any accessory will do for this test, but we need at least
# one or the rssi sensor will not be created
await setup_test_component(
hass,
get_next_aid(),
create_battery_level_sensor,
suffix="battery",
connection="BLE",
)
assert hass.states.get("sensor.renamed_rssi").state == "-56"
assert (
entity_registry.async_get(rssi_sensor.entity_id).unique_id