hbldh / bleak

A cross platform Bluetooth Low Energy Client for Python using asyncio
MIT License
1.72k stars 289 forks source link

WinRT: pairing crashes if accepting too soon #700

Open marianacalzon opened 2 years ago

marianacalzon commented 2 years ago

Description

Trying to pair and connect to the device, after accepting to pair on the OS pop-up, Bleak fails. The device I am trying to connect to requires pairing. If I accept pairing as soon as it pops up, the script fails. If I wait until after services discovery has finished, and only then accept the pairing, it will work OK.

What I Did

With this code:

#  bleak_minimal_example.py
import asyncio
from bleak import BleakScanner
from bleak import BleakClient

address = "AA:AA:AA:AA:AA:AA"
# Characteristics UUIDs
NUM_SENSORS_UUID = "b9e634a8-57ef-11e9-8647-d663bd873d93"

# ===============================================================
# Connect and read
# ===============================================================
async def run_connection(address, debug=False):

    async with BleakClient(address) as client:

        print("  >>  Please accept pairing")
        await asyncio.sleep(5.0)

        print("  >>  Reading...")
        num_sensors = await client.read_gatt_char(NUM_SENSORS_UUID, use_cached = True)
        print(" Number of sensors: {0}".format(num_sensors.hex()))

# ===============================================================
#    MAIN
# ===============================================================
if __name__ == "__main__":

    loop = asyncio.get_event_loop()
    loop.run_until_complete(run_connection(address))

    print ('>> Goodbye!')
    exit(0)

Use case 1: fails Accepting the pairing before it finished discovering services - fails

  1. the script calls BleakClient.connect()
  2. connects to the device and Windows pop-up requests to pair
  3. Bleak.connect() is still doing the discovery (and it takes long, because the device has many services with many chars.)
  4. if I accept the pairing before it finished discovering it fails 

The traceback of the error:

Traceback (most recent call last):
  File "bleak_minimal_example.py", line 86, in <module>
    loop.run_until_complete(run_connection(device_address))
  File "C:\Program Files\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "bleak_minimal_example.py", line 50, in run_connection
    async with BleakClient(address) as client:
  File "C:\Program Files\Python38\lib\site-packages\bleak\backends\client.py", line 61, in __aenter__
    await self.connect()
  File "C:\Program Files\Python38\lib\site-packages\bleak\backends\winrt\client.py", line 258, in connect
    await self.get_services()
  File "C:\Program Files\Python38\lib\site-packages\bleak\backends\winrt\client.py", line 446, in get_services
    await service.get_characteristics_async(
OSError: [WinError -2147418113] Catastrophic failure

Use case 2: succeeds Waiting to accept pairing, then succeeds

  1. the script calls BleakClient.connect()
  2. connects to the device and Windows pop-up requests to pair
  3. Bleak.connect() is still doing the discovery (and it takes long, because the device has many services with many chars.)
  4. after connect returned I print ('Please accept pairing')
  5. I accept the pairing
  6. the script reads + writes... all good

Alternative use of the API:

With the script modified to call pair() before connect():

#  bleak_minimal_pair_example.py
import asyncio
from bleak import BleakScanner
from bleak import BleakClient

address = "AA:AA:AA:AA:AA:AA"
# Characteristics UUIDs
NUM_SENSORS_UUID = "b9e634a8-57ef-11e9-8647-d663bd873d93"

# ===============================================================
# Connect and read
# ===============================================================
async def run_connection(address, debug=False):

    client = BleakClient(address)

    if await client.pair():
        pritnt("paired")

    await client.connect()

    print("  >>  Reading...")
    num_sensors = await client.read_gatt_char(NUM_SENSORS_UUID, use_cached = True)
    print(" Number of sensors: {0}".format(num_sensors.hex()))

# ===============================================================
#    MAIN
# ===============================================================
if __name__ == "__main__":

    loop = asyncio.get_event_loop()
    loop.run_until_complete(run_connection(address))

    print ('>> Goodbye!')
    exit(0)

The execution fails as follows:

The traceback of the error:

python bleak_minimal_pair_example.py
Traceback (most recent call last):
  File "bleak_minimal_pair_example.py", line 47, in <module>
    loop.run_until_complete(run_connection(address))
  File "C:\Program Files\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "bleak_minimal_pair_example.py", line 31, in run_connection
    if await client.pair():
  File "C:\Program Files\Python38\lib\site-packages\bleak\backends\winrt\client.py", line 336, in pair
    self._requester.device_information.pairing.can_pair
AttributeError: 'NoneType' object has no attribute 'device_information'
dlech commented 2 years ago

OSError: [WinError -2147418113] Catastrophic failure

Unfortunately, this means the error comes from the OS and there probably isn't a way to fix it in Bleak.

There is #640 that would allow implementing the pairing in Python instead of using the OS dialog, but it needs some more work.

Logging Bluetooth packets might also offer more insight in to what Windows is actually doing.

bojanpotocnik commented 1 year ago

@marianacalzon can you perhaps try again using test pairing branch

pip install --force-reinstall git+https://github.com/bojanpotocnik/bleak@pairing_no_service_discovery

and using

    client = BleakClient(address, skip_service_discovery=True)

as described here https://github.com/bojanpotocnik/bleak/blob/cfd09baddfbd27538e9a1cdd2ef3c7b8f2af229c/examples/pairing_agent.py#L82-L88 (from https://github.com/hbldh/bleak/pull/1133)? Based on my tests, this should solve your issue.