Repository: hbldh/bleak (language: python)

Issue:
bleak.exc.BleakError: failed to discover services, device disconnected
* bleak version: 0.21.1
* Python version: 3.10
* Operating System: Ubuntu
* BlueZ version (`bluetoothctl -v`) in case of Linux: 5.64

### Description

I have a Vernier Caliper from TESA which has a BLE module and it gives data from that BLE Module. I am trying to read the data from the BLE Module. I am ble to read it in My Phone using LightBlue App (a ble scanner app).

### What I Did

When I am rnning the Service_Explorer I am able to discover the device and the advertised service UUID is also visible. Its the same service UUID that i get in my phone app.

It looks like this:
```
E0:4B:16:4A:55:EA: TLC-BLE
--------------------------
AdvertisementData(local_name='TLC-BLE', manufacturer_data={1341: b'\x0800530322\x069E0445'}, service_uuids=['6e400001-b5a3-f393-e0a9-e50e24dcca9e'], tx_power=4, rssi=-49)
```

But when I am trying to connect to the same BLE device I am getting error which says:


```
bleak.exc.BleakError: failed to discover services, device disconnected
```

here is the code which I am using to trying to connect:

```
import asyncio
from bleak import BleakClient


async def main():
    async with BleakClient("E0:4B:16:4A:55:EA") as client:
        # Read a characteristic, etc.
        print("Connected")

    # Device will disconnect when block exits.
    print("disconnect")

# Using asyncio.run() is important to ensure that device disconnects on
# KeyboardInterrupt or other unhandled exception.
asyncio.run(main())

```


### Logs

```
Traceback (most recent call last):
  File "/home/biswa/PycharmProjects/BLEScanner/main.py", line 15, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/home/biswa/PycharmProjects/BLEScanner/main.py", line 6, in main
    async with BleakClient("E0:4B:16:4A:55:EA") as client:
  File "/home/biswa/PycharmProjects/BLEScanner/.venv/lib/python3.10/site-packages/bleak/__init__.py", line 565, in __aenter__
    await self.connect()
  File "/home/biswa/PycharmProjects/BLEScanner/.venv/lib/python3.10/site-packages/bleak/__init__.py", line 605, in connect
    return await self._backend.connect(**kwargs)
  File "/home/biswa/PycharmProjects/BLEScanner/.venv/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 268, in connect
    await self.get_services(
  File "/home/biswa/PycharmProjects/BLEScanner/.venv/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 656, in get_services
    self.services = await manager.get_services(
  File "/home/biswa/PycharmProjects/BLEScanner/.venv/lib/python3.10/site-packages/bleak/backends/bluezdbus/manager.py", line 644, in get_services
    await self._wait_for_services_discovery(device_path)
  File "/home/biswa/PycharmProjects/BLEScanner/.venv/lib/python3.10/site-packages/bleak/backends/bluezdbus/manager.py", line 779, in _wait_for_services_discovery
    raise BleakError("failed to discover services, device disconnected")
bleak.exc.BleakError: failed to discover services, device disconnected

```


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml
index 01c66de4..05faa02f 100644
--- a/.github/workflows/format_and_lint.yml
+++ b/.github/workflows/format_and_lint.yml
@@ -16,6 +16,8 @@ jobs:
                 uses: actions/setup-python@v4
             -   name: Install development dependencies
                 run: pipx run poetry install --only docs,lint
+            -   name: Check import sort with isort
+                run: pipx run poetry run isort . --check --diff
             -   name: Check code formatting with black
                 run: pipx run poetry run black . --check --diff
             -   name: Lint with flake8
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 95b9bf1d..614cce70 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -16,8 +16,8 @@ sphinx:
    configuration: docs/conf.py
 
 # If using Sphinx, optionally build your docs in additional formats such as PDF
-# formats:
-#    - pdf
+formats:
+   - pdf
 
 # Optionally declare the Python requirements required to build your docs
 python:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d0bf3eaf..2c717a66 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,7 +3,7 @@
         "editor.defaultFormatter": "ms-python.black-formatter",
         "editor.formatOnSave": true,
         "editor.codeActionsOnSave": {
-            "source.organizeImports": true
+            "source.organizeImports": "explicit"
         },
       },
       "black-formatter.importStrategy": "fromEnvironment",
diff --git a/AUTHORS.rst b/AUTHORS.rst
index d4e73d8e..3526f0dd 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -21,6 +21,8 @@ Contributors
 * Kyle J. Williams <kyle@kjwill.tech>
 * Edward Betts <edward@4angle.com>
 * Robbe Gaeremynck <robbe.gaeremynck@outlook.com>
+* David Johansen <davejohansen@gmail.com>
+* JP Hutchins <jphutchins@gmail.com>
 
 Sponsors
 --------
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 31068be2..01c44dc5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -10,6 +10,35 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
 `Unreleased`_
 =============
 
+`0.22.0`_ (2024-04-04)
+======================
+
+Added
+-----
+* Added ``BleakCharacteristicNotFoundError`` which is raised if a device does not support a characteristic.
+* Added utility function to work around ``pywin32`` setting threading model to STA on Windows.
+
+Changed
+-------
+* Updated PyObjC dependency on macOS to v10.x.
+* Updated missing Bluetooth SIG characteristics and service UUIDs.
+* Updated ``BlueZManager`` to remove empty interfaces from `_properties` during InterfacesRemoved message.
+* Updated PyWinRT dependency to v2. Fixes #1529.
+* Raise exception when trying to scan while in a single-treaded apartment (STA) on Windows. Fixes #1132.
+
+Fixed
+-----
+* Fixed BlueZ version in passive scanning error message. Fixes #1433.
+* Fixed mypy requiring ``Unpack[ExtraArgs]`` that were intended to be optional.  Fixes #1487.
+* Fixed ``KeyError`` in BlueZ ``is_connected()`` and ``get_global_bluez_manager()`` when device is not present. Fixes #1507.
+* Fixed BlueZ ``_wait_removed`` completion on invalid object path. Fixes #1489.
+* Fixed rare unhandled exception when scanning on macOS when using ``use_bdaddr``. Fixes #1523.
+* Fixed scanning silently failing on Windows when Bluetooth is off. Fixes #1535.
+* 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)
 ======================
 
@@ -985,7 +1014,8 @@ Fixed
 * Bleak created.
 
 
-.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.21.1...develop
+.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.22.0...develop
+.. _0.22.0: https://github.com/hbldh/bleak/compare/v0.21.1...v0.22.0
 .. _0.21.1: https://github.com/hbldh/bleak/compare/v0.21.0...v0.21.1
 .. _0.21.0: https://github.com/hbldh/bleak/compare/v0.20.2...v0.21.0
 .. _0.20.2: https://github.com/hbldh/bleak/compare/v0.20.1...v0.20.2
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 9299ac6f..9f9f2692 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -81,6 +81,7 @@ and your favorite text editor. And Python of course.
 
 5. When you're done making changes, check that your changes pass linting and the tests::
 
+    $ poetry run isort .
     $ poetry run black .
     $ poetry run flake8
     $ poetry run pytest
diff --git a/bleak/__init__.py b/bleak/__init__.py
index 28b5e3bc..68e17812 100644
--- a/bleak/__init__.py
+++ b/bleak/__init__.py
@@ -14,6 +14,7 @@
 import os
 import sys
 import uuid
+from types import TracebackType
 from typing import (
     TYPE_CHECKING,
     AsyncGenerator,
@@ -22,6 +23,7 @@
     Dict,
     Iterable,
     List,
+    Literal,
     Optional,
     Set,
     Tuple,
@@ -31,7 +33,6 @@
     overload,
 )
 from warnings import warn
-from typing import Literal
 
 if sys.version_info < (3, 12):
     from typing_extensions import Buffer
@@ -45,7 +46,6 @@
     from asyncio import timeout as async_timeout
     from typing import Unpack
 
-
 from .backends.characteristic import BleakGATTCharacteristic
 from .backends.client import BaseBleakClient, get_platform_client_backend_type
 from .backends.device import BLEDevice
@@ -57,7 +57,7 @@
     get_platform_scanner_backend_type,
 )
 from .backends.service import BleakGATTServiceCollection
-from .exc import BleakError
+from .exc import BleakCharacteristicNotFoundError, BleakError
 from .uuids import normalize_uuid_str
 
 if TYPE_CHECKING:
@@ -121,11 +121,11 @@ class BleakScanner:
         are matching a device based on other data but want to display the local
         name to the user, be sure to wait for ``adv_data.local_name is not None``.
 
-    .. versionchanged:: 0.15.0
+    .. versionchanged:: 0.15
         ``detection_callback``, ``service_uuids`` and ``scanning_mode`` are no longer keyword-only.
         Added ``bluez`` parameter.
 
-    .. versionchanged:: 0.18.0
+    .. versionchanged:: 0.18
         No longer is alias for backend type and no longer inherits from :class:`BaseBleakScanner`.
         Added ``backend`` parameter.
     """
@@ -140,7 +140,7 @@ def __init__(
         cb: CBScannerArgs = {},
         backend: Optional[Type[BaseBleakScanner]] = None,
         **kwargs,
-    ):
+    ) -> None:
         PlatformBleakScanner = (
             get_platform_scanner_backend_type() if backend is None else backend
         )
@@ -154,11 +154,16 @@ def __init__(
             **kwargs,
         )
 
-    async def __aenter__(self):
+    async def __aenter__(self) -> BleakScanner:
         await self._backend.start()
         return self
 
-    async def __aexit__(self, exc_type, exc_val, exc_tb):
+    async def __aexit__(
+        self,
+        exc_type: Type[BaseException],
+        exc_val: BaseException,
+        exc_tb: TracebackType,
+    ) -> None:
         await self._backend.stop()
 
     def register_detection_callback(
@@ -193,15 +198,15 @@ def register_detection_callback(
             unregister = self._backend.register_detection_callback(callback)
             setattr(self, "_unregister_", unregister)
 
-    async def start(self):
+    async def start(self) -> None:
         """Start scanning for devices"""
         await self._backend.start()
 
-    async def stop(self):
+    async def stop(self) -> None:
         """Stop scanning for devices"""
         await self._backend.stop()
 
-    def set_scanning_filter(self, **kwargs):
+    def set_scanning_filter(self, **kwargs) -> None:
         """
         Set scanning filter for the BleakScanner.
 
