Skip to content

Commit d49dee6

Browse files
committed
bluez: Support registering pairing agent before initiating connection
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.
1 parent 208ce44 commit d49dee6

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

bleak/backends/bluezdbus/client.py

+26-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import os
99
import sys
1010
import warnings
11-
from typing import Callable, Dict, Optional, Union, cast
11+
from typing import Callable, Dict, Optional, Union, cast, AsyncContextManager
1212
from uuid import UUID
1313

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

103+
# pairing agent used in manual pairing process, together with pairing_callbacks
104+
self._pairing_agent: Optional[AsyncContextManager] = None
105+
103106
def close(self):
104107
self._bus.disconnect()
105108

109+
async def __aenter__(self) -> "BaseBleakClient":
110+
if self._pairing_callbacks:
111+
if not self._bus.connected:
112+
await self._bus.connect()
113+
114+
self._pairing_agent = bluez_agent(self._bus, self._pairing_callbacks)
115+
await self._pairing_agent.__aenter__()
116+
117+
return await super().__aenter__()
118+
119+
async def __aexit__(self, exc_type, exc_val, exc_tb):
120+
if self._pairing_agent:
121+
await self._pairing_agent.__aexit__(exc_type, exc_val, exc_tb)
122+
self._pairing_agent = None
123+
124+
return await super().__aexit__(exc_type, exc_val, exc_tb)
125+
106126
# Connectivity methods
107127

108128
async def connect(self, dangerous_use_bleak_cache: bool = False, **kwargs) -> bool:
@@ -354,11 +374,14 @@ async def pair(
354374
"""
355375
Pair with the peripheral.
356376
"""
357-
if callbacks and self._pairing_callbacks:
377+
# Check _pairing_agent instead of _pairing_callbacks, because even if _pairing_callbacks
378+
# were provided, this class might not be used as a context manager and therefore pairing
379+
# agent was not yet registered.
380+
if callbacks and self._pairing_agent:
358381
warnings.warn(
359382
"Ignoring callbacks parameters because pairing_callbacks were provided"
360383
)
361-
callbacks = self._pairing_callbacks
384+
callbacks = None
362385

363386
# See if it is already paired.
364387
reply = await self._bus.call(

examples/pairing_agent.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import argparse
22
import asyncio
3+
import contextlib
34
import sys
5+
from typing import Tuple
46

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

6163

64+
# If the peripheral device sends Slave Security Request immediately upon establishing
65+
# connection, pairing agent must be registered before initiating connection - in such
66+
# cases the central device sends the pairing request (as a response to Slave Security
67+
# Request) even before starting/finishing service discovery - before connect() method
68+
# even returns, so registering the pairing agent in pair() method is too late and the
69+
# pairing process usually fails with "Pairing not supported" error.
70+
@contextlib.asynccontextmanager
71+
async def client_setup(
72+
device: BLEDevice, support_security_req: bool
73+
) -> Tuple[BLEDevice, AgentCallbacks]:
74+
if support_security_req:
75+
76+
async with AgentCallbacks() as callbacks, BleakClient(
77+
device, pairing_callbacks=callbacks
78+
) as client:
79+
# Do not provide callbacks to pair() method if already provided to constructor
80+
yield client, None
81+
82+
else:
83+
84+
async with BleakClient(device) as client, AgentCallbacks() as callbacks:
85+
yield client, callbacks
86+
87+
6288
async def main(addr: str, unpair: bool) -> None:
6389
if unpair:
6490
print("unpairing...")
@@ -78,7 +104,7 @@ async def main(addr: str, unpair: bool) -> None:
78104

79105
print("pairing...")
80106

81-
async with BleakClient(device) as client, AgentCallbacks() as callbacks:
107+
async with client_setup(device, support_security_req=True) as (client, callbacks):
82108
try:
83109
await client.pair(callbacks)
84110
print("pairing successful")

0 commit comments

Comments
 (0)