Update to bleak 0.15 (#75941)

This commit is contained in:
J. Nick Koston 2022-07-29 14:53:33 -10:00 committed by GitHub
parent c4ad6d46ae
commit 80a9659524
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 223 additions and 152 deletions

View file

@ -8,7 +8,7 @@ from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum from enum import Enum
import logging import logging
from typing import Final, Union from typing import Final
import async_timeout import async_timeout
from bleak import BleakError from bleak import BleakError
@ -96,12 +96,8 @@ SCANNING_MODE_TO_BLEAK = {
BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT") BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT")
BluetoothCallback = Callable[ BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None]
[Union[BluetoothServiceInfoBleak, BluetoothServiceInfo], BluetoothChange], None ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool]
]
ProcessAdvertisementCallback = Callable[
[Union[BluetoothServiceInfoBleak, BluetoothServiceInfo]], bool
]
@hass_callback @hass_callback
@ -157,9 +153,15 @@ def async_register_callback(
hass: HomeAssistant, hass: HomeAssistant,
callback: BluetoothCallback, callback: BluetoothCallback,
match_dict: BluetoothCallbackMatcher | None, match_dict: BluetoothCallbackMatcher | None,
mode: BluetoothScanningMode,
) -> Callable[[], None]: ) -> Callable[[], None]:
"""Register to receive a callback on bluetooth change. """Register to receive a callback on bluetooth change.
mode is currently not used as we only support active scanning.
Passive scanning will be available in the future. The flag
is required to be present to avoid a future breaking change
when we support passive scanning.
Returns a callback that can be used to cancel the registration. Returns a callback that can be used to cancel the registration.
""" """
manager: BluetoothManager = hass.data[DOMAIN] manager: BluetoothManager = hass.data[DOMAIN]
@ -170,19 +172,20 @@ async def async_process_advertisements(
hass: HomeAssistant, hass: HomeAssistant,
callback: ProcessAdvertisementCallback, callback: ProcessAdvertisementCallback,
match_dict: BluetoothCallbackMatcher, match_dict: BluetoothCallbackMatcher,
mode: BluetoothScanningMode,
timeout: int, timeout: int,
) -> BluetoothServiceInfo: ) -> BluetoothServiceInfoBleak:
"""Process advertisements until callback returns true or timeout expires.""" """Process advertisements until callback returns true or timeout expires."""
done: Future[BluetoothServiceInfo] = Future() done: Future[BluetoothServiceInfoBleak] = Future()
@hass_callback @hass_callback
def _async_discovered_device( def _async_discovered_device(
service_info: BluetoothServiceInfo, change: BluetoothChange service_info: BluetoothServiceInfoBleak, change: BluetoothChange
) -> None: ) -> None:
if callback(service_info): if callback(service_info):
done.set_result(service_info) done.set_result(service_info)
unload = async_register_callback(hass, _async_discovered_device, match_dict) unload = async_register_callback(hass, _async_discovered_device, match_dict, mode)
try: try:
async with async_timeout.timeout(timeout): async with async_timeout.timeout(timeout):
@ -333,7 +336,7 @@ class BluetoothManager:
) )
try: try:
async with async_timeout.timeout(START_TIMEOUT): async with async_timeout.timeout(START_TIMEOUT):
await self.scanner.start() await self.scanner.start() # type: ignore[no-untyped-call]
except asyncio.TimeoutError as ex: except asyncio.TimeoutError as ex:
self._cancel_device_detected() self._cancel_device_detected()
raise ConfigEntryNotReady( raise ConfigEntryNotReady(
@ -500,7 +503,7 @@ class BluetoothManager:
self._cancel_unavailable_tracking = None self._cancel_unavailable_tracking = None
if self.scanner: if self.scanner:
try: try:
await self.scanner.stop() await self.scanner.stop() # type: ignore[no-untyped-call]
except BleakError as ex: except BleakError as ex:
# This is not fatal, and they may want to reload # This is not fatal, and they may want to reload
# the config entry to restart the scanner if they # the config entry to restart the scanner if they

View file

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/integrations/bluetooth", "documentation": "https://www.home-assistant.io/integrations/bluetooth",
"dependencies": ["websocket_api"], "dependencies": ["websocket_api"],
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["bleak==0.14.3", "bluetooth-adapters==0.1.2"], "requirements": ["bleak==0.15.0", "bluetooth-adapters==0.1.2"],
"codeowners": ["@bdraco"], "codeowners": ["@bdraco"],
"config_flow": true, "config_flow": true,
"iot_class": "local_push" "iot_class": "local_push"

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio import asyncio
import contextlib import contextlib
import logging import logging
from typing import Any, Final, cast from typing import Any, Final
from bleak import BleakScanner from bleak import BleakScanner
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@ -32,7 +32,7 @@ def _dispatch_callback(
"""Dispatch the callback.""" """Dispatch the callback."""
if not callback: if not callback:
# Callback destroyed right before being called, ignore # Callback destroyed right before being called, ignore
return return # type: ignore[unreachable]
if (uuids := filters.get(FILTER_UUIDS)) and not uuids.intersection( if (uuids := filters.get(FILTER_UUIDS)) and not uuids.intersection(
advertisement_data.service_uuids advertisement_data.service_uuids
@ -45,7 +45,7 @@ def _dispatch_callback(
_LOGGER.exception("Error in callback: %s", callback) _LOGGER.exception("Error in callback: %s", callback)
class HaBleakScanner(BleakScanner): # type: ignore[misc] class HaBleakScanner(BleakScanner):
"""BleakScanner that cannot be stopped.""" """BleakScanner that cannot be stopped."""
def __init__( # pylint: disable=super-init-not-called def __init__( # pylint: disable=super-init-not-called
@ -106,16 +106,29 @@ class HaBleakScanner(BleakScanner): # type: ignore[misc]
_dispatch_callback(*callback_filters, device, advertisement_data) _dispatch_callback(*callback_filters, device, advertisement_data)
class HaBleakScannerWrapper(BaseBleakScanner): # type: ignore[misc] class HaBleakScannerWrapper(BaseBleakScanner):
"""A wrapper that uses the single instance.""" """A wrapper that uses the single instance."""
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(
self,
*args: Any,
detection_callback: AdvertisementDataCallback | None = None,
service_uuids: list[str] | None = None,
**kwargs: Any,
) -> None:
"""Initialize the BleakScanner.""" """Initialize the BleakScanner."""
self._detection_cancel: CALLBACK_TYPE | None = None self._detection_cancel: CALLBACK_TYPE | None = None
self._mapped_filters: dict[str, set[str]] = {} self._mapped_filters: dict[str, set[str]] = {}
self._adv_data_callback: AdvertisementDataCallback | None = None self._adv_data_callback: AdvertisementDataCallback | None = None
self._map_filters(*args, **kwargs) remapped_kwargs = {
super().__init__(*args, **kwargs) "detection_callback": detection_callback,
"service_uuids": service_uuids or [],
**kwargs,
}
self._map_filters(*args, **remapped_kwargs)
super().__init__(
detection_callback=detection_callback, service_uuids=service_uuids or []
)
async def stop(self, *args: Any, **kwargs: Any) -> None: async def stop(self, *args: Any, **kwargs: Any) -> None:
"""Stop scanning for devices.""" """Stop scanning for devices."""
@ -153,9 +166,11 @@ class HaBleakScannerWrapper(BaseBleakScanner): # type: ignore[misc]
def discovered_devices(self) -> list[BLEDevice]: def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices.""" """Return a list of discovered devices."""
assert HA_BLEAK_SCANNER is not None assert HA_BLEAK_SCANNER is not None
return cast(list[BLEDevice], HA_BLEAK_SCANNER.discovered_devices) return HA_BLEAK_SCANNER.discovered_devices
def register_detection_callback(self, callback: AdvertisementDataCallback) -> None: def register_detection_callback(
self, callback: AdvertisementDataCallback | None
) -> None:
"""Register a callback that is called when a device is discovered or has a property changed. """Register a callback that is called when a device is discovered or has a property changed.
This method takes the callback and registers it with the long running This method takes the callback and registers it with the long running
@ -171,6 +186,7 @@ class HaBleakScannerWrapper(BaseBleakScanner): # type: ignore[misc]
self._cancel_callback() self._cancel_callback()
super().register_detection_callback(self._adv_data_callback) super().register_detection_callback(self._adv_data_callback)
assert HA_BLEAK_SCANNER is not None assert HA_BLEAK_SCANNER is not None
assert self._callback is not None
self._detection_cancel = HA_BLEAK_SCANNER.async_register_callback( self._detection_cancel = HA_BLEAK_SCANNER.async_register_callback(
self._callback, self._mapped_filters self._callback, self._mapped_filters
) )

View file

@ -6,10 +6,9 @@ import logging
from typing import Any from typing import Any
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import BluetoothChange from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak
from .update_coordinator import BasePassiveBluetoothCoordinator from .update_coordinator import BasePassiveBluetoothCoordinator
@ -25,9 +24,10 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator):
hass: HomeAssistant, hass: HomeAssistant,
logger: logging.Logger, logger: logging.Logger,
address: str, address: str,
mode: BluetoothScanningMode,
) -> None: ) -> None:
"""Initialize PassiveBluetoothDataUpdateCoordinator.""" """Initialize PassiveBluetoothDataUpdateCoordinator."""
super().__init__(hass, logger, address) super().__init__(hass, logger, address, mode)
self._listeners: dict[CALLBACK_TYPE, tuple[CALLBACK_TYPE, object | None]] = {} self._listeners: dict[CALLBACK_TYPE, tuple[CALLBACK_TYPE, object | None]] = {}
@callback @callback
@ -65,7 +65,7 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator):
@callback @callback
def _async_handle_bluetooth_event( def _async_handle_bluetooth_event(
self, self,
service_info: BluetoothServiceInfo, service_info: BluetoothServiceInfoBleak,
change: BluetoothChange, change: BluetoothChange,
) -> None: ) -> None:
"""Handle a Bluetooth event.""" """Handle a Bluetooth event."""

View file

@ -6,14 +6,12 @@ import dataclasses
import logging import logging
from typing import Any, Generic, TypeVar from typing import Any, Generic, TypeVar
from home_assistant_bluetooth import BluetoothServiceInfo
from homeassistant.const import ATTR_IDENTIFIERS, ATTR_NAME from homeassistant.const import ATTR_IDENTIFIERS, ATTR_NAME
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BluetoothChange from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak
from .const import DOMAIN from .const import DOMAIN
from .update_coordinator import BasePassiveBluetoothCoordinator from .update_coordinator import BasePassiveBluetoothCoordinator
@ -62,9 +60,10 @@ class PassiveBluetoothProcessorCoordinator(BasePassiveBluetoothCoordinator):
hass: HomeAssistant, hass: HomeAssistant,
logger: logging.Logger, logger: logging.Logger,
address: str, address: str,
mode: BluetoothScanningMode,
) -> None: ) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
super().__init__(hass, logger, address) super().__init__(hass, logger, address, mode)
self._processors: list[PassiveBluetoothDataProcessor] = [] self._processors: list[PassiveBluetoothDataProcessor] = []
@callback @callback
@ -92,7 +91,7 @@ class PassiveBluetoothProcessorCoordinator(BasePassiveBluetoothCoordinator):
@callback @callback
def _async_handle_bluetooth_event( def _async_handle_bluetooth_event(
self, self,
service_info: BluetoothServiceInfo, service_info: BluetoothServiceInfoBleak,
change: BluetoothChange, change: BluetoothChange,
) -> None: ) -> None:
"""Handle a Bluetooth event.""" """Handle a Bluetooth event."""
@ -122,7 +121,7 @@ class PassiveBluetoothDataProcessor(Generic[_T]):
The processor will call the update_method every time the bluetooth device The processor will call the update_method every time the bluetooth device
receives a new advertisement data from the coordinator with the following signature: receives a new advertisement data from the coordinator with the following signature:
update_method(service_info: BluetoothServiceInfo) -> PassiveBluetoothDataUpdate update_method(service_info: BluetoothServiceInfoBleak) -> PassiveBluetoothDataUpdate
As the size of each advertisement is limited, the update_method should As the size of each advertisement is limited, the update_method should
return a PassiveBluetoothDataUpdate object that contains only data that return a PassiveBluetoothDataUpdate object that contains only data that
@ -135,7 +134,9 @@ class PassiveBluetoothDataProcessor(Generic[_T]):
def __init__( def __init__(
self, self,
update_method: Callable[[BluetoothServiceInfo], PassiveBluetoothDataUpdate[_T]], update_method: Callable[
[BluetoothServiceInfoBleak], PassiveBluetoothDataUpdate[_T]
],
) -> None: ) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
self.coordinator: PassiveBluetoothProcessorCoordinator self.coordinator: PassiveBluetoothProcessorCoordinator
@ -241,7 +242,7 @@ class PassiveBluetoothDataProcessor(Generic[_T]):
@callback @callback
def async_handle_bluetooth_event( def async_handle_bluetooth_event(
self, self,
service_info: BluetoothServiceInfo, service_info: BluetoothServiceInfoBleak,
change: BluetoothChange, change: BluetoothChange,
) -> None: ) -> None:
"""Handle a Bluetooth event.""" """Handle a Bluetooth event."""

View file

@ -4,13 +4,13 @@ from __future__ import annotations
import logging import logging
import time import time
from home_assistant_bluetooth import BluetoothServiceInfo
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from . import ( from . import (
BluetoothCallbackMatcher, BluetoothCallbackMatcher,
BluetoothChange, BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfoBleak,
async_register_callback, async_register_callback,
async_track_unavailable, async_track_unavailable,
) )
@ -27,6 +27,7 @@ class BasePassiveBluetoothCoordinator:
hass: HomeAssistant, hass: HomeAssistant,
logger: logging.Logger, logger: logging.Logger,
address: str, address: str,
mode: BluetoothScanningMode,
) -> None: ) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
self.hass = hass self.hass = hass
@ -36,6 +37,7 @@ class BasePassiveBluetoothCoordinator:
self._cancel_track_unavailable: CALLBACK_TYPE | None = None self._cancel_track_unavailable: CALLBACK_TYPE | None = None
self._cancel_bluetooth_advertisements: CALLBACK_TYPE | None = None self._cancel_bluetooth_advertisements: CALLBACK_TYPE | None = None
self._present = False self._present = False
self.mode = mode
self.last_seen = 0.0 self.last_seen = 0.0
@callback @callback
@ -61,6 +63,7 @@ class BasePassiveBluetoothCoordinator:
self.hass, self.hass,
self._async_handle_bluetooth_event, self._async_handle_bluetooth_event,
BluetoothCallbackMatcher(address=self.address), BluetoothCallbackMatcher(address=self.address),
self.mode,
) )
self._cancel_track_unavailable = async_track_unavailable( self._cancel_track_unavailable = async_track_unavailable(
self.hass, self.hass,
@ -86,7 +89,7 @@ class BasePassiveBluetoothCoordinator:
@callback @callback
def _async_handle_bluetooth_event( def _async_handle_bluetooth_event(
self, self,
service_info: BluetoothServiceInfo, service_info: BluetoothServiceInfoBleak,
change: BluetoothChange, change: BluetoothChange,
) -> None: ) -> None:
"""Handle a Bluetooth event.""" """Handle a Bluetooth event."""

View file

@ -1,4 +1,5 @@
"""bluetooth usage utility to handle multiple instances.""" """bluetooth usage utility to handle multiple instances."""
from __future__ import annotations from __future__ import annotations
import bleak import bleak
@ -10,9 +11,9 @@ ORIGINAL_BLEAK_SCANNER = bleak.BleakScanner
def install_multiple_bleak_catcher() -> None: def install_multiple_bleak_catcher() -> None:
"""Wrap the bleak classes to return the shared instance if multiple instances are detected.""" """Wrap the bleak classes to return the shared instance if multiple instances are detected."""
bleak.BleakScanner = HaBleakScannerWrapper bleak.BleakScanner = HaBleakScannerWrapper # type: ignore[misc, assignment]
def uninstall_multiple_bleak_catcher() -> None: def uninstall_multiple_bleak_catcher() -> None:
"""Unwrap the bleak classes.""" """Unwrap the bleak classes."""
bleak.BleakScanner = ORIGINAL_BLEAK_SCANNER bleak.BleakScanner = ORIGINAL_BLEAK_SCANNER # type: ignore[misc]

View file

@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio import asyncio
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import TYPE_CHECKING
from uuid import UUID from uuid import UUID
from bleak import BleakClient, BleakError from bleak import BleakClient, BleakError
@ -31,9 +30,6 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
if TYPE_CHECKING:
from bleak.backends.device import BLEDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Base UUID: 00000000-0000-1000-8000-00805F9B34FB # Base UUID: 00000000-0000-1000-8000-00805F9B34FB
@ -139,15 +135,12 @@ async def async_setup_scanner( # noqa: C901
async def _async_see_update_ble_battery( async def _async_see_update_ble_battery(
mac: str, mac: str,
now: datetime, now: datetime,
service_info: bluetooth.BluetoothServiceInfo, service_info: bluetooth.BluetoothServiceInfoBleak,
) -> None: ) -> None:
"""Lookup Bluetooth LE devices and update status.""" """Lookup Bluetooth LE devices and update status."""
battery = None battery = None
ble_device: BLEDevice | str = (
bluetooth.async_ble_device_from_address(hass, mac) or mac
)
try: try:
async with BleakClient(ble_device) as client: async with BleakClient(service_info.device) as client:
bat_char = await client.read_gatt_char(BATTERY_CHARACTERISTIC_UUID) bat_char = await client.read_gatt_char(BATTERY_CHARACTERISTIC_UUID)
battery = ord(bat_char) battery = ord(bat_char)
except asyncio.TimeoutError: except asyncio.TimeoutError:
@ -168,7 +161,8 @@ async def async_setup_scanner( # noqa: C901
@callback @callback
def _async_update_ble( def _async_update_ble(
service_info: bluetooth.BluetoothServiceInfo, change: bluetooth.BluetoothChange service_info: bluetooth.BluetoothServiceInfoBleak,
change: bluetooth.BluetoothChange,
) -> None: ) -> None:
"""Update from a ble callback.""" """Update from a ble callback."""
mac = service_info.address mac = service_info.address
@ -202,7 +196,9 @@ async def async_setup_scanner( # noqa: C901
_async_update_ble(service_info, bluetooth.BluetoothChange.ADVERTISEMENT) _async_update_ble(service_info, bluetooth.BluetoothChange.ADVERTISEMENT)
cancels = [ cancels = [
bluetooth.async_register_callback(hass, _async_update_ble, None), bluetooth.async_register_callback(
hass, _async_update_ble, None, bluetooth.BluetoothScanningMode.ACTIVE
),
async_track_time_interval(hass, _async_refresh_ble, interval), async_track_time_interval(hass, _async_refresh_ble, interval),
] ]

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth.passive_update_processor import ( from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothProcessorCoordinator, PassiveBluetoothProcessorCoordinator,
) )
@ -27,6 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass, hass,
_LOGGER, _LOGGER,
address=address, address=address,
mode=BluetoothScanningMode.ACTIVE,
) )
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload( entry.async_on_unload(

View file

@ -7,7 +7,7 @@ from govee_ble import GoveeBluetoothDeviceData as DeviceData
import voluptuous as vol import voluptuous as vol
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfo, BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
@ -24,12 +24,12 @@ class GoveeConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self._discovery_info: BluetoothServiceInfo | None = None self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_device: DeviceData | None = None self._discovered_device: DeviceData | None = None
self._discovered_devices: dict[str, str] = {} self._discovered_devices: dict[str, str] = {}
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address) await self.async_set_unique_id(discovery_info.address)

View file

@ -20,13 +20,16 @@ from homeassistant.components import zeroconf
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.data_entry_flow import AbortFlow, FlowResult
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.service_info import bluetooth
from .connection import HKDevice from .connection import HKDevice
from .const import DOMAIN, KNOWN_DEVICES from .const import DOMAIN, KNOWN_DEVICES
from .storage import async_get_entity_storage from .storage import async_get_entity_storage
from .utils import async_get_controller from .utils import async_get_controller
if TYPE_CHECKING:
from homeassistant.components import bluetooth
HOMEKIT_DIR = ".homekit" HOMEKIT_DIR = ".homekit"
HOMEKIT_BRIDGE_DOMAIN = "homekit" HOMEKIT_BRIDGE_DOMAIN = "homekit"
@ -359,7 +362,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self._async_step_pair_show_form() return self._async_step_pair_show_form()
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: bluetooth.BluetoothServiceInfo self, discovery_info: bluetooth.BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
if not aiohomekit_const.BLE_TRANSPORT_SUPPORTED: if not aiohomekit_const.BLE_TRANSPORT_SUPPORTED:

View file

@ -32,7 +32,7 @@ async def async_get_controller(hass: HomeAssistant) -> Controller:
controller = Controller( controller = Controller(
async_zeroconf_instance=async_zeroconf_instance, async_zeroconf_instance=async_zeroconf_instance,
bleak_scanner_instance=bleak_scanner_instance, bleak_scanner_instance=bleak_scanner_instance, # type: ignore[arg-type]
) )
hass.data[CONTROLLER] = controller hass.data[CONTROLLER] = controller

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth.passive_update_processor import ( from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothProcessorCoordinator, PassiveBluetoothProcessorCoordinator,
) )
@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = hass.data.setdefault(DOMAIN, {})[ coordinator = hass.data.setdefault(DOMAIN, {})[
entry.entry_id entry.entry_id
] = PassiveBluetoothProcessorCoordinator( ] = PassiveBluetoothProcessorCoordinator(
hass, hass, _LOGGER, address=address, mode=BluetoothScanningMode.ACTIVE
_LOGGER,
address=address,
) )
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload( entry.async_on_unload(

View file

@ -7,7 +7,7 @@ from inkbird_ble import INKBIRDBluetoothDeviceData as DeviceData
import voluptuous as vol import voluptuous as vol
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfo, BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
@ -24,12 +24,12 @@ class INKBIRDConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self._discovery_info: BluetoothServiceInfo | None = None self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_device: DeviceData | None = None self._discovered_device: DeviceData | None = None
self._discovered_devices: dict[str, str] = {} self._discovered_devices: dict[str, str] = {}
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address) await self.async_set_unique_id(discovery_info.address)

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth.passive_update_processor import ( from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothProcessorCoordinator, PassiveBluetoothProcessorCoordinator,
) )
@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = hass.data.setdefault(DOMAIN, {})[ coordinator = hass.data.setdefault(DOMAIN, {})[
entry.entry_id entry.entry_id
] = PassiveBluetoothProcessorCoordinator( ] = PassiveBluetoothProcessorCoordinator(
hass, hass, _LOGGER, address=address, mode=BluetoothScanningMode.PASSIVE
_LOGGER,
address=address,
) )
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload( entry.async_on_unload(

View file

@ -7,7 +7,7 @@ from moat_ble import MoatBluetoothDeviceData as DeviceData
import voluptuous as vol import voluptuous as vol
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfo, BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
@ -24,12 +24,12 @@ class MoatConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self._discovery_info: BluetoothServiceInfo | None = None self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_device: DeviceData | None = None self._discovered_device: DeviceData | None = None
self._discovered_devices: dict[str, str] = {} self._discovered_devices: dict[str, str] = {}
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address) await self.async_set_unique_id(discovery_info.address)

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth.passive_update_processor import ( from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothProcessorCoordinator, PassiveBluetoothProcessorCoordinator,
) )
@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = hass.data.setdefault(DOMAIN, {})[ coordinator = hass.data.setdefault(DOMAIN, {})[
entry.entry_id entry.entry_id
] = PassiveBluetoothProcessorCoordinator( ] = PassiveBluetoothProcessorCoordinator(
hass, hass, _LOGGER, address=address, mode=BluetoothScanningMode.PASSIVE
_LOGGER,
address=address,
) )
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload( entry.async_on_unload(

View file

@ -7,7 +7,7 @@ from sensorpush_ble import SensorPushBluetoothDeviceData as DeviceData
import voluptuous as vol import voluptuous as vol
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfo, BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
@ -24,12 +24,12 @@ class SensorPushConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self._discovery_info: BluetoothServiceInfo | None = None self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_device: DeviceData | None = None self._discovered_device: DeviceData | None = None
self._discovered_devices: dict[str, str] = {} self._discovered_devices: dict[str, str] = {}
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address) await self.async_set_unique_id(discovery_info.address)

View file

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any, cast from typing import Any
from switchbot import SwitchBotAdvertisement, parse_advertisement_data from switchbot import SwitchBotAdvertisement, parse_advertisement_data
import voluptuous as vol import voluptuous as vol
@ -15,7 +15,6 @@ from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_SENSOR_TYPE from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_SENSOR_TYPE
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from .const import CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT, DOMAIN, SUPPORTED_MODEL_TYPES from .const import CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT, DOMAIN, SUPPORTED_MODEL_TYPES
@ -46,15 +45,14 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovered_advs: dict[str, SwitchBotAdvertisement] = {} self._discovered_advs: dict[str, SwitchBotAdvertisement] = {}
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
_LOGGER.debug("Discovered bluetooth device: %s", discovery_info) _LOGGER.debug("Discovered bluetooth device: %s", discovery_info)
await self.async_set_unique_id(format_unique_id(discovery_info.address)) await self.async_set_unique_id(format_unique_id(discovery_info.address))
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
discovery_info_bleak = cast(BluetoothServiceInfoBleak, discovery_info)
parsed = parse_advertisement_data( parsed = parse_advertisement_data(
discovery_info_bleak.device, discovery_info_bleak.advertisement discovery_info.device, discovery_info.advertisement
) )
if not parsed or parsed.data.get("modelName") not in SUPPORTED_MODEL_TYPES: if not parsed or parsed.data.get("modelName") not in SUPPORTED_MODEL_TYPES:
return self.async_abort(reason="not_supported") return self.async_abort(reason="not_supported")

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any
import switchbot import switchbot
@ -39,7 +39,9 @@ class SwitchbotDataUpdateCoordinator(PassiveBluetoothDataUpdateCoordinator):
device: switchbot.SwitchbotDevice, device: switchbot.SwitchbotDevice,
) -> None: ) -> None:
"""Initialize global switchbot data updater.""" """Initialize global switchbot data updater."""
super().__init__(hass, logger, ble_device.address) super().__init__(
hass, logger, ble_device.address, bluetooth.BluetoothScanningMode.ACTIVE
)
self.ble_device = ble_device self.ble_device = ble_device
self.device = device self.device = device
self.data: dict[str, Any] = {} self.data: dict[str, Any] = {}
@ -48,14 +50,13 @@ class SwitchbotDataUpdateCoordinator(PassiveBluetoothDataUpdateCoordinator):
@callback @callback
def _async_handle_bluetooth_event( def _async_handle_bluetooth_event(
self, self,
service_info: bluetooth.BluetoothServiceInfo, service_info: bluetooth.BluetoothServiceInfoBleak,
change: bluetooth.BluetoothChange, change: bluetooth.BluetoothChange,
) -> None: ) -> None:
"""Handle a Bluetooth event.""" """Handle a Bluetooth event."""
super()._async_handle_bluetooth_event(service_info, change) super()._async_handle_bluetooth_event(service_info, change)
discovery_info_bleak = cast(bluetooth.BluetoothServiceInfoBleak, service_info)
if adv := switchbot.parse_advertisement_data( if adv := switchbot.parse_advertisement_data(
discovery_info_bleak.device, discovery_info_bleak.advertisement service_info.device, service_info.advertisement
): ):
self.data = flatten_sensors_data(adv.data) self.data = flatten_sensors_data(adv.data)
if "modelName" in self.data: if "modelName" in self.data:

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth.passive_update_processor import ( from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothProcessorCoordinator, PassiveBluetoothProcessorCoordinator,
) )
@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = hass.data.setdefault(DOMAIN, {})[ coordinator = hass.data.setdefault(DOMAIN, {})[
entry.entry_id entry.entry_id
] = PassiveBluetoothProcessorCoordinator( ] = PassiveBluetoothProcessorCoordinator(
hass, hass, _LOGGER, address=address, mode=BluetoothScanningMode.PASSIVE
_LOGGER,
address=address,
) )
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload( entry.async_on_unload(

View file

@ -11,7 +11,8 @@ from xiaomi_ble.parser import EncryptionScheme
from homeassistant.components import onboarding from homeassistant.components import onboarding
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfo, BluetoothScanningMode,
BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
async_process_advertisements, async_process_advertisements,
) )
@ -30,11 +31,11 @@ class Discovery:
"""A discovered bluetooth device.""" """A discovered bluetooth device."""
title: str title: str
discovery_info: BluetoothServiceInfo discovery_info: BluetoothServiceInfoBleak
device: DeviceData device: DeviceData
def _title(discovery_info: BluetoothServiceInfo, device: DeviceData) -> str: def _title(discovery_info: BluetoothServiceInfoBleak, device: DeviceData) -> str:
return device.title or device.get_device_name() or discovery_info.name return device.title or device.get_device_name() or discovery_info.name
@ -45,18 +46,20 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self._discovery_info: BluetoothServiceInfo | None = None self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_device: DeviceData | None = None self._discovered_device: DeviceData | None = None
self._discovered_devices: dict[str, Discovery] = {} self._discovered_devices: dict[str, Discovery] = {}
async def _async_wait_for_full_advertisement( async def _async_wait_for_full_advertisement(
self, discovery_info: BluetoothServiceInfo, device: DeviceData self, discovery_info: BluetoothServiceInfoBleak, device: DeviceData
) -> BluetoothServiceInfo: ) -> BluetoothServiceInfoBleak:
"""Sometimes first advertisement we receive is blank or incomplete. Wait until we get a useful one.""" """Sometimes first advertisement we receive is blank or incomplete. Wait until we get a useful one."""
if not device.pending: if not device.pending:
return discovery_info return discovery_info
def _process_more_advertisements(service_info: BluetoothServiceInfo) -> bool: def _process_more_advertisements(
service_info: BluetoothServiceInfoBleak,
) -> bool:
device.update(service_info) device.update(service_info)
return not device.pending return not device.pending
@ -64,11 +67,12 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN):
self.hass, self.hass,
_process_more_advertisements, _process_more_advertisements,
{"address": discovery_info.address}, {"address": discovery_info.address},
BluetoothScanningMode.ACTIVE,
ADDITIONAL_DISCOVERY_TIMEOUT, ADDITIONAL_DISCOVERY_TIMEOUT,
) )
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address) await self.async_set_unique_id(discovery_info.address)

View file

@ -27,12 +27,12 @@ from .util import uuid as uuid_util
from .util.decorator import Registry from .util.decorator import Registry
if TYPE_CHECKING: if TYPE_CHECKING:
from .components.bluetooth import BluetoothServiceInfoBleak
from .components.dhcp import DhcpServiceInfo from .components.dhcp import DhcpServiceInfo
from .components.hassio import HassioServiceInfo from .components.hassio import HassioServiceInfo
from .components.ssdp import SsdpServiceInfo from .components.ssdp import SsdpServiceInfo
from .components.usb import UsbServiceInfo from .components.usb import UsbServiceInfo
from .components.zeroconf import ZeroconfServiceInfo from .components.zeroconf import ZeroconfServiceInfo
from .helpers.service_info.bluetooth import BluetoothServiceInfo
from .helpers.service_info.mqtt import MqttServiceInfo from .helpers.service_info.mqtt import MqttServiceInfo
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -1485,7 +1485,7 @@ class ConfigFlow(data_entry_flow.FlowHandler):
) )
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> data_entry_flow.FlowResult: ) -> data_entry_flow.FlowResult:
"""Handle a flow initialized by Bluetooth discovery.""" """Handle a flow initialized by Bluetooth discovery."""
return await self._async_step_discovery_without_unique_id() return await self._async_step_discovery_without_unique_id()

View file

@ -15,11 +15,11 @@ from .typing import DiscoveryInfoType
if TYPE_CHECKING: if TYPE_CHECKING:
import asyncio import asyncio
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.dhcp import DhcpServiceInfo from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.components.ssdp import SsdpServiceInfo from homeassistant.components.ssdp import SsdpServiceInfo
from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.components.zeroconf import ZeroconfServiceInfo
from .service_info.bluetooth import BluetoothServiceInfo
from .service_info.mqtt import MqttServiceInfo from .service_info.mqtt import MqttServiceInfo
_R = TypeVar("_R", bound="Awaitable[bool] | bool") _R = TypeVar("_R", bound="Awaitable[bool] | bool")
@ -97,7 +97,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]):
return await self.async_step_confirm() return await self.async_step_confirm()
async def async_step_bluetooth( async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfo self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle a flow initialized by bluetooth discovery.""" """Handle a flow initialized by bluetooth discovery."""
if self._async_in_progress() or self._async_current_entries(): if self._async_in_progress() or self._async_current_entries():

View file

@ -1,4 +1,5 @@
"""The bluetooth integration service info.""" """The bluetooth integration service info."""
from home_assistant_bluetooth import BluetoothServiceInfo from home_assistant_bluetooth import BluetoothServiceInfo
__all__ = ["BluetoothServiceInfo"] __all__ = ["BluetoothServiceInfo"]

View file

@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1
attrs==21.2.0 attrs==21.2.0
awesomeversion==22.6.0 awesomeversion==22.6.0
bcrypt==3.1.7 bcrypt==3.1.7
bleak==0.14.3 bleak==0.15.0
bluetooth-adapters==0.1.2 bluetooth-adapters==0.1.2
certifi>=2021.5.30 certifi>=2021.5.30
ciso8601==2.2.0 ciso8601==2.2.0

View file

@ -405,7 +405,7 @@ bimmer_connected==0.10.1
bizkaibus==0.1.1 bizkaibus==0.1.1
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak==0.14.3 bleak==0.15.0
# homeassistant.components.blebox # homeassistant.components.blebox
blebox_uniapi==2.0.2 blebox_uniapi==2.0.2

View file

@ -326,7 +326,7 @@ bellows==0.31.2
bimmer_connected==0.10.1 bimmer_connected==0.10.1
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak==0.14.3 bleak==0.15.0
# homeassistant.components.blebox # homeassistant.components.blebox
blebox_uniapi==2.0.2 blebox_uniapi==2.0.2

View file

@ -12,6 +12,7 @@ from homeassistant.components.bluetooth import (
SOURCE_LOCAL, SOURCE_LOCAL,
UNAVAILABLE_TRACK_SECONDS, UNAVAILABLE_TRACK_SECONDS,
BluetoothChange, BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfo, BluetoothServiceInfo,
async_process_advertisements, async_process_advertisements,
async_track_unavailable, async_track_unavailable,
@ -675,6 +676,7 @@ async def test_register_callbacks(hass, mock_bleak_scanner_start, enable_bluetoo
hass, hass,
_fake_subscriber, _fake_subscriber,
{"service_uuids": {"cba20d00-224d-11e6-9fb8-0002a5d5c51b"}}, {"service_uuids": {"cba20d00-224d-11e6-9fb8-0002a5d5c51b"}},
BluetoothScanningMode.ACTIVE,
) )
assert len(mock_bleak_scanner_start.mock_calls) == 1 assert len(mock_bleak_scanner_start.mock_calls) == 1
@ -760,6 +762,7 @@ async def test_register_callback_by_address(
hass, hass,
_fake_subscriber, _fake_subscriber,
{"address": "44:44:33:11:23:45"}, {"address": "44:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE,
) )
assert len(mock_bleak_scanner_start.mock_calls) == 1 assert len(mock_bleak_scanner_start.mock_calls) == 1
@ -799,6 +802,7 @@ async def test_register_callback_by_address(
hass, hass,
_fake_subscriber, _fake_subscriber,
{"address": "44:44:33:11:23:45"}, {"address": "44:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE,
) )
cancel() cancel()
@ -808,6 +812,7 @@ async def test_register_callback_by_address(
hass, hass,
_fake_subscriber, _fake_subscriber,
{"address": "44:44:33:11:23:45"}, {"address": "44:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE,
) )
cancel() cancel()
@ -832,7 +837,11 @@ async def test_process_advertisements_bail_on_good_advertisement(
handle = hass.async_create_task( handle = hass.async_create_task(
async_process_advertisements( async_process_advertisements(
hass, _callback, {"address": "aa:44:33:11:23:45"}, 5 hass,
_callback,
{"address": "aa:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE,
5,
) )
) )
@ -873,7 +882,11 @@ async def test_process_advertisements_ignore_bad_advertisement(
handle = hass.async_create_task( handle = hass.async_create_task(
async_process_advertisements( async_process_advertisements(
hass, _callback, {"address": "aa:44:33:11:23:45"}, 5 hass,
_callback,
{"address": "aa:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE,
5,
) )
) )
@ -903,7 +916,9 @@ async def test_process_advertisements_timeout(
return False return False
with pytest.raises(asyncio.TimeoutError): with pytest.raises(asyncio.TimeoutError):
await async_process_advertisements(hass, _callback, {}, 0) await async_process_advertisements(
hass, _callback, {}, BluetoothScanningMode.ACTIVE, 0
)
async def test_wrapped_instance_with_filter( async def test_wrapped_instance_with_filter(

View file

@ -10,6 +10,7 @@ from homeassistant.components.bluetooth import (
DOMAIN, DOMAIN,
UNAVAILABLE_TRACK_SECONDS, UNAVAILABLE_TRACK_SECONDS,
BluetoothChange, BluetoothChange,
BluetoothScanningMode,
) )
from homeassistant.components.bluetooth.passive_update_coordinator import ( from homeassistant.components.bluetooth.passive_update_coordinator import (
PassiveBluetoothCoordinatorEntity, PassiveBluetoothCoordinatorEntity,
@ -42,9 +43,9 @@ GENERIC_BLUETOOTH_SERVICE_INFO = BluetoothServiceInfo(
class MyCoordinator(PassiveBluetoothDataUpdateCoordinator): class MyCoordinator(PassiveBluetoothDataUpdateCoordinator):
"""An example coordinator that subclasses PassiveBluetoothDataUpdateCoordinator.""" """An example coordinator that subclasses PassiveBluetoothDataUpdateCoordinator."""
def __init__(self, hass, logger, device_id) -> None: def __init__(self, hass, logger, device_id, mode) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
super().__init__(hass, logger, device_id) super().__init__(hass, logger, device_id, mode)
self.data: dict[str, Any] = {} self.data: dict[str, Any] = {}
def _async_handle_bluetooth_event( def _async_handle_bluetooth_event(
@ -60,11 +61,13 @@ class MyCoordinator(PassiveBluetoothDataUpdateCoordinator):
async def test_basic_usage(hass, mock_bleak_scanner_start): async def test_basic_usage(hass, mock_bleak_scanner_start):
"""Test basic usage of the PassiveBluetoothDataUpdateCoordinator.""" """Test basic usage of the PassiveBluetoothDataUpdateCoordinator."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") coordinator = MyCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
)
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -100,11 +103,13 @@ async def test_context_compatiblity_with_data_update_coordinator(
): ):
"""Test contexts can be passed for compatibility with DataUpdateCoordinator.""" """Test contexts can be passed for compatibility with DataUpdateCoordinator."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") coordinator = MyCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
)
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -149,11 +154,13 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable(
): ):
await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") coordinator = MyCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.PASSIVE
)
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -208,13 +215,15 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable(
async def test_passive_bluetooth_coordinator_entity(hass, mock_bleak_scanner_start): async def test_passive_bluetooth_coordinator_entity(hass, mock_bleak_scanner_start):
"""Test integration of PassiveBluetoothDataUpdateCoordinator with PassiveBluetoothCoordinatorEntity.""" """Test integration of PassiveBluetoothDataUpdateCoordinator with PassiveBluetoothCoordinatorEntity."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") coordinator = MyCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
)
entity = PassiveBluetoothCoordinatorEntity(coordinator) entity = PassiveBluetoothCoordinatorEntity(coordinator)
assert entity.available is False assert entity.available is False
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None

View file

@ -16,6 +16,7 @@ from homeassistant.components.bluetooth import (
DOMAIN, DOMAIN,
UNAVAILABLE_TRACK_SECONDS, UNAVAILABLE_TRACK_SECONDS,
BluetoothChange, BluetoothChange,
BluetoothScanningMode,
) )
from homeassistant.components.bluetooth.passive_update_processor import ( from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothDataProcessor, PassiveBluetoothDataProcessor,
@ -90,12 +91,12 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -192,12 +193,12 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -277,12 +278,12 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -336,12 +337,12 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -389,12 +390,12 @@ async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start):
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -731,12 +732,12 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start):
return GOVEE_B5178_REMOTE_PASSIVE_BLUETOOTH_DATA_UPDATE return GOVEE_B5178_REMOTE_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -841,12 +842,12 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner
return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -905,12 +906,12 @@ async def test_passive_bluetooth_entity_with_entity_platform(
return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -992,12 +993,12 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st
await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
coordinator = PassiveBluetoothProcessorCoordinator( coordinator = PassiveBluetoothProcessorCoordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff" hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None

View file

@ -5,8 +5,9 @@ from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from bleak import BleakError from bleak import BleakError
from bleak.backends.scanner import AdvertisementData, BLEDevice
from homeassistant.components.bluetooth import BluetoothServiceInfo from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.bluetooth_le_tracker import device_tracker from homeassistant.components.bluetooth_le_tracker import device_tracker
from homeassistant.components.bluetooth_le_tracker.device_tracker import ( from homeassistant.components.bluetooth_le_tracker.device_tracker import (
CONF_TRACK_BATTERY, CONF_TRACK_BATTERY,
@ -79,7 +80,7 @@ async def test_preserve_new_tracked_device_name(
device_tracker, "MIN_SEEN_NEW", 3 device_tracker, "MIN_SEEN_NEW", 3
): ):
device = BluetoothServiceInfo( device = BluetoothServiceInfoBleak(
name=name, name=name,
address=address, address=address,
rssi=-19, rssi=-19,
@ -87,6 +88,8 @@ async def test_preserve_new_tracked_device_name(
service_data={}, service_data={},
service_uuids=[], service_uuids=[],
source="local", source="local",
device=BLEDevice(address, None),
advertisement=AdvertisementData(local_name="empty"),
) )
# Return with name when seen first time # Return with name when seen first time
mock_async_discovered_service_info.return_value = [device] mock_async_discovered_service_info.return_value = [device]
@ -100,7 +103,7 @@ async def test_preserve_new_tracked_device_name(
assert result assert result
# Seen once here; return without name when seen subsequent times # Seen once here; return without name when seen subsequent times
device = BluetoothServiceInfo( device = BluetoothServiceInfoBleak(
name=None, name=None,
address=address, address=address,
rssi=-19, rssi=-19,
@ -108,6 +111,8 @@ async def test_preserve_new_tracked_device_name(
service_data={}, service_data={},
service_uuids=[], service_uuids=[],
source="local", source="local",
device=BLEDevice(address, None),
advertisement=AdvertisementData(local_name="empty"),
) )
# Return with name when seen first time # Return with name when seen first time
mock_async_discovered_service_info.return_value = [device] mock_async_discovered_service_info.return_value = [device]
@ -140,7 +145,7 @@ async def test_tracking_battery_times_out(
device_tracker, "MIN_SEEN_NEW", 3 device_tracker, "MIN_SEEN_NEW", 3
): ):
device = BluetoothServiceInfo( device = BluetoothServiceInfoBleak(
name=name, name=name,
address=address, address=address,
rssi=-19, rssi=-19,
@ -148,6 +153,8 @@ async def test_tracking_battery_times_out(
service_data={}, service_data={},
service_uuids=[], service_uuids=[],
source="local", source="local",
device=BLEDevice(address, None),
advertisement=AdvertisementData(local_name="empty"),
) )
# Return with name when seen first time # Return with name when seen first time
mock_async_discovered_service_info.return_value = [device] mock_async_discovered_service_info.return_value = [device]
@ -202,7 +209,7 @@ async def test_tracking_battery_fails(hass, mock_bluetooth, mock_device_tracker_
device_tracker, "MIN_SEEN_NEW", 3 device_tracker, "MIN_SEEN_NEW", 3
): ):
device = BluetoothServiceInfo( device = BluetoothServiceInfoBleak(
name=name, name=name,
address=address, address=address,
rssi=-19, rssi=-19,
@ -210,6 +217,8 @@ async def test_tracking_battery_fails(hass, mock_bluetooth, mock_device_tracker_
service_data={}, service_data={},
service_uuids=[], service_uuids=[],
source="local", source="local",
device=BLEDevice(address, None),
advertisement=AdvertisementData(local_name="empty"),
) )
# Return with name when seen first time # Return with name when seen first time
mock_async_discovered_service_info.return_value = [device] mock_async_discovered_service_info.return_value = [device]
@ -266,7 +275,7 @@ async def test_tracking_battery_successful(
device_tracker, "MIN_SEEN_NEW", 3 device_tracker, "MIN_SEEN_NEW", 3
): ):
device = BluetoothServiceInfo( device = BluetoothServiceInfoBleak(
name=name, name=name,
address=address, address=address,
rssi=-19, rssi=-19,
@ -274,6 +283,8 @@ async def test_tracking_battery_successful(
service_data={}, service_data={},
service_uuids=[], service_uuids=[],
source="local", source="local",
device=BLEDevice(address, None),
advertisement=AdvertisementData(local_name="empty"),
) )
# Return with name when seen first time # Return with name when seen first time
mock_async_discovered_service_info.return_value = [device] mock_async_discovered_service_info.return_value = [device]

View file

@ -17,6 +17,12 @@ def fixture_scanner(hass):
class MockScanner(BaseBleakScanner): class MockScanner(BaseBleakScanner):
"""Mock Scanner.""" """Mock Scanner."""
def __init__(self, *args, **kwargs) -> None:
"""Initialize the scanner."""
super().__init__(
detection_callback=kwargs.pop("detection_callback"), service_uuids=[]
)
async def start(self): async def start(self):
"""Start scanning for devices.""" """Start scanning for devices."""
for device in devices: for device in devices:

View file

@ -22,7 +22,7 @@ async def test_sensors(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None

View file

@ -22,7 +22,7 @@ async def test_sensors(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None

View file

@ -22,7 +22,7 @@ async def test_sensors(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None

View file

@ -22,7 +22,7 @@ async def test_sensors(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None

View file

@ -59,7 +59,9 @@ async def test_async_step_bluetooth_valid_device_but_missing_payload(hass):
async def test_async_step_bluetooth_valid_device_but_missing_payload_then_full(hass): async def test_async_step_bluetooth_valid_device_but_missing_payload_then_full(hass):
"""Test discovering a valid device. Payload is too short, but later we get full one.""" """Test discovering a valid device. Payload is too short, but later we get full one."""
async def _async_process_advertisements(_hass, _callback, _matcher, _timeout): async def _async_process_advertisements(
_hass, _callback, _matcher, _mode, _timeout
):
service_info = make_advertisement( service_info = make_advertisement(
"A4:C1:38:56:53:84", "A4:C1:38:56:53:84",
b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e", b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e",
@ -378,7 +380,9 @@ async def test_async_step_user_short_payload_then_full(hass):
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
async def _async_process_advertisements(_hass, _callback, _matcher, _timeout): async def _async_process_advertisements(
_hass, _callback, _matcher, _mode, _timeout
):
service_info = make_advertisement( service_info = make_advertisement(
"A4:C1:38:56:53:84", "A4:C1:38:56:53:84",
b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e", b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e",

View file

@ -22,7 +22,7 @@ async def test_sensors(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -60,7 +60,7 @@ async def test_xiaomi_formaldeyhde(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -107,7 +107,7 @@ async def test_xiaomi_consumable(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -154,7 +154,7 @@ async def test_xiaomi_battery_voltage(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -208,7 +208,7 @@ async def test_xiaomi_HHCCJCY01(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None
@ -291,7 +291,7 @@ async def test_xiaomi_CGDK2(hass):
saved_callback = None saved_callback = None
def _async_register_callback(_hass, _callback, _matcher): def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback nonlocal saved_callback
saved_callback = _callback saved_callback = _callback
return lambda: None return lambda: None