mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Add bluetooth API to allow rediscovery of address (#76005)
* Add API to allow rediscovery of domains * Switch to clearing per device * Drop unneded change
This commit is contained in:
parent
741efb89d5
commit
cdde4f9925
@ -214,6 +214,13 @@ def async_track_unavailable(
|
|||||||
return manager.async_track_unavailable(callback, address)
|
return manager.async_track_unavailable(callback, address)
|
||||||
|
|
||||||
|
|
||||||
|
@hass_callback
|
||||||
|
def async_rediscover_address(hass: HomeAssistant, address: str) -> None:
|
||||||
|
"""Trigger discovery of devices which have already been seen."""
|
||||||
|
manager: BluetoothManager = hass.data[DOMAIN]
|
||||||
|
manager.async_rediscover_address(address)
|
||||||
|
|
||||||
|
|
||||||
async def _async_has_bluetooth_adapter() -> bool:
|
async def _async_has_bluetooth_adapter() -> bool:
|
||||||
"""Return if the device has a bluetooth adapter."""
|
"""Return if the device has a bluetooth adapter."""
|
||||||
return bool(await async_get_bluetooth_adapters())
|
return bool(await async_get_bluetooth_adapters())
|
||||||
@ -545,3 +552,8 @@ class BluetoothManager:
|
|||||||
# change the bluetooth dongle.
|
# change the bluetooth dongle.
|
||||||
_LOGGER.error("Error stopping scanner: %s", ex)
|
_LOGGER.error("Error stopping scanner: %s", ex)
|
||||||
uninstall_multiple_bleak_catcher()
|
uninstall_multiple_bleak_catcher()
|
||||||
|
|
||||||
|
@hass_callback
|
||||||
|
def async_rediscover_address(self, address: str) -> None:
|
||||||
|
"""Trigger discovery of devices which have already been seen."""
|
||||||
|
self._integration_matcher.async_clear_address(address)
|
||||||
|
@ -10,7 +10,7 @@ from lru import LRU # pylint: disable=no-name-in-module
|
|||||||
from homeassistant.loader import BluetoothMatcher, BluetoothMatcherOptional
|
from homeassistant.loader import BluetoothMatcher, BluetoothMatcherOptional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Mapping
|
from collections.abc import MutableMapping
|
||||||
|
|
||||||
from bleak.backends.device import BLEDevice
|
from bleak.backends.device import BLEDevice
|
||||||
from bleak.backends.scanner import AdvertisementData
|
from bleak.backends.scanner import AdvertisementData
|
||||||
@ -70,7 +70,7 @@ class IntegrationMatcher:
|
|||||||
self._integration_matchers = integration_matchers
|
self._integration_matchers = integration_matchers
|
||||||
# Some devices use a random address so we need to use
|
# Some devices use a random address so we need to use
|
||||||
# an LRU to avoid memory issues.
|
# an LRU to avoid memory issues.
|
||||||
self._matched: Mapping[str, IntegrationMatchHistory] = LRU(
|
self._matched: MutableMapping[str, IntegrationMatchHistory] = LRU(
|
||||||
MAX_REMEMBER_ADDRESSES
|
MAX_REMEMBER_ADDRESSES
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,6 +78,10 @@ class IntegrationMatcher:
|
|||||||
"""Clear the history."""
|
"""Clear the history."""
|
||||||
self._matched = {}
|
self._matched = {}
|
||||||
|
|
||||||
|
def async_clear_address(self, address: str) -> None:
|
||||||
|
"""Clear the history matches for a set of domains."""
|
||||||
|
self._matched.pop(address, None)
|
||||||
|
|
||||||
def match_domains(self, device: BLEDevice, adv_data: AdvertisementData) -> set[str]:
|
def match_domains(self, device: BLEDevice, adv_data: AdvertisementData) -> set[str]:
|
||||||
"""Return the domains that are matched."""
|
"""Return the domains that are matched."""
|
||||||
matched_domains: set[str] = set()
|
matched_domains: set[str] = set()
|
||||||
@ -98,7 +102,7 @@ class IntegrationMatcher:
|
|||||||
previous_match.service_data |= bool(adv_data.service_data)
|
previous_match.service_data |= bool(adv_data.service_data)
|
||||||
previous_match.service_uuids |= bool(adv_data.service_uuids)
|
previous_match.service_uuids |= bool(adv_data.service_uuids)
|
||||||
else:
|
else:
|
||||||
self._matched[device.address] = IntegrationMatchHistory( # type: ignore[index]
|
self._matched[device.address] = IntegrationMatchHistory(
|
||||||
manufacturer_data=bool(adv_data.manufacturer_data),
|
manufacturer_data=bool(adv_data.manufacturer_data),
|
||||||
service_data=bool(adv_data.service_data),
|
service_data=bool(adv_data.service_data),
|
||||||
service_uuids=bool(adv_data.service_uuids),
|
service_uuids=bool(adv_data.service_uuids),
|
||||||
|
@ -14,11 +14,13 @@ from homeassistant.components.bluetooth import (
|
|||||||
BluetoothScanningMode,
|
BluetoothScanningMode,
|
||||||
BluetoothServiceInfoBleak,
|
BluetoothServiceInfoBleak,
|
||||||
async_address_present,
|
async_address_present,
|
||||||
|
async_rediscover_address,
|
||||||
async_register_callback,
|
async_register_callback,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
@ -113,6 +115,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
device = Device(service_info.device)
|
device = Device(service_info.device)
|
||||||
device_info = DeviceInfo(
|
device_info = DeviceInfo(
|
||||||
|
connections={(dr.CONNECTION_BLUETOOTH, service_info.address)},
|
||||||
identifiers={(DOMAIN, service_info.address)},
|
identifiers={(DOMAIN, service_info.address)},
|
||||||
manufacturer="Fjäråskupan",
|
manufacturer="Fjäråskupan",
|
||||||
name="Fjäråskupan",
|
name="Fjäråskupan",
|
||||||
@ -175,4 +178,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
for device_entry in dr.async_entries_for_config_entry(
|
||||||
|
dr.async_get(hass), entry.entry_id
|
||||||
|
):
|
||||||
|
for conn in device_entry.connections:
|
||||||
|
if conn[0] == dr.CONNECTION_BLUETOOTH:
|
||||||
|
async_rediscover_address(hass, conn[1])
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -16,6 +16,7 @@ from homeassistant.components.bluetooth import (
|
|||||||
BluetoothScanningMode,
|
BluetoothScanningMode,
|
||||||
BluetoothServiceInfo,
|
BluetoothServiceInfo,
|
||||||
async_process_advertisements,
|
async_process_advertisements,
|
||||||
|
async_rediscover_address,
|
||||||
async_track_unavailable,
|
async_track_unavailable,
|
||||||
models,
|
models,
|
||||||
)
|
)
|
||||||
@ -560,6 +561,45 @@ async def test_discovery_match_first_by_service_uuid_and_then_manufacturer_id(
|
|||||||
assert len(mock_config_flow.mock_calls) == 0
|
assert len(mock_config_flow.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rediscovery(hass, mock_bleak_scanner_start, enable_bluetooth):
|
||||||
|
"""Test bluetooth discovery can be re-enabled for a given domain."""
|
||||||
|
mock_bt = [
|
||||||
|
{"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"}
|
||||||
|
]
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||||
|
), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow:
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
|
||||||
|
)
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_bleak_scanner_start.mock_calls) == 1
|
||||||
|
|
||||||
|
switchbot_device = BLEDevice("44:44:33:11:23:45", "wohand")
|
||||||
|
switchbot_adv = AdvertisementData(
|
||||||
|
local_name="wohand", service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"]
|
||||||
|
)
|
||||||
|
|
||||||
|
_get_underlying_scanner()._callback(switchbot_device, switchbot_adv)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
_get_underlying_scanner()._callback(switchbot_device, switchbot_adv)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_config_flow.mock_calls) == 1
|
||||||
|
assert mock_config_flow.mock_calls[0][1][0] == "switchbot"
|
||||||
|
|
||||||
|
async_rediscover_address(hass, "44:44:33:11:23:45")
|
||||||
|
|
||||||
|
_get_underlying_scanner()._callback(switchbot_device, switchbot_adv)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_config_flow.mock_calls) == 2
|
||||||
|
assert mock_config_flow.mock_calls[1][1][0] == "switchbot"
|
||||||
|
|
||||||
|
|
||||||
async def test_async_discovered_device_api(hass, mock_bleak_scanner_start):
|
async def test_async_discovered_device_api(hass, mock_bleak_scanner_start):
|
||||||
"""Test the async_discovered_device API."""
|
"""Test the async_discovered_device API."""
|
||||||
mock_bt = []
|
mock_bt = []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user