From ea199f139995f13ffd4dd5c0ac1c7f9a989a0e1a Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 30 Apr 2024 18:48:05 -0500 Subject: [PATCH 1/6] backends/scanner: always filter by service_uuids It was noted that on Linux, if another app was scanning at the same time, BlueZ would trigger RSSI changes for all devices, even if they they didn't match the service_uuids filter. This change ensures that we always filter by service_uuids, even if the OS isn't doing it for us. On Windows, the OS wasn't filtering for us anyway, so we can just move that code to the shared call_detection_callbacks() so that all backends will make use of it. Fixes: https://github.com/hbldh/bleak/issues/1534 --- CHANGELOG.rst | 1 + bleak/backends/scanner.py | 18 ++++++++++++++++++ bleak/backends/winrt/scanner.py | 19 +++++-------------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aef37052..ecfd4b95 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -34,6 +34,7 @@ Fixed * Fixed using wrong value for ``tx_power`` in Android backend. Fixes #1532. * Fixed 4-character UUIDs not working on ``BleakClient.*_gatt_char`` methods. Fixes #1498. * Fixed race condition with getting max PDU size on Windows. Fixes #1497. +* Fixed filtering advertisement data by service UUID when multiple apps are scanning. Fixes #1534. `0.21.1`_ (2023-09-08) ====================== diff --git a/bleak/backends/scanner.py b/bleak/backends/scanner.py index f59557b9..693b20db 100644 --- a/bleak/backends/scanner.py +++ b/bleak/backends/scanner.py @@ -206,6 +206,24 @@ def call_detection_callbacks( Backend implementations should call this method when an advertisement event is received from the OS. """ + + # Backends will make best effort to filter out advertisements that + # don't match the service UUIDs, but if other apps are scanning at the + # same time or something like that, we may still receive advertisements + # that don't match. So we need to do more filtering here to get the + # expected behavior. + + if self._service_uuids: + if not advertisement_data.service_uuids: + return + + for uuid in advertisement_data.service_uuids: + if uuid in self._service_uuids: + break + else: + # if there were no matching service uuids, the don't call the callback + return + for callback in self._ad_callbacks.values(): callback(device, advertisement_data) diff --git a/bleak/backends/winrt/scanner.py b/bleak/backends/winrt/scanner.py index f250fa98..651fbc75 100644 --- a/bleak/backends/winrt/scanner.py +++ b/bleak/backends/winrt/scanner.py @@ -96,6 +96,11 @@ def __init__( else: self._scanning_mode = BluetoothLEScanningMode.ACTIVE + # Unfortunately, due to the way Windows handles filtering, we can't + # make use of the service_uuids filter here. If we did we would only + # get the advertisement data or the scan data, but not both, so would + # miss out on other essential data. Advanced users can pass their own + # filters though if they want to. self._signal_strength_filter = kwargs.get("SignalStrengthFilter", None) self._advertisement_filter = kwargs.get("AdvertisementFilter", None) @@ -198,20 +203,6 @@ def _received_handler( bdaddr, local_name, raw_data, advertisement_data ) - # On Windows, we have to fake service UUID filtering. If we were to pass - # a BluetoothLEAdvertisementFilter to the BluetoothLEAdvertisementWatcher - # with the service UUIDs appropriately set, we would no longer receive - # scan response data (which commonly contains the local device name). - # So we have to do it like this instead. - - if self._service_uuids: - for uuid in uuids: - if uuid in self._service_uuids: - break - else: - # if there were no matching service uuids, the don't call the callback - return - self.call_detection_callbacks(device, advertisement_data) def _stopped_handler(self, sender, e): From dfe9e63df79f987c9097d619732d6d6ceda255dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 21:56:46 +0000 Subject: [PATCH 2/6] build(deps-dev): bump jinja2 from 3.1.3 to 3.1.4 Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4) --- updated-dependencies: - dependency-name: jinja2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index abab64fc..c3278b39 100644 --- a/poetry.lock +++ b/poetry.lock @@ -372,13 +372,13 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -950,6 +950,7 @@ files = [ {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp39-cp39-win32.whl", hash = "sha256:dffff7e6801b8e69e694b36fe1d147094fb6ac29ce54fd3ca3e52ab417473cc4"}, {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:62bae806ecdf3021e1ec685d5a44012657c0961ca2027eeb1c37864f53577e51"}, {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:7f3b102e9b4bea1915cc922b571e0c226956c161102d228ec1788e3caf4e226d"}, + {file = "winrt_windows_devices_bluetooth-2.0.1.tar.gz", hash = "sha256:c91b3f54bfe1ed7e1e597566b83a625d32efe397b21473668046ccb4b57f5a28"}, ] [package.dependencies] @@ -977,6 +978,7 @@ files = [ {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp39-cp39-win32.whl", hash = "sha256:86d11fd5c055f76eefac7f6cc02450832811503b83280e26a83613afe1d17c92"}, {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c8495ce12fda8fce3da130664917eb199d19ca1ebf7d5ab996f5df584b5e3a1f"}, {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:0e91160a98e5b0fffae196982b5670e678ac919a6e14eb7e9798fdcbff45f8d2"}, + {file = "winrt_windows_devices_bluetooth_advertisement-2.0.1.tar.gz", hash = "sha256:130e6238a1897bfef98a711cdb1b02694fa0e18eb67d8fd4019a64a53685b331"}, ] [package.dependencies] @@ -1004,6 +1006,7 @@ files = [ {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp39-cp39-win32.whl", hash = "sha256:3e2a54db384dcf05265a855a2548e2abd9b7726c8ec4b9ad06059606c5d90409"}, {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2bdbb55d4bef15c762a5d5b4e27b534146ec6580075ed9cc681e75e6ff0d5a97"}, {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:01e74c76d4f16b4490d78c8c7509f2570c843366c1c6bf196a5b729520a31258"}, + {file = "winrt_windows_devices_bluetooth_genericattributeprofile-2.0.1.tar.gz", hash = "sha256:69d7dabd53fbf9acdc2d206def60f5c9777416a9d6911c3420be700aaff4e492"}, ] [package.dependencies] @@ -1031,6 +1034,7 @@ files = [ {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp39-cp39-win32.whl", hash = "sha256:9301f5e00bd2562b063e0f6e0de6f0596b7fb3eabc443bd7e115772de6cc08f9"}, {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:9999d93ae9441d35c564d498bb4d6767b593254a92b7c1559058a7450a0c304e"}, {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:504ca45a9b90387a2f4f727dbbeefcf79beb013ac7a29081bb14c8ab13e10367"}, + {file = "winrt_windows_devices_enumeration-2.0.1.tar.gz", hash = "sha256:ed227dd22ece253db913de24e4fc5194d9f3272e2a5959a2450ae79e81bf7949"}, ] [package.dependencies] @@ -1058,6 +1062,7 @@ files = [ {file = "winrt_Windows.Foundation-2.0.1-cp39-cp39-win32.whl", hash = "sha256:7abbf10666d6da5dbfb6a47125786a05dac267731a3d38feb8faddade9bf1151"}, {file = "winrt_Windows.Foundation-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:aab18ad12de63a353ab1847aff3216ba4e5499e328da5edcb72c8007da6bdb02"}, {file = "winrt_Windows.Foundation-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:bde9ecfc1c75410d669ee3124a84ba101d5a8ab1911807ad227658624fc22ffb"}, + {file = "winrt_windows_foundation-2.0.1.tar.gz", hash = "sha256:6e4da10cff652ac17740753c38ebe69565f5f970f60100106469b2e004ef312c"}, ] [package.dependencies] @@ -1085,6 +1090,7 @@ files = [ {file = "winrt_Windows.Foundation.Collections-2.0.1-cp39-cp39-win32.whl", hash = "sha256:c26ab7b3342669dc09be62db5c5434e7194fb6eb1ec5b03fba1163f6b3e7b843"}, {file = "winrt_Windows.Foundation.Collections-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2f9bc7e28f3ade1c1f3113939dbf630bfef5e3c3018c039a404d7e4d39aae4cb"}, {file = "winrt_Windows.Foundation.Collections-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:1f3e76f3298bec3938d94e4857c29af9776ec78112bdd09bb7794f06fd38bb13"}, + {file = "winrt_windows_foundation_collections-2.0.1.tar.gz", hash = "sha256:7d18955f161ba27d785c8fe2ef340f338b6edd2c5226fe2b005840e2a855e708"}, ] [package.dependencies] @@ -1112,6 +1118,7 @@ files = [ {file = "winrt_Windows.Storage.Streams-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f6dec418ad0118c258a1b2999fc8d4fc0d9575e6353a75a242ff8cc63c9b2146"}, {file = "winrt_Windows.Storage.Streams-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:9fbc40f600ab44a45cda47b698bd8e494e80e221446a5958c4d8d59a8d46f117"}, {file = "winrt_Windows.Storage.Streams-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:08059774c6d49d195ce00c3802d19364f418a6f3e42b94373621551792d2da60"}, + {file = "winrt_windows_storage_streams-2.0.1.tar.gz", hash = "sha256:3de8351ed3a9cfcfd1d028ce97ffe90bb95744f906eef025b06e7f4431943ee6"}, ] [package.dependencies] From 4b4262346e574427453b85e82faea0db96dafbab Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 7 May 2024 15:17:12 -0500 Subject: [PATCH 3/6] docs/troubleshooting: fix typo --- docs/troubleshooting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index e299d18f..d1de7340 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -86,7 +86,7 @@ To work around this, you can use a utility function provided by Bleak to uninitialize the threading model after importing an offending package:: import win32com # this sets current thread to STA :-( - from bleak.backends.winrt.utils import uninitialize_sta + from bleak.backends.winrt.util import uninitialize_sta uninitialize_sta() # undo the unwanted side effect From b9cded2b7016c7f79d085c874e635dc0483d0a18 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 7 May 2024 15:07:41 -0500 Subject: [PATCH 4/6] backends.winrt: add allow_sta() utility function We had lots of users reporting issues with the WinRT backend hanging forever when trying to connect to a device. This was happening because some other imported library was initializing the the main thread to STA which caused some async callback to never be called. To work around this, we added a check to make sure the main thread is set to MTA rather than STA. Unfortunately, in cases where there is a graphical user interface library being used AND that library is properly interated with asynco so that Bleak runs in the main thread rather than in a background thread, the thread type does need to be STA for the GUI to work. So in those very specific conditions, we need to not raise an exception. We don't know of a way to detect this automatically, so we added a new utility function `allow_sta()` that the user can call to allow Bleak to run in an STA thread. --- CHANGELOG.rst | 5 +++++ bleak/backends/winrt/util.py | 21 +++++++++++++++++++++ docs/troubleshooting.rst | 21 +++++++++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 01c44dc5..333dc00c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,11 @@ and this project adheres to `Semantic Versioning None: .. versionadded:: 0.22 """ + if hasattr(allow_sta, "_allowed"): + return + try: apt_type, _ = _get_apartment_type() if apt_type != _AptType.MTA: @@ -81,6 +84,24 @@ def assert_mta() -> None: raise +def allow_sta(): + """ + Suppress check for MTA thread type and allow STA. + + Bleak will hang forever if the current thread is not MTA - unless there is + a Windows event loop running that is properly integrated with asyncio in + Python. + + If your program meets that condition, you must call this function do disable + the check for MTA. If your program doesn't have a graphical user interface + you probably shouldn't call this function. and use ``uninitialize_sta()`` + instead. + + .. versionadded:: unreleased + """ + allow_sta._allowed = True + + def uninitialize_sta(): """ Uninitialize the COM library on the current thread if it was not initialized diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index d1de7340..514eac05 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -82,8 +82,25 @@ Bleak should detect this and raise an exception with a message similar to:: The current thread apartment type is not MTA: STA. -To work around this, you can use a utility function provided by Bleak to -uninitialize the threading model after importing an offending package:: +To work around this, you can use one of the utility functions provided by Bleak. + +If your program has a graphical user interface and the UI framework *and* it is +properly integrated with asyncio *and* Bleak is not running on a background +thread then call ``allow_sta()`` before calling any other Bleak APis:: + + try: + from bleak.backends.winrt.util import allow_sta + # tell Bleak we are using a graphical user interface that has been properly + # configured to work with asyncio + allow_sta() + except ImportError: + # other OSes and older versions of Bleak will raise ImportError which we + # can safely ignore + pass + +The more typical case, though, is that some library has imported something like +``pywin32`` which breaks Bleak. In this case, you can uninitialize the threading +model like this:: import win32com # this sets current thread to STA :-( from bleak.backends.winrt.util import uninitialize_sta From 9896c8b3b96f2d9c3259f1cc7479000264cc9a77 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 7 May 2024 15:57:56 -0500 Subject: [PATCH 5/6] CHANGELOG: fix wrong release date --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 333dc00c..cdc00245 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,7 +15,7 @@ Added * Added ``bleak.backends.winrt.util.allow_sta()`` method to allow integration with graphical user interfaces on Windows. Fixes #1565. -`0.22.0`_ (2024-04-04) +`0.22.0`_ (2024-05-04) ====================== Added From e285de5876c5b31bcb265668cc32ac054598211d Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 7 May 2024 15:59:26 -0500 Subject: [PATCH 6/6] v0.22.1 --- CHANGELOG.rst | 6 +++++- bleak/backends/winrt/util.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cdc00245..77f18e30 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,9 @@ and this project adheres to `Semantic Versioning "] license = "MIT"