Offload platform specific bluetooth code to bluetooth-adapters (#82196)

* Offload platform specific bluetooth code to bluetooth-adapters

* adjust

* fix some more patch targets

* more test fixes

* almost there

* may not be setup yet

* more fixes

* fixes

* fix test

* fix merge
pull/82367/head
J. Nick Koston 2022-11-17 13:34:19 -06:00 committed by GitHub
parent d0efdd750f
commit 47c66dbed4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 158 additions and 262 deletions

View File

@ -10,6 +10,16 @@ from typing import TYPE_CHECKING, cast
import async_timeout import async_timeout
from awesomeversion import AwesomeVersion from awesomeversion import AwesomeVersion
from bluetooth_adapters import (
ADAPTER_ADDRESS,
ADAPTER_HW_VERSION,
ADAPTER_SW_VERSION,
DEFAULT_ADDRESS,
AdapterDetails,
adapter_human_name,
adapter_unique_name,
get_adapters,
)
from homeassistant.components import usb from homeassistant.components import usb
from homeassistant.config_entries import ( from homeassistant.config_entries import (
@ -32,20 +42,15 @@ from homeassistant.loader import async_get_bluetooth
from . import models from . import models
from .const import ( from .const import (
ADAPTER_ADDRESS,
ADAPTER_HW_VERSION,
ADAPTER_SW_VERSION,
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS, BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
CONF_ADAPTER, CONF_ADAPTER,
CONF_DETAILS, CONF_DETAILS,
CONF_PASSIVE, CONF_PASSIVE,
DATA_MANAGER, DATA_MANAGER,
DEFAULT_ADDRESS,
DOMAIN, DOMAIN,
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS, LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
SOURCE_LOCAL, SOURCE_LOCAL,
AdapterDetails,
) )
from .manager import BluetoothManager from .manager import BluetoothManager
from .match import BluetoothCallbackMatcher, IntegrationMatcher from .match import BluetoothCallbackMatcher, IntegrationMatcher
@ -62,7 +67,6 @@ from .models import (
ProcessAdvertisementCallback, ProcessAdvertisementCallback,
) )
from .scanner import HaScanner, ScannerStartError from .scanner import HaScanner, ScannerStartError
from .util import adapter_human_name, adapter_unique_name, async_default_adapter
if TYPE_CHECKING: if TYPE_CHECKING:
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@ -288,13 +292,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the bluetooth integration.""" """Set up the bluetooth integration."""
integration_matcher = IntegrationMatcher(await async_get_bluetooth(hass)) integration_matcher = IntegrationMatcher(await async_get_bluetooth(hass))
integration_matcher.async_setup() integration_matcher.async_setup()
manager = BluetoothManager(hass, integration_matcher) bluetooth_adapters = get_adapters()
manager = BluetoothManager(hass, integration_matcher, bluetooth_adapters)
await manager.async_setup() await manager.async_setup()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, manager.async_stop) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, manager.async_stop)
hass.data[DATA_MANAGER] = models.MANAGER = manager hass.data[DATA_MANAGER] = models.MANAGER = manager
adapters = await manager.async_get_bluetooth_adapters() adapters = await manager.async_get_bluetooth_adapters()
async_migrate_entries(hass, adapters) async_migrate_entries(hass, adapters, bluetooth_adapters.default_adapter)
await async_discover_adapters(hass, adapters) await async_discover_adapters(hass, adapters)
async def _async_rediscover_adapters() -> None: async def _async_rediscover_adapters() -> None:
@ -347,17 +352,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STARTED,
hass_callback(lambda event: _async_check_haos(hass)), hass_callback(lambda event: _async_check_haos(hass)),
) )
return True return True
@hass_callback @hass_callback
def async_migrate_entries( def async_migrate_entries(
hass: HomeAssistant, adapters: dict[str, AdapterDetails] hass: HomeAssistant, adapters: dict[str, AdapterDetails], default_adapter: str
) -> None: ) -> None:
"""Migrate config entries to support multiple.""" """Migrate config entries to support multiple."""
current_entries = hass.config_entries.async_entries(DOMAIN) current_entries = hass.config_entries.async_entries(DOMAIN)
default_adapter = async_default_adapter()
for entry in current_entries: for entry in current_entries:
if entry.unique_id: if entry.unique_id:

View File

@ -3,6 +3,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, cast
from bluetooth_adapters import (
ADAPTER_ADDRESS,
AdapterDetails,
adapter_human_name,
adapter_unique_name,
get_adapters,
)
import voluptuous as vol import voluptuous as vol
from homeassistant.components import onboarding from homeassistant.components import onboarding
@ -11,15 +18,7 @@ from homeassistant.core import callback
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
from . import models from . import models
from .const import ( from .const import CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, DOMAIN
ADAPTER_ADDRESS,
CONF_ADAPTER,
CONF_DETAILS,
CONF_PASSIVE,
DOMAIN,
AdapterDetails,
)
from .util import adapter_human_name, adapter_unique_name, async_get_bluetooth_adapters
if TYPE_CHECKING: if TYPE_CHECKING:
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
@ -87,7 +86,9 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
) )
configured_addresses = self._async_current_ids() configured_addresses = self._async_current_ids()
self._adapters = await async_get_bluetooth_adapters() bluetooth_adapters = get_adapters()
await bluetooth_adapters.refresh()
self._adapters = bluetooth_adapters.adapters
unconfigured_adapters = [ unconfigured_adapters = [
adapter adapter
for adapter, details in self._adapters.items() for adapter, details in self._adapters.items()

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from typing import Final, TypedDict from typing import Final
DOMAIN = "bluetooth" DOMAIN = "bluetooth"
@ -10,18 +10,6 @@ CONF_ADAPTER = "adapter"
CONF_DETAILS = "details" CONF_DETAILS = "details"
CONF_PASSIVE = "passive" CONF_PASSIVE = "passive"
WINDOWS_DEFAULT_BLUETOOTH_ADAPTER = "bluetooth"
MACOS_DEFAULT_BLUETOOTH_ADAPTER = "Core Bluetooth"
UNIX_DEFAULT_BLUETOOTH_ADAPTER = "hci0"
DEFAULT_ADAPTER_BY_PLATFORM = {
"Windows": WINDOWS_DEFAULT_BLUETOOTH_ADAPTER,
"Darwin": MACOS_DEFAULT_BLUETOOTH_ADAPTER,
}
# Some operating systems hide the adapter address for privacy reasons (ex MacOS)
DEFAULT_ADDRESS: Final = "00:00:00:00:00:00"
SOURCE_LOCAL: Final = "local" SOURCE_LOCAL: Final = "local"
@ -66,18 +54,3 @@ SCANNER_WATCHDOG_INTERVAL: Final = timedelta(seconds=30)
# are not present # are not present
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS = 120 LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS = 120
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS = 5 BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS = 5
class AdapterDetails(TypedDict, total=False):
"""Adapter details."""
address: str
sw_version: str
hw_version: str | None
passive_scan: bool
ADAPTER_ADDRESS: Final = "address"
ADAPTER_SW_VERSION: Final = "sw_version"
ADAPTER_HW_VERSION: Final = "hw_version"
ADAPTER_PASSIVE_SCAN: Final = "passive_scan"

View File

@ -10,6 +10,12 @@ from typing import TYPE_CHECKING, Any, Final
from bleak.backends.scanner import AdvertisementDataCallback from bleak.backends.scanner import AdvertisementDataCallback
from bleak_retry_connector import NO_RSSI_VALUE, RSSI_SWITCH_THRESHOLD from bleak_retry_connector import NO_RSSI_VALUE, RSSI_SWITCH_THRESHOLD
from bluetooth_adapters import (
ADAPTER_ADDRESS,
ADAPTER_PASSIVE_SCAN,
AdapterDetails,
BluetoothAdapters,
)
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import ( from homeassistant.core import (
@ -24,11 +30,8 @@ from homeassistant.util.dt import monotonic_time_coarse
from .advertisement_tracker import AdvertisementTracker from .advertisement_tracker import AdvertisementTracker
from .const import ( from .const import (
ADAPTER_ADDRESS,
ADAPTER_PASSIVE_SCAN,
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
UNAVAILABLE_TRACK_SECONDS, UNAVAILABLE_TRACK_SECONDS,
AdapterDetails,
) )
from .match import ( from .match import (
ADDRESS, ADDRESS,
@ -47,7 +50,7 @@ from .models import (
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
) )
from .usage import install_multiple_bleak_catcher, uninstall_multiple_bleak_catcher from .usage import install_multiple_bleak_catcher, uninstall_multiple_bleak_catcher
from .util import async_get_bluetooth_adapters, async_load_history_from_system from .util import async_load_history_from_system
if TYPE_CHECKING: if TYPE_CHECKING:
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@ -102,6 +105,7 @@ class BluetoothManager:
self, self,
hass: HomeAssistant, hass: HomeAssistant,
integration_matcher: IntegrationMatcher, integration_matcher: IntegrationMatcher,
bluetooth_adapters: BluetoothAdapters,
) -> None: ) -> None:
"""Init bluetooth manager.""" """Init bluetooth manager."""
self.hass = hass self.hass = hass
@ -127,6 +131,7 @@ class BluetoothManager:
self._connectable_scanners: list[BaseHaScanner] = [] self._connectable_scanners: list[BaseHaScanner] = []
self._adapters: dict[str, AdapterDetails] = {} self._adapters: dict[str, AdapterDetails] = {}
self._sources: set[str] = set() self._sources: set[str] = set()
self._bluetooth_adapters = bluetooth_adapters
@property @property
def supports_passive_scan(self) -> bool: def supports_passive_scan(self) -> bool:
@ -172,21 +177,25 @@ class BluetoothManager:
self, cached: bool = True self, cached: bool = True
) -> dict[str, AdapterDetails]: ) -> dict[str, AdapterDetails]:
"""Get bluetooth adapters.""" """Get bluetooth adapters."""
if not cached or not self._adapters: if not self._adapters or not cached:
self._adapters = await async_get_bluetooth_adapters() if not cached:
await self._bluetooth_adapters.refresh()
self._adapters = self._bluetooth_adapters.adapters
return self._adapters return self._adapters
async def async_get_adapter_from_address(self, address: str) -> str | None: async def async_get_adapter_from_address(self, address: str) -> str | None:
"""Get adapter from address.""" """Get adapter from address."""
if adapter := self._find_adapter_by_address(address): if adapter := self._find_adapter_by_address(address):
return adapter return adapter
self._adapters = await async_get_bluetooth_adapters() await self._bluetooth_adapters.refresh()
self._adapters = self._bluetooth_adapters.adapters
return self._find_adapter_by_address(address) return self._find_adapter_by_address(address)
async def async_setup(self) -> None: async def async_setup(self) -> None:
"""Set up the bluetooth manager.""" """Set up the bluetooth manager."""
await self._bluetooth_adapters.refresh()
install_multiple_bleak_catcher() install_multiple_bleak_catcher()
history = await async_load_history_from_system() history = async_load_history_from_system(self._bluetooth_adapters)
# Everything is connectable so it fall into both # Everything is connectable so it fall into both
# buckets since the host system can only provide # buckets since the host system can only provide
# connectable devices # connectable devices

View File

@ -8,7 +8,7 @@
"requirements": [ "requirements": [
"bleak==0.19.2", "bleak==0.19.2",
"bleak-retry-connector==2.8.4", "bleak-retry-connector==2.8.4",
"bluetooth-adapters==0.7.0", "bluetooth-adapters==0.8.0",
"bluetooth-auto-recovery==0.4.0", "bluetooth-auto-recovery==0.4.0",
"bluetooth-data-tools==0.3.0", "bluetooth-data-tools==0.3.0",
"dbus-fast==1.74.1" "dbus-fast==1.74.1"

View File

@ -16,6 +16,7 @@ from bleak.backends.bluezdbus.advertisement_monitor import OrPattern
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData, AdvertisementDataCallback from bleak.backends.scanner import AdvertisementData, AdvertisementDataCallback
from bluetooth_adapters import DEFAULT_ADDRESS, adapter_human_name
from dbus_fast import InvalidMessageError from dbus_fast import InvalidMessageError
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
@ -25,14 +26,13 @@ from homeassistant.util.dt import monotonic_time_coarse
from homeassistant.util.package import is_docker_env from homeassistant.util.package import is_docker_env
from .const import ( from .const import (
DEFAULT_ADDRESS,
SCANNER_WATCHDOG_INTERVAL, SCANNER_WATCHDOG_INTERVAL,
SCANNER_WATCHDOG_TIMEOUT, SCANNER_WATCHDOG_TIMEOUT,
SOURCE_LOCAL, SOURCE_LOCAL,
START_TIMEOUT, START_TIMEOUT,
) )
from .models import BaseHaScanner, BluetoothScanningMode, BluetoothServiceInfoBleak from .models import BaseHaScanner, BluetoothScanningMode, BluetoothServiceInfoBleak
from .util import adapter_human_name, async_reset_adapter from .util import async_reset_adapter
OriginalBleakScanner = bleak.BleakScanner OriginalBleakScanner = bleak.BleakScanner
MONOTONIC_TIME = monotonic_time_coarse MONOTONIC_TIME = monotonic_time_coarse

View File

@ -1,34 +1,20 @@
"""The bluetooth integration utilities.""" """The bluetooth integration utilities."""
from __future__ import annotations from __future__ import annotations
import platform from bluetooth_adapters import BluetoothAdapters
from bluetooth_auto_recovery import recover_adapter from bluetooth_auto_recovery import recover_adapter
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.util.dt import monotonic_time_coarse from homeassistant.util.dt import monotonic_time_coarse
from .const import (
DEFAULT_ADAPTER_BY_PLATFORM,
DEFAULT_ADDRESS,
MACOS_DEFAULT_BLUETOOTH_ADAPTER,
UNIX_DEFAULT_BLUETOOTH_ADAPTER,
WINDOWS_DEFAULT_BLUETOOTH_ADAPTER,
AdapterDetails,
)
from .models import BluetoothServiceInfoBleak from .models import BluetoothServiceInfoBleak
async def async_load_history_from_system() -> dict[str, BluetoothServiceInfoBleak]: @callback
def async_load_history_from_system(
adapters: BluetoothAdapters,
) -> dict[str, BluetoothServiceInfoBleak]:
"""Load the device and advertisement_data history if available on the current system.""" """Load the device and advertisement_data history if available on the current system."""
if platform.system() != "Linux":
return {}
from bluetooth_adapters import ( # pylint: disable=import-outside-toplevel
BlueZDBusObjects,
)
bluez_dbus = BlueZDBusObjects()
await bluez_dbus.load()
now = monotonic_time_coarse() now = monotonic_time_coarse()
return { return {
address: BluetoothServiceInfoBleak( address: BluetoothServiceInfoBleak(
@ -46,65 +32,10 @@ async def async_load_history_from_system() -> dict[str, BluetoothServiceInfoBlea
connectable=False, connectable=False,
time=now, time=now,
) )
for address, history in bluez_dbus.history.items() for address, history in adapters.history.items()
} }
async def async_get_bluetooth_adapters() -> dict[str, AdapterDetails]:
"""Return a list of bluetooth adapters."""
if platform.system() == "Windows":
return {
WINDOWS_DEFAULT_BLUETOOTH_ADAPTER: AdapterDetails(
address=DEFAULT_ADDRESS,
sw_version=platform.release(),
passive_scan=False,
)
}
if platform.system() == "Darwin":
return {
MACOS_DEFAULT_BLUETOOTH_ADAPTER: AdapterDetails(
address=DEFAULT_ADDRESS,
sw_version=platform.release(),
passive_scan=False,
)
}
from bluetooth_adapters import ( # pylint: disable=import-outside-toplevel
get_bluetooth_adapter_details,
)
adapters: dict[str, AdapterDetails] = {}
adapter_details = await get_bluetooth_adapter_details()
for adapter, details in adapter_details.items():
adapter1 = details["org.bluez.Adapter1"]
adapters[adapter] = AdapterDetails(
address=adapter1["Address"],
sw_version=adapter1["Name"], # This is actually the BlueZ version
hw_version=adapter1.get("Modalias"),
passive_scan="org.bluez.AdvertisementMonitorManager1" in details,
)
return adapters
@callback
def async_default_adapter() -> str:
"""Return the default adapter for the platform."""
return DEFAULT_ADAPTER_BY_PLATFORM.get(
platform.system(), UNIX_DEFAULT_BLUETOOTH_ADAPTER
)
@callback
def adapter_human_name(adapter: str, address: str) -> str:
"""Return a human readable name for the adapter."""
return adapter if address == DEFAULT_ADDRESS else f"{adapter} ({address})"
@callback
def adapter_unique_name(adapter: str, address: str) -> str:
"""Return a unique name for the adapter."""
return adapter if address == DEFAULT_ADDRESS else address
async def async_reset_adapter(adapter: str | None) -> bool | None: async def async_reset_adapter(adapter: str | None) -> bool | None:
"""Reset the adapter.""" """Reset the adapter."""
if adapter and adapter.startswith("hci"): if adapter and adapter.startswith("hci"):

View File

@ -12,7 +12,7 @@ awesomeversion==22.9.0
bcrypt==3.1.7 bcrypt==3.1.7
bleak-retry-connector==2.8.4 bleak-retry-connector==2.8.4
bleak==0.19.2 bleak==0.19.2
bluetooth-adapters==0.7.0 bluetooth-adapters==0.8.0
bluetooth-auto-recovery==0.4.0 bluetooth-auto-recovery==0.4.0
bluetooth-data-tools==0.3.0 bluetooth-data-tools==0.3.0
certifi>=2021.5.30 certifi>=2021.5.30

View File

@ -447,7 +447,7 @@ bluemaestro-ble==0.2.0
# bluepy==1.3.0 # bluepy==1.3.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-adapters==0.7.0 bluetooth-adapters==0.8.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-auto-recovery==0.4.0 bluetooth-auto-recovery==0.4.0

View File

@ -361,7 +361,7 @@ blinkpy==0.19.2
bluemaestro-ble==0.2.0 bluemaestro-ble==0.2.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-adapters==0.7.0 bluetooth-adapters==0.8.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-auto-recovery==0.4.0 bluetooth-auto-recovery==0.4.0

View File

@ -6,6 +6,7 @@ from typing import Any
from unittest.mock import patch from unittest.mock import patch
from bleak.backends.scanner import AdvertisementData, BLEDevice from bleak.backends.scanner import AdvertisementData, BLEDevice
from bluetooth_adapters import DEFAULT_ADDRESS
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
DOMAIN, DOMAIN,
@ -13,7 +14,6 @@ from homeassistant.components.bluetooth import (
async_get_advertisement_callback, async_get_advertisement_callback,
models, models,
) )
from homeassistant.components.bluetooth.const import DEFAULT_ADDRESS
from homeassistant.components.bluetooth.manager import BluetoothManager from homeassistant.components.bluetooth.manager import BluetoothManager
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component

View File

@ -1,6 +1,6 @@
"""Tests for the bluetooth component.""" """Tests for the bluetooth component."""
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import patch
import pytest import pytest
@ -43,16 +43,6 @@ def mock_operating_system_90():
yield yield
@pytest.fixture(name="bluez_dbus_mock")
def bluez_dbus_mock():
"""Fixture that mocks out the bluez dbus calls."""
# Must patch directly since this is loaded on demand only
with patch(
"bluetooth_adapters.BlueZDBusObjects", return_value=MagicMock(load=AsyncMock())
):
yield
@pytest.fixture(name="macos_adapter") @pytest.fixture(name="macos_adapter")
def macos_adapter(): def macos_adapter():
"""Fixture that mocks the macos adapter.""" """Fixture that mocks the macos adapter."""
@ -62,7 +52,7 @@ def macos_adapter():
"homeassistant.components.bluetooth.scanner.platform.system", "homeassistant.components.bluetooth.scanner.platform.system",
return_value="Darwin", return_value="Darwin",
), patch( ), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Darwin" "bluetooth_adapters.systems.platform.system", return_value="Darwin"
): ):
yield yield
@ -71,14 +61,14 @@ def macos_adapter():
def windows_adapter(): def windows_adapter():
"""Fixture that mocks the windows adapter.""" """Fixture that mocks the windows adapter."""
with patch( with patch(
"homeassistant.components.bluetooth.util.platform.system", "bluetooth_adapters.systems.platform.system",
return_value="Windows", return_value="Windows",
): ):
yield yield
@pytest.fixture(name="no_adapters") @pytest.fixture(name="no_adapters")
def no_adapter_fixture(bluez_dbus_mock): def no_adapter_fixture():
"""Fixture that mocks no adapters on Linux.""" """Fixture that mocks no adapters on Linux."""
with patch( with patch(
"homeassistant.components.bluetooth.platform.system", return_value="Linux" "homeassistant.components.bluetooth.platform.system", return_value="Linux"
@ -86,16 +76,18 @@ def no_adapter_fixture(bluez_dbus_mock):
"homeassistant.components.bluetooth.scanner.platform.system", "homeassistant.components.bluetooth.scanner.platform.system",
return_value="Linux", return_value="Linux",
), patch( ), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
return_value={}, ), patch(
"bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
{},
): ):
yield yield
@pytest.fixture(name="one_adapter") @pytest.fixture(name="one_adapter")
def one_adapter_fixture(bluez_dbus_mock): def one_adapter_fixture():
"""Fixture that mocks one adapter on Linux.""" """Fixture that mocks one adapter on Linux."""
with patch( with patch(
"homeassistant.components.bluetooth.platform.system", return_value="Linux" "homeassistant.components.bluetooth.platform.system", return_value="Linux"
@ -103,20 +95,17 @@ def one_adapter_fixture(bluez_dbus_mock):
"homeassistant.components.bluetooth.scanner.platform.system", "homeassistant.components.bluetooth.scanner.platform.system",
return_value="Linux", return_value="Linux",
), patch( ), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
return_value={ ), patch(
"bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
{
"hci0": { "hci0": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:01",
"Address": "00:00:00:00:00:01", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": True,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
},
"org.bluez.AdvertisementMonitorManager1": {
"SupportedMonitorTypes": ["or_patterns"],
"SupportedFeatures": [],
},
}, },
}, },
): ):
@ -124,7 +113,7 @@ def one_adapter_fixture(bluez_dbus_mock):
@pytest.fixture(name="two_adapters") @pytest.fixture(name="two_adapters")
def two_adapters_fixture(bluez_dbus_mock): def two_adapters_fixture():
"""Fixture that mocks two adapters on Linux.""" """Fixture that mocks two adapters on Linux."""
with patch( with patch(
"homeassistant.components.bluetooth.platform.system", return_value="Linux" "homeassistant.components.bluetooth.platform.system", return_value="Linux"
@ -132,27 +121,23 @@ def two_adapters_fixture(bluez_dbus_mock):
"homeassistant.components.bluetooth.scanner.platform.system", "homeassistant.components.bluetooth.scanner.platform.system",
return_value="Linux", return_value="Linux",
), patch( ), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
return_value={ ), patch(
"bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
{
"hci0": { "hci0": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:01",
"Address": "00:00:00:00:00:01", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": False,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
}
}, },
"hci1": { "hci1": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:02",
"Address": "00:00:00:00:00:02", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": True,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
},
"org.bluez.AdvertisementMonitorManager1": {
"SupportedMonitorTypes": ["or_patterns"],
"SupportedFeatures": [],
},
}, },
}, },
): ):
@ -160,7 +145,7 @@ def two_adapters_fixture(bluez_dbus_mock):
@pytest.fixture(name="one_adapter_old_bluez") @pytest.fixture(name="one_adapter_old_bluez")
def one_adapter_old_bluez(bluez_dbus_mock): def one_adapter_old_bluez():
"""Fixture that mocks two adapters on Linux.""" """Fixture that mocks two adapters on Linux."""
with patch( with patch(
"homeassistant.components.bluetooth.platform.system", return_value="Linux" "homeassistant.components.bluetooth.platform.system", return_value="Linux"
@ -168,15 +153,17 @@ def one_adapter_old_bluez(bluez_dbus_mock):
"homeassistant.components.bluetooth.scanner.platform.system", "homeassistant.components.bluetooth.scanner.platform.system",
return_value="Linux", return_value="Linux",
), patch( ), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
return_value={ ), patch(
"bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
{
"hci0": { "hci0": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:01",
"Address": "00:00:00:00:00:01", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.43", "passive_scan": False,
} "sw_version": "homeassistant",
}, },
}, },
): ):

View File

@ -2,14 +2,14 @@
from unittest.mock import patch from unittest.mock import patch
from bluetooth_adapters import DEFAULT_ADDRESS, AdapterDetails
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.bluetooth.const import ( from homeassistant.components.bluetooth.const import (
CONF_ADAPTER, CONF_ADAPTER,
CONF_DETAILS, CONF_DETAILS,
CONF_PASSIVE, CONF_PASSIVE,
DEFAULT_ADDRESS,
DOMAIN, DOMAIN,
AdapterDetails,
) )
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component

View File

@ -4,9 +4,9 @@
from unittest.mock import ANY, patch from unittest.mock import ANY, patch
from bleak.backends.scanner import BLEDevice from bleak.backends.scanner import BLEDevice
from bluetooth_adapters import DEFAULT_ADDRESS
from homeassistant.components import bluetooth from homeassistant.components import bluetooth
from homeassistant.components.bluetooth.const import DEFAULT_ADDRESS
from . import generate_advertisement_data, inject_advertisement from . import generate_advertisement_data, inject_advertisement
@ -68,15 +68,15 @@ async def test_diagnostics(
"adapters": { "adapters": {
"hci0": { "hci0": {
"address": "00:00:00:00:00:01", "address": "00:00:00:00:00:01",
"hw_version": "usbid:1234", "hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": False, "passive_scan": False,
"sw_version": "BlueZ 4.63", "sw_version": "homeassistant",
}, },
"hci1": { "hci1": {
"address": "00:00:00:00:00:02", "address": "00:00:00:00:00:02",
"hw_version": "usbid:1234", "hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": True, "passive_scan": True,
"sw_version": "BlueZ 4.63", "sw_version": "homeassistant",
}, },
}, },
"dbus": { "dbus": {
@ -99,15 +99,15 @@ async def test_diagnostics(
"adapters": { "adapters": {
"hci0": { "hci0": {
"address": "00:00:00:00:00:01", "address": "00:00:00:00:00:01",
"hw_version": "usbid:1234", "hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": False, "passive_scan": False,
"sw_version": "BlueZ 4.63", "sw_version": "homeassistant",
}, },
"hci1": { "hci1": {
"address": "00:00:00:00:00:02", "address": "00:00:00:00:00:02",
"hw_version": "usbid:1234", "hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": True, "passive_scan": True,
"sw_version": "BlueZ 4.63", "sw_version": "homeassistant",
}, },
}, },
"advertisement_tracker": { "advertisement_tracker": {

View File

@ -6,6 +6,7 @@ from unittest.mock import ANY, MagicMock, Mock, patch
from bleak import BleakError from bleak import BleakError
from bleak.backends.scanner import AdvertisementData, BLEDevice from bleak.backends.scanner import AdvertisementData, BLEDevice
from bluetooth_adapters import DEFAULT_ADDRESS
import pytest import pytest
from homeassistant.components import bluetooth from homeassistant.components import bluetooth
@ -22,7 +23,6 @@ from homeassistant.components.bluetooth import (
from homeassistant.components.bluetooth.const import ( from homeassistant.components.bluetooth.const import (
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS, BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
CONF_PASSIVE, CONF_PASSIVE,
DEFAULT_ADDRESS,
DOMAIN, DOMAIN,
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS, LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
SOURCE_LOCAL, SOURCE_LOCAL,
@ -2522,7 +2522,7 @@ async def test_async_ble_device_from_address(
async def test_can_unsetup_bluetooth_single_adapter_macos( async def test_can_unsetup_bluetooth_single_adapter_macos(
hass, mock_bleak_scanner_start, enable_bluetooth, macos_adapter hass, mock_bleak_scanner_start, macos_adapter
): ):
"""Test we can setup and unsetup bluetooth.""" """Test we can setup and unsetup bluetooth."""
entry = MockConfigEntry(domain=bluetooth.DOMAIN, data={}, unique_id=DEFAULT_ADDRESS) entry = MockConfigEntry(domain=bluetooth.DOMAIN, data={}, unique_id=DEFAULT_ADDRESS)
@ -2605,12 +2605,13 @@ async def test_auto_detect_bluetooth_adapters_linux_multiple(hass, two_adapters)
assert len(hass.config_entries.flow.async_progress(bluetooth.DOMAIN)) == 2 assert len(hass.config_entries.flow.async_progress(bluetooth.DOMAIN)) == 2
async def test_auto_detect_bluetooth_adapters_linux_none_found(hass, bluez_dbus_mock): async def test_auto_detect_bluetooth_adapters_linux_none_found(hass):
"""Test we auto detect bluetooth adapters on linux with no adapters found.""" """Test we auto detect bluetooth adapters on linux with no adapters found."""
with patch( with patch(
"bluetooth_adapters.get_bluetooth_adapter_details", return_value={} "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
{},
): ):
assert await async_setup_component(hass, bluetooth.DOMAIN, {}) assert await async_setup_component(hass, bluetooth.DOMAIN, {})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -2620,9 +2621,7 @@ async def test_auto_detect_bluetooth_adapters_linux_none_found(hass, bluez_dbus_
async def test_auto_detect_bluetooth_adapters_macos(hass): async def test_auto_detect_bluetooth_adapters_macos(hass):
"""Test we auto detect bluetooth adapters on macos.""" """Test we auto detect bluetooth adapters on macos."""
with patch( with patch("bluetooth_adapters.systems.platform.system", return_value="Darwin"):
"homeassistant.components.bluetooth.util.platform.system", return_value="Darwin"
):
assert await async_setup_component(hass, bluetooth.DOMAIN, {}) assert await async_setup_component(hass, bluetooth.DOMAIN, {})
await hass.async_block_till_done() await hass.async_block_till_done()
assert not hass.config_entries.async_entries(bluetooth.DOMAIN) assert not hass.config_entries.async_entries(bluetooth.DOMAIN)
@ -2632,7 +2631,7 @@ async def test_auto_detect_bluetooth_adapters_macos(hass):
async def test_no_auto_detect_bluetooth_adapters_windows(hass): async def test_no_auto_detect_bluetooth_adapters_windows(hass):
"""Test we auto detect bluetooth adapters on windows.""" """Test we auto detect bluetooth adapters on windows."""
with patch( with patch(
"homeassistant.components.bluetooth.util.platform.system", "bluetooth_adapters.systems.platform.system",
return_value="Windows", return_value="Windows",
): ):
assert await async_setup_component(hass, bluetooth.DOMAIN, {}) assert await async_setup_component(hass, bluetooth.DOMAIN, {})
@ -2710,23 +2709,21 @@ async def test_discover_new_usb_adapters(hass, mock_bleak_scanner_start, one_ada
assert not hass.config_entries.flow.async_progress(DOMAIN) assert not hass.config_entries.flow.async_progress(DOMAIN)
with patch( with patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
return_value={ {
"hci0": { "hci0": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:01",
"Address": "00:00:00:00:00:01", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": False,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
}
}, },
"hci1": { "hci1": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:02",
"Address": "00:00:00:00:00:02", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": False,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
}
}, },
}, },
): ):
@ -2768,10 +2765,10 @@ async def test_discover_new_usb_adapters_with_firmware_fallback_delay(
assert not hass.config_entries.flow.async_progress(DOMAIN) assert not hass.config_entries.flow.async_progress(DOMAIN)
with patch( with patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
return_value={}, {},
): ):
async_fire_time_changed( async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS * 2) hass, dt_util.utcnow() + timedelta(BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS * 2)
@ -2781,23 +2778,21 @@ async def test_discover_new_usb_adapters_with_firmware_fallback_delay(
assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 0 assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 0
with patch( with patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch(
"bluetooth_adapters.get_bluetooth_adapter_details", "bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
return_value={ {
"hci0": { "hci0": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:01",
"Address": "00:00:00:00:00:01", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": False,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
}
}, },
"hci1": { "hci1": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:02",
"Address": "00:00:00:00:00:02", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": False,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
}
}, },
}, },
): ):

View File

@ -1,7 +1,7 @@
"""Tests for the Bluetooth integration manager.""" """Tests for the Bluetooth integration manager."""
import time import time
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import patch
from bleak.backends.scanner import BLEDevice from bleak.backends.scanner import BLEDevice
from bluetooth_adapters import AdvertisementHistory from bluetooth_adapters import AdvertisementHistory
@ -275,8 +275,8 @@ async def test_restore_history_from_dbus(hass, one_adapter):
} }
with patch( with patch(
"bluetooth_adapters.BlueZDBusObjects", "bluetooth_adapters.systems.linux.LinuxAdapters.history",
return_value=MagicMock(load=AsyncMock(), history=history), history,
): ):
assert await async_setup_component(hass, bluetooth.DOMAIN, {}) assert await async_setup_component(hass, bluetooth.DOMAIN, {})
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -1041,18 +1041,15 @@ async def mock_enable_bluetooth(
def mock_bluetooth_adapters(): def mock_bluetooth_adapters():
"""Fixture to mock bluetooth adapters.""" """Fixture to mock bluetooth adapters."""
with patch( with patch(
"homeassistant.components.bluetooth.util.platform.system", return_value="Linux" "bluetooth_adapters.systems.platform.system", return_value="Linux"
), patch( ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch(
"bluetooth_adapters.BlueZDBusObjects", return_value=MagicMock(load=AsyncMock()) "bluetooth_adapters.systems.linux.LinuxAdapters.adapters",
), patch( {
"bluetooth_adapters.get_bluetooth_adapter_details",
return_value={
"hci0": { "hci0": {
"org.bluez.Adapter1": { "address": "00:00:00:00:00:01",
"Address": "00:00:00:00:00:01", "hw_version": "usb:v1D6Bp0246d053F",
"Name": "BlueZ 4.63", "passive_scan": False,
"Modalias": "usbid:1234", "sw_version": "homeassistant",
}
}, },
}, },
): ):