@@ -245,7 +250,7 @@ async def advertisement_data(
         finally:
             unregister_callback()
 
-    class ExtraArgs(TypedDict):
+    class ExtraArgs(TypedDict, total=False):
         """
         Keyword args from :class:`~bleak.BleakScanner` that can be passed to
         other convenience methods.
@@ -281,15 +286,13 @@ class ExtraArgs(TypedDict):
     @classmethod
     async def discover(
         cls, timeout: float = 5.0, *, return_adv: Literal[False] = False, **kwargs
-    ) -> List[BLEDevice]:
-        ...
+    ) -> List[BLEDevice]: ...
 
     @overload
     @classmethod
     async def discover(
         cls, timeout: float = 5.0, *, return_adv: Literal[True], **kwargs
-    ) -> Dict[str, Tuple[BLEDevice, AdvertisementData]]:
-        ...
+    ) -> Dict[str, Tuple[BLEDevice, AdvertisementData]]: ...
 
     @classmethod
     async def discover(
@@ -311,7 +314,7 @@ async def discover(
             The value of :attr:`discovered_devices_and_advertisement_data` if
             ``return_adv`` is ``True``, otherwise the value of :attr:`discovered_devices`.
 
-        .. versionchanged:: 0.19.0
+        .. versionchanged:: 0.19
             Added ``return_adv`` parameter.
         """
         async with cls(**kwargs) as scanner:
@@ -344,7 +347,7 @@ def discovered_devices_and_advertisement_data(
         ``discovered_devices_and_advertisement_data.values()`` to just get the
         values instead.
 
-        .. versionadded:: 0.19.0
+        .. versionadded:: 0.19
         """
         return self._backend.seen_devices
 
@@ -402,7 +405,7 @@ async def find_device_by_name(
         Returns:
             The ``BLEDevice`` sought or ``None`` if not detected.
 
-        .. versionadded:: 0.20.0
+        .. versionadded:: 0.20
         """
         return await cls.find_device_by_filter(
             lambda d, ad: ad.local_name == name,
@@ -495,10 +498,10 @@ class BleakClient:
             This is known to cause problems when trying to connect to multiple
             devices at the same time.
 
-    .. versionchanged:: 0.15.0
+    .. versionchanged:: 0.15
         ``disconnected_callback`` is no longer keyword-only. Added ``winrt`` parameter.
 
-    .. versionchanged:: 0.18.0
+    .. versionchanged:: 0.18
         No longer is alias for backend type and no longer inherits from :class:`BaseBleakClient`.
         Added ``backend`` parameter.
     """
@@ -513,19 +516,21 @@ def __init__(
         winrt: WinRTClientArgs = {},
         backend: Optional[Type[BaseBleakClient]] = None,
         **kwargs,
-    ):
+    ) -> None:
         PlatformBleakClient = (
             get_platform_client_backend_type() if backend is None else backend
         )
 
         self._backend = PlatformBleakClient(
             address_or_ble_device,
-            disconnected_callback=None
-            if disconnected_callback is None
-            else functools.partial(disconnected_callback, self),
-            services=None
-            if services is None
-            else set(map(normalize_uuid_str, services)),
+            disconnected_callback=(
+                None
+                if disconnected_callback is None
+                else functools.partial(disconnected_callback, self)
+            ),
+            services=(
+                None if services is None else set(map(normalize_uuid_str, services))
+            ),
             timeout=timeout,
             winrt=winrt,
             **kwargs,
@@ -553,19 +558,24 @@ def mtu_size(self) -> int:
         """
         return self._backend.mtu_size
 
-    def __str__(self):
+    def __str__(self) -> str:
         return f"{self.__class__.__name__}, {self.address}"
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return f"<{self.__class__.__name__}, {self.address}, {type(self._backend)}>"
 
     # Async Context managers
 
-    async def __aenter__(self):
+    async def __aenter__(self) -> BleakClient:
         await self.connect()
         return self
 
-    async def __aexit__(self, exc_type, exc_val, exc_tb):
+    async def __aexit__(
+        self,
+        exc_type: Type[BaseException],
+        exc_val: BaseException,
+        exc_tb: TracebackType,
+    ) -> None:
         await self.disconnect()
 
     # Connectivity methods
@@ -766,7 +776,7 @@ async def write_gatt_char(
             characteristic = self.services.get_characteristic(char_specifier)
 
         if not characteristic:
-            raise BleakError("Characteristic {char_specifier} was not found!")
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         if response is None:
             # if not specified, prefer write-with-response over write-without-
@@ -806,7 +816,7 @@ def callback(sender: BleakGATTCharacteristic, data: bytearray):
                 function or async function.
 
 
-        .. versionchanged:: 0.18.0
+        .. versionchanged:: 0.18
             The first argument of the callback is now a :class:`BleakGATTCharacteristic`
             instead of an ``int``.
         """
@@ -819,11 +829,11 @@ def callback(sender: BleakGATTCharacteristic, data: bytearray):
             characteristic = char_specifier
 
         if not characteristic:
-            raise BleakError(f"Characteristic {char_specifier} not found!")
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         if inspect.iscoroutinefunction(callback):
 
-            def wrapped_callback(data):
+            def wrapped_callback(data: bytearray) -> None:
                 task = asyncio.create_task(callback(characteristic, data))
                 _background_tasks.add(task)
                 task.add_done_callback(_background_tasks.discard)
@@ -893,7 +903,7 @@ def discover(*args, **kwargs):
     return BleakScanner.discover(*args, **kwargs)
 
 
-def cli():
+def cli() -> None:
     import argparse
 
     parser = argparse.ArgumentParser(
diff --git a/bleak/assigned_numbers.py b/bleak/assigned_numbers.py
index 0fe2ce85..d52220fb 100644
--- a/bleak/assigned_numbers.py
+++ b/bleak/assigned_numbers.py
@@ -7,7 +7,6 @@
 See <https://www.bluetooth.com/specifications/assigned-numbers/>.
 """
 
-
 from enum import IntEnum
 
 
@@ -17,7 +16,7 @@ class AdvertisementDataType(IntEnum):
 
     `Source <https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Generic%20Access%20Profile.pdf>`.
 
-    .. versionadded:: 0.15.0
+    .. versionadded:: 0.15
     """
 
     FLAGS = 0x01
diff --git a/bleak/backends/bluezdbus/advertisement_monitor.py b/bleak/backends/bluezdbus/advertisement_monitor.py
index 83de82f1..922ffab4 100644
--- a/bleak/backends/bluezdbus/advertisement_monitor.py
+++ b/bleak/backends/bluezdbus/advertisement_monitor.py
@@ -3,17 +3,16 @@
 ---------------------
 
 This module contains types associated with the BlueZ D-Bus `advertisement
-monitor api <https://github.com/bluez/bluez/blob/master/doc/advertisement-monitor-api.txt>`.
+monitor api <https://github.com/bluez/bluez/blob/master/doc/org.bluez.AdvertisementMonitor.rst>`.
 """
 
 import logging
 from typing import Iterable, NamedTuple, Tuple, Union, no_type_check
 
-from dbus_fast.service import ServiceInterface, dbus_property, method, PropertyAccess
+from dbus_fast.service import PropertyAccess, ServiceInterface, dbus_property, method
 
-from . import defs
 from ...assigned_numbers import AdvertisementDataType
-
+from . import defs
 
 logger = logging.getLogger(__name__)
 
@@ -22,7 +21,7 @@ class OrPattern(NamedTuple):
     """
     BlueZ advertisement monitor or-pattern.
 
-    https://github.com/bluez/bluez/blob/master/doc/advertisement-monitor-api.txt
+    https://github.com/bluez/bluez/blob/master/doc/org.bluez.AdvertisementMonitor.rst#arrayuint8-uint8-arraybyte-patterns-read-only-optional
     """
 
     start_position: int
diff --git a/bleak/backends/bluezdbus/characteristic.py b/bleak/backends/bluezdbus/characteristic.py
index 5208d19b..3b41147b 100644
--- a/bleak/backends/bluezdbus/characteristic.py
+++ b/bleak/backends/bluezdbus/characteristic.py
@@ -94,7 +94,7 @@ def get_descriptor(
         except StopIteration:
             return None
 
-    def add_descriptor(self, descriptor: BleakGATTDescriptor):
+    def add_descriptor(self, descriptor: BleakGATTDescriptor) -> None:
         """Add a :py:class:`~BleakGATTDescriptor` to the characteristic.
 
         Should not be used by end user, but rather by `bleak` itself.
diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py
index d1ee0733..ec225584 100644
--- a/bleak/backends/bluezdbus/client.py
+++ b/bleak/backends/bluezdbus/client.py
@@ -26,7 +26,12 @@
 from dbus_fast.signature import Variant
 
 from ... import BleakScanner
-from ...exc import BleakDBusError, BleakError, BleakDeviceNotFoundError
+from ...exc import (
+    BleakCharacteristicNotFoundError,
+    BleakDBusError,
+    BleakDeviceNotFoundError,
+    BleakError,
+)
 from ..characteristic import BleakGATTCharacteristic
 from ..client import BaseBleakClient, NotifyCallback
 from ..device import BLEDevice
@@ -183,9 +188,9 @@ def on_value_changed(char_path: str, value: bytes) -> None:
                     watcher
                 )
 
-                self._disconnect_monitor_event = (
-                    local_disconnect_monitor_event
-                ) = asyncio.Event()
+                self._disconnect_monitor_event = local_disconnect_monitor_event = (
+                    asyncio.Event()
+                )
 
                 try:
                     try:
@@ -725,11 +730,7 @@ async def read_gatt_char(
                 )
                 return value
 
-            raise BleakError(
-                "Characteristic with UUID {0} could not be found!".format(
-                    char_specifier
-                )
-            )
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         while True:
             assert self._bus
@@ -977,7 +978,7 @@ async def stop_notify(
         else:
             characteristic = char_specifier
         if not characteristic:
-            raise BleakError("Characteristic {} not found!".format(char_specifier))
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         reply = await self._bus.call(
             Message(
diff --git a/bleak/backends/bluezdbus/defs.py b/bleak/backends/bluezdbus/defs.py
index 50054540..18ed0c33 100644
--- a/bleak/backends/bluezdbus/defs.py
+++ b/bleak/backends/bluezdbus/defs.py
@@ -23,7 +23,7 @@
 
 
 # D-Bus properties for interfaces
-# https://github.com/bluez/bluez/blob/master/doc/adapter-api.txt
+# https://github.com/bluez/bluez/blob/master/doc/org.bluez.Adapter.rst
 
 
 class Adapter1(TypedDict):
@@ -43,7 +43,7 @@ class Adapter1(TypedDict):
     ExperimentalFeatures: List[str]
 
 
-# https://github.com/bluez/bluez/blob/master/doc/advertisement-monitor-api.txt
+# https://github.com/bluez/bluez/blob/master/doc/org.bluez.AdvertisementMonitor.rst
 
 
 class AdvertisementMonitor1(TypedDict):
@@ -56,12 +56,15 @@ class AdvertisementMonitor1(TypedDict):
     Patterns: List[Tuple[int, int, bytes]]
 
 
+# https://github.com/bluez/bluez/blob/master/doc/org.bluez.AdvertisementMonitorManager.rst
+
+
 class AdvertisementMonitorManager1(TypedDict):
     SupportedMonitorTypes: List[str]
     SupportedFeatures: List[str]
 
 
-# https://github.com/bluez/bluez/blob/master/doc/battery-api.txt
+# https://github.com/bluez/bluez/blob/master/doc/org.bluez.Battery.rst
 
 
 class Battery1(TypedDict):
@@ -69,7 +72,7 @@ class Battery1(TypedDict):
     SupportedFeatures: List[str]
 
 
-# https://github.com/bluez/bluez/blob/master/doc/device-api.txt
+# https://github.com/bluez/bluez/blob/master/doc/org.bluez.Device.rst
 
 
 class Device1(TypedDict):
@@ -99,7 +102,7 @@ class Device1(TypedDict):
     AdvertisingData: Dict[int, bytes]
 
 
-# https://github.com/bluez/bluez/blob/master/doc/gatt-api.txt
+# https://github.com/bluez/bluez/blob/master/doc/org.bluez.GattService.rst
 
 
 class GattService1(TypedDict):
diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py
index 325cd43a..3953fc18 100644
--- a/bleak/backends/bluezdbus/manager.py
+++ b/bleak/backends/bluezdbus/manager.py
@@ -33,14 +33,14 @@
 from . import defs
 from .advertisement_monitor import AdvertisementMonitor, OrPatternLike
 from .characteristic import BleakGATTCharacteristicBlueZDBus
-from .defs import Device1, GattService1, GattCharacteristic1, GattDescriptor1
+from .defs import Device1, GattCharacteristic1, GattDescriptor1, GattService1
 from .descriptor import BleakGATTDescriptorBlueZDBus
 from .service import BleakGATTServiceBlueZDBus
 from .signals import MatchRules, add_match
 from .utils import (
     assert_reply,
-    get_dbus_authenticator,
     device_path_from_characteristic_path,
+    get_dbus_authenticator,
 )
 
 logger = logging.getLogger(__name__)
@@ -216,7 +216,29 @@ def _check_device(self, device_path: str) -> None:
         if device_path not in self._properties:
             raise BleakError(f"device '{device_path.split('/')[-1]}' not found")
 
-    async def async_init(self):
+    def _get_device_property(
+        self, device_path: str, interface: str, property_name: str
+    ) -> Any:
+        self._check_device(device_path)
+        device_properties = self._properties[device_path]
+
+        try:
+            interface_properties = device_properties[interface]
+        except KeyError:
+            raise BleakError(
+                f"Interface {interface} not found for device '{device_path}'"
+            )
+
+        try:
+            value = interface_properties[property_name]
+        except KeyError:
+            raise BleakError(
+                f"Property '{property_name}' not found for '{interface}' in '{device_path}'"
+            )
+
+        return value
+
+    async def async_init(self) -> None:
         """
         Connects to the D-Bus message bus and begins monitoring signals.
 
@@ -521,7 +543,7 @@ async def passive_scan(
                     and reply.error_name == "org.freedesktop.DBus.Error.UnknownMethod"
                 ):
                     raise BleakError(
-                        "passive scanning on Linux requires BlueZ >= 5.55 with --experimental enabled and Linux kernel >= 5.10"
+                        "passive scanning on Linux requires BlueZ >= 5.56 with --experimental enabled and Linux kernel >= 5.10"
                     )
 
                 assert_reply(reply)
@@ -530,7 +552,7 @@ async def passive_scan(
                 # won't use the monitor
                 self._bus.export(monitor_path, monitor)
 
-                async def stop():
+                async def stop() -> None:
                     # need to remove callbacks first, otherwise we get TxPower
                     # and RSSI properties removed during stop which causes
                     # incorrect advertisement data callbacks
@@ -711,9 +733,7 @@ def get_device_name(self, device_path: str) -> str:
         Raises:
             BleakError: if the device is not present in BlueZ
         """
-        self._check_device(device_path)
-
-        return self._properties[device_path][defs.DEVICE_INTERFACE]["Name"]
+        return self._get_device_property(device_path, defs.DEVICE_INTERFACE, "Name")
 
     def is_connected(self, device_path: str) -> bool:
         """
@@ -792,8 +812,9 @@ async def _wait_removed(self, device_path: str) -> None:
 
         event = asyncio.Event()
 
-        def callback(_: str):
-            event.set()
+        def callback(o: str) -> None:
+            if o == device_path:
+                event.set()
 
         device_removed_callback_and_state = DeviceRemovedCallbackAndState(
             callback, self._properties[device_path][defs.DEVICE_INTERFACE]["Adapter"]
@@ -820,12 +841,11 @@ async def _wait_condition(
         Raises:
             BleakError: if the device is not present in BlueZ
         """
-        self._check_device(device_path)
+        value = self._get_device_property(
+            device_path, defs.DEVICE_INTERFACE, property_name
+        )
 
-        if (
-            self._properties[device_path][defs.DEVICE_INTERFACE][property_name]
-            == property_value
-        ):
+        if value == property_value:
             return
 
         event = asyncio.Event()
@@ -848,7 +868,7 @@ def _wait_condition_callback(new_value: Optional[Any]) -> None:
             if not device_callbacks:
                 del condition_callbacks[device_path]
 
-    def _parse_msg(self, message: Message):
+    def _parse_msg(self, message: Message) -> None:
         """
         Handles callbacks from dbus_fast.
         """
@@ -942,6 +962,12 @@ def _parse_msg(self, message: Message):
                         del self._descriptor_map[obj_path]
                     except KeyError:
                         pass
+
+            # Remove empty properties when all interfaces have been removed.
+            # This avoids wasting memory for people who have noisy devices
+            # with private addresses that change frequently.
+            if obj_path in self._properties and not self._properties[obj_path]:
+                del self._properties[obj_path]
         elif message.member == "PropertiesChanged":
             interface, changed, invalidated = message.body
             message_path = message.path
diff --git a/bleak/backends/bluezdbus/scanner.py b/bleak/backends/bluezdbus/scanner.py
index 24591398..d325ce92 100644
--- a/bleak/backends/bluezdbus/scanner.py
+++ b/bleak/backends/bluezdbus/scanner.py
@@ -19,7 +19,7 @@ class BlueZDiscoveryFilters(TypedDict, total=False):
     Dictionary of arguments for the ``org.bluez.Adapter1.SetDiscoveryFilter``
     D-Bus method.
 
-    https://github.com/bluez/bluez/blob/master/doc/adapter-api.txt
+    https://github.com/bluez/bluez/blob/master/doc/org.bluez.Adapter.rst#void-setdiscoveryfilterdict-filter
     """
 
     UUIDs: List[str]
@@ -93,7 +93,7 @@ class BleakScannerBlueZDBus(BaseBleakScanner):
 
     For possible values for `filters`, see the parameters to the
     ``SetDiscoveryFilter`` method in the `BlueZ docs
-    <https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt?h=5.48&id=0d1e3b9c5754022c779da129025d493a198d49cf>`_
+    <https://github.com/bluez/bluez/blob/master/doc/org.bluez.Adapter.rst#void-setdiscoveryfilterdict-filter>`_
 
     Args:
         detection_callback:
@@ -201,7 +201,7 @@ def set_scanning_filter(self, **kwargs) -> None:
 
         For possible values for `filters`, see the parameters to the
         ``SetDiscoveryFilter`` method in the `BlueZ docs
-        <https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt?h=5.48&id=0d1e3b9c5754022c779da129025d493a198d49cf>`_
+        <https://github.com/bluez/bluez/blob/master/doc/org.bluez.Adapter.rst#void-setdiscoveryfilterdict-filter>`_
 
         See variant types here: <https://python-dbus-next.readthedocs.io/en/latest/type-system/>
 
diff --git a/bleak/backends/bluezdbus/service.py b/bleak/backends/bluezdbus/service.py
index a6563782..a1a8d3c2 100644
--- a/bleak/backends/bluezdbus/service.py
+++ b/bleak/backends/bluezdbus/service.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import Any, List
 
 from ..service import BleakGATTService
 from .characteristic import BleakGATTCharacteristicBlueZDBus
@@ -8,7 +8,7 @@
 class BleakGATTServiceBlueZDBus(BleakGATTService):
     """GATT Service implementation for the BlueZ DBus backend"""
 
-    def __init__(self, obj, path):
+    def __init__(self, obj: Any, path: str):
         super().__init__(obj)
         self.__characteristics = []
         self.__path = path
@@ -29,7 +29,9 @@ def characteristics(self) -> List[BleakGATTCharacteristicBlueZDBus]:
         """List of characteristics for this service"""
         return self.__characteristics
 
-    def add_characteristic(self, characteristic: BleakGATTCharacteristicBlueZDBus):
+    def add_characteristic(
+        self, characteristic: BleakGATTCharacteristicBlueZDBus
+    ) -> None:
         """Add a :py:class:`~BleakGATTCharacteristicBlueZDBus` to the service.
 
         Should not be used by end user, but rather by `bleak` itself.
@@ -37,6 +39,6 @@ def add_characteristic(self, characteristic: BleakGATTCharacteristicBlueZDBus):
         self.__characteristics.append(characteristic)
 
     @property
-    def path(self):
+    def path(self) -> str:
         """The DBus path. Mostly needed by `bleak`, not by end user"""
         return self.__path
diff --git a/bleak/backends/bluezdbus/signals.py b/bleak/backends/bluezdbus/signals.py
index 059b65ad..6a2ce353 100644
--- a/bleak/backends/bluezdbus/signals.py
+++ b/bleak/backends/bluezdbus/signals.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from __future__ import annotations
 
 import re
 from typing import Any, Coroutine, Dict, Optional
@@ -19,7 +20,7 @@
 
 
 class InvalidMessageTypeError(TypeError):
-    def __init__(self, type):
+    def __init__(self, type: str):
         super().__init__(f"invalid message type: {type}")
 
 
@@ -37,7 +38,7 @@ def is_message_type_valid(type: str) -> bool:
     return type in _message_types
 
 
-def assert_bus_name_valid(type: str):
+def assert_bus_name_valid(type: str) -> None:
     """Raise an error if this is not a valid message type.
 
     .. seealso:: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
@@ -136,7 +137,7 @@ def __init__(
             self.args = None
 
     @staticmethod
-    def parse(rules: str):
+    def parse(rules: str) -> MatchRules:
         return MatchRules(**dict(r.split("=") for r in rules.split(",")))
 
     def __str__(self) -> str:
diff --git a/bleak/backends/bluezdbus/utils.py b/bleak/backends/bluezdbus/utils.py
index c8547dd8..e1acf99f 100644
--- a/bleak/backends/bluezdbus/utils.py
+++ b/bleak/backends/bluezdbus/utils.py
@@ -1,17 +1,15 @@
 # -*- coding: utf-8 -*-
 import os
-import re
+from typing import Optional
 
 from dbus_fast.auth import AuthExternal
 from dbus_fast.constants import MessageType
 from dbus_fast.message import Message
 
-from ...exc import BleakError, BleakDBusError
+from ...exc import BleakDBusError, BleakError
 
-_address_regex = re.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
 
-
-def assert_reply(reply: Message):
+def assert_reply(reply: Message) -> None:
     """Checks that a D-Bus message is a valid reply.
 
     Raises:
@@ -23,11 +21,7 @@ def assert_reply(reply: Message):
     assert reply.message_type == MessageType.METHOD_RETURN
 
 
-def validate_address(address):
-    return _address_regex.match(address) is not None
-
-
-def extract_service_handle_from_path(path):
+def extract_service_handle_from_path(path: str) -> int:
     try:
         return int(path[-4:], 16)
     except Exception as e:
@@ -61,7 +55,7 @@ def device_path_from_characteristic_path(characteristic_path: str) -> str:
     return characteristic_path[:37]
 
 
-def get_dbus_authenticator():
+def get_dbus_authenticator() -> Optional[AuthExternal]:
     uid = None
     try:
         uid = int(os.environ.get("BLEAK_DBUS_AUTH_UID", ""))
diff --git a/bleak/backends/bluezdbus/version.py b/bleak/backends/bluezdbus/version.py
index 3e3c616d..40842cb9 100644
--- a/bleak/backends/bluezdbus/version.py
+++ b/bleak/backends/bluezdbus/version.py
@@ -7,7 +7,7 @@
 logger = logging.getLogger(__name__)
 
 
-async def _get_bluetoothctl_version():
+async def _get_bluetoothctl_version() -> Optional[re.Match]:
     """Get the version of bluetoothctl."""
     with contextlib.suppress(Exception):
         proc = await asyncio.create_subprocess_exec(
diff --git a/bleak/backends/characteristic.py b/bleak/backends/characteristic.py
index c73d58b1..f83917a8 100644
--- a/bleak/backends/characteristic.py
+++ b/bleak/backends/characteristic.py
@@ -89,7 +89,7 @@ def max_write_without_response_size(self) -> int:
         .. warning:: Linux quirk: For BlueZ versions < 5.62, this property
             will always return ``20``.
 
-        .. versionadded:: 0.16.0
+        .. versionadded:: 0.16
         """
         return self._max_write_without_response_size
 
@@ -107,7 +107,7 @@ def get_descriptor(
         raise NotImplementedError()
 
     @abc.abstractmethod
-    def add_descriptor(self, descriptor: BleakGATTDescriptor):
+    def add_descriptor(self, descriptor: BleakGATTDescriptor) -> None:
         """Add a :py:class:`~BleakGATTDescriptor` to the characteristic.
 
         Should not be used by end user, but rather by `bleak` itself.
diff --git a/bleak/backends/client.py b/bleak/backends/client.py
index 307753e5..ddf77f2f 100644
--- a/bleak/backends/client.py
+++ b/bleak/backends/client.py
@@ -20,9 +20,9 @@
     from collections.abc import Buffer
 
 from ..exc import BleakError
-from .service import BleakGATTServiceCollection
 from .characteristic import BleakGATTCharacteristic
 from .device import BLEDevice
+from .service import BleakGATTServiceCollection
 
 NotifyCallback = Callable[[bytearray], None]
 
diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py
index 701319ec..6b876641 100644
--- a/bleak/backends/corebluetooth/CentralManagerDelegate.py
+++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py
@@ -10,7 +10,7 @@
 import logging
 import sys
 import threading
-from typing import Any, Callable, Dict, Optional
+from typing import Any, Callable, Dict, List, Optional
 
 if sys.version_info < (3, 11):
     from async_timeout import timeout as async_timeout
@@ -19,6 +19,7 @@
 
 import objc
 from CoreBluetooth import (
+    CBUUID,
     CBCentralManager,
     CBManagerStatePoweredOff,
     CBManagerStatePoweredOn,
@@ -27,9 +28,9 @@
     CBManagerStateUnknown,
     CBManagerStateUnsupported,
     CBPeripheral,
-    CBUUID,
 )
 from Foundation import (
+    NSUUID,
     NSArray,
     NSDictionary,
     NSError,
@@ -38,9 +39,8 @@
     NSNumber,
     NSObject,
     NSString,
-    NSUUID,
 )
-from libdispatch import dispatch_queue_create, DISPATCH_QUEUE_SERIAL
+from libdispatch import DISPATCH_QUEUE_SERIAL, dispatch_queue_create
 
 from ...exc import BleakError
 
@@ -103,7 +103,7 @@ def init(self) -> Optional["CentralManagerDelegate"]:
 
         return self
 
-    def __del__(self):
+    def __del__(self) -> None:
         if objc.macos_available(10, 13):
             try:
                 self.central_manager.removeObserver_forKeyPath_(self, "isScanning")
@@ -116,7 +116,7 @@ def __del__(self):
     # User defined functions
 
     @objc.python_method
-    async def start_scan(self, service_uuids) -> None:
+    async def start_scan(self, service_uuids: Optional[List[str]]) -> None:
         service_uuids = (
             NSArray.alloc().initWithArray_(
                 list(map(CBUUID.UUIDWithString_, service_uuids))
@@ -158,7 +158,7 @@ async def connect(
         self,
         peripheral: CBPeripheral,
         disconnect_callback: DisconnectCallback,
-        timeout=10.0,
+        timeout: float = 10.0,
     ) -> None:
         try:
             self._disconnect_callbacks[peripheral.identifier()] = disconnect_callback
diff --git a/bleak/backends/corebluetooth/PeripheralDelegate.py b/bleak/backends/corebluetooth/PeripheralDelegate.py
index 940d686d..d48f477c 100644
--- a/bleak/backends/corebluetooth/PeripheralDelegate.py
+++ b/bleak/backends/corebluetooth/PeripheralDelegate.py
@@ -6,6 +6,8 @@
 
 """
 
+from __future__ import annotations
+
 import asyncio
 import itertools
 import logging
@@ -18,14 +20,14 @@
     from asyncio import timeout as async_timeout
 
 import objc
-from Foundation import NSNumber, NSObject, NSArray, NSData, NSError, NSUUID, NSString
 from CoreBluetooth import (
-    CBPeripheral,
-    CBService,
     CBCharacteristic,
-    CBDescriptor,
     CBCharacteristicWriteWithResponse,
+    CBDescriptor,
+    CBPeripheral,
+    CBService,
 )
+from Foundation import NSUUID, NSArray, NSData, NSError, NSNumber, NSObject, NSString
 
 from ...exc import BleakError
 from ..client import NotifyCallback
@@ -43,7 +45,9 @@ class PeripheralDelegate(NSObject):
 
     ___pyobjc_protocols__ = [CBPeripheralDelegate]
 
-    def initWithPeripheral_(self, peripheral: CBPeripheral):
+    def initWithPeripheral_(
+        self, peripheral: CBPeripheral
+    ) -> Optional[PeripheralDelegate]:
         """macOS init function for NSObject"""
         self = objc.super(PeripheralDelegate, self).init()
 
@@ -123,9 +127,9 @@ async def discover_characteristics(self, service: CBService) -> NSArray:
     async def discover_descriptors(self, characteristic: CBCharacteristic) -> NSArray:
         future = self._event_loop.create_future()
 
-        self._characteristic_descriptor_discover_futures[
-            characteristic.handle()
-        ] = future
+        self._characteristic_descriptor_discover_futures[characteristic.handle()] = (
+            future
+        )
         try:
             self.peripheral.discoverDescriptorsForCharacteristic_(characteristic)
             await future
@@ -287,7 +291,7 @@ def did_discover_characteristics_for_service(
         service: CBService,
         characteristics: NSArray,
         error: Optional[NSError],
-    ):
+    ) -> None:
         future = self._service_characteristic_discovered_futures.get(
             service.startHandle()
         )
@@ -307,7 +311,7 @@ def did_discover_characteristics_for_service(
 
     def peripheral_didDiscoverCharacteristicsForService_error_(
         self, peripheral: CBPeripheral, service: CBService, error: Optional[NSError]
-    ):
+    ) -> None:
         logger.debug("peripheral_didDiscoverCharacteristicsForService_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_discover_characteristics_for_service,
@@ -323,7 +327,7 @@ def did_discover_descriptors_for_characteristic(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         future = self._characteristic_descriptor_discover_futures.get(
             characteristic.handle()
         )
@@ -346,7 +350,7 @@ def peripheral_didDiscoverDescriptorsForCharacteristic_error_(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         logger.debug("peripheral_didDiscoverDescriptorsForCharacteristic_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_discover_descriptors_for_characteristic,
@@ -362,7 +366,7 @@ def did_update_value_for_characteristic(
         characteristic: CBCharacteristic,
         value: NSData,
         error: Optional[NSError],
-    ):
+    ) -> None:
         c_handle = characteristic.handle()
 
         future = self._characteristic_read_futures.get(c_handle)
@@ -389,7 +393,7 @@ def peripheral_didUpdateValueForCharacteristic_error_(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         logger.debug("peripheral_didUpdateValueForCharacteristic_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_update_value_for_characteristic,
@@ -406,7 +410,7 @@ def did_update_value_for_descriptor(
         descriptor: CBDescriptor,
         value: NSObject,
         error: Optional[NSError],
-    ):
+    ) -> None:
         future = self._descriptor_read_futures.get(descriptor.handle())
         if not future:
             logger.warning("Unexpected event didUpdateValueForDescriptor")
@@ -425,7 +429,7 @@ def peripheral_didUpdateValueForDescriptor_error_(
         peripheral: CBPeripheral,
         descriptor: CBDescriptor,
         error: Optional[NSError],
-    ):
+    ) -> None:
         logger.debug("peripheral_didUpdateValueForDescriptor_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_update_value_for_descriptor,
@@ -441,7 +445,7 @@ def did_write_value_for_characteristic(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         future = self._characteristic_write_futures.get(characteristic.handle(), None)
         if not future:
             return  # event only expected on write with response
@@ -459,7 +463,7 @@ def peripheral_didWriteValueForCharacteristic_error_(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         logger.debug("peripheral_didWriteValueForCharacteristic_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_write_value_for_characteristic,
@@ -474,7 +478,7 @@ def did_write_value_for_descriptor(
         peripheral: CBPeripheral,
         descriptor: CBDescriptor,
         error: Optional[NSError],
-    ):
+    ) -> None:
         future = self._descriptor_write_futures.get(descriptor.handle())
         if not future:
             logger.warning("Unexpected event didWriteValueForDescriptor")
@@ -493,7 +497,7 @@ def peripheral_didWriteValueForDescriptor_error_(
         peripheral: CBPeripheral,
         descriptor: CBDescriptor,
         error: Optional[NSError],
-    ):
+    ) -> None:
         logger.debug("peripheral_didWriteValueForDescriptor_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_write_value_for_descriptor,
@@ -508,7 +512,7 @@ def did_update_notification_for_characteristic(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         c_handle = characteristic.handle()
         future = self._characteristic_notify_change_futures.get(c_handle)
         if not future:
@@ -530,7 +534,7 @@ def peripheral_didUpdateNotificationStateForCharacteristic_error_(
         peripheral: CBPeripheral,
         characteristic: CBCharacteristic,
         error: Optional[NSError],
-    ):
+    ) -> None:
         logger.debug("peripheral_didUpdateNotificationStateForCharacteristic_error_")
         self._event_loop.call_soon_threadsafe(
             self.did_update_notification_for_characteristic,
diff --git a/bleak/backends/corebluetooth/characteristic.py b/bleak/backends/corebluetooth/characteristic.py
index 09f60729..116c7267 100644
--- a/bleak/backends/corebluetooth/characteristic.py
+++ b/bleak/backends/corebluetooth/characteristic.py
@@ -4,6 +4,7 @@
 Created on 2019-06-28 by kevincar <kevincarrolldavis@gmail.com>
 
 """
+
 from enum import Enum
 from typing import Dict, List, Optional, Tuple, Union
 
diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py
index c27aeabf..470fc983 100644
--- a/bleak/backends/corebluetooth/client.py
+++ b/bleak/backends/corebluetooth/client.py
@@ -3,6 +3,7 @@
 
 Created on 2019-06-26 by kevincar <kevincarrolldavis@gmail.com>
 """
+
 import asyncio
 import logging
 import sys
@@ -24,7 +25,11 @@
 from Foundation import NSArray, NSData
 
 from ... import BleakScanner
-from ...exc import BleakError, BleakDeviceNotFoundError
+from ...exc import (
+    BleakCharacteristicNotFoundError,
+    BleakDeviceNotFoundError,
+    BleakError,
+)
 from ..characteristic import BleakGATTCharacteristic
 from ..client import BaseBleakClient, NotifyCallback
 from ..device import BLEDevice
@@ -76,7 +81,7 @@ def __init__(
             else None
         )
 
-    def __str__(self):
+    def __str__(self) -> str:
         return "BleakClientCoreBluetooth ({})".format(self.address)
 
     async def connect(self, **kwargs) -> bool:
@@ -107,7 +112,7 @@ async def connect(self, **kwargs) -> bool:
                 self._peripheral
             )
 
-        def disconnect_callback():
+        def disconnect_callback() -> None:
             # Ensure that `get_services` retrieves services again, rather
             # than using the cached object
             self.services = None
@@ -252,7 +257,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection:
     async def read_gatt_char(
         self,
         char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID],
-        use_cached=False,
+        use_cached: bool = False,
         **kwargs,
     ) -> bytearray:
         """Perform read operation on the specified GATT characteristic.
@@ -273,7 +278,7 @@ async def read_gatt_char(
         else:
             characteristic = char_specifier
         if not characteristic:
-            raise BleakError("Characteristic {} was not found!".format(char_specifier))
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         output = await self._delegate.read_characteristic(
             characteristic.obj, use_cached=use_cached
@@ -283,7 +288,7 @@ async def read_gatt_char(
         return value
 
     async def read_gatt_descriptor(
-        self, handle: int, use_cached=False, **kwargs
+        self, handle: int, use_cached: bool = False, **kwargs
     ) -> bytearray:
         """Perform read operation on the specified GATT descriptor.
 
@@ -321,9 +326,11 @@ async def write_gatt_char(
         await self._delegate.write_characteristic(
             characteristic.obj,
             value,
-            CBCharacteristicWriteWithResponse
-            if response
-            else CBCharacteristicWriteWithoutResponse,
+            (
+                CBCharacteristicWriteWithResponse
+                if response
+                else CBCharacteristicWriteWithoutResponse
+            ),
         )
         logger.debug(f"Write Characteristic {characteristic.uuid} : {data}")
 
@@ -373,7 +380,7 @@ async def stop_notify(
         else:
             characteristic = char_specifier
         if not characteristic:
-            raise BleakError("Characteristic {} not found!".format(char_specifier))
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         await self._delegate.stop_notifications(characteristic.obj)
 
diff --git a/bleak/backends/corebluetooth/descriptor.py b/bleak/backends/corebluetooth/descriptor.py
index 923134dd..646a1605 100644
--- a/bleak/backends/corebluetooth/descriptor.py
+++ b/bleak/backends/corebluetooth/descriptor.py
@@ -4,6 +4,7 @@
 Created on 2019-06-28 by kevincar <kevincarrolldavis@gmail.com>
 
 """
+
 from CoreBluetooth import CBDescriptor
 
 from ..corebluetooth.utils import cb_uuid_to_str
diff --git a/bleak/backends/corebluetooth/scanner.py b/bleak/backends/corebluetooth/scanner.py
index e6d20c2d..6452a28d 100644
--- a/bleak/backends/corebluetooth/scanner.py
+++ b/bleak/backends/corebluetooth/scanner.py
@@ -130,6 +130,11 @@ def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None:
                 address_bytes: bytes = (
                     self._manager.central_manager.retrieveAddressForPeripheral_(p)
                 )
+                if address_bytes is None:
+                    logger.debug(
+                        "Could not get Bluetooth address for %s. Ignoring this device.",
+                        p.identifier().UUIDString(),
+                    )
                 address = address_bytes.hex(":").upper()
             else:
                 address = p.identifier().UUIDString()
diff --git a/bleak/backends/corebluetooth/service.py b/bleak/backends/corebluetooth/service.py
index 695181c7..14a35a7a 100644
--- a/bleak/backends/corebluetooth/service.py
+++ b/bleak/backends/corebluetooth/service.py
@@ -32,7 +32,9 @@ def characteristics(self) -> List[BleakGATTCharacteristicCoreBluetooth]:
         """List of characteristics for this service"""
         return self.__characteristics
 
-    def add_characteristic(self, characteristic: BleakGATTCharacteristicCoreBluetooth):
+    def add_characteristic(
+        self, characteristic: BleakGATTCharacteristicCoreBluetooth
+    ) -> None:
         """Add a :py:class:`~BleakGATTCharacteristicCoreBluetooth` to the service.
 
         Should not be used by end user, but rather by `bleak` itself.
diff --git a/bleak/backends/corebluetooth/utils.py b/bleak/backends/corebluetooth/utils.py
index e635151a..92095473 100644
--- a/bleak/backends/corebluetooth/utils.py
+++ b/bleak/backends/corebluetooth/utils.py
@@ -1,5 +1,5 @@
-from Foundation import NSData
 from CoreBluetooth import CBUUID
+from Foundation import NSData
 
 from ...uuids import normalize_uuid_str
 
diff --git a/bleak/backends/p4android/client.py b/bleak/backends/p4android/client.py
index 4bab6913..92d51075 100644
--- a/bleak/backends/p4android/client.py
+++ b/bleak/backends/p4android/client.py
@@ -11,7 +11,7 @@
 from android.broadcast import BroadcastReceiver
 from jnius import java_method
 
-from ...exc import BleakError
+from ...exc import BleakCharacteristicNotFoundError, BleakError
 from ..characteristic import BleakGATTCharacteristic
 from ..client import BaseBleakClient, NotifyCallback
 from ..device import BLEDevice
@@ -316,9 +316,7 @@ async def read_gatt_char(
             characteristic = char_specifier
 
         if not characteristic:
-            raise BleakError(
-                f"Characteristic with UUID {char_specifier} could not be found!"
-            )
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         (value,) = await self.__callbacks.perform_and_wait(
             dispatchApi=self.__gatt.readCharacteristic,
@@ -469,7 +467,7 @@ async def stop_notify(
         else:
             characteristic = char_specifier
         if not characteristic:
-            raise BleakError(f"Characteristic {char_specifier} not found!")
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         await self.write_gatt_descriptor(
             characteristic.notification_descriptor,
diff --git a/bleak/backends/p4android/defs.py b/bleak/backends/p4android/defs.py
index 0fbee94d..f5245450 100644
--- a/bleak/backends/p4android/defs.py
+++ b/bleak/backends/p4android/defs.py
@@ -2,9 +2,10 @@
 
 import enum
 
-import bleak.exc
 from jnius import autoclass, cast
 
+import bleak.exc
+
 # caching constants avoids unnecessary extra use of the jni-python interface, which can be slow
 
 List = autoclass("java.util.ArrayList")
diff --git a/bleak/backends/p4android/recipes/bleak/__init__.py b/bleak/backends/p4android/recipes/bleak/__init__.py
index 1e9c17d3..5c10397f 100644
--- a/bleak/backends/p4android/recipes/bleak/__init__.py
+++ b/bleak/backends/p4android/recipes/bleak/__init__.py
@@ -1,9 +1,9 @@
 import os
+from os.path import join
 
-from pythonforandroid.recipe import PythonRecipe
-from pythonforandroid.toolchain import shprint, info
 import sh
-from os.path import join
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.toolchain import info, shprint
 
 
 class BleakRecipe(PythonRecipe):
diff --git a/bleak/backends/p4android/scanner.py b/bleak/backends/p4android/scanner.py
index e53662f6..c9a6070d 100644
--- a/bleak/backends/p4android/scanner.py
+++ b/bleak/backends/p4android/scanner.py
@@ -243,10 +243,10 @@ def _handle_scan_result(self, result) -> None:
             entry.getKey().toString(): bytes(entry.getValue())
             for entry in record.getServiceData().entrySet()
         }
-        tx_power = result.getTxPower()
+        tx_power = record.getTxPowerLevel()
 
         # change "not present" value to None to match other backends
-        if tx_power == defs.ScanResult.TX_POWER_NOT_PRESENT:
+        if tx_power == -2147483648:  # Integer#MIN_VALUE
             tx_power = None
 
         advertisement = AdvertisementData(
diff --git a/bleak/backends/scanner.py b/bleak/backends/scanner.py
index 60396fa7..693b20db 100644
--- a/bleak/backends/scanner.py
+++ b/bleak/backends/scanner.py
@@ -55,16 +55,16 @@ class AdvertisementData(NamedTuple):
 
     tx_power: Optional[int]
     """
-    Tx Power data from the received advertising data or ``None`` if not present.
+    TX Power Level of the remote device from the received advertising data or ``None`` if not present.
 
-    .. versionadded:: 0.17.0
+    .. versionadded:: 0.17
     """
 
     rssi: int
     """
     The Radio Receive Signal Strength (RSSI) in dBm.
 
-    .. versionadded:: 0.19.0
+    .. versionadded:: 0.19
     """
 
     platform_data: Tuple
@@ -180,7 +180,7 @@ def register_detection_callback(
 
         if inspect.iscoroutinefunction(callback):
 
-            def detection_callback(s, d):
+            def detection_callback(s: BLEDevice, d: AdvertisementData) -> None:
                 task = asyncio.create_task(callback(s, d))
                 _background_tasks.add(task)
                 task.add_done_callback(_background_tasks.discard)
@@ -192,7 +192,7 @@ def detection_callback(s, d):
 
         self._ad_callbacks[token] = detection_callback
 
-        def remove():
+        def remove() -> None:
             self._ad_callbacks.pop(token, None)
 
         return remove
@@ -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/service.py b/bleak/backends/service.py
index 305e8bd2..09c503c9 100644
--- a/bleak/backends/service.py
+++ b/bleak/backends/service.py
@@ -7,11 +7,11 @@
 """
 import abc
 import logging
-from typing import Dict, Iterator, List, Optional, Union
+from typing import Any, Dict, Iterator, List, Optional, Union
 from uuid import UUID
 
 from ..exc import BleakError
-from ..uuids import uuidstr_to_str, normalize_uuid_str
+from ..uuids import normalize_uuid_str, uuidstr_to_str
 from .characteristic import BleakGATTCharacteristic
 from .descriptor import BleakGATTDescriptor
 
@@ -21,10 +21,10 @@
 class BleakGATTService(abc.ABC):
     """Interface for the Bleak representation of a GATT Service."""
 
-    def __init__(self, obj):
+    def __init__(self, obj: Any) -> None:
         self.obj = obj
 
-    def __str__(self):
+    def __str__(self) -> str:
         return f"{self.uuid} (Handle: {self.handle}): {self.description}"
 
     @property
@@ -51,7 +51,7 @@ def characteristics(self) -> List[BleakGATTCharacteristic]:
         raise NotImplementedError()
 
     @abc.abstractmethod
-    def add_characteristic(self, characteristic: BleakGATTCharacteristic):
+    def add_characteristic(self, characteristic: BleakGATTCharacteristic) -> None:
         """Add a :py:class:`~BleakGATTCharacteristic` to the service.
 
         Should not be used by end user, but rather by `bleak` itself.
@@ -81,7 +81,7 @@ def get_characteristic(
 class BleakGATTServiceCollection:
     """Simple data container for storing the peripheral's service complement."""
 
-    def __init__(self):
+    def __init__(self) -> None:
         self.__services = {}
         self.__characteristics = {}
         self.__descriptors = {}
@@ -117,7 +117,7 @@ def descriptors(self) -> Dict[int, BleakGATTDescriptor]:
         """Returns a dictionary of integer handles mapping to BleakGATTDescriptor"""
         return self.__descriptors
 
-    def add_service(self, service: BleakGATTService):
+    def add_service(self, service: BleakGATTService) -> None:
         """Add a :py:class:`~BleakGATTService` to the service collection.
 
         Should not be used by end user, but rather by `bleak` itself.
@@ -153,7 +153,7 @@ def get_service(
 
         return x[0] if x else None
 
-    def add_characteristic(self, characteristic: BleakGATTCharacteristic):
+    def add_characteristic(self, characteristic: BleakGATTCharacteristic) -> None:
         """Add a :py:class:`~BleakGATTCharacteristic` to the service collection.
 
         Should not be used by end user, but rather by `bleak` itself.
@@ -176,10 +176,12 @@ def get_characteristic(
         if isinstance(specifier, int):
             return self.characteristics.get(specifier)
 
+        uuid = normalize_uuid_str(str(specifier))
+
         # Assume uuid usage.
         x = list(
             filter(
-                lambda x: x.uuid == str(specifier).lower(),
+                lambda x: x.uuid == uuid,
                 self.characteristics.values(),
             )
         )
@@ -191,7 +193,7 @@ def get_characteristic(
 
         return x[0] if x else None
 
-    def add_descriptor(self, descriptor: BleakGATTDescriptor):
+    def add_descriptor(self, descriptor: BleakGATTDescriptor) -> None:
         """Add a :py:class:`~BleakGATTDescriptor` to the service collection.
 
         Should not be used by end user, but rather by `bleak` itself.
diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py
index 6a84e21e..518fa0fe 100644
--- a/bleak/backends/winrt/client.py
+++ b/bleak/backends/winrt/client.py
@@ -101,7 +101,12 @@
     from bleak_winrt.windows.storage.streams import Buffer as WinBuffer
 
 from ... import BleakScanner
-from ...exc import PROTOCOL_ERROR_CODES, BleakDeviceNotFoundError, BleakError
+from ...exc import (
+    PROTOCOL_ERROR_CODES,
+    BleakCharacteristicNotFoundError,
+    BleakDeviceNotFoundError,
+    BleakError,
+)
 from ..characteristic import BleakGATTCharacteristic
 from ..client import BaseBleakClient, NotifyCallback
 from ..device import BLEDevice
@@ -376,6 +381,8 @@ def session_status_changed_event_handler(
             )
             loop.call_soon_threadsafe(handle_session_status_changed, args)
 
+        pdu_size_event = asyncio.Event()
+
         def max_pdu_size_changed_handler(sender: GattSession, args):
             try:
                 max_pdu_size = sender.max_pdu_size
@@ -386,6 +393,7 @@ def max_pdu_size_changed_handler(sender: GattSession, args):
                 return
 
             logger.debug("max_pdu_size_changed_handler: %d", max_pdu_size)
+            pdu_size_event.set()
 
         # Start a GATT Session to connect
         event = asyncio.Event()
@@ -482,6 +490,29 @@ def max_pdu_size_changed_handler(sender: GattSession, args):
                             cache_mode=cache_mode,
                         )
 
+                # There is a race condition where the max_pdu_size_changed event
+                # might not be received before the get_services() call completes.
+                # We could put this wait before getting services, but that would
+                # make the connection time longer. So we put it here instead and
+                # fix up the characteristics if necessary.
+                if not pdu_size_event.is_set():
+                    try:
+                        # REVISIT: Devices that don't support > default PDU size
+                        # may be punished by this timeout with a slow connection
+                        # time. We may want to consider an option to ignore this
+                        # timeout for such devices.
+                        async with async_timeout(1):
+                            await pdu_size_event.wait()
+                    except asyncio.TimeoutError:
+                        logger.debug(
+                            "max_pdu_size_changed event not received, using default"
+                        )
+
+                for char in self.services.characteristics.values():
+                    char._max_write_without_response_size = (
+                        self._session.max_pdu_size - 3
+                    )
+
                 # a connection may not be made until we request info from the
                 # device, so we have to get services before the GATT session
                 # is set to active
@@ -758,10 +789,10 @@ def dispose_on_cancel(future):
                         f"Could not get GATT descriptors for characteristic {characteristic.uuid} ({characteristic.attribute_handle})",
                     )
 
+                    # NB: max_pdu_size might not be valid at this time so we
+                    # start with default size and will update later
                     new_services.add_characteristic(
-                        BleakGATTCharacteristicWinRT(
-                            characteristic, self._session.max_pdu_size - 3
-                        )
+                        BleakGATTCharacteristicWinRT(characteristic, 20)
                     )
 
                     for descriptor in descriptors:
@@ -820,7 +851,7 @@ async def read_gatt_char(
         else:
             characteristic = char_specifier
         if not characteristic:
-            raise BleakError(f"Characteristic {char_specifier} was not found!")
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         value = bytearray(
             _ensure_success(
@@ -1007,7 +1038,7 @@ async def stop_notify(
         else:
             characteristic = char_specifier
         if not characteristic:
-            raise BleakError(f"Characteristic {char_specifier} not found!")
+            raise BleakCharacteristicNotFoundError(char_specifier)
 
         _ensure_success(
             await characteristic.obj.write_client_characteristic_configuration_descriptor_async(
diff --git a/bleak/backends/winrt/scanner.py b/bleak/backends/winrt/scanner.py
index bfd46c71..651fbc75 100644
--- a/bleak/backends/winrt/scanner.py
+++ b/bleak/backends/winrt/scanner.py
@@ -4,6 +4,8 @@
 from typing import Dict, List, Literal, NamedTuple, Optional
 from uuid import UUID
 
+from .util import assert_mta
+
 if sys.version_info >= (3, 12):
     from winrt.windows.devices.bluetooth.advertisement import (
         BluetoothLEAdvertisementReceivedEventArgs,
@@ -22,6 +24,7 @@
     )
 
 from ...assigned_numbers import AdvertisementDataType
+from ...exc import BleakError
 from ...uuids import normalize_uuid_str
 from ..scanner import AdvertisementData, AdvertisementDataCallback, BaseBleakScanner
 
@@ -83,7 +86,7 @@ def __init__(
     ):
         super(BleakScannerWinRT, self).__init__(detection_callback, service_uuids)
 
-        self.watcher = None
+        self.watcher: Optional[BluetoothLEAdvertisementWatcher] = None
         self._advertisement_pairs: Dict[int, _RawAdvData] = {}
         self._stopped_event = None
 
@@ -93,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)
 
@@ -195,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):
@@ -220,6 +214,13 @@ def _stopped_handler(self, sender, e):
         self._stopped_event.set()
 
     async def start(self) -> None:
+        if self.watcher:
+            raise BleakError("Scanner already started")
+
+        # Callbacks for WinRT async methods will never happen in STA mode if
+        # there is nothing pumping a Windows message loop.
+        assert_mta()
+
         # start with fresh list of discovered devices
         self.seen_devices = {}
         self._advertisement_pairs.clear()
@@ -244,6 +245,16 @@ async def start(self) -> None:
 
         self.watcher.start()
 
+        # no events for status changes, so we have to poll :-(
+        while self.watcher.status == BluetoothLEAdvertisementWatcherStatus.CREATED:
+            await asyncio.sleep(0.01)
+
+        if self.watcher.status == BluetoothLEAdvertisementWatcherStatus.ABORTED:
+            raise BleakError("Failed to start scanner. Is Bluetooth turned on?")
+
+        if self.watcher.status != BluetoothLEAdvertisementWatcherStatus.STARTED:
+            raise BleakError(f"Unexpected watcher status: {self.watcher.status.name}")
+
     async def stop(self) -> None:
         self.watcher.stop()
 
diff --git a/bleak/backends/winrt/util.py b/bleak/backends/winrt/util.py
new file mode 100644
index 00000000..363cf858
--- /dev/null
+++ b/bleak/backends/winrt/util.py
@@ -0,0 +1,100 @@
+import ctypes
+from enum import IntEnum
+from typing import Tuple
+
+from ...exc import BleakError
+
+
+def _check_hresult(result, func, args):
+    if result:
+        raise ctypes.WinError(result)
+
+    return args
+
+
+# https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cogetapartmenttype
+_CoGetApartmentType = ctypes.windll.ole32.CoGetApartmentType
+_CoGetApartmentType.restype = ctypes.c_int
+_CoGetApartmentType.argtypes = [
+    ctypes.POINTER(ctypes.c_int),
+    ctypes.POINTER(ctypes.c_int),
+]
+_CoGetApartmentType.errcheck = _check_hresult
+
+_CO_E_NOTINITIALIZED = -2147221008
+
+
+# https://learn.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-apttype
+class _AptType(IntEnum):
+    CURRENT = -1
+    STA = 0
+    MTA = 1
+    NA = 2
+    MAIN_STA = 3
+
+
+# https://learn.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-apttypequalifier
+class _AptQualifierType(IntEnum):
+    NONE = 0
+    IMPLICIT_MTA = 1
+    NA_ON_MTA = 2
+    NA_ON_STA = 3
+    NA_ON_IMPLICIT_STA = 4
+    NA_ON_MAIN_STA = 5
+    APPLICATION_STA = 6
+    RESERVED_1 = 7
+
+
+def _get_apartment_type() -> Tuple[_AptType, _AptQualifierType]:
+    """
+    Calls CoGetApartmentType to get the current apartment type and qualifier.
+
+    Returns:
+        The current apartment type and qualifier.
+    Raises:
+        OSError: If the call to CoGetApartmentType fails.
+    """
+    api_type = ctypes.c_int()
+    api_type_qualifier = ctypes.c_int()
+    _CoGetApartmentType(ctypes.byref(api_type), ctypes.byref(api_type_qualifier))
+    return _AptType(api_type.value), _AptQualifierType(api_type_qualifier.value)
+
+
+def assert_mta() -> None:
+    """
+    Asserts that the current apartment type is MTA.
+
+    Raises:
+        BleakError: If the current apartment type is not MTA.
+
+    .. versionadded:: 0.22
+    """
+    try:
+        apt_type, _ = _get_apartment_type()
+        if apt_type != _AptType.MTA:
+            raise BleakError(
+                f"The current thread apartment type is not MTA: {apt_type.name}. Beware of packages like pywin32 that may change the apartment type implicitly."
+            )
+    except OSError as e:
+        # All is OK if not initialized yet. WinRT will initialize it.
+        if e.winerror != _CO_E_NOTINITIALIZED:
+            raise
+
+
+def uninitialize_sta():
+    """
+    Uninitialize the COM library on the current thread if it was not initialized
+    as MTA.
+
+    This is intended to undo the implicit initialization of the COM library as STA
+    by packages like pywin32.
+
+    It should be called as early as possible in your application after the
+    offending package has been imported.
+
+    .. versionadded:: 0.22
+    """
+    try:
+        assert_mta()
+    except BleakError:
+        ctypes.windll.ole32.CoUninitialize()
diff --git a/bleak/exc.py b/bleak/exc.py
index 2126de15..d03d2035 100644
--- a/bleak/exc.py
+++ b/bleak/exc.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
-from typing import Optional
+import uuid
+from typing import Optional, Union
 
 
 class BleakError(Exception):
@@ -8,12 +9,30 @@ class BleakError(Exception):
     pass
 
 
+class BleakCharacteristicNotFoundError(BleakError):
+    """
+    Exception which is raised if a device does not support a characteristic.
+
+    .. versionadded: 0.22
+    """
+
+    char_specifier: Union[int, str, uuid.UUID]
+
+    def __init__(self, char_specifier: Union[int, str, uuid.UUID]) -> None:
+        """
+        Args:
+            characteristic (str): handle or UUID of the characteristic which was not found
+        """
+        super().__init__(f"Characteristic {char_specifier} was not found!")
+        self.char_specifier = char_specifier
+
+
 class BleakDeviceNotFoundError(BleakError):
     """
     Exception which is raised if a device can not be found by ``connect``, ``pair`` and ``unpair``.
     This is the case if the OS Bluetooth stack has never seen this device or it was removed and forgotten.
 
-    .. versionadded: 0.19.0
+    .. versionadded: 0.19
     """
 
     identifier: str
diff --git a/bleak/uuids.py b/bleak/uuids.py
index 386af91b..ed92698c 100644
--- a/bleak/uuids.py
+++ b/bleak/uuids.py
@@ -3,7 +3,6 @@
 from typing import Dict
 from uuid import UUID
 
-
 uuid16_dict: Dict[int, str] = {
     0x0001: "SDP",
     0x0003: "RFCOMM",
@@ -150,9 +149,10 @@
     0x183A: "Insulin Delivery",
     0x183B: "Binary Sensor",
     0x183C: "Emergency Configuration",
-    # 0x183D undefined
+    0x183D: "Authorization Control",
     0x183E: "Physical Activity Monitor",
-    # 0x183F-0x1842 undefined
+    0x183F: "Elapsed Time",
+    0x1840: "Generic Health Sensor",
     0x1843: "Audio Input Control",
     0x1844: "Volume Control",
     0x1845: "Volume Offset Control",
@@ -169,7 +169,13 @@
     0x1850: "Published Audio Capabilities Service",
     0x1851: "Basic Audio Announcement Service",
     0x1852: "Broadcast Audio Announcement Service",
-    # 0x1853 to 0x26ff undefined */
+    0x1853: "Common Audio",
+    0x1854: "Hearing Access",
+    0x1855: "Telephony and Media Audio",
+    0x1856: "Public Broadcast Announcement",
+    0x1857: "Electronic Shelf Label",
+    0x1859: "Mesh Proxy Solicitation",
+    # 0x185A to 0x26ff undefined */
     # 0x2700.. GATT Units
     0x2700: "unitless",
     0x2701: "length (metre)",
@@ -617,10 +623,15 @@
     0x2B2C: "BSS Response",
     0x2B2D: "Emergency ID",
     0x2B2E: "Emergency Text",
+    0x2B2F: "ACS Status",
+    0x2B30: "ACS Data In",
+    0x2B31: "ACS Data Out Notify",
+    0x2B32: "ACS Data Out Indicate",
+    0x2B33: "ACS Control Point",
     0x2B34: "Enhanced Blood Pressure Measurement",
     0x2B35: "Enhanced Intermediate Cuff Pressure",
     0x2B36: "Blood Pressure Record",
-    # 0x2B37 undefined
+    0x2B37: "Registered User",
     0x2B38: "BR-EDR Handover Data",
     0x2B39: "Bluetooth SIG Data",
     0x2B3A: "Server Supported Features",
@@ -646,6 +657,7 @@
     0x2B4E: "Activity Goal",
     0x2B4F: "Sedentary Interval Notification",
     0x2B50: "Caloric Intake",
+    0x2B51: "TMAP Role",
     0x2B77: "Audio Input State",
     0x2B78: "Gain Settings Attribute",
     0x2B79: "Audio Input Type",
@@ -663,6 +675,12 @@
     0x2B85: "Size Characteristic",
     0x2B86: "Lock Characteristic",
     0x2B87: "Rank Characteristic",
+    0x2B88: "Encrypted Data Key Material",
+    0x2B89: "Apparent Energy 32",
+    0x2B8A: "Apparent Power",
+    0x2B8B: "Live Health Observations",
+    0x2B8C: "CO\textsubscript{2} Concentration",
+    0x2B8D: "Cosine of the Angle",
     0x2B8E: "Device Time Feature",
     0x2B8F: "Device Time Parameters",
     0x2B90: "Device Time",
@@ -689,6 +707,7 @@
     0x2BA5: "Media Control Point Opcodes Supported",
     0x2BA6: "Search Results Object ID",
     0x2BA7: "Search Control Point",
+    0x2BA8: "Energy 32",
     0x2BA9: "Media Player Icon Object Type",
     0x2BAA: "Track Segments Object Type",
     0x2BAB: "Track Object Type",
@@ -738,6 +757,44 @@
     0x2BD7: "Particulate Matter - PM10 Concentration",
     0x2BD8: "Sulfur Dioxide Concentration",
     0x2BD9: "Sulfur Hexafluoride Concentration",
+    0x2BDA: "Hearing Aid Features",
+    0x2BDB: "Hearing Aid Preset Control Point",
+    0x2BDC: "Active Preset Index",
+    0x2BDD: "Stored Health Observations",
+    0x2BDE: "Fixed String 64",
+    0x2BDF: "High Temperature",
+    0x2BE0: "High Voltage",
+    0x2BE1: "Light Distribution",
+    0x2BE2: "Light Output",
+    0x2BE3: "Light Source Type",
+    0x2BE4: "Noise",
+    0x2BE5: "Relative Runtime in a Correlated Color Temperature Range",
+    0x2BE6: "Time Second 32",
+    0x2BE7: "VOC Concentration",
+    0x2BE8: "Voltage Frequency",
+    0x2BE9: "Battery Critical Status",
+    0x2BEA: "Battery Health Status",
+    0x2BEB: "Battery Health Information",
+    0x2BEC: "Battery Information",
+    0x2BED: "Battery Level Status",
+    0x2BEE: "Battery Time Status",
+    0x2BEF: "Estimated Service Date",
+    0x2BF0: "Battery Energy Status",
+    0x2BF1: "Observation Schedule Changed",
+    0x2BF2: "Current Elapsed Time",
+    0x2BF3: "Health Sensor Features",
+    0x2BF4: "GHS Control Point",
+    0x2BF5: "LE GATT Security Levels",
+    0x2BF6: "ESL Address",
+    0x2BF7: "AP Sync Key Material",
+    0x2BF8: "ESL Response Key Material",
+    0x2BF9: "ESL Current Absolute Time",
+    0x2BFA: "ESL Display Information",
+    0x2BFB: "ESL Image Information",
+    0x2BFC: "ESL Sensor Information",
+    0x2BFD: "ESL LED Information",
+    0x2BFE: "ESL Control Point",
+    0x2BFF: "UDI for Medical Devices",
     0xFE1C: "NetMedia: Inc.",
     0xFE1D: "Illuminati Instrument Corporation",
     0xFE1E: "Smart Innovations Co.: Ltd",
@@ -1101,11 +1158,7 @@
     "9fa480e0-4967-4542-9390-d343dc5d04ae": "Apple Nearby Service",
     "af0badb1-5b99-43cd-917a-a77bc549e3cc": "Nearby Characteristic",
     "69d1d8f3-45e1-49a8-9821-9bbdfdaad9d9": "Control Point",
-    "9fbf120d-6301-42d9-8c58-25e699a21dbd": "Notification Source",
     "89d3502b-0f36-433a-8ef4-c502ad55f8dc": "Apple Media Service",
-    "9b3c81d8-57b1-4a8a-b8df-0e56f7ca51c2": "Remote Command",
-    "2f7cabce-808d-411f-9a0c-bb92ba96c102": "Entity Update",
-    "c6b2f38c-23ab-46d8-a6ab-a3a870bbd5d7": "Entity Attribute",
     "955a1523-0fe2-f5aa-a094-84b8d4f3e8ad": "Beacon Config",
     "a6c31337-6c07-453e-961a-d8a8a41bf368": "Candy Dispenser Service",
     "00001530-1212-efde-1523-785feabcd123": "Device Firmware Update Service",
@@ -1123,7 +1176,7 @@
 }
 
 
-def uuidstr_to_str(uuid_):
+def uuidstr_to_str(uuid_: str) -> str:
     uuid_ = uuid_.lower()
     s = uuid128_dict.get(uuid_)
     if s:
diff --git a/docs/backends/windows.rst b/docs/backends/windows.rst
index 5f3c628e..d3acfa8a 100644
--- a/docs/backends/windows.rst
+++ b/docs/backends/windows.rst
@@ -19,6 +19,12 @@ Client
 API
 ---
 
+Utilities
+~~~~~~~~~
+
+.. automodule:: bleak.backends.winrt.util
+    :members:
+
 Scanner
 ~~~~~~~
 
diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst
index 441dcade..e299d18f 100644
--- a/docs/troubleshooting.rst
+++ b/docs/troubleshooting.rst
@@ -65,6 +65,32 @@ See `#635 <https://github.com/hbldh/bleak/issues/635>`_ and
 `#720 <https://github.com/hbldh/bleak/issues/720>`_ for more information
 including some partial workarounds if you need to support these macOS versions.
 
+------------
+Windows Bugs
+------------
+
+Not working when threading model is STA
+=======================================
+
+Packages like ``pywin32`` and it's subsidiaries have an unfortunate side effect
+of initializing the threading model to Single Threaded Apartment (STA) when
+imported. This causes async WinRT functions to never complete. because there
+isn't a message loop running. Bleak needs to run in a Multi Threaded Apartment
+(MTA) instead (this happens automatically on the first WinRT call).
+
+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::
+
+    import win32com  # this sets current thread to STA :-(
+    from bleak.backends.winrt.utils import uninitialize_sta
+
+    uninitialize_sta()  # undo the unwanted side effect
+
+
 --------------
 Enable Logging
 --------------
diff --git a/examples/async_callback_with_queue.py b/examples/async_callback_with_queue.py
index 960e0f77..262854b5 100644
--- a/examples/async_callback_with_queue.py
+++ b/examples/async_callback_with_queue.py
@@ -9,10 +9,11 @@
 Created on 2021-02-25 by hbldh <henrik.blidh@nedomkull.com>
 
 """
+
 import argparse
-import time
 import asyncio
 import logging
+import time
 
 from bleak import BleakClient, BleakScanner
 
diff --git a/examples/disconnect_callback.py b/examples/disconnect_callback.py
index 9c455836..e1dcb4bc 100644
--- a/examples/disconnect_callback.py
+++ b/examples/disconnect_callback.py
@@ -12,8 +12,7 @@
 import asyncio
 import logging
 
-from bleak import BleakScanner, BleakClient
-
+from bleak import BleakClient, BleakScanner
 
 logger = logging.getLogger(__name__)
 
diff --git a/examples/kivy/main.py b/examples/kivy/main.py
index c90e340a..dd965d3b 100644
--- a/examples/kivy/main.py
+++ b/examples/kivy/main.py
@@ -1,15 +1,13 @@
 import asyncio
-import bleak
 
+import bleak
 from kivy.app import App
-
-# from kivy.core.window import Window
 from kivy.uix.label import Label
 from kivy.uix.scrollview import ScrollView
 
 # bind bleak's python logger into kivy's logger before importing python module using logging
-from kivy.logger import Logger
-import logging
+from kivy.logger import Logger  # isort: skip
+import logging  # isort: skip
 
 logging.Logger.manager.root = Logger
 
diff --git a/examples/mtu_size.py b/examples/mtu_size.py
index 512f6dc5..35416330 100644
--- a/examples/mtu_size.py
+++ b/examples/mtu_size.py
@@ -4,8 +4,8 @@
 
 import asyncio
 
-from bleak import BleakScanner, BleakClient
-from bleak.backends.scanner import BLEDevice, AdvertisementData
+from bleak import BleakClient, BleakScanner
+from bleak.backends.scanner import AdvertisementData, BLEDevice
 
 # replace with real characteristic UUID
 CHAR_UUID = "00000000-0000-0000-0000-000000000000"
diff --git a/examples/philips_hue.py b/examples/philips_hue.py
index 9f7f76ef..9519240e 100644
--- a/examples/philips_hue.py
+++ b/examples/philips_hue.py
@@ -22,8 +22,8 @@
 
 """
 
-import sys
 import asyncio
+import sys
 
 from bleak import BleakClient
 
diff --git a/examples/scan_iterator.py b/examples/scan_iterator.py
index 5a407b38..e5696eb8 100644
--- a/examples/scan_iterator.py
+++ b/examples/scan_iterator.py
@@ -7,6 +7,7 @@
 Created on 2023-07-07 by bojanpotocnik <info@bojanpotocnik.com>
 
 """
+
 import asyncio
 
 from bleak import BleakScanner
diff --git a/examples/service_explorer.py b/examples/service_explorer.py
index b841b0a2..5649bb54 100644
--- a/examples/service_explorer.py
+++ b/examples/service_explorer.py
@@ -51,24 +51,21 @@ async def main(args: argparse.Namespace):
                 if "read" in char.properties:
                     try:
                         value = await client.read_gatt_char(char.uuid)
-                        logger.info(
-                            "  [Characteristic] %s (%s), Value: %r",
-                            char,
-                            ",".join(char.properties),
-                            value,
-                        )
+                        extra = f", Value: {value}"
                     except Exception as e:
-                        logger.error(
-                            "  [Characteristic] %s (%s), Error: %s",
-                            char,
-                            ",".join(char.properties),
-                            e,
-                        )
-
+                        extra = f", Error: {e}"
                 else:
-                    logger.info(
-                        "  [Characteristic] %s (%s)", char, ",".join(char.properties)
-                    )
+                    extra = ""
+
+                if "write-without-response" in char.properties:
+                    extra += f", Max write w/o rsp size: {char.max_write_without_response_size}"
+
+                logger.info(
+                    "  [Characteristic] %s (%s)%s",
+                    char,
+                    ",".join(char.properties),
+                    extra,
+                )
 
                 for descriptor in char.descriptors:
                     try:
diff --git a/examples/two_devices.py b/examples/two_devices.py
index b5d53ef8..86082912 100644
--- a/examples/two_devices.py
+++ b/examples/two_devices.py
@@ -4,7 +4,7 @@
 import logging
 from typing import Iterable
 
-from bleak import BleakScanner, BleakClient
+from bleak import BleakClient, BleakScanner
 
 
 async def connect_to_device(
diff --git a/poetry.lock b/poetry.lock
index 1b2fa019..abab64fc 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
 
 [[package]]
 name = "alabaster"
@@ -55,47 +55,47 @@ pytz = ">=2015.7"
 
 [[package]]
 name = "black"
-version = "22.8.0"
+version = "24.4.2"
 description = "The uncompromising code formatter."
 optional = false
-python-versions = ">=3.6.2"
+python-versions = ">=3.8"
 files = [
-    {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"},
-    {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"},
-    {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"},
-    {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"},
-    {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"},
-    {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"},
-    {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"},
-    {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"},
-    {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"},
-    {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"},
-    {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"},
-    {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"},
-    {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"},
-    {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"},
-    {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"},
-    {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"},
-    {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"},
-    {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"},
-    {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"},
-    {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"},
-    {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"},
-    {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
-    {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
+    {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
+    {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
+    {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
+    {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
+    {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
+    {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
+    {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
+    {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
+    {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
+    {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
+    {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
+    {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
+    {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
+    {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
+    {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
+    {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
+    {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
+    {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
+    {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
+    {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
+    {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
+    {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
 ]
 
 [package.dependencies]
 click = ">=8.0.0"
 mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
 pathspec = ">=0.9.0"
 platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
-typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
 
 [package.extras]
 colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
 jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
 uvloop = ["uvloop (>=0.15.2)"]
 
@@ -306,13 +306,13 @@ pyflakes = ">=2.5.0,<2.6.0"
 
 [[package]]
 name = "idna"
-version = "3.3"
+version = "3.7"
 description = "Internationalized Domain Names in Applications (IDNA)"
 optional = false
 python-versions = ">=3.5"
 files = [
-    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
-    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+    {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+    {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
 ]
 
 [[package]]
@@ -357,14 +357,28 @@ files = [
 ]
 
 [[package]]
-name = "Jinja2"
-version = "3.1.2"
+name = "isort"
+version = "5.13.2"
+description = "A Python utility / library to sort Python imports."
+optional = false
+python-versions = ">=3.8.0"
+files = [
+    {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
+    {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
+]
+
+[package.extras]
+colors = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "jinja2"
+version = "3.1.3"
 description = "A very fast and expressive template engine."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
-    {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+    {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+    {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
 ]
 
 [package.dependencies]
@@ -446,18 +460,15 @@ files = [
 
 [[package]]
 name = "packaging"
-version = "21.3"
+version = "24.0"
 description = "Core utilities for Python packages"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
-    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
-    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+    {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
+    {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
 ]
 
-[package.dependencies]
-pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
-
 [[package]]
 name = "pathspec"
 version = "0.10.1"
@@ -548,91 +559,74 @@ plugins = ["importlib-metadata"]
 
 [[package]]
 name = "pyobjc-core"
-version = "9.2"
+version = "10.0"
 description = "Python<->ObjC Interoperability Module"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "pyobjc-core-9.2.tar.gz", hash = "sha256:d734b9291fec91ff4e3ae38b9c6839debf02b79c07314476e87da8e90b2c68c3"},
-    {file = "pyobjc_core-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa674a39949f5cde8e5c7bbcd24496446bfc67592b028aedbec7f81dc5fc4daa"},
-    {file = "pyobjc_core-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bbc8de304ee322a1ee530b4d2daca135a49b4a49aa3cedc6b2c26c43885f4842"},
-    {file = "pyobjc_core-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0fa950f092673883b8bd28bc18397415cabb457bf410920762109b411789ade9"},
-    {file = "pyobjc_core-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:586e4cae966282eaa61b21cae66ccdcee9d69c036979def26eebdc08ddebe20f"},
-    {file = "pyobjc_core-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41189c2c680931c0395a55691763c481fc681f454f21bb4f1644f98c24a45954"},
-    {file = "pyobjc_core-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2d23ee539f2ba5e9f5653d75a13f575c7e36586fc0086792739e69e4c2617eda"},
-    {file = "pyobjc_core-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b9809cf96678797acb72a758f34932fe8e2602d5ab7abec15c5ac68ddb481720"},
+    {file = "pyobjc-core-10.0.tar.gz", hash = "sha256:3dd0a7b3acd7e0b8ffd3f5331b29a3aaebe79a03323e61efeece38627a6020b3"},
+    {file = "pyobjc_core-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61ea5112a672d21b5b0ed945778707c655b17c400672aef144705674c4b95499"},
+    {file = "pyobjc_core-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99b72cda4593e0c66037b25a178f2bcc6efffb6d5d9dcd477ecca859a1f9ae8e"},
+    {file = "pyobjc_core-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2843ca32e86a01ccee67d7ad82a325ddd72d754929d1f2c0d96bc8741dc9af09"},
+    {file = "pyobjc_core-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a886b9d2a93210cab4ae72601ab005ca6f627fa2f0cc62c43c03ef1405067a11"},
+    {file = "pyobjc_core-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:166666b5c380a49e8aa1ad1dda978c581e29a00703d82203216f3c65a3f397a4"},
+    {file = "pyobjc_core-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:198a0360f64e4c0148eed07b42d1de0545f56c498c356d1d5524422bb3352907"},
 ]
 
 [[package]]
 name = "pyobjc-framework-cocoa"
-version = "9.2"
+version = "10.0"
 description = "Wrappers for the Cocoa frameworks on macOS"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "pyobjc-framework-Cocoa-9.2.tar.gz", hash = "sha256:efd78080872d8c8de6c2b97e0e4eac99d6203a5d1637aa135d071d464eb2db53"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9e02d8a7cc4eb7685377c50ba4f17345701acf4c05b1e7480d421bff9e2f62a4"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3b1e6287b3149e4c6679cdbccd8e9ef6557a4e492a892e80a77df143f40026d2"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:312977ce2e3989073c6b324c69ba24283de206fe7acd6dbbbaf3e29238a22537"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aae7841cf40c26dd915f4dd828f91c6616e6b7998630b72e704750c09e00f334"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:739a421e14382a46cbeb9a883f192dceff368ad28ec34d895c48c0ad34cf2c1d"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:32d9ac1033fac1b821ddee8c68f972a7074ad8c50bec0bea9a719034c1c2fb94"},
-    {file = "pyobjc_framework_Cocoa-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b236bb965e41aeb2e215d4e98a5a230d4b63252c6d26e00924ea2e69540a59d6"},
+    {file = "pyobjc-framework-Cocoa-10.0.tar.gz", hash = "sha256:723421eff4f59e4ca9a9bb8ec6dafbc0f778141236fa85a49fdd86732d58a74c"},
+    {file = "pyobjc_framework_Cocoa-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:80c22a8fc7f085746d9cd222adeca8fe6790e3e6ad7eed5fc70b32aa87c10adb"},
+    {file = "pyobjc_framework_Cocoa-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0187cba228976a45f41116c74aab079b64bacb3ffc3c886a4bd8e472bf9be581"},
+    {file = "pyobjc_framework_Cocoa-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a81dabdc40268591e3196087388e680c6570fed1b521df9b04733cb3ece0414e"},
+    {file = "pyobjc_framework_Cocoa-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a23db9ab99e338e1d8a268d873cc15408f78cec9946308393ca2241820c18b8"},
+    {file = "pyobjc_framework_Cocoa-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a3c66fe56a5156a818fbf056c589f8140a5fdb1dcb1f1075cb34d3755474d900"},
+    {file = "pyobjc_framework_Cocoa-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bf9020e85ead569021b15272dcd90207aab6c754093f520b11d4210a2efbdd06"},
 ]
 
 [package.dependencies]
-pyobjc-core = ">=9.2"
+pyobjc-core = ">=10.0"
 
 [[package]]
 name = "pyobjc-framework-corebluetooth"
-version = "9.2"
+version = "10.0"
 description = "Wrappers for the framework CoreBluetooth on macOS"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "pyobjc-framework-CoreBluetooth-9.2.tar.gz", hash = "sha256:cb2481b1dfe211ae9ce55f36537dc8155dbf0dc8ff26e0bc2e13f7afb0a291d1"},
-    {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:53d888742119d0f0c725d0b0c2389f68e8f21f0cba6d6aec288c53260a0196b6"},
-    {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:179532882126526e38fe716a50fb0ee8f440e0b838d290252c515e622b5d0e49"},
-    {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:256a5031ea9d8a7406541fa1b0dfac549b1de93deae8284605f9355b13fb58be"},
+    {file = "pyobjc-framework-CoreBluetooth-10.0.tar.gz", hash = "sha256:dddc9020cd2ca008c7037c61026641fff5d91a608b9e3bda51d4ba6afbb04e3c"},
+    {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5dc85f0acea4ec8fb59ed91f89e677133b0e5efab21fe14694328ddcdbdc22c1"},
+    {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:155b2f9814970a07c602286ce19d3d3c3a812951a62b9f19aaa80475f9b49f05"},
+    {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:119c4747dcc8a1b9980b8bac4f2ffd90a3950b048b9fbf03e70656eaaaefe7d4"},
 ]
 
 [package.dependencies]
-pyobjc-core = ">=9.2"
-pyobjc-framework-Cocoa = ">=9.2"
+pyobjc-core = ">=10.0"
+pyobjc-framework-Cocoa = ">=10.0"
 
 [[package]]
 name = "pyobjc-framework-libdispatch"
-version = "9.2"
+version = "10.0"
 description = "Wrappers for libdispatch on macOS"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "pyobjc-framework-libdispatch-9.2.tar.gz", hash = "sha256:542e7f7c2b041939db5ed6f3119c1d67d73ec14a996278b92485f8513039c168"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88d4091d4bcb5702783d6e86b4107db973425a17d1de491543f56bd348909b60"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a67b007113328538b57893cc7829a722270764cdbeae6d5e1460a1d911314df"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6fccea1a57436cf1ac50d9ebc6e3e725bcf77f829ba6b118e62e6ed7866d359d"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6eba747b7ad91b0463265a7aee59235bb051fb97687f35ca2233690369b5e4e4"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e835495860d04f63c2d2f73ae3dd79da4222864c107096dc0f99e8382700026"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1b107e5c3580b09553030961ea6b17abad4a5132101eab1af3ad2cb36d0f08bb"},
-    {file = "pyobjc_framework_libdispatch-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:83cdb672acf722717b5ecf004768f215f02ac02d7f7f2a9703da6e921ab02222"},
+    {file = "pyobjc-framework-libdispatch-10.0.tar.gz", hash = "sha256:228adf364c895d2a0e8b08bd06f7a23cfbd8e82e9ea6cfdba73bdee0651a4e1f"},
+    {file = "pyobjc_framework_libdispatch-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:67fe10a671117fe7e8c35b7eaf8914084fbd0c64f3d635a1147782e3408dbc40"},
+    {file = "pyobjc_framework_libdispatch-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:386d53ffd2389c2494a806e3bad2d709ef5d597d1500cb643a817464c20ab8f8"},
+    {file = "pyobjc_framework_libdispatch-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d2fbd247ebac8fa57bb0d7a2c4e769118c995bfd81bfc7f189a8ebe297399776"},
+    {file = "pyobjc_framework_libdispatch-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:66dc959837b898bdb83a85ed34c2c368d0d38aaed9883c3769d4d1ca1aad23d7"},
+    {file = "pyobjc_framework_libdispatch-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c4a1e13172617916b894246b03579814f4e8151f2600403f4a799e4d4c7a032e"},
+    {file = "pyobjc_framework_libdispatch-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a55a33fc71e385ff7a8c9f1d29276f44dc0f7ff5b0597d2fd769620438058152"},
 ]
 
 [package.dependencies]
-pyobjc-core = ">=9.2"
-
-[[package]]
-name = "pyparsing"
-version = "3.0.9"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-optional = false
-python-versions = ">=3.6.8"
-files = [
-    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
-    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
-]
-
-[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
+pyobjc-core = ">=10.0"
 
 [[package]]
 name = "pytest"
@@ -901,237 +895,230 @@ files = [
 
 [[package]]
 name = "urllib3"
-version = "1.26.12"
+version = "1.26.18"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 files = [
-    {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
-    {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+    {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
+    {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
 ]
 
 [package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
 secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
 socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
 name = "winrt-runtime"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-runtime-2.0.0b1.tar.gz", hash = "sha256:28db2ebe7bfb347d110224e9f23fe8079cea45af0fcbd643d039524ced07d22c"},
-    {file = "winrt_runtime-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:8f812b01e2c8dd3ca68aa51a7aa02e815cc2ac3c8520a883b4ec7a4fc63afb04"},
-    {file = "winrt_runtime-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:f36f6102f9b7a08d917a6809117c085639b66be2c579f4089d3fd47b83e8f87b"},
-    {file = "winrt_runtime-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:4a99f267da96edc977623355b816b46c1344c66dc34732857084417d8cf9a96b"},
-    {file = "winrt_runtime-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:ba998e3fc452338c5e2d7bf5174a6206580245066d60079ee4130082d0eb61c2"},
-    {file = "winrt_runtime-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:e7838f0fdf5653ce245888590214177a1f54884cece2c8dfbfe3d01b2780171e"},
-    {file = "winrt_runtime-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:2afa45b7385e99a63d55ccda29096e6a84fcd4c654479005c147b0e65e274abf"},
-    {file = "winrt_runtime-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:edda124ff965cec3a6bfdb26fbe88e004f96975dd84115176e30c1efbcb16f4c"},
-    {file = "winrt_runtime-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:d8935951efeec6b3d546dce8f48bb203aface57a1ba991c066f0e12e84c8f91e"},
-    {file = "winrt_runtime-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:509fb9a03af5e1125433f58522725716ceef040050d33625460b5a5eb98a46ac"},
-    {file = "winrt_runtime-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:41138fe4642345d7143e817ce0905d82e60b3832558143e0a17bfea8654c6512"},
-    {file = "winrt_runtime-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:081a429fe85c33cb6610c4a799184b7650b30f15ab1d89866f2bda246d3a5c0a"},
-    {file = "winrt_runtime-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:e6984604c6ae1f3258973ba2503d1ea5aa15e536ca41d6a131ad305ebbb6519d"},
+    {file = "winrt_runtime-2.0.1-cp310-cp310-win32.whl", hash = "sha256:b1d8c2c01b40755b8f546eaf01fef2c722af4fb6934e4ce7ad7e5eb7ba404846"},
+    {file = "winrt_runtime-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:66bd7b98b5e2e2a0ae81089c26b5c284ee5f36603121584c82f2d1e0dfbfec37"},
+    {file = "winrt_runtime-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:6136966a7c9f01c6cd55c7e2bc3b67573069b7f8b8ee910f1f791bece09ad597"},
+    {file = "winrt_runtime-2.0.1-cp311-cp311-win32.whl", hash = "sha256:136142cecca5a87e13571277bace0ced0eee73f6d16dda967bc142bb7d4a0091"},
+    {file = "winrt_runtime-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d67360b744b1cc82efbe168ee21ed067483263f466aaab2f6321d9148b1b0552"},
+    {file = "winrt_runtime-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:33d9b3cb99bf96082e883af0db97adc74a7f0fc7339f9ab9d28f64d59bba1212"},
+    {file = "winrt_runtime-2.0.1-cp312-cp312-win32.whl", hash = "sha256:0947009e5f049bd7f3dd6284bb4161f644304877c9527c140f26e6daf9755767"},
+    {file = "winrt_runtime-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:72e3ddcda15b35e77b4e334c6cdbf345e76b12e176510cc9c3b7c61a08fb6ce8"},
+    {file = "winrt_runtime-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:e13d3ba43f3a63b506965b32353589968507f608c6c6484fa2684eb4cb9a230a"},
+    {file = "winrt_runtime-2.0.1-cp39-cp39-win32.whl", hash = "sha256:a734860406b445325168b7fd9b7e98539cec6c24dc8095e3b6e6db84b2c1a656"},
+    {file = "winrt_runtime-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:5dec5fc5e5f1be8fb7fd062ff30707500ff684b5fd5063fe4d7e9a196fdb9594"},
+    {file = "winrt_runtime-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:c13f40456b78f25934392a72b87cca3e50380c06732afedb7a18698f281ccd25"},
+    {file = "winrt_runtime-2.0.1.tar.gz", hash = "sha256:4d485fe7d2528ae220aca621a94aeafa28d938ed679599b8c4bbad0fc8877d9d"},
 ]
 
 [[package]]
 name = "winrt-windows-devices-bluetooth"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Devices.Bluetooth-2.0.0b1.tar.gz", hash = "sha256:786bd43786b873a083b89debece538974f720584662a2573d6a8a8501a532860"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:79631bf3f96954da260859df9228a028835ffade0d885ba3942c5a86a853d150"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:cd85337a95065d0d2045c06db1a5edd4a447aad47cf7027818f6fb69f831c56c"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:6a963869ed003d260e90e9bedc334129303f263f068ea1c0d994df53317db2bc"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:7c5951943a3911d94a8da190f4355dc70128d7d7f696209316372c834b34d462"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:b0bb154ae92235649ed234982f609c490a467d5049c27d63397be9abbb00730e"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:6688dfb0fc3b7dc517bf8cf40ae00544a50b4dec91470d37be38fc33c4523632"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:613c6ff4125df46189b3bef6d3110d94ec725d357ab734f00eedb11c4116c367"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:59c403b64e9f4e417599c6f6aea6ee6fac960597c21eac6b3fd8a84f64aa387c"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:b7f6e1b9bb6e33be80045adebd252cf25cd648759fad6e86c61a393ddd709f7f"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:eae7a89106eab047e96843e28c3c6ce0886dd7dee60180a1010498925e9503f9"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:8dfd1915c894ac19dd0b24aba38ef676c92c3473c0d9826762ba9616ad7df68b"},
-    {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:49058587e6d82ba33da0767b97a378ddfea8e3a5991bdeff680faa287bfae57e"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp310-cp310-win32.whl", hash = "sha256:80ef50eb1d82cb869b6dc1f312bb5cd28a4c1f88946fc3abe7fc3c4f7ef2e4c8"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:838f66afb145e6a93fa5151f1e5883e55a781c5f1fdf37d2d903fcee7680565f"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:81971d961f41b71ca9999f3a6c03f35f50f9939bc144455ec6a7aea63aba8167"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp311-cp311-win32.whl", hash = "sha256:89042d64cc556ac1c49fef46b0a25ad969a66a2c473ddd5fd5f4cbd735c30c77"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b6bac9fa687ab2ab4a98de2d7e96e21dfa7291bb388fcff4247096c099327cd6"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:03b461fd1d2005ff22f212ee418cc9d387502f1ea86ace9a347e81554dc95822"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp312-cp312-win32.whl", hash = "sha256:695e62296d87c676d385d427374d4f8452fc457b58d5ecd3118af01e3829370d"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:f80dcbd2f297e2789f367ad1fd4033e0d69057eb9dfc631327215915a95ba0b4"},
+    {file = "winrt_Windows.Devices.Bluetooth-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:5a5bc9e541f23151255bd82ee3bffa319c35e9ba95879cdedf597cc8cc903f94"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.Devices.Bluetooth.GenericAttributeProfile[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Bluetooth.Rfcomm[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Enumeration[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Radios[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Networking[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.Devices.Bluetooth.GenericAttributeProfile[all] (==2.0.1)", "winrt-Windows.Devices.Bluetooth.Rfcomm[all] (==2.0.1)", "winrt-Windows.Devices.Enumeration[all] (==2.0.1)", "winrt-Windows.Devices.Radios[all] (==2.0.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.1)", "winrt-Windows.Foundation[all] (==2.0.1)", "winrt-Windows.Networking[all] (==2.0.1)", "winrt-Windows.Storage.Streams[all] (==2.0.1)"]
 
 [[package]]
 name = "winrt-windows-devices-bluetooth-advertisement"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Devices.Bluetooth.Advertisement-2.0.0b1.tar.gz", hash = "sha256:d9050faa4377d410d4f0e9cabb5ec555a267531c9747370555ac9ec93ec9f399"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:ac9b703d16adc87c3541585525b8fcf6d84391e2fa010c2f001e714c405cc3b7"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:593cade7853a8b0770e8ef30462b5d5f477b82e17e0aa590094b1c26efd3e05a"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:574698c08895e2cfee7379bdf34a5f319fe440d7dfcc7bc9858f457c08e9712c"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:652a096f8210036bbb539d7f971eaf1f472a3aeb60b7e31278e3d0d30a355292"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:e5cfb866c44dad644fb44b441f4fdbddafc9564075f1f68f756e20f438105c67"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:6c2503eaaf5cd988b5510b86347dba45ad6ee52656f9656a1a97abae6d35386e"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:780c766725a55f4211f921c773c92c2331803e70f65d6ad6676a60f903d39a54"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:39c8633d01039eb2c2f6f20cfc43c045a333b9f3a45229e2ce443f71bb2a562c"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:eaa0d44b4158b16937eac8102249e792f0299dbb0aefc56cc9adc9552e8f9afe"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:d171487e23f7671ad2923544bfa6545d0a29a1a9ae1f5c1d5e5e5f473a5d62b2"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:442eecac87653a03617e65bdb2ef79ddc0582dfdacc2be8af841fba541577f8b"},
-    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:b30ab9b8c1ecf818be08bac86bee425ef40f75060c4011d4e6c2e624a7b9916e"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp310-cp310-win32.whl", hash = "sha256:a63919f00fb15574443886be32295a1e95656eeda5c0a6299169338a276d03b0"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:266755af11ecb01c0c8a626da5072011ab4e1aea90426f80e1269107b8e8780a"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:856b00087a93763db157441eda78a308dc21cee816cbcf51ca7b4b07fde6cdb5"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp311-cp311-win32.whl", hash = "sha256:480050e8928da6c7f1f99a2b60206fbfb3252817fee0a9123142c9f8754e5687"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9272aa9ca77b356892218f32d1ffac0215fcdf331802d3bed842fd4d1448aeef"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:231af49fc2154a6a248d3e7c2a3ea131ac3fb870a1701c3fa5be65f258eb70e0"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3038f08fc6151055b4d11468b0dda3ee46ae6080b25221c4ea54c11b255a0f50"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b547929477be00350118589f25dfc49f825e5df5da618afec465cede6af7e0a5"},
+    {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:ea5c861e11bc6d565ab5fe6a4af6e6064c1812fee1d0e8f9c6dff2daafbcc046"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.Devices.Bluetooth[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.Devices.Bluetooth[all] (==2.0.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.1)", "winrt-Windows.Foundation[all] (==2.0.1)", "winrt-Windows.Storage.Streams[all] (==2.0.1)"]
 
 [[package]]
 name = "winrt-windows-devices-bluetooth-genericattributeprofile"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1.tar.gz", hash = "sha256:93b745d51ecfb3e9d3a21623165cc065735c9e0146cb7a26744182c164e63e14"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:db740aaedd80cca5b1a390663b26c7733eb08f4c57ade6a04b055d548e9d042b"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:7c81aa6c066cdab58bcc539731f208960e094a6d48b59118898e1e804dbbdf7f"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:92277a6bbcbe2225ad1be92968af597dc77bc37a63cd729690d2d9fb5094ae25"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:6b48209669c1e214165530793cf9916ae44a0ae2618a9be7a489e8c94f7e745f"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:2f17216e6ce748eaef02fb0658213515d3ff31e2dbb18f070a614876f818c90d"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:db798a0f0762e390da5a9f02f822daff00692bd951a492224bf46782713b2938"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:b8d9dba04b9cfa53971c35117fc3c68c94bfa5e2ed18ce680f731743598bf246"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:e5260b3f33dee8a896604297e05efc04d04298329c205a74ded8e2d6333e84b7"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:822ef539389ecb546004345c4dce8b9b7788e2e99a1d6f0947a4b123dceb7fed"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:11e6863e7a94d2b6dd76ddcd19c01e311895810a4ce6ad08c7b5534294753243"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:20de8d04c301c406362c93e78d41912aea0af23c4b430704aba329420d7c2cdf"},
-    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:918059796f2f123216163b928ecde8ecec17994fb7a94042af07fda82c132a6d"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp310-cp310-win32.whl", hash = "sha256:0a5118852dce4d50fd8d6c73ff3dc2c68403899b86060c0a85f7f0da284230cb"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:0695c73f0f20745c83ec9d7b5b8a6f55efd0df974dea81ad8382fad193f71275"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:8a6385cc8a1749f049f29037b2cfcce781bc80721ca4734de8ef0375b55ca1e5"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp311-cp311-win32.whl", hash = "sha256:858e48931713ddfb2ad52614bb87a653d91c30f602e335a3ed27daca15860b54"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:494000287b9b4e1b89fb4feb1379d95b2147dbf4bd4b1942f88c6c56afc2ba97"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:26270ea815c26df35c5c74ad7f10a99dd976e27031cc7316350a0c0395b19ee6"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp312-cp312-win32.whl", hash = "sha256:21e8c0f158adcf0b40c4b5bfd5144aa312e5edaaf759d6599c85c118ebf60214"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b05d929b819e83e91299b0e5b937ea1ca524b15486791ee5b513ae026ef25efa"},
+    {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:944215e5623d0c746a6c2e356dec36cf3a7281933f0819a3b4f2fbbddb4af382"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.Devices.Bluetooth[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Enumeration[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.Devices.Bluetooth[all] (==2.0.1)", "winrt-Windows.Devices.Enumeration[all] (==2.0.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.1)", "winrt-Windows.Foundation[all] (==2.0.1)", "winrt-Windows.Storage.Streams[all] (==2.0.1)"]
 
 [[package]]
 name = "winrt-windows-devices-enumeration"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Devices.Enumeration-2.0.0b1.tar.gz", hash = "sha256:8f214040e4edbe57c4943488887db89f4a00d028c34169aafd2205e228026100"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:dcb9e7d230aefec8531a46d393ecb1063b9d4b97c9f3ff2fc537ce22bdfa2444"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:22a3e1fef40786cc8d51320b6f11ff25de6c674475f3ba608a46915e1dadf0f5"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:2edcfeb70a71d40622873cad96982a28e92a7ee71f33968212dd3598b2d8d469"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:ce4eb88add7f5946d2666761a97a3bb04cac2a061d264f03229c1e15dbd7ce91"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:a9001f17991572abdddab7ab074e08046e74e05eeeaf3b2b01b8b47d2879b64c"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:0440b91ce144111e207f084cec6b1277162ef2df452d321951e989ce87dc9ced"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:e4fae13126f13a8d9420b74fb5a5ff6a6b2f91f7718c4be2d4a8dc1337c58f59"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:e352eebc23dc94fb79e67a056c057fb0e16c20c8cb881dc826094c20ed4791e3"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:b43f5c1f053a170e6e4b44ba69838ac223f9051adca1a56506d4c46e98d1485f"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:ed245fad8de6a134d5c3a630204e7f8238aa944a40388005bce0ce3718c410fa"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:22a9eefdbfe520778512266d0b48ff239eaa8d272fce6f5cb1ff352bed0619f4"},
-    {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:397d43f8fd2621a7719b9eab6a4a8e72a1d6fa2d9c36525a30812f8e7bad3bdf"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp310-cp310-win32.whl", hash = "sha256:828456ed950d8b427d78dfedd54bf7514e9793019dbf6a5a8f725560be364578"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:217c9d14e94aea5e497f7aa8cd808e9255df98e28a12417194729debe0b77e65"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:751c20ad01a58a3bb5f273c3cf653475448d8f77f7331b96af7fc87204f1bc6a"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp311-cp311-win32.whl", hash = "sha256:8b28f4bc052a7442fdd7e3113021a264de972ca5421cc08ba53dd724f3826174"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:e3c0316f8487547fab36556b9ac94dec75551c70e0c977b92c4eb47c12d86bb0"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:572b0918f9babc9d2c31d8df800a988df2cf18492e462de602babc5d97639b16"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp312-cp312-win32.whl", hash = "sha256:6b6108466574846c969ba7d14e1d085948c1724cd087a494e1913315fb3ea4e0"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8af4510ef393c3d97c71f082fc31412cf8b1cc8d8b74a30bab4144a5fd2edfe7"},
+    {file = "winrt_Windows.Devices.Enumeration-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:b3d36f47715550ba980fb1be43cdfbac17a833744cb2f08c634349a8a31feb9c"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.ApplicationModel.Background[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Security.Credentials[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)", "winrt-Windows.UI.Popups[all] (==2.0.0-beta.1)", "winrt-Windows.UI[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.ApplicationModel.Background[all] (==2.0.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.1)", "winrt-Windows.Foundation[all] (==2.0.1)", "winrt-Windows.Security.Credentials[all] (==2.0.1)", "winrt-Windows.Storage.Streams[all] (==2.0.1)", "winrt-Windows.UI.Popups[all] (==2.0.1)", "winrt-Windows.UI[all] (==2.0.1)"]
 
 [[package]]
 name = "winrt-windows-foundation"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Foundation-2.0.0b1.tar.gz", hash = "sha256:976b6da942747a7ca5a179a35729d8dc163f833e03b085cf940332a5e9070d54"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:5337ac1ec260132fbff868603e73a3738d4001911226e72669b3d69c8a256d5e"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:af969e5bb9e2e41e4e86a361802528eafb5eb8fe87ec1dba6048c0702d63caa8"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:bbbfa6b3c444a1074a630fd4a1b71171be7a5c9bb07c827ad9259fadaed56cf2"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:b91bd92b1854c073acd81aa87cf8df571d2151b1dd050b6181aa36f7acc43df4"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:2f5359f25703347e827dbac982150354069030f1deecd616f7ce37ad90cbcb00"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:0f1f1978173ddf0ee6262c2edb458f62d628b9fa0df10cd1e8c78c833af3197e"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:c1d23b737f733104b91c89c507b58d0b3ef5f3234a1b608ef6dfb6dbbb8777ea"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:95de6c29e9083fe63f127b965b54dfa52a6424a93a94ce87cfad4c1900a6e887"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:4707063a5a6980e3f71aebeea5ac93101c753ec13a0b47be9ea4dbc0d5ff361e"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:d0259f1f4a1b8e20d0cbd935a889c0f7234f720645590260f9cf3850fdc1e1fa"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:15c7b324d0f59839fb4492d84bb1c870881c5c67cb94ac24c664a7c4dce1c475"},
-    {file = "winrt_Windows.Foundation-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:16ad741f4d38e99f8409ba5760299d0052003255f970f49f4b8ba2e0b609c8b7"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp310-cp310-win32.whl", hash = "sha256:f8cdc6f1f81e241a8a4d19f9d323828e61d75bd77fecfbe0c4d735e385326c4e"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:b8868c73642c66798c18ce8021796acb9beea59b6d2361344e8776c27ab847c7"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:4b5df8a9f69b15c71fe9b6f4f8e8589fe043d4d7ab843bb73607c7a4adde68f1"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp311-cp311-win32.whl", hash = "sha256:28ad6cd21126cc75cfb28527489e06699b36d8d6d5fdea5487e51c85ea9cc358"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:75d5a4974662ca11eab1438a711433951bef2e7db156b0c7ca34f47fbd19f117"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:d129a9fdfe5205bff2e9ccad705539fabd485ce6a8e47ded876aa664545b5216"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp312-cp312-win32.whl", hash = "sha256:a1dd4c93f435fd2f8f6e180af1cd2d3af8d22518b4c25c843e0b850b38e8be0f"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c54ac18fea4a488dae8a3261f6633c17ed2a632c35d15112caa9294f8c5560d"},
+    {file = "winrt_Windows.Foundation-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:29f648a66a3e3285fcb5fdd1821582d60f6b3021e5dcc02acb008c8f48f15e7a"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.Foundation.Collections[all] (==2.0.1)"]
 
 [[package]]
 name = "winrt-windows-foundation-collections"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Foundation.Collections-2.0.0b1.tar.gz", hash = "sha256:185d30f8103934124544a40aac005fa5918a9a7cb3179f45e9863bb86e22ad43"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:042142e916a170778b7154498aae61254a1a94c552954266b73479479d24f01d"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:9f68e66055121fc1e04c4fda627834aceee6fbe922e77d6ccaecf9582e714c57"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:a4609411263cc7f5e93a9a5677b21e2ef130e26f9030bfa960b3e82595324298"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:5296858aa44c53936460a119794b80eedd6bd094016c1bf96822f92cb95ea419"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:3db1e1c80c97474e7c88b6052bd8982ca61723fd58ace11dc91a5522662e0b2a"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:c3a594e660c59f9fab04ae2f40bda7c809e8ec4748bada4424dfb02b43d4bfe1"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:0f355ee943ec5b835e694d97e9e93545a42d6fb984a61f442467789550d62c3f"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:c4a0cd2eb9f47c7ca3b66d12341cc822250bf26854a93fd58ab77f7a48dfab3a"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:744dbef50e8b8f34904083cae9ad43ac6e28facb9e166c4f123ce8e758141067"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:b7c767184aec3a3d7cba2cd84fadcd68106854efabef1a61092052294d6d6f4f"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:7c1ffe99c12f14fc4ab7027757780e6d850fa2fb23ec404a54311fbd9f1970d3"},
-    {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:870fa040ed36066e4c240c35973d8b2e0d7c38cc6050a42d993715ec9e3b748c"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp310-cp310-win32.whl", hash = "sha256:805f8d9a2f61276eb8e4284d439c61893c5acca0c17265f6dc10b8747c89bc39"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:2464c2b67e732f7e05497ea04ee43b9926743f10e8766c90a279270a3afe4b6a"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:ab1094c0091c573b1ecf72fd3cd8380d6eef9e6d0f59ccc5e676c697702eddc2"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp311-cp311-win32.whl", hash = "sha256:be0bd1b45252da5142b6ee80e3e488ad50931c6595e70674556ccad36080f2a9"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:a4f299a006266632240cc66ade8d82db6167a769fb1b5ec76cf22f2dfb43777a"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:84d66f1e2c8896534cfca80eaf4508e25d34e34b37b1e2eb4beb7462220edf78"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp312-cp312-win32.whl", hash = "sha256:d36a49ee53c8726148cac1920bf57a95b07eab576275b5efb0e97adea6fdacb2"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:c63e46569024e1504cc8e3b4f233e69162bc92e0428b4e92cb7dbc7cdc89db5f"},
+    {file = "winrt_Windows.Foundation.Collections-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:5f5aac867f2b2fbc65e453c942bfde5bb158e60f47d2615455143fab335694e8"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.Foundation[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.Foundation[all] (==2.0.1)"]
 
 [[package]]
 name = "winrt-windows-storage-streams"
-version = "2.0.0b1"
+version = "2.0.1"
 description = "Python projection of Windows Runtime (WinRT) APIs"
 optional = false
 python-versions = "<3.13,>=3.9"
 files = [
-    {file = "winrt-Windows.Storage.Streams-2.0.0b1.tar.gz", hash = "sha256:029d67cdc9b092d56c682740fe3c42f267dc5d3346b5c0b12ebc03f38e7d2f1f"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:49c90d4bfd539f6676226dfcb4b3574ddd6be528ffc44aa214c55af88c2de89e"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:22cc82779cada84aa2633841e25b33f3357737d912a1d9ecc1ee5a8b799b5171"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:b1750a111be32466f4f0781cbb5df195ac940690571dff4564492b921b162563"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:e79b1183ab26d9b95cf3e6dbe3f488a40605174a5a112694dbb7dbfb50899daf"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:3e90a1207eb3076f051a7785132f7b056b37343a68e9481a50c6defb3f660099"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:4da06522b4fa9cfcc046b604cc4aa1c6a887cc4bb5b8a637ed9bff8028a860bb"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:6f74f8ab8ac0d8de61c709043315361d8ac63f8144f3098d428472baadf8246a"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:5cf7c8d67836c60392d167bfe4f98ac7abcb691bfba2d19e322d0f9181f58347"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:f7f679f2c0f71791eca835856f57942ee5245094c1840a6c34bc7c2176b1bcd6"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:5beb53429fa9a11ede56b4a7cefe28c774b352dd355f7951f2a4dd7e9ec9b39a"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:f84233c4b500279d8f5840cb8c47776bc040fcecba05c6c9ab9767053698fc8b"},
-    {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:cfb163ddbb435906f75ef92a768573b0190e194e1438cea5a4c1d4d32a6b9386"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp310-cp310-win32.whl", hash = "sha256:9e88593f17983b5714957c6f48e7ecdc8fb4410d57cd4b097aded722dc5243d3"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:23bfecd91c2b4355f2281dc0cf8a29a32f1783d61f7df8162c29516a30df98a2"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:4bbbb6476e25563395834ca0d18a674f3ae97dde8b10e1713f569ec60557ed92"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp311-cp311-win32.whl", hash = "sha256:ed64b2215b6a1ff21c6849948e02fd7f31d1dbf81a4c25871d87dbb97029410e"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ba46b0c7ca819598d2559c2374be9fb1e374a0d866a4fdca15fc1900d83e080"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:0e802596bac1b59476a4a5a0cd68c7bd0483c4c3b4e4a2489768108729213113"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp312-cp312-win32.whl", hash = "sha256:8d04759f1370514f95e486c7fad1ec33047dc8db275dd839bb906d79d2ee6088"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:af2a459bdef54702972e05d9ba3bdc3583816404bddb79831f4a7b9a74e37ff1"},
+    {file = "winrt_Windows.Storage.Streams-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0a5446194fb88125569e293657f6bac926731d8ff6126f1b693848fbd2c72167"},
+    {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"},
 ]
 
 [package.dependencies]
-winrt-runtime = "2.0.0-beta.1"
+winrt-runtime = "2.0.1"
 
 [package.extras]
-all = ["winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Storage[all] (==2.0.0-beta.1)", "winrt-Windows.System[all] (==2.0.0-beta.1)"]
+all = ["winrt-Windows.Foundation.Collections[all] (==2.0.1)", "winrt-Windows.Foundation[all] (==2.0.1)", "winrt-Windows.Storage[all] (==2.0.1)", "winrt-Windows.System[all] (==2.0.1)"]
 
 [[package]]
 name = "zipp"
@@ -1151,4 +1138,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8,<3.13"
-content-hash = "2e2a0282f988d9173d2da2955365ac6a7439210a8ad2a69103815b029803bc3a"
+content-hash = "65f1bbdea293cf8fef2b3531e7f28b9aff5b62df6c9bbf4a290d9cdfe5f8884d"
diff --git a/pyproject.toml b/pyproject.toml
index c6f74263..2a12526a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "bleak"
-version = "0.21.1"
+version = "0.22.0"
 description = "Bluetooth Low Energy platform Agnostic Klient"
 authors = ["Henrik Blidh <henrik.blidh@nedomkull.com>"]
 license = "MIT"
@@ -25,17 +25,18 @@ classifiers = [
 python = ">=3.8,<3.13"
 async-timeout = { version = ">= 3.0.0, < 5", python = "<3.11" }
 typing-extensions = { version = ">=4.7.0", python = "<3.12" }
-pyobjc-core = { version = "^9.2", markers = "platform_system=='Darwin'" }
-pyobjc-framework-CoreBluetooth = { version = "^9.2", markers = "platform_system=='Darwin'" }
-pyobjc-framework-libdispatch = { version = "^9.2", markers = "platform_system=='Darwin'" }
+pyobjc-core = { version = "^10.0", markers = "platform_system=='Darwin'" }
+pyobjc-framework-CoreBluetooth = { version = "^10.0", markers = "platform_system=='Darwin'" }
+pyobjc-framework-libdispatch = { version = "^10.0", markers = "platform_system=='Darwin'" }
 bleak-winrt = { version = "^1.2.0", markers = "platform_system=='Windows'", python = "<3.12" }
-"winrt-Windows.Devices.Bluetooth" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
-"winrt-Windows.Devices.Bluetooth.Advertisement" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
-"winrt-Windows.Devices.Bluetooth.GenericAttributeProfile" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
-"winrt-Windows.Devices.Enumeration" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
-"winrt-Windows.Foundation" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
-"winrt-Windows.Foundation.Collections" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
-"winrt-Windows.Storage.Streams" = { version = "2.0.0b1", allow-prereleases = true, markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-runtime" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Devices.Bluetooth" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Devices.Bluetooth.Advertisement" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Devices.Bluetooth.GenericAttributeProfile" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Devices.Enumeration" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Foundation" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Foundation.Collections" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
+"winrt-Windows.Storage.Streams" = { version = "^2", markers = "platform_system=='Windows'", python = ">=3.12" }
 dbus-fast = { version = ">=1.83.0, < 3", markers = "platform_system == 'Linux'" }
 
 [tool.poetry.group.docs.dependencies]
@@ -43,8 +44,9 @@ Sphinx = "^5.1.1"
 sphinx-rtd-theme = "^1.0.0"
 
 [tool.poetry.group.lint.dependencies]
-black = "^22.1.0"
+black = ">=24.3,<25.0"
 flake8 = "^5.0.0"
+isort = "^5.13.2"
 
 [tool.poetry.group.test.dependencies]
 pytest = "^7.0.0"
@@ -54,3 +56,9 @@ pytest-cov = "^3.0.0 "
 [build-system]
 requires = ["poetry-core"]
 build-backend = "poetry.core.masonry.api"
+
+[tool.isort]
+profile = "black"
+py_version=38
+src_paths = ["bleak", "examples", "tests", "typings"]
+extend_skip = [".buildozer"]
diff --git a/setup.cfg b/setup.cfg
index 1e14f075..2a5d6a43 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [flake8]
 exclude = docs,.venv,*.pyi,.buildozer,build,dist,.eggs
-ignore = E203,E501,W503
+ignore = E203,E501,E704,W503
 
 [aliases]
 test = pytest
diff --git a/typings/CoreBluetooth/__init__.pyi b/typings/CoreBluetooth/__init__.pyi
index f5b84ff8..ae988feb 100644
--- a/typings/CoreBluetooth/__init__.pyi
+++ b/typings/CoreBluetooth/__init__.pyi
@@ -1,7 +1,7 @@
 from typing import Any, NewType, Optional, Type, TypeVar
 
-from ..libdispatch import dispatch_queue_t
 from ..Foundation import (
+    NSUUID,
     NSArray,
     NSData,
     NSDictionary,
@@ -9,8 +9,8 @@ from ..Foundation import (
     NSNumber,
     NSObject,
     NSString,
-    NSUUID,
 )
+from ..libdispatch import dispatch_queue_t
 
 class CBManager(NSObject):
     def state(self) -> CBManagerState: ...
@@ -52,9 +52,6 @@ class CBCentralManager(CBManager):
     def registerForConnectionEventsWithOptions_(
         self, options: NSDictionary
     ) -> None: ...
-    def registerForConnectionEventsWithOptions_(
-        self, options: NSDictionary
-    ) -> None: ...
 
 CBConnectPeripheralOptionNotifyOnConnectionKey: NSString
 CBConnectPeripheralOptionNotifyOnDisconnectionKey: NSString
@@ -202,15 +199,15 @@ class CBPeripheralDelegate:
         self, peripheral: CBPeripheral, invalidatedServices: NSArray
     ) -> None: ...
 
+class CBAttribute(NSObject):
+    def UUID(self) -> CBUUID: ...
+
 class CBService(CBAttribute):
     def peripheral(self) -> CBPeripheral: ...
     def isPrimary(self) -> bool: ...
     def characteristics(self) -> Optional[NSArray]: ...
     def includedServices(self) -> Optional[NSArray]: ...
 
-class CBAttribute(NSObject):
-    def UUID(self) -> CBUUID: ...
-
 class CBUUID(NSObject):
     @classmethod
     def UUIDWithString_(cls, theString: NSString) -> CBUUID: ...
diff --git a/typings/Foundation/__init__.pyi b/typings/Foundation/__init__.pyi
index 0a4485b0..21e4be06 100644
--- a/typings/Foundation/__init__.pyi
+++ b/typings/Foundation/__init__.pyi
@@ -26,8 +26,8 @@ class NSData(NSObject): ...
 class NSArray(NSObject):
     def initWithArray_(self, array: Sequence) -> NSArray: ...
 
-class NSNumber(NSValue): ...
 class NSValue(NSObject): ...
+class NSNumber(NSValue): ...
 
 NSKeyValueObservingOptions = NewType("NSKeyValueObservingOptions", int)
 NSKeyValueObservingOptionNew: NSKeyValueObservingOptions
diff --git a/typings/objc/__init__.py b/typings/objc/__init__.py
index 1315a6c5..0e92f679 100644
--- a/typings/objc/__init__.py
+++ b/typings/objc/__init__.py
@@ -5,17 +5,13 @@
 T = TypeVar("T")
 
 
-def super(cls: Type[T], self: T) -> T:
-    ...
+def super(cls: Type[T], self: T) -> T: ...
 
 
-def macos_available(major: int, minor: int, patch: int = 0) -> bool:
-    ...
+def macos_available(major: int, minor: int, patch: int = 0) -> bool: ...
 
 
 class WeakRef:
-    def __init__(self, object: NSObject) -> None:
-        ...
+    def __init__(self, object: NSObject) -> None: ...
 
-    def __call__(self) -> Optional[NSObject]:
-        ...
+    def __call__(self) -> Optional[NSObject]: ...


Test patch:
diff --git a/tests/bleak/backends/bluezdbus/test_utils.py b/tests/bleak/backends/bluezdbus/test_utils.py
index cad1dff3..5e728433 100644
--- a/tests/bleak/backends/bluezdbus/test_utils.py
+++ b/tests/bleak/backends/bluezdbus/test_utils.py
@@ -2,9 +2,10 @@
 
 """Tests for `bleak.backends.bluezdbus.utils` package."""
 
-import pytest
 import sys
 
+import pytest
+
 
 @pytest.mark.skipif(
     not sys.platform.startswith("linux"), reason="requires dbus-fast on Linux"