diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py index d29023acef7..c216abde4ea 100644 --- a/homeassistant/components/bluetooth/manager.py +++ b/homeassistant/components/bluetooth/manager.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio from collections.abc import Callable, Iterable -from dataclasses import replace from datetime import datetime, timedelta import itertools import logging @@ -442,7 +441,19 @@ class BluetoothManager: # route any connection attempts to the connectable path, we # mark the service_info as connectable so that the callbacks # 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) _LOGGER.debug( diff --git a/homeassistant/components/bluetooth/models.py b/homeassistant/components/bluetooth/models.py index 4e386ea3746..f324d6086f7 100644 --- a/homeassistant/components/bluetooth/models.py +++ b/homeassistant/components/bluetooth/models.py @@ -21,11 +21,14 @@ from bleak.backends.scanner import ( BaseBleakScanner, ) 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.helpers.event import async_track_time_interval 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 .const import FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS @@ -53,41 +56,6 @@ MANAGER: BluetoothManager | None = None 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): """The mode of scanning for bluetooth devices.""" diff --git a/homeassistant/components/bluetooth/update_coordinator.py b/homeassistant/components/bluetooth/update_coordinator.py index 2c99f189852..a02e601a878 100644 --- a/homeassistant/components/bluetooth/update_coordinator.py +++ b/homeassistant/components/bluetooth/update_coordinator.py @@ -3,6 +3,7 @@ from __future__ import annotations from abc import abstractmethod import logging +from typing import cast from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback @@ -69,7 +70,7 @@ class BasePassiveBluetoothCoordinator: if service_info := async_last_service_info( 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 @property diff --git a/homeassistant/components/ibeacon/coordinator.py b/homeassistant/components/ibeacon/coordinator.py index ed62649de2d..bab0a5b5655 100644 --- a/homeassistant/components/ibeacon/coordinator.py +++ b/homeassistant/components/ibeacon/coordinator.py @@ -62,7 +62,7 @@ def async_name( """Return a name for the device.""" if service_info.address in ( service_info.name, - service_info.name.replace("_", ":"), + service_info.name.replace("-", ":"), ): base_name = f"{ibeacon_advertisement.uuid}_{ibeacon_advertisement.major}_{ibeacon_advertisement.minor}" else: diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 346b61fd1a8..0a862e040b6 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -21,7 +21,7 @@ cryptography==38.0.3 dbus-fast==1.74.0 fnvhash==0.1.0 hass-nabucasa==0.56.0 -home-assistant-bluetooth==1.6.0 +home-assistant-bluetooth==1.8.0 home-assistant-frontend==20221108.0 httpx==0.23.0 ifaddr==0.1.7 diff --git a/pyproject.toml b/pyproject.toml index 3a74b2c5994..6aa8c9d3f0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all "httpx==0.23.0", - "home-assistant-bluetooth==1.6.0", + "home-assistant-bluetooth==1.8.0", "ifaddr==0.1.7", "jinja2==3.1.2", "lru-dict==1.1.8", diff --git a/requirements.txt b/requirements.txt index 96a9f801df9..e748db517b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 httpx==0.23.0 -home-assistant-bluetooth==1.6.0 +home-assistant-bluetooth==1.8.0 ifaddr==0.1.7 jinja2==3.1.2 lru-dict==1.1.8 diff --git a/tests/components/bluetooth/test_diagnostics.py b/tests/components/bluetooth/test_diagnostics.py index a8d4d7aa142..56321175241 100644 --- a/tests/components/bluetooth/test_diagnostics.py +++ b/tests/components/bluetooth/test_diagnostics.py @@ -227,6 +227,10 @@ async def test_diagnostics_macos( -127, [[]], ], + "device": { + "__type": "", + "repr": "BLEDevice(44:44:33:11:23:45, " "wohand)", + }, "connectable": True, "manufacturer_data": { "1": {"__type": "", "repr": "b'\\x01'"} @@ -251,6 +255,10 @@ async def test_diagnostics_macos( -127, [[]], ], + "device": { + "__type": "", + "repr": "BLEDevice(44:44:33:11:23:45, " "wohand)", + }, "connectable": True, "manufacturer_data": { "1": {"__type": "", "repr": "b'\\x01'"} diff --git a/tests/components/ibeacon/__init__.py b/tests/components/ibeacon/__init__.py index 56d5eb78467..50636ee9d48 100644 --- a/tests/components/ibeacon/__init__.py +++ b/tests/components/ibeacon/__init__.py @@ -1,4 +1,6 @@ """Tests for the ibeacon integration.""" +from typing import Any + from bleak.backends.device import BLEDevice from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo @@ -115,3 +117,18 @@ FEASY_BEACON_SERVICE_INFO_2 = BluetoothServiceInfo( ], 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), + ) diff --git a/tests/components/ibeacon/test_coordinator.py b/tests/components/ibeacon/test_coordinator.py index 6acbf5569f8..89bc932f7a0 100644 --- a/tests/components/ibeacon/test_coordinator.py +++ b/tests/components/ibeacon/test_coordinator.py @@ -1,7 +1,6 @@ """Test the ibeacon sensors.""" -from dataclasses import replace from datetime import timedelta import time @@ -19,6 +18,7 @@ from . import ( BLUECHARM_BEACON_SERVICE_INFO_DBUS, TESLA_TRANSIENT, TESLA_TRANSIENT_BLE_DEVICE, + bluetooth_service_info_replace as replace, ) 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 -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.""" entry = MockConfigEntry( domain=DOMAIN, ) 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) await hass.async_block_till_done() + before_entity_count = len(hass.states.async_entity_ids("device_tracker")) + for i in range(100): service_info = BluetoothServiceInfo( name="BlueCharm_177999", @@ -186,9 +187,10 @@ async def test_rotating_major_minor_and_mac_no_name(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) await hass.async_block_till_done() + before_entity_count = len(hass.states.async_entity_ids("device_tracker")) + for i in range(51): service_info = BluetoothServiceInfo( name=f"AA:BB:CC:DD:EE:{i:02X}", diff --git a/tests/components/ibeacon/test_device_tracker.py b/tests/components/ibeacon/test_device_tracker.py index f3520f835ca..b16144d7d90 100644 --- a/tests/components/ibeacon/test_device_tracker.py +++ b/tests/components/ibeacon/test_device_tracker.py @@ -1,7 +1,6 @@ """Test the ibeacon device trackers.""" -from dataclasses import replace from datetime import timedelta import time from unittest.mock import patch @@ -22,6 +21,7 @@ from . import ( BEACON_RANDOM_ADDRESS_SERVICE_INFO, BLUECHARM_BEACON_SERVICE_INFO, BLUECHARM_BLE_DEVICE, + bluetooth_service_info_replace as replace, ) from tests.common import MockConfigEntry, async_fire_time_changed diff --git a/tests/components/ibeacon/test_sensor.py b/tests/components/ibeacon/test_sensor.py index 671172efe93..f6a2ab51430 100644 --- a/tests/components/ibeacon/test_sensor.py +++ b/tests/components/ibeacon/test_sensor.py @@ -1,7 +1,6 @@ """Test the ibeacon sensors.""" -from dataclasses import replace from datetime import timedelta import pytest @@ -24,6 +23,7 @@ from . import ( FEASY_BEACON_SERVICE_INFO_1, FEASY_BEACON_SERVICE_INFO_2, NO_NAME_BEACON_SERVICE_INFO, + bluetooth_service_info_replace as replace, ) from tests.common import MockConfigEntry, async_fire_time_changed