Skip to content

Commit

Permalink
bluez: Support registering pairing agent before initiating connection
Browse files Browse the repository at this point in the history
Register pairing agent before initiating connection, to support
peripheral devices which send Slave Security Request immediately upon
establishing connection. In such cases the central device sends pairing
request even before starting/finishing service discovery - before
connect() method even returns, so registering the pairing agent in
pair() method is too late.
  • Loading branch information
bojanpotocnik committed Nov 22, 2022
1 parent 208ce44 commit d49dee6
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 4 deletions.
29 changes: 26 additions & 3 deletions bleak/backends/bluezdbus/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import os
import sys
import warnings
from typing import Callable, Dict, Optional, Union, cast
from typing import Callable, Dict, Optional, Union, cast, AsyncContextManager
from uuid import UUID

if sys.version_info < (3, 11):
Expand Down Expand Up @@ -100,9 +100,29 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs):
# used to override mtu_size property
self._mtu_size: Optional[int] = None

# pairing agent used in manual pairing process, together with pairing_callbacks
self._pairing_agent: Optional[AsyncContextManager] = None

def close(self):
self._bus.disconnect()

async def __aenter__(self) -> "BaseBleakClient":
if self._pairing_callbacks:
if not self._bus.connected:
await self._bus.connect()

self._pairing_agent = bluez_agent(self._bus, self._pairing_callbacks)
await self._pairing_agent.__aenter__()

return await super().__aenter__()

async def __aexit__(self, exc_type, exc_val, exc_tb):
if self._pairing_agent:
await self._pairing_agent.__aexit__(exc_type, exc_val, exc_tb)
self._pairing_agent = None

return await super().__aexit__(exc_type, exc_val, exc_tb)

# Connectivity methods

async def connect(self, dangerous_use_bleak_cache: bool = False, **kwargs) -> bool:
Expand Down Expand Up @@ -354,11 +374,14 @@ async def pair(
"""
Pair with the peripheral.
"""
if callbacks and self._pairing_callbacks:
# Check _pairing_agent instead of _pairing_callbacks, because even if _pairing_callbacks
# were provided, this class might not be used as a context manager and therefore pairing
# agent was not yet registered.
if callbacks and self._pairing_agent:
warnings.warn(
"Ignoring callbacks parameters because pairing_callbacks were provided"
)
callbacks = self._pairing_callbacks
callbacks = None

# See if it is already paired.
reply = await self._bus.call(
Expand Down
28 changes: 27 additions & 1 deletion examples/pairing_agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import argparse
import asyncio
import contextlib
import sys
from typing import Tuple

from bleak import BleakScanner, BleakClient, BaseBleakAgentCallbacks
from bleak.backends.device import BLEDevice
Expand Down Expand Up @@ -59,6 +61,30 @@ async def request_pin(self, device: BLEDevice) -> str:
return response


# If the peripheral device sends Slave Security Request immediately upon establishing
# connection, pairing agent must be registered before initiating connection - in such
# cases the central device sends the pairing request (as a response to Slave Security
# Request) even before starting/finishing service discovery - before connect() method
# even returns, so registering the pairing agent in pair() method is too late and the
# pairing process usually fails with "Pairing not supported" error.
@contextlib.asynccontextmanager
async def client_setup(
device: BLEDevice, support_security_req: bool
) -> Tuple[BLEDevice, AgentCallbacks]:
if support_security_req:

async with AgentCallbacks() as callbacks, BleakClient(
device, pairing_callbacks=callbacks
) as client:
# Do not provide callbacks to pair() method if already provided to constructor
yield client, None

else:

async with BleakClient(device) as client, AgentCallbacks() as callbacks:
yield client, callbacks


async def main(addr: str, unpair: bool) -> None:
if unpair:
print("unpairing...")
Expand All @@ -78,7 +104,7 @@ async def main(addr: str, unpair: bool) -> None:

print("pairing...")

async with BleakClient(device) as client, AgentCallbacks() as callbacks:
async with client_setup(device, support_security_req=True) as (client, callbacks):
try:
await client.pair(callbacks)
print("pairing successful")
Expand Down

0 comments on commit d49dee6

Please sign in to comment.