Add async_current_scanners API to Bluetooth integration

This commit is contained in:
J. Nick Koston
2025-09-11 09:50:38 -05:00
parent 7d2cdf8024
commit 8dc9fa7bba
4 changed files with 96 additions and 0 deletions

View File

@@ -57,6 +57,7 @@ from .api import (
_get_manager, _get_manager,
async_address_present, async_address_present,
async_ble_device_from_address, async_ble_device_from_address,
async_current_scanners,
async_discovered_service_info, async_discovered_service_info,
async_get_advertisement_callback, async_get_advertisement_callback,
async_get_fallback_availability_interval, async_get_fallback_availability_interval,
@@ -114,6 +115,7 @@ __all__ = [
"HomeAssistantRemoteScanner", "HomeAssistantRemoteScanner",
"async_address_present", "async_address_present",
"async_ble_device_from_address", "async_ble_device_from_address",
"async_current_scanners",
"async_discovered_service_info", "async_discovered_service_info",
"async_get_advertisement_callback", "async_get_advertisement_callback",
"async_get_fallback_availability_interval", "async_get_fallback_availability_interval",

View File

@@ -66,6 +66,22 @@ def async_scanner_count(hass: HomeAssistant, connectable: bool = True) -> int:
return _get_manager(hass).async_scanner_count(connectable) return _get_manager(hass).async_scanner_count(connectable)
@hass_callback
def async_current_scanners(hass: HomeAssistant) -> list[BaseHaScanner]:
"""Return the list of currently active scanners.
This method returns a list of all active Bluetooth scanners registered
with Home Assistant, including both connectable and non-connectable scanners.
Args:
hass: Home Assistant instance
Returns:
List of all active scanner instances
"""
return _get_manager(hass).async_current_scanners()
@hass_callback @hass_callback
def async_discovered_service_info( def async_discovered_service_info(
hass: HomeAssistant, connectable: bool = True hass: HomeAssistant, connectable: bool = True

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import logging import logging
from typing import Any from typing import Any
from habluetooth import BluetoothScanningMode
from switchbot import ( from switchbot import (
SwitchbotAccountConnectionError, SwitchbotAccountConnectionError,
SwitchBotAdvertisement, SwitchBotAdvertisement,
@@ -18,6 +19,7 @@ import voluptuous as vol
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
async_current_scanners,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.config_entries import ( from homeassistant.config_entries import (
@@ -323,6 +325,15 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle the user step to choose cloud login or direct discovery.""" """Handle the user step to choose cloud login or direct discovery."""
# Check if all scanners are in active mode
# If so, skip the menu and go directly to device selection
scanners = async_current_scanners(self.hass)
if scanners and all(
scanner.current_mode == BluetoothScanningMode.ACTIVE for scanner in scanners
):
# All scanners are active, skip the menu
return await self.async_step_select_device()
return self.async_show_menu( return self.async_show_menu(
step_id="user", step_id="user",
menu_options=["cloud_login", "select_device"], menu_options=["cloud_login", "select_device"],

View File

@@ -9,6 +9,7 @@ from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
MONOTONIC_TIME, MONOTONIC_TIME,
BaseHaRemoteScanner, BaseHaRemoteScanner,
BluetoothScanningMode,
HaBluetoothConnector, HaBluetoothConnector,
async_scanner_by_source, async_scanner_by_source,
async_scanner_devices_by_address, async_scanner_devices_by_address,
@@ -16,6 +17,7 @@ from homeassistant.components.bluetooth import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import ( from . import (
FakeRemoteScanner,
FakeScanner, FakeScanner,
MockBleakClient, MockBleakClient,
_get_manager, _get_manager,
@@ -161,3 +163,68 @@ async def test_async_scanner_devices_by_address_non_connectable(
assert devices[0].ble_device.name == switchbot_device.name assert devices[0].ble_device.name == switchbot_device.name
assert devices[0].advertisement.local_name == switchbot_device_adv.local_name assert devices[0].advertisement.local_name == switchbot_device_adv.local_name
cancel() cancel()
@pytest.mark.usefixtures("enable_bluetooth")
async def test_async_current_scanners(hass: HomeAssistant) -> None:
"""Test getting the list of current scanners."""
# The enable_bluetooth fixture registers one scanner
initial_scanners = bluetooth.async_current_scanners(hass)
assert len(initial_scanners) == 1
initial_scanner_count = len(initial_scanners)
# Verify current_mode is accessible on the initial scanner
for scanner in initial_scanners:
assert hasattr(scanner, "current_mode")
# The mode might be None or a BluetoothScanningMode enum value
# Register additional connectable scanners
hci0_scanner = FakeScanner("hci0", "hci0")
hci1_scanner = FakeScanner("hci1", "hci1")
cancel_hci0 = bluetooth.async_register_scanner(hass, hci0_scanner)
cancel_hci1 = bluetooth.async_register_scanner(hass, hci1_scanner)
# Test that the new scanners are added
scanners = bluetooth.async_current_scanners(hass)
assert len(scanners) == initial_scanner_count + 2
assert hci0_scanner in scanners
assert hci1_scanner in scanners
# Verify current_mode is accessible on all scanners
for scanner in scanners:
assert hasattr(scanner, "current_mode")
# Verify it's None or the correct type (BluetoothScanningMode)
assert scanner.current_mode is None or isinstance(
scanner.current_mode, BluetoothScanningMode
)
# Register non-connectable scanner
connector = HaBluetoothConnector(
MockBleakClient, "mock_bleak_client", lambda: False
)
hci2_scanner = FakeRemoteScanner("hci2", "hci2", connector, False)
cancel_hci2 = bluetooth.async_register_scanner(hass, hci2_scanner)
# Test that all scanners are returned (both connectable and non-connectable)
all_scanners = bluetooth.async_current_scanners(hass)
assert len(all_scanners) == initial_scanner_count + 3
assert hci0_scanner in all_scanners
assert hci1_scanner in all_scanners
assert hci2_scanner in all_scanners
# Verify current_mode is accessible on all scanners including non-connectable
for scanner in all_scanners:
assert hasattr(scanner, "current_mode")
# The mode should be None or a BluetoothScanningMode instance
assert scanner.current_mode is None or isinstance(
scanner.current_mode, BluetoothScanningMode
)
# Clean up our scanners
cancel_hci0()
cancel_hci1()
cancel_hci2()
# Verify we're back to the initial scanner
final_scanners = bluetooth.async_current_scanners(hass)
assert len(final_scanners) == initial_scanner_count