Move BluetoothServiceInfoBleak to home_assistant_bluetooth (#82064)

This commit is contained in:
J. Nick Koston 2022-11-15 14:00:52 -06:00 committed by GitHub
parent c940ad9920
commit 682187541f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 56 additions and 49 deletions

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable, Iterable from collections.abc import Callable, Iterable
from dataclasses import replace
from datetime import datetime, timedelta from datetime import datetime, timedelta
import itertools import itertools
import logging import logging
@ -442,7 +441,19 @@ class BluetoothManager:
# route any connection attempts to the connectable path, we # route any connection attempts to the connectable path, we
# mark the service_info as connectable so that the callbacks # mark the service_info as connectable so that the callbacks
# will be called and the device can be discovered. # will be called and the device can be discovered.
service_info = replace(service_info, connectable=True) service_info = BluetoothServiceInfoBleak(
name=service_info.name,
address=service_info.address,
rssi=service_info.rssi,
manufacturer_data=service_info.manufacturer_data,
service_data=service_info.service_data,
service_uuids=service_info.service_uuids,
source=service_info.source,
device=service_info.device,
advertisement=service_info.advertisement,
connectable=True,
time=service_info.time,
)
matched_domains = self._integration_matcher.match_domains(service_info) matched_domains = self._integration_matcher.match_domains(service_info)
_LOGGER.debug( _LOGGER.debug(

View File

@ -21,11 +21,14 @@ from bleak.backends.scanner import (
BaseBleakScanner, BaseBleakScanner,
) )
from bleak_retry_connector import NO_RSSI_VALUE, freshen_ble_device from bleak_retry_connector import NO_RSSI_VALUE, freshen_ble_device
from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.frame import report from homeassistant.helpers.frame import report
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from homeassistant.helpers.service_info.bluetooth import ( # noqa: F401 # pylint: disable=unused-import
BluetoothServiceInfo,
)
from homeassistant.util.dt import monotonic_time_coarse from homeassistant.util.dt import monotonic_time_coarse
from .const import FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS from .const import FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS
@ -53,41 +56,6 @@ MANAGER: BluetoothManager | None = None
MONOTONIC_TIME: Final = monotonic_time_coarse MONOTONIC_TIME: Final = monotonic_time_coarse
@dataclass
class BluetoothServiceInfoBleak(BluetoothServiceInfo):
"""BluetoothServiceInfo with bleak data.
Integrations may need BLEDevice and AdvertisementData
to connect to the device without having bleak trigger
another scan to translate the address to the system's
internal details.
"""
device: BLEDevice
advertisement: AdvertisementData
connectable: bool
time: float
def as_dict(self) -> dict[str, Any]:
"""Return as dict.
The dataclass asdict method is not used because
it will try to deepcopy pyobjc data which will fail.
"""
return {
"name": self.name,
"address": self.address,
"rssi": self.rssi,
"manufacturer_data": self.manufacturer_data,
"service_data": self.service_data,
"service_uuids": self.service_uuids,
"source": self.source,
"advertisement": self.advertisement,
"connectable": self.connectable,
"time": self.time,
}
class BluetoothScanningMode(Enum): class BluetoothScanningMode(Enum):
"""The mode of scanning for bluetooth devices.""" """The mode of scanning for bluetooth devices."""

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from abc import abstractmethod from abc import abstractmethod
import logging import logging
from typing import cast
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
@ -69,7 +70,7 @@ class BasePassiveBluetoothCoordinator:
if service_info := async_last_service_info( if service_info := async_last_service_info(
self.hass, self.address, self.connectable self.hass, self.address, self.connectable
): ):
return service_info.name return cast(str, service_info.name) # for compat this can be a pyobjc
return self._last_name return self._last_name
@property @property

View File

@ -62,7 +62,7 @@ def async_name(
"""Return a name for the device.""" """Return a name for the device."""
if service_info.address in ( if service_info.address in (
service_info.name, service_info.name,
service_info.name.replace("_", ":"), service_info.name.replace("-", ":"),
): ):
base_name = f"{ibeacon_advertisement.uuid}_{ibeacon_advertisement.major}_{ibeacon_advertisement.minor}" base_name = f"{ibeacon_advertisement.uuid}_{ibeacon_advertisement.major}_{ibeacon_advertisement.minor}"
else: else:

View File

@ -21,7 +21,7 @@ cryptography==38.0.3
dbus-fast==1.74.0 dbus-fast==1.74.0
fnvhash==0.1.0 fnvhash==0.1.0
hass-nabucasa==0.56.0 hass-nabucasa==0.56.0
home-assistant-bluetooth==1.6.0 home-assistant-bluetooth==1.8.0
home-assistant-frontend==20221108.0 home-assistant-frontend==20221108.0
httpx==0.23.0 httpx==0.23.0
ifaddr==0.1.7 ifaddr==0.1.7

View File

@ -36,7 +36,7 @@ dependencies = [
# When bumping httpx, please check the version pins of # When bumping httpx, please check the version pins of
# httpcore, anyio, and h11 in gen_requirements_all # httpcore, anyio, and h11 in gen_requirements_all
"httpx==0.23.0", "httpx==0.23.0",
"home-assistant-bluetooth==1.6.0", "home-assistant-bluetooth==1.8.0",
"ifaddr==0.1.7", "ifaddr==0.1.7",
"jinja2==3.1.2", "jinja2==3.1.2",
"lru-dict==1.1.8", "lru-dict==1.1.8",

View File

@ -11,7 +11,7 @@ bcrypt==3.1.7
certifi>=2021.5.30 certifi>=2021.5.30
ciso8601==2.2.0 ciso8601==2.2.0
httpx==0.23.0 httpx==0.23.0
home-assistant-bluetooth==1.6.0 home-assistant-bluetooth==1.8.0
ifaddr==0.1.7 ifaddr==0.1.7
jinja2==3.1.2 jinja2==3.1.2
lru-dict==1.1.8 lru-dict==1.1.8

View File

@ -227,6 +227,10 @@ async def test_diagnostics_macos(
-127, -127,
[[]], [[]],
], ],
"device": {
"__type": "<class " "'bleak.backends.device.BLEDevice'>",
"repr": "BLEDevice(44:44:33:11:23:45, " "wohand)",
},
"connectable": True, "connectable": True,
"manufacturer_data": { "manufacturer_data": {
"1": {"__type": "<class " "'bytes'>", "repr": "b'\\x01'"} "1": {"__type": "<class " "'bytes'>", "repr": "b'\\x01'"}
@ -251,6 +255,10 @@ async def test_diagnostics_macos(
-127, -127,
[[]], [[]],
], ],
"device": {
"__type": "<class " "'bleak.backends.device.BLEDevice'>",
"repr": "BLEDevice(44:44:33:11:23:45, " "wohand)",
},
"connectable": True, "connectable": True,
"manufacturer_data": { "manufacturer_data": {
"1": {"__type": "<class " "'bytes'>", "repr": "b'\\x01'"} "1": {"__type": "<class " "'bytes'>", "repr": "b'\\x01'"}

View File

@ -1,4 +1,6 @@
"""Tests for the ibeacon integration.""" """Tests for the ibeacon integration."""
from typing import Any
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
@ -115,3 +117,18 @@ FEASY_BEACON_SERVICE_INFO_2 = BluetoothServiceInfo(
], ],
source="local", source="local",
) )
def bluetooth_service_info_replace(
info: BluetoothServiceInfo, **kwargs: Any
) -> BluetoothServiceInfo:
"""Replace attributes of a BluetoothServiceInfoBleak."""
return BluetoothServiceInfo(
address=kwargs.get("address", info.address),
name=kwargs.get("name", info.name),
rssi=kwargs.get("rssi", info.rssi),
manufacturer_data=kwargs.get("manufacturer_data", info.manufacturer_data),
service_data=kwargs.get("service_data", info.service_data),
service_uuids=kwargs.get("service_uuids", info.service_uuids),
source=kwargs.get("source", info.source),
)

View File

@ -1,7 +1,6 @@
"""Test the ibeacon sensors.""" """Test the ibeacon sensors."""
from dataclasses import replace
from datetime import timedelta from datetime import timedelta
import time import time
@ -19,6 +18,7 @@ from . import (
BLUECHARM_BEACON_SERVICE_INFO_DBUS, BLUECHARM_BEACON_SERVICE_INFO_DBUS,
TESLA_TRANSIENT, TESLA_TRANSIENT,
TESLA_TRANSIENT_BLE_DEVICE, TESLA_TRANSIENT_BLE_DEVICE,
bluetooth_service_info_replace as replace,
) )
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed
@ -145,16 +145,17 @@ async def test_ignore_default_name(hass):
assert len(hass.states.async_entity_ids()) == before_entity_count assert len(hass.states.async_entity_ids()) == before_entity_count
async def test_rotating_major_minor_and_mac(hass): async def test_rotating_major_minor_and_mac_with_name(hass):
"""Test the different uuid, major, minor from many addresses removes all associated entities.""" """Test the different uuid, major, minor from many addresses removes all associated entities."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
before_entity_count = len(hass.states.async_entity_ids("device_tracker"))
assert await hass.config_entries.async_setup(entry.entry_id) assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
before_entity_count = len(hass.states.async_entity_ids("device_tracker"))
for i in range(100): for i in range(100):
service_info = BluetoothServiceInfo( service_info = BluetoothServiceInfo(
name="BlueCharm_177999", name="BlueCharm_177999",
@ -186,9 +187,10 @@ async def test_rotating_major_minor_and_mac_no_name(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
before_entity_count = len(hass.states.async_entity_ids("device_tracker"))
assert await hass.config_entries.async_setup(entry.entry_id) assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
before_entity_count = len(hass.states.async_entity_ids("device_tracker"))
for i in range(51): for i in range(51):
service_info = BluetoothServiceInfo( service_info = BluetoothServiceInfo(
name=f"AA:BB:CC:DD:EE:{i:02X}", name=f"AA:BB:CC:DD:EE:{i:02X}",

View File

@ -1,7 +1,6 @@
"""Test the ibeacon device trackers.""" """Test the ibeacon device trackers."""
from dataclasses import replace
from datetime import timedelta from datetime import timedelta
import time import time
from unittest.mock import patch from unittest.mock import patch
@ -22,6 +21,7 @@ from . import (
BEACON_RANDOM_ADDRESS_SERVICE_INFO, BEACON_RANDOM_ADDRESS_SERVICE_INFO,
BLUECHARM_BEACON_SERVICE_INFO, BLUECHARM_BEACON_SERVICE_INFO,
BLUECHARM_BLE_DEVICE, BLUECHARM_BLE_DEVICE,
bluetooth_service_info_replace as replace,
) )
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed

View File

@ -1,7 +1,6 @@
"""Test the ibeacon sensors.""" """Test the ibeacon sensors."""
from dataclasses import replace
from datetime import timedelta from datetime import timedelta
import pytest import pytest
@ -24,6 +23,7 @@ from . import (
FEASY_BEACON_SERVICE_INFO_1, FEASY_BEACON_SERVICE_INFO_1,
FEASY_BEACON_SERVICE_INFO_2, FEASY_BEACON_SERVICE_INFO_2,
NO_NAME_BEACON_SERVICE_INFO, NO_NAME_BEACON_SERVICE_INFO,
bluetooth_service_info_replace as replace,
) )
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed