hbldh / bleak

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

backends/scanner: add handle_early_stop hooks #1146

Open dlech opened 1 year ago

dlech commented 1 year ago

This is inspired by https://github.com/hbldh/bleak/pull/1140#issuecomment-1327781184. Quite a long time ago, I noticed that we weren't really handling scanner stop events when a scanner stops before we actually request it to. (This is somewhat similar to how we have a disconnect event for devices except in that case, the callback is called even if we requested the disconnect or not.) There haven't been any reported problems due to not having such a feature until now, so it has never been addressed until now.

So far, this PR just implements the platform-specific parts to call a common scanner backend method when an unrequested scanner stop event occurs. The idea is to use this as part of the context manager so that it will cancel the task if an early stop happens.

async with BleakScanner():
    await asyncio.sleep(10)  # BleakError("scanner stopped early") will be raised here
    # e.g. if something unexpected happened, like Bluetooth is turned off

We probably also need some sort of API for anyone using start and stop without a context manager (although maybe we don't want to support this and require a context manager if you want the callback to do something?).

Code hasn't been tested yet on any platform, so expect bugs.

cc: @bojanpotocnik

dlech commented 1 year ago

This has now been tested on Windows, Linux and Mac. It is working as expected (when used with #1147 and #1148). The test case is running the discover.py example and turning off Bluetooth before the scan stops. I replaced the ... implementation of handle_early_stop() with a print to see if it ran.

In the case of Linux with passive scanning, it is not working because "Release" of the advertisement watcher is not called. I think this could be considered a bug in BlueZ (the advertisement watcher is still experimental after all) since "Release" is called on other errors. It seems like Release should be called if the adapter is powered off or disappears.

Release was called as expected when I gave bad args for or_patterns so it does seem to be working as expected for cases other than adapter power off. (although for this particular case, we should check the type of the args in __init__ to avoid this altogether, but that is a separate issue #1149).