Skip to content

Commit

Permalink
backends/corebluetooth/scanner: add use_bdaddr option
Browse files Browse the repository at this point in the history
This adds an CoreBluetooth=specific option `use_bdaddr` to BleakScanner.
When enabled, the BLEDevice objects will use the actual Bluetooth address
instead of the UUID identifier provided by the CoreBluetooth APIs.

This uses an undocumented IOBluetoothDevice method to convert the
CBPeripheral object to an IOBluetoothDevice object so could stop
working in future macOS releases.
  • Loading branch information
dlech committed Oct 7, 2022
1 parent fc8795c commit 3781384
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Added
* Added ``rssi`` attribute to ``AdvertisementData``.
* Added ``BleakScanner.discovered_devices_and_advertisement_data`` property.
* Added ``return_adv`` argument to ``BleakScanner.discover`` method.
* Added optional hack to use Bluetooth address instead of UUID on macOS.

Changed
-------
Expand Down
11 changes: 10 additions & 1 deletion bleak/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@

if TYPE_CHECKING:
from .backends.bluezdbus.scanner import BlueZScannerArgs
from .backends.corebluetooth.scanner import CBScannerArgs
from .backends.winrt.client import WinRTClientArgs


Expand Down Expand Up @@ -83,6 +84,8 @@ class BleakScanner:
:class:`BleakError` if set to ``"passive"`` on macOS.
bluez:
Dictionary of arguments specific to the BlueZ backend.
cb:
Dictionary of arguments specific to the CoreBluetooth backend.
backend:
Used to override the automatically selected backend (i.e. for a
custom backend).
Expand All @@ -105,6 +108,7 @@ def __init__(
scanning_mode: Literal["active", "passive"] = "active",
*,
bluez: BlueZScannerArgs = {},
cb: CBScannerArgs = {},
backend: Optional[Type[BaseBleakScanner]] = None,
**kwargs,
):
Expand All @@ -113,7 +117,12 @@ def __init__(
)

self._backend = PlatformBleakScanner(
detection_callback, service_uuids, scanning_mode, bluez=bluez, **kwargs
detection_callback,
service_uuids,
scanning_mode,
bluez=bluez,
cb=cb,
**kwargs,
)

async def __aenter__(self):
Expand Down
23 changes: 23 additions & 0 deletions bleak/backends/corebluetooth/io_bluetooth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# no upstream bindings https://github.com/ronaldoussoren/pyobjc/issues/381

import objc

io_bluetooth = objc.ObjCLazyModule(
"IOBluetooth",
frameworkIdentifier="com.apple.IOBluetooth",
frameworkPath=objc.pathForFramework(
"/System/Library/Frameworks/IOBluetooth.framework"
),
metadict=globals(),
)

locals().update(
{
a: getattr(io_bluetooth, a)
for a in dir(io_bluetooth)
if a.startswith("IOBluetooth")
}
)

del io_bluetooth
del objc
32 changes: 29 additions & 3 deletions bleak/backends/corebluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from typing import Any, Dict, List, Optional

if sys.version_info[:2] < (3, 8):
from typing_extensions import Literal
from typing_extensions import Literal, TypedDict
else:
from typing import Literal
from typing import Literal, TypedDict

import objc
from CoreBluetooth import CBPeripheral
Expand All @@ -14,11 +14,26 @@
from ...exc import BleakError
from ..scanner import AdvertisementData, AdvertisementDataCallback, BaseBleakScanner
from .CentralManagerDelegate import CentralManagerDelegate
from .io_bluetooth import IOBluetoothDevice
from .utils import cb_uuid_to_str

logger = logging.getLogger(__name__)


class CBScannerArgs(TypedDict, total=False):
"""
Platform-specific :class:`BleakScanner` args for the CoreBluetooth backend.
"""

use_bdaddr: bool
"""
If true, use Bluetooth address instead of UUID.
.. warning:: This uses an undocumented IOBluetooth API to get the Bluetooth
address and may break in the future macOS releases.
"""


class BleakScannerCoreBluetooth(BaseBleakScanner):
"""The native macOS Bleak BLE Scanner.
Expand Down Expand Up @@ -52,12 +67,16 @@ def __init__(
detection_callback: Optional[AdvertisementDataCallback],
service_uuids: Optional[List[str]],
scanning_mode: Literal["active", "passive"],
*,
cb: CBScannerArgs,
**kwargs
):
super(BleakScannerCoreBluetooth, self).__init__(
detection_callback, service_uuids
)

self._use_bdaddr = cb.get("use_bdaddr", False)

if scanning_mode == "passive":
raise BleakError("macOS does not support passive scanning")

Expand Down Expand Up @@ -112,8 +131,15 @@ def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None:
platform_data=(p, a, r),
)

if self._use_bdaddr:
# initWithCBPeripheral_ is undocumented but seems to do the trick
io_bluetooth_device = IOBluetoothDevice.alloc().initWithCBPeripheral_(p)
address = io_bluetooth_device.addressString().replace("-", ":").upper()
else:
address = p.identifier().UUIDString()

device = self.create_or_update_device(
p.identifier().UUIDString(),
address,
p.name(),
(p, self._manager.central_manager.delegate()),
advertisement_data,
Expand Down

0 comments on commit 3781384

Please sign in to comment.