diff -pruN 0.14.3-1/AUTHORS.rst 0.15.1-1/AUTHORS.rst
--- 0.14.3-1/AUTHORS.rst	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/AUTHORS.rst	2022-08-03 16:15:28.000000000 +0000
@@ -19,3 +19,8 @@ Contributors
 * Bernie Conrad <bernie@allthenticate.net>
 * Jonathan Soto <jsotogaviard@alum.mit.edu>
 * Kyle J. Williams <kyle@kjwill.tech>
+
+Sponsors
+--------
+
+* Nabu Casa <https://www.nabucasa.com/>
diff -pruN 0.14.3-1/bleak/assigned_numbers.py 0.15.1-1/bleak/assigned_numbers.py
--- 0.14.3-1/bleak/assigned_numbers.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.15.1-1/bleak/assigned_numbers.py	2022-08-03 16:15:28.000000000 +0000
@@ -0,0 +1,37 @@
+"""
+Bluetooth Assigned Numbers
+--------------------------
+
+This module contains useful assigned numbers from the Bluetooth spec.
+
+See <https://www.bluetooth.com/specifications/assigned-numbers/>.
+"""
+
+
+from enum import IntEnum
+
+
+class AdvertisementDataType(IntEnum):
+    """
+    Generic Access Profile advertisement data types.
+
+    `Source <https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Generic%20Access%20Profile.pdf>`.
+    """
+
+    FLAGS = 0x01
+    INCOMPLETE_LIST_SERVICE_UUID16 = 0x02
+    COMPLETE_LIST_SERVICE_UUID16 = 0x03
+    INCOMPLETE_LIST_SERVICE_UUID32 = 0x04
+    COMPLETE_LIST_SERVICE_UUID32 = 0x05
+    INCOMPLETE_LIST_SERVICE_UUID128 = 0x06
+    COMPLETE_LIST_SERVICE_UUID128 = 0x07
+    SHORTENED_LOCAL_NAME = 0x08
+    COMPLETE_LOCAL_NAME = 0x09
+    TX_POWER_LEVEL = 0x0A
+    CLASS_OF_DEVICE = 0x0D
+
+    SERVICE_DATA_UUID16 = 0x16
+    SERVICE_DATA_UUID32 = 0x20
+    SERVICE_DATA_UUID128 = 0x21
+
+    MANUFACTURER_SPECIFIC_DATA = 0xFF
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/advertisement_monitor.py 0.15.1-1/bleak/backends/bluezdbus/advertisement_monitor.py
--- 0.14.3-1/bleak/backends/bluezdbus/advertisement_monitor.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/advertisement_monitor.py	2022-08-03 16:15:28.000000000 +0000
@@ -0,0 +1,119 @@
+"""
+Advertisement Monitor
+---------------------
+
+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>`.
+"""
+
+import logging
+from typing import Iterable, NamedTuple, Tuple, Union, no_type_check
+
+from dbus_next.service import ServiceInterface, dbus_property, method, PropertyAccess
+
+from . import defs
+from ...assigned_numbers import AdvertisementDataType
+
+
+logger = logging.getLogger(__name__)
+
+
+class OrPattern(NamedTuple):
+    """
+    BlueZ advertisement monitor or-pattern.
+
+    https://github.com/bluez/bluez/blob/master/doc/advertisement-monitor-api.txt
+    """
+
+    start_position: int
+    ad_data_type: AdvertisementDataType
+    content_of_pattern: bytes
+
+
+# Windows has a similar structure, so we allow generic tuple for cross-platform compatibility
+OrPatternLike = Union[OrPattern, Tuple[int, AdvertisementDataType, bytes]]
+
+
+class AdvertisementMonitor(ServiceInterface):
+    """
+    Implementation of the org.bluez.AdvertisementMonitor1 D-Bus interface.
+
+    The BlueZ advertisement monitor API design seems to be just for device
+    presence (is it in range or out of range), but this isn't really what
+    we want in Bleak, we want to monitor changes in advertisement data, just
+    like in active scanning.
+
+    So the only thing we are using here is the "or_patterns" since it is
+    currently required, but really we don't need that either. Hopefully an
+    "all" "Type" could be added to BlueZ in the future.
+    """
+
+    def __init__(
+        self,
+        or_patterns: Iterable[OrPatternLike],
+    ):
+        """
+        Args:
+            or_patterns:
+                List of or patterns that will be returned by the ``Patterns`` property.
+        """
+        super().__init__(defs.ADVERTISEMENT_MONITOR_INTERFACE)
+        # dbus_next marshaling requires list instead of tuple
+        self._or_patterns = [list(p) for p in or_patterns]
+
+    @method()
+    def Release(self):
+        logger.debug("Release")
+
+    @method()
+    def Activate(self):
+        logger.debug("Activate")
+
+    # REVISIT: mypy is broke, so we have to add redundant @no_type_check
+    # https://github.com/python/mypy/issues/6583
+
+    @method()
+    @no_type_check
+    def DeviceFound(self, device: "o"):  # noqa: F821
+        logger.debug("DeviceFound %s", device)
+
+    @method()
+    @no_type_check
+    def DeviceLost(self, device: "o"):  # noqa: F821
+        logger.debug("DeviceLost %s", device)
+
+    @dbus_property(PropertyAccess.READ)
+    @no_type_check
+    def Type(self) -> "s":  # noqa: F821
+        # this is currently the only type supported in BlueZ
+        return "or_patterns"
+
+    @dbus_property(PropertyAccess.READ, disabled=True)
+    @no_type_check
+    def RSSILowThreshold(self) -> "n":  # noqa: F821
+        ...
+
+    @dbus_property(PropertyAccess.READ, disabled=True)
+    @no_type_check
+    def RSSIHighThreshold(self) -> "n":  # noqa: F821
+        ...
+
+    @dbus_property(PropertyAccess.READ, disabled=True)
+    @no_type_check
+    def RSSILowTimeout(self) -> "q":  # noqa: F821
+        ...
+
+    @dbus_property(PropertyAccess.READ, disabled=True)
+    @no_type_check
+    def RSSIHighTimeout(self) -> "q":  # noqa: F821
+        ...
+
+    @dbus_property(PropertyAccess.READ, disabled=True)
+    @no_type_check
+    def RSSISamplingPeriod(self) -> "q":  # noqa: F821
+        ...
+
+    @dbus_property(PropertyAccess.READ)
+    @no_type_check
+    def Patterns(self) -> "a(yyay)":  # noqa: F821
+        return self._or_patterns
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/client.py 0.15.1-1/bleak/backends/bluezdbus/client.py
--- 0.14.3-1/bleak/backends/bluezdbus/client.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/client.py	2022-08-03 16:15:28.000000000 +0000
@@ -7,30 +7,27 @@ import logging
 import asyncio
 import os
 import warnings
-from typing import Any, Callable, Dict, Optional, Union
+from typing import Callable, Optional, Union
 from uuid import UUID
 
 from dbus_next.aio import MessageBus
-from dbus_next.constants import BusType, ErrorType, MessageType
+from dbus_next.constants import BusType, ErrorType
 from dbus_next.message import Message
 from dbus_next.signature import Variant
 
-from bleak.backends.bluezdbus import check_bluez_version, defs
+from bleak.backends.bluezdbus import defs
 from bleak.backends.bluezdbus.characteristic import BleakGATTCharacteristicBlueZDBus
-from bleak.backends.bluezdbus.descriptor import BleakGATTDescriptorBlueZDBus
+from bleak.backends.bluezdbus.manager import get_global_bluez_manager
 from bleak.backends.bluezdbus.scanner import BleakScannerBlueZDBus
-from bleak.backends.bluezdbus.service import BleakGATTServiceBlueZDBus
-from bleak.backends.bluezdbus.signals import MatchRules, add_match
 from bleak.backends.bluezdbus.utils import (
     assert_reply,
     extract_service_handle_from_path,
-    unpack_variants,
 )
 from bleak.backends.client import BaseBleakClient
 from bleak.backends.device import BLEDevice
 from bleak.backends.service import BleakGATTServiceCollection
 from bleak.exc import BleakDBusError, BleakError
-
+from .version import BlueZFeatures
 
 logger = logging.getLogger(__name__)
 
@@ -66,10 +63,10 @@ class BleakClientBlueZDBus(BaseBleakClie
 
         # D-Bus message bus
         self._bus: Optional[MessageBus] = None
-        # D-Bus properties for the device
-        self._properties: Dict[str, Any] = {}
-        # provides synchronization between get_services() and PropertiesChanged signal
-        self._services_resolved_event: Optional[asyncio.Event] = None
+        # tracks device watcher subscription
+        self._remove_device_watcher: Optional[Callable] = None
+        # private backing for is_connected property
+        self._is_connected = False
         # indicates disconnect request in progress when not None
         self._disconnecting_event: Optional[asyncio.Event] = None
         # used to ensure device gets disconnected if event loop crashes
@@ -78,12 +75,6 @@ class BleakClientBlueZDBus(BaseBleakClie
         # used to override mtu_size property
         self._mtu_size: Optional[int] = None
 
-        # BlueZ version features
-        self._can_write_without_response = check_bluez_version(5, 46)
-        self._write_without_response_workaround_needed = not check_bluez_version(5, 51)
-        self._hides_battery_characteristic = check_bluez_version(5, 48)
-        self._hides_device_name_characteristic = check_bluez_version(5, 48)
-
     # Connectivity methods
 
     async def connect(self, **kwargs) -> bool:
@@ -105,6 +96,10 @@ class BleakClientBlueZDBus(BaseBleakClie
         if self.is_connected:
             raise BleakError("Client is already connected")
 
+        if not BlueZFeatures.checked_bluez_version:
+            await BlueZFeatures.check_bluez_version()
+        if not BlueZFeatures.supported_version:
+            raise BleakError("Bleak requires BlueZ >= 5.43.")
         # A Discover must have been run before connecting to any devices.
         # Find the desired device before trying to connect.
         timeout = kwargs.get("timeout", self._timeout)
@@ -121,163 +116,63 @@ class BleakClientBlueZDBus(BaseBleakClie
                     "Device with address {0} was not found.".format(self.address)
                 )
 
-        # Create system bus
+        manager = await get_global_bluez_manager()
+
+        # Each BLE connection session needs a new D-Bus connection to avoid a
+        # BlueZ quirk where notifications are automatically enabled on reconnect.
         self._bus = await MessageBus(
             bus_type=BusType.SYSTEM, negotiate_unix_fd=True
         ).connect()
 
-        try:
-            # Add signal handlers. These monitor the device D-Bus object and
-            # all of its descendats (services, characteristics, descriptors).
-            # This we always have an up-to-date state for all of these that is
-            # maintained automatically in the background.
-
-            self._bus.add_message_handler(self._parse_msg)
-
-            rules = MatchRules(
-                interface=defs.OBJECT_MANAGER_INTERFACE,
-                member="InterfacesAdded",
-                arg0path=f"{self._device_path}/",
-            )
-            reply = await add_match(self._bus, rules)
-            assert_reply(reply)
-
-            rules = MatchRules(
-                interface=defs.OBJECT_MANAGER_INTERFACE,
-                member="InterfacesRemoved",
-                arg0path=f"{self._device_path}/",
-            )
-            reply = await add_match(self._bus, rules)
-            assert_reply(reply)
+        def on_connected_changed(connected: bool) -> None:
+            if not connected:
+                logger.debug(f"Device disconnected ({self._device_path})")
+
+                self._is_connected = False
+
+                if self._disconnect_monitor_event:
+                    self._disconnect_monitor_event.set()
+                    self._disconnect_monitor_event = None
+
+                self._cleanup_all()
+                if self._disconnected_callback is not None:
+                    self._disconnected_callback(self)
+                disconnecting_event = self._disconnecting_event
+                if disconnecting_event:
+                    disconnecting_event.set()
+
+        def on_value_changed(char_path: str, value: bytes) -> None:
+            if char_path in self._notification_callbacks:
+                handle = extract_service_handle_from_path(char_path)
+                self._notification_callbacks[char_path](handle, bytearray(value))
 
-            rules = MatchRules(
-                interface=defs.PROPERTIES_INTERFACE,
-                member="PropertiesChanged",
-                path_namespace=self._device_path,
-            )
-            reply = await add_match(self._bus, rules)
-            assert_reply(reply)
-
-            # Find the HCI device to use for scanning and get cached device properties
-            reply = await self._bus.call(
-                Message(
-                    destination=defs.BLUEZ_SERVICE,
-                    path="/",
-                    member="GetManagedObjects",
-                    interface=defs.OBJECT_MANAGER_INTERFACE,
-                )
-            )
-            assert_reply(reply)
-
-            interfaces_and_props: Dict[str, Dict[str, Variant]] = reply.body[0]
+        watcher = manager.add_device_watcher(
+            self._device_path, on_connected_changed, on_value_changed
+        )
+        self._remove_device_watcher = lambda: manager.remove_device_watcher(watcher)
 
-            # The device may have been removed from BlueZ since the time we stopped scanning
-            if self._device_path not in interfaces_and_props:
-                # Sometimes devices can be removed from the BlueZ object manager
-                # before we connect to them. In this case we try using the
-                # org.bluez.Adapter1.ConnectDevice method instead. This method
-                # requires that bluetoothd is run with the --experimental flag
-                # and is available since BlueZ 5.49.
-                logger.debug(
-                    f"org.bluez.Device1 object not found, trying org.bluez.Adapter1.ConnectDevice ({self._device_path})"
-                )
+        try:
+            try:
                 reply = await asyncio.wait_for(
                     self._bus.call(
                         Message(
                             destination=defs.BLUEZ_SERVICE,
-                            interface=defs.ADAPTER_INTERFACE,
-                            path=f"/org/bluez/{self._adapter}",
-                            member="ConnectDevice",
-                            signature="a{sv}",
-                            body=[
-                                {
-                                    "Address": Variant(
-                                        "s", self._device_info["Address"]
-                                    ),
-                                    "AddressType": Variant(
-                                        "s", self._device_info["AddressType"]
-                                    ),
-                                }
-                            ],
+                            interface=defs.DEVICE_INTERFACE,
+                            path=self._device_path,
+                            member="Connect",
                         )
                     ),
                     timeout,
                 )
-
-                # FIXME: how to cancel connection if timeout?
-
-                if (
-                    reply.message_type == MessageType.ERROR
-                    and reply.error_name == ErrorType.UNKNOWN_METHOD.value
-                ):
-                    logger.debug(
-                        f"org.bluez.Adapter1.ConnectDevice not found ({self._device_path}), try enabling bluetoothd --experimental"
-                    )
-                    raise BleakError(
-                        "Device with address {0} could not be found. "
-                        "Try increasing `timeout` value or moving the device closer.".format(
-                            self.address
-                        )
-                    )
-
                 assert_reply(reply)
-            else:
-                # required interface
-                self._properties = unpack_variants(
-                    interfaces_and_props[self._device_path][defs.DEVICE_INTERFACE]
-                )
 
-                # optional interfaces - services and characteristics may not
-                # be populated yet
-                for path, interfaces in interfaces_and_props.items():
-                    if not path.startswith(self._device_path):
-                        continue
-
-                    if defs.GATT_SERVICE_INTERFACE in interfaces:
-                        obj = unpack_variants(interfaces[defs.GATT_SERVICE_INTERFACE])
-                        self.services.add_service(BleakGATTServiceBlueZDBus(obj, path))
-
-                    if defs.GATT_CHARACTERISTIC_INTERFACE in interfaces:
-                        obj = unpack_variants(
-                            interfaces[defs.GATT_CHARACTERISTIC_INTERFACE]
-                        )
-                        service = interfaces_and_props[obj["Service"]][
-                            defs.GATT_SERVICE_INTERFACE
-                        ]
-                        uuid = service["UUID"].value
-                        handle = extract_service_handle_from_path(obj["Service"])
-                        self.services.add_characteristic(
-                            BleakGATTCharacteristicBlueZDBus(obj, path, uuid, handle)
-                        )
-
-                    if defs.GATT_DESCRIPTOR_INTERFACE in interfaces:
-                        obj = unpack_variants(
-                            interfaces[defs.GATT_DESCRIPTOR_INTERFACE]
-                        )
-                        characteristic = interfaces_and_props[obj["Characteristic"]][
-                            defs.GATT_CHARACTERISTIC_INTERFACE
-                        ]
-                        uuid = characteristic["UUID"].value
-                        handle = extract_service_handle_from_path(obj["Characteristic"])
-                        self.services.add_descriptor(
-                            BleakGATTDescriptorBlueZDBus(obj, path, uuid, handle)
-                        )
-
-                try:
-                    reply = await asyncio.wait_for(
-                        self._bus.call(
-                            Message(
-                                destination=defs.BLUEZ_SERVICE,
-                                interface=defs.DEVICE_INTERFACE,
-                                path=self._device_path,
-                                member="Connect",
-                            )
-                        ),
-                        timeout,
-                    )
-                    assert_reply(reply)
-                except BaseException:
-                    # calling Disconnect cancels any pending connect request
+                self._is_connected = True
+            except BaseException:
+                # calling Disconnect cancels any pending connect request
+                if self._bus:
+                    # If disconnected callback already fired, this will be a no-op
+                    # since self._bus will be None and the _cleanup_all call will
+                    # have already disconnected.
                     try:
                         reply = await self._bus.call(
                             Message(
@@ -300,14 +195,7 @@ class BleakClientBlueZDBus(BaseBleakClie
                             f"Failed to cancel connection ({self._device_path}): {e}"
                         )
 
-                    raise
-
-            if self.is_connected:
-                logger.debug(f"Connection successful ({self._device_path})")
-            else:
-                raise BleakError(
-                    f"Connection was not successful! ({self._device_path})"
-                )
+                raise
 
             # Create a task that runs until the device is disconnected.
             self._disconnect_monitor_event = asyncio.Event()
@@ -329,7 +217,7 @@ class BleakClientBlueZDBus(BaseBleakClie
         # loop is called with asyncio.run() or otherwise runs pending tasks
         # after the original event loop stops. This will also cause an exception
         # if a run loop is stopped before the device is disconnected since this
-        # task will still be running and asyncio compains if a loop with running
+        # task will still be running and asyncio complains if a loop with running
         # tasks is stopped.
         try:
             await self._disconnect_monitor_event.wait()
@@ -356,6 +244,10 @@ class BleakClientBlueZDBus(BaseBleakClie
         """
         logger.debug(f"_cleanup_all({self._device_path})")
 
+        if self._remove_device_watcher:
+            self._remove_device_watcher()
+            self._remove_device_watcher = None
+
         if not self._bus:
             logger.debug(f"already disconnected ({self._device_path})")
             return
@@ -363,6 +255,9 @@ class BleakClientBlueZDBus(BaseBleakClie
         # Try to disconnect the System Bus.
         try:
             self._bus.disconnect()
+
+            # work around https://github.com/altdesktop/python-dbus-next/issues/112
+            self._bus._sock.close()
         except Exception as e:
             logger.error(
                 f"Attempt to disconnect system bus failed ({self._device_path}): {e}"
@@ -513,7 +408,7 @@ class BleakClientBlueZDBus(BaseBleakClie
 
         """
         return self._DeprecatedIsConnectedReturn(
-            False if self._bus is None else self._properties.get("Connected", False)
+            False if self._bus is None else self._is_connected
         )
 
     async def _acquire_mtu(self) -> None:
@@ -564,7 +459,7 @@ class BleakClientBlueZDBus(BaseBleakClie
         """Get ATT MTU size for active connection"""
         if self._mtu_size is None:
             warnings.warn(
-                "Using default MTU value. Call _assign_mtu() or set _mtu_size first to avoid this warning."
+                "Using default MTU value. Call _acquire_mtu() or set _mtu_size first to avoid this warning."
             )
             return 23
 
@@ -585,15 +480,11 @@ class BleakClientBlueZDBus(BaseBleakClie
         if self._services_resolved:
             return self.services
 
-        if not self._properties["ServicesResolved"]:
-            logger.debug(f"Waiting for ServicesResolved ({self._device_path})")
-            self._services_resolved_event = asyncio.Event()
-            try:
-                await asyncio.wait_for(self._services_resolved_event.wait(), 5)
-            finally:
-                self._services_resolved_event = None
+        manager = await get_global_bluez_manager()
 
+        self.services = await manager.get_services(self._device_path)
         self._services_resolved = True
+
         return self.services
 
     # IO methods
@@ -625,8 +516,9 @@ class BleakClientBlueZDBus(BaseBleakClie
         if not characteristic:
             # Special handling for BlueZ >= 5.48, where Battery Service (0000180f-0000-1000-8000-00805f9b34fb:)
             # has been moved to interface org.bluez.Battery1 instead of as a regular service.
-            if str(char_specifier) == "00002a19-0000-1000-8000-00805f9b34fb" and (
-                self._hides_battery_characteristic
+            if (
+                str(char_specifier) == "00002a19-0000-1000-8000-00805f9b34fb"
+                and BlueZFeatures.hides_battery_characteristic
             ):
                 reply = await self._bus.call(
                     Message(
@@ -647,11 +539,13 @@ class BleakClientBlueZDBus(BaseBleakClie
                     )
                 )
                 return value
-            if str(char_specifier) == "00002a00-0000-1000-8000-00805f9b34fb" and (
-                self._hides_device_name_characteristic
+            if (
+                str(char_specifier) == "00002a00-0000-1000-8000-00805f9b34fb"
+                and BlueZFeatures.hides_device_name_characteristic
             ):
                 # Simulate regular characteristics read to be consistent over all platforms.
-                value = bytearray(self._properties["Name"].encode("ascii"))
+                manager = await get_global_bluez_manager()
+                value = bytearray(manager.get_device_name(self._device_path).encode())
                 logger.debug(
                     "Read Device Name {0} | {1}: {2}".format(
                         char_specifier, self._device_path, value
@@ -780,9 +674,9 @@ class BleakClientBlueZDBus(BaseBleakClie
             )
 
         # See docstring for details about this handling.
-        if not response and not self._can_write_without_response:
+        if not response and not BlueZFeatures.can_write_without_response:
             raise BleakError("Write without response requires at least BlueZ 5.46")
-        if response or not self._write_without_response_workaround_needed:
+        if response or not BlueZFeatures.write_without_response_workaround_needed:
             # TODO: Add OnValueUpdated handler for response=True?
             reply = await self._bus.call(
                 Message(
@@ -963,81 +857,3 @@ class BleakClientBlueZDBus(BaseBleakClie
         assert_reply(reply)
 
         self._notification_callbacks.pop(characteristic.path, None)
-
-    # Internal Callbacks
-
-    def _parse_msg(self, message: Message):
-        if message.message_type != MessageType.SIGNAL:
-            return
-
-        logger.debug(
-            "received D-Bus signal: {0}.{1} ({2}): {3}".format(
-                message.interface, message.member, message.path, message.body
-            )
-        )
-
-        if message.member == "InterfacesAdded":
-            path, interfaces = message.body
-
-            if defs.GATT_SERVICE_INTERFACE in interfaces:
-                obj = unpack_variants(interfaces[defs.GATT_SERVICE_INTERFACE])
-                # if this assert fails, it means our match rules are probably wrong
-                assert obj["Device"] == self._device_path
-                self.services.add_service(BleakGATTServiceBlueZDBus(obj, path))
-
-            if defs.GATT_CHARACTERISTIC_INTERFACE in interfaces:
-                obj = unpack_variants(interfaces[defs.GATT_CHARACTERISTIC_INTERFACE])
-                service = next(
-                    x
-                    for x in self.services.services.values()
-                    if x.path == obj["Service"]
-                )
-                self.services.add_characteristic(
-                    BleakGATTCharacteristicBlueZDBus(
-                        obj, path, service.uuid, service.handle
-                    )
-                )
-
-            if defs.GATT_DESCRIPTOR_INTERFACE in interfaces:
-                obj = unpack_variants(interfaces[defs.GATT_DESCRIPTOR_INTERFACE])
-                handle = extract_service_handle_from_path(obj["Characteristic"])
-                characteristic = self.services.characteristics[handle]
-                self.services.add_descriptor(
-                    BleakGATTDescriptorBlueZDBus(obj, path, characteristic.uuid, handle)
-                )
-        elif message.member == "InterfacesRemoved":
-            path, interfaces = message.body
-
-        elif message.member == "PropertiesChanged":
-            interface, changed, _ = message.body
-            changed = unpack_variants(changed)
-
-            if interface == defs.GATT_CHARACTERISTIC_INTERFACE:
-                if message.path in self._notification_callbacks and "Value" in changed:
-                    handle = extract_service_handle_from_path(message.path)
-                    self._notification_callbacks[message.path](
-                        handle, bytearray(changed["Value"])
-                    )
-            elif interface == defs.DEVICE_INTERFACE:
-                self._properties.update(changed)
-
-                if "ServicesResolved" in changed:
-                    if changed["ServicesResolved"]:
-                        if self._services_resolved_event:
-                            self._services_resolved_event.set()
-                    else:
-                        self._services_resolved = False
-
-                if "Connected" in changed and not changed["Connected"]:
-                    logger.debug(f"Device disconnected ({self._device_path})")
-
-                    if self._disconnect_monitor_event:
-                        self._disconnect_monitor_event.set()
-                        self._disconnect_monitor_event = None
-
-                    self._cleanup_all()
-                    if self._disconnected_callback is not None:
-                        self._disconnected_callback(self)
-                    disconnecting_event = self._disconnecting_event
-                    if disconnecting_event:
-                        disconnecting_event.set()
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/defs.py 0.15.1-1/bleak/backends/bluezdbus/defs.py
--- 0.14.3-1/bleak/backends/bluezdbus/defs.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/defs.py	2022-08-03 16:15:28.000000000 +0000
@@ -7,6 +7,8 @@ PROPERTIES_INTERFACE = "org.freedesktop.
 # Bluez specific DBUS
 BLUEZ_SERVICE = "org.bluez"
 ADAPTER_INTERFACE = "org.bluez.Adapter1"
+ADVERTISEMENT_MONITOR_INTERFACE = "org.bluez.AdvertisementMonitor1"
+ADVERTISEMENT_MONITOR_MANAGER_INTERFACE = "org.bluez.AdvertisementMonitorManager1"
 DEVICE_INTERFACE = "org.bluez.Device1"
 BATTERY_INTERFACE = "org.bluez.Battery1"
 
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/descriptor.py 0.15.1-1/bleak/backends/bluezdbus/descriptor.py
--- 0.14.3-1/bleak/backends/bluezdbus/descriptor.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/descriptor.py	2022-08-03 16:15:28.000000000 +0000
@@ -19,7 +19,7 @@ class BleakGATTDescriptorBlueZDBus(Bleak
 
     @property
     def characteristic_handle(self) -> int:
-        """handle for the characteristic that this descriptor belongs to"""
+        """Handle for the characteristic that this descriptor belongs to"""
         return self.__characteristic_handle
 
     @property
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/__init__.py 0.15.1-1/bleak/backends/bluezdbus/__init__.py
--- 0.14.3-1/bleak/backends/bluezdbus/__init__.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/__init__.py	2022-08-03 16:15:28.000000000 +0000
@@ -1,28 +1 @@
-import re
-import subprocess
-
-from ...exc import BleakError
-
-
-def check_bluez_version(major: int, minor: int) -> bool:
-    """
-    Checks the BlueZ version.
-
-    Returns:
-        ``True`` if the BlueZ major version is equal to *major* and the minor
-        version is greater than or equal to *minor*, otherwise ``False``.
-    """
-    # lazy-get the version and store it so we only have to run subprocess once
-    if not hasattr(check_bluez_version, "version"):
-        p = subprocess.Popen(["bluetoothctl", "--version"], stdout=subprocess.PIPE)
-        out, _ = p.communicate()
-        s = re.search(b"(\\d+).(\\d+)", out.strip(b"'"))
-
-        if not s:
-            raise BleakError(f"Could not determine BlueZ version: {out.decode()}")
-
-        setattr(check_bluez_version, "version", tuple(map(int, s.groups())))
-
-    bluez_major, bluez_minor = getattr(check_bluez_version, "version")
-
-    return bluez_major == major and bluez_minor >= minor
+"""BlueZ backend."""
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/manager.py 0.15.1-1/bleak/backends/bluezdbus/manager.py
--- 0.14.3-1/bleak/backends/bluezdbus/manager.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/manager.py	2022-08-03 16:15:28.000000000 +0000
@@ -0,0 +1,814 @@
+"""
+BlueZ D-Bus manager module
+--------------------------
+
+This module contains code for the global BlueZ D-Bus object manager that is
+used internally by Bleak.
+"""
+
+import asyncio
+import logging
+import os
+from typing import (
+    Any,
+    Callable,
+    Coroutine,
+    Dict,
+    Iterable,
+    List,
+    NamedTuple,
+    Optional,
+    Set,
+    Tuple,
+    cast,
+)
+
+from dbus_next import BusType, Message, MessageType, Variant
+from dbus_next.aio.message_bus import MessageBus
+from typing_extensions import Literal, TypedDict
+
+from ...exc import BleakError
+from ..service import BleakGATTServiceCollection
+from . import defs
+from .advertisement_monitor import AdvertisementMonitor, OrPatternLike
+from .characteristic import BleakGATTCharacteristicBlueZDBus
+from .descriptor import BleakGATTDescriptorBlueZDBus
+from .service import BleakGATTServiceBlueZDBus
+from .signals import MatchRules, add_match
+from .utils import assert_reply, unpack_variants
+
+logger = logging.getLogger(__name__)
+
+
+# D-Bus properties for interfaces
+# https://github.com/bluez/bluez/blob/master/doc/adapter-api.txt
+
+
+class Adapter1(TypedDict):
+    Address: str
+    Name: str
+    Alias: str
+    Class: int
+    Powered: bool
+    Discoverable: bool
+    Pairable: bool
+    PairableTimeout: int
+    DiscoverableTimeout: int
+    Discovering: int
+    UUIDs: List[str]
+    Modalias: str
+    Roles: List[str]
+    ExperimentalFeatures: List[str]
+
+
+# https://github.com/bluez/bluez/blob/master/doc/advertisement-monitor-api.txt
+
+
+class AdvertisementMonitor1(TypedDict):
+    Type: str
+    RSSILowThreshold: int
+    RSSIHighThreshold: int
+    RSSILowTimeout: int
+    RSSIHighTimeout: int
+    RSSISamplingPeriod: int
+    Patterns: List[Tuple[int, int, bytes]]
+
+
+class AdvertisementMonitorManager1(TypedDict):
+    SupportedMonitorTypes: List[str]
+    SupportedFeatures: List[str]
+
+
+# https://github.com/bluez/bluez/blob/master/doc/battery-api.txt
+
+
+class Battery1(TypedDict):
+    SupportedMonitorTypes: List[str]
+    SupportedFeatures: List[str]
+
+
+# https://github.com/bluez/bluez/blob/master/doc/device-api.txt
+
+
+class Device1(TypedDict):
+    Address: str
+    AddressType: str
+    Name: str
+    Icon: str
+    Class: int
+    Appearance: int
+    UUIDs: List[str]
+    Paired: bool
+    Bonded: bool
+    Connected: bool
+    Trusted: bool
+    Blocked: bool
+    WakeAllowed: bool
+    Alias: str
+    Adapter: str
+    LegacyPairing: bool
+    Modalias: str
+    RSSI: int
+    TxPower: int
+    ManufacturerData: Dict[int, bytes]
+    ServiceData: Dict[str, bytes]
+    ServicesResolved: bool
+    AdvertisingFlags: bytes
+    AdvertisingData: Dict[int, bytes]
+
+
+# https://github.com/bluez/bluez/blob/master/doc/gatt-api.txt
+
+
+class GattService1(TypedDict):
+    UUID: str
+    Primary: bool
+    Device: str
+    Includes: List[str]
+    # Handle is server-only and not available in Bleak
+
+
+class GattCharacteristic1(TypedDict):
+    UUID: str
+    Service: str
+    Value: bytes
+    WriteAcquired: bool
+    NotifyAcquired: bool
+    Notifying: bool
+    Flags: List[
+        Literal[
+            "broadcast",
+            "read",
+            "write-without-response",
+            "write",
+            "notify",
+            "indicate",
+            "authenticated-signed-writes",
+            "extended-properties",
+            "reliable-write",
+            "writable-auxiliaries",
+            "encrypt-read",
+            "encrypt-write",
+            # "encrypt-notify" and "encrypt-indicate" are server-only
+            "encrypt-authenticated-read",
+            "encrypt-authenticated-write",
+            # "encrypt-authenticated-notify", "encrypt-authenticated-indicate",
+            # "secure-read", "secure-write", "secure-notify", "secure-indicate"
+            # are server-only
+            "authorize",
+        ]
+    ]
+    MTU: int
+    # Handle is server-only and not available in Bleak
+
+
+class GattDescriptor1(TypedDict):
+    UUID: str
+    Characteristic: str
+    Value: bytes
+    Flags: List[
+        Literal[
+            "read",
+            "write",
+            "encrypt-read",
+            "encrypt-write",
+            "encrypt-authenticated-read",
+            "encrypt-authenticated-write",
+            # "secure-read" and "secure-write" are server-only and not available in Bleak
+            "authorize",
+        ]
+    ]
+    # Handle is server-only and not available in Bleak
+
+
+AdvertisementCallback = Callable[[str, Device1], None]
+"""
+A callback that is called when advertisement data is received.
+
+Args:
+    arg0: The D-Bus object path of the device.
+    arg1: The D-Bus properties of the device object.
+"""
+
+
+class CallbackAndState(NamedTuple):
+    """
+    Encapsulates an :data:`AdvertisementCallback` and some state.
+    """
+
+    callback: AdvertisementCallback
+    """
+    The callback.
+    """
+
+    adapter_path: str
+    """
+    The D-Bus object path of the adapter associated with the callback.
+    """
+
+    seen_devices: Set[str]
+    """
+    Set of device D-Bus object paths that have been "seen" already by this callback.
+    """
+
+
+DeviceConnectedChangedCallback = Callable[[bool], None]
+"""
+A callback that is called when a device's "Connected" property changes.
+
+Args:
+    arg0: The current value of the "Connected" property.
+"""
+
+CharacteristicValueChangedCallback = Callable[[str, bytes], None]
+"""
+A callback that is called when a characteristics's "Value" property changes.
+
+Args:
+    arg0: The D-Bus object path of the characteristic.
+    arg1: The current value of the "Value" property.
+"""
+
+
+class DeviceWatcher(NamedTuple):
+
+    device_path: str
+    """
+    The D-Bus object path of the device.
+    """
+
+    on_connected_changed: DeviceConnectedChangedCallback
+    """
+    A callback that is called when a device's "Connected" property changes.
+    """
+
+    on_characteristic_value_changed: CharacteristicValueChangedCallback
+    """
+    A callback that is called when a characteristics's "Value" property changes.
+    """
+
+
+# set of org.bluez.Device1 property names that come from advertising data
+_ADVERTISING_DATA_PROPERTIES = {
+    "AdvertisingData",
+    "AdvertisingFlags",
+    "ManufacturerData",
+    "Name",
+    "ServiceData",
+    "UUIDs",
+}
+
+
+class BlueZManager:
+    """
+    BlueZ D-Bus object manager.
+
+    Use :func:`bleak.backends.bluezdbus.get_global_bluez_manager` to get the global instance.
+    """
+
+    def __init__(self):
+        self._bus: Optional[MessageBus] = None
+        self._bus_lock = asyncio.Lock()
+
+        # dict of object path: dict of interface name: dict of property name: property value
+        self._properties: Dict[str, Dict[str, Dict[str, Any]]] = {}
+
+        self._advertisement_callbacks: List[CallbackAndState] = []
+        self._device_watchers: Set[DeviceWatcher] = set()
+        self._condition_callbacks: Set[Callable] = set()
+
+    async def async_init(self):
+        """
+        Connects to the D-Bus message bus and begins monitoring signals.
+
+        It is safe to call this method multiple times. If the bus is already
+        connected, no action is performed.
+        """
+        async with self._bus_lock:
+            if self._bus and self._bus.connected:
+                return
+
+            # We need to create a new MessageBus each time as
+            # dbus-next will destory the underlying file descriptors
+            # when the previous one is closed in its finalizer.
+            bus = MessageBus(bus_type=BusType.SYSTEM)
+            await bus.connect()
+
+            try:
+                # Add signal listeners
+
+                bus.add_message_handler(self._parse_msg)
+
+                rules = MatchRules(
+                    interface=defs.OBJECT_MANAGER_INTERFACE,
+                    member="InterfacesAdded",
+                    arg0path="/org/bluez/",
+                )
+                reply = await add_match(bus, rules)
+                assert_reply(reply)
+
+                rules = MatchRules(
+                    interface=defs.OBJECT_MANAGER_INTERFACE,
+                    member="InterfacesRemoved",
+                    arg0path="/org/bluez/",
+                )
+                reply = await add_match(bus, rules)
+                assert_reply(reply)
+
+                rules = MatchRules(
+                    interface=defs.PROPERTIES_INTERFACE,
+                    member="PropertiesChanged",
+                    path_namespace="/org/bluez",
+                )
+                reply = await add_match(bus, rules)
+                assert_reply(reply)
+
+                # get existing objects after adding signal handlers to avoid
+                # race condition
+
+                reply = await bus.call(
+                    Message(
+                        destination=defs.BLUEZ_SERVICE,
+                        path="/",
+                        member="GetManagedObjects",
+                        interface=defs.OBJECT_MANAGER_INTERFACE,
+                    )
+                )
+                assert_reply(reply)
+
+                # dictionary is replaced in case AddInterfaces was received first
+                self._properties = {
+                    path: unpack_variants(interfaces)
+                    for path, interfaces in reply.body[0].items()
+                }
+
+                logger.debug(f"initial properties: {self._properties}")
+
+            except BaseException:
+                # if setup failed, disconnect
+                bus.disconnect()
+                raise
+
+            # Everything is setup, so save the bus
+            self._bus = bus
+
+    async def active_scan(
+        self,
+        adapter_path: str,
+        filters: Dict[str, Variant],
+        callback: AdvertisementCallback,
+    ) -> Callable[[], Coroutine]:
+        """
+        Configures the advertisement data filters and starts scanning.
+
+        Args:
+            adapter_path: The D-Bus object path of the adapter to use for scanning.
+            filters: A dictionary of filters to pass to ``SetDiscoveryFilter``.
+            callback: A callable that will be called when new advertisement data is received.
+
+        Returns:
+            An async function that is used to stop scanning and remove the filters.
+        """
+        async with self._bus_lock:
+            # If the adapter doesn't exist, then the message calls below would
+            # fail with "method not found". This provides a more informative
+            # error message.
+            if adapter_path not in self._properties:
+                raise BleakError(f"adapter '{adapter_path.split('/')[-1]}' not found")
+
+            callback_and_state = CallbackAndState(callback, adapter_path, set())
+            self._advertisement_callbacks.append(callback_and_state)
+
+            try:
+                # Apply the filters
+                reply = await self._bus.call(
+                    Message(
+                        destination=defs.BLUEZ_SERVICE,
+                        path=adapter_path,
+                        interface=defs.ADAPTER_INTERFACE,
+                        member="SetDiscoveryFilter",
+                        signature="a{sv}",
+                        body=[filters],
+                    )
+                )
+                assert_reply(reply)
+
+                # Start scanning
+                reply = await self._bus.call(
+                    Message(
+                        destination=defs.BLUEZ_SERVICE,
+                        path=adapter_path,
+                        interface=defs.ADAPTER_INTERFACE,
+                        member="StartDiscovery",
+                    )
+                )
+                assert_reply(reply)
+
+                async def stop() -> None:
+                    async with self._bus_lock:
+                        reply = await self._bus.call(
+                            Message(
+                                destination=defs.BLUEZ_SERVICE,
+                                path=adapter_path,
+                                interface=defs.ADAPTER_INTERFACE,
+                                member="StopDiscovery",
+                            )
+                        )
+                        assert_reply(reply)
+
+                        # remove the filters
+                        reply = await self._bus.call(
+                            Message(
+                                destination=defs.BLUEZ_SERVICE,
+                                path=adapter_path,
+                                interface=defs.ADAPTER_INTERFACE,
+                                member="SetDiscoveryFilter",
+                                signature="a{sv}",
+                                body=[{}],
+                            )
+                        )
+                        assert_reply(reply)
+
+                        self._advertisement_callbacks.remove(callback_and_state)
+
+                return stop
+            except BaseException:
+                # if starting scanning failed, don't leak the callback
+                self._advertisement_callbacks.remove(callback_and_state)
+                raise
+
+    async def passive_scan(
+        self,
+        adapter_path: str,
+        filters: List[OrPatternLike],
+        callback: AdvertisementCallback,
+    ) -> Callable[[], Coroutine]:
+        """
+        Configures the advertisement data filters and starts scanning.
+
+        Args:
+            adapter_path: The D-Bus object path of the adapter to use for scanning.
+            filters: A list of "or patterns" to pass to ``org.bluez.AdvertisementMonitor1``.
+            callback: A callable that will be called when new advertisement data is received.
+
+        Returns:
+            An async function that is used to stop scanning and remove the filters.
+        """
+        async with self._bus_lock:
+            # If the adapter doesn't exist, then the message calls below would
+            # fail with "method not found". This provides a more informative
+            # error message.
+            if adapter_path not in self._properties:
+                raise BleakError(f"adapter '{adapter_path.split('/')[-1]}' not found")
+
+            callback_and_state = CallbackAndState(callback, adapter_path, set())
+            self._advertisement_callbacks.append(callback_and_state)
+
+            try:
+                monitor = AdvertisementMonitor(filters)
+
+                # this should be a unique path to allow multiple python interpreters
+                # running bleak and multiple scanners within a single interpreter
+                monitor_path = f"/org/bleak/{os.getpid()}/{id(monitor)}"
+
+                reply = await self._bus.call(
+                    Message(
+                        destination=defs.BLUEZ_SERVICE,
+                        path=adapter_path,
+                        interface=defs.ADVERTISEMENT_MONITOR_MANAGER_INTERFACE,
+                        member="RegisterMonitor",
+                        signature="o",
+                        body=[monitor_path],
+                    )
+                )
+
+                if (
+                    reply.message_type == MessageType.ERROR
+                    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"
+                    )
+
+                assert_reply(reply)
+
+                # It is important to export after registering, otherwise BlueZ
+                # won't use the monitor
+                self._bus.export(monitor_path, monitor)
+
+                async def stop():
+                    async with self._bus_lock:
+                        self._bus.unexport(monitor_path, monitor)
+
+                        reply = await self._bus.call(
+                            Message(
+                                destination=defs.BLUEZ_SERVICE,
+                                path=adapter_path,
+                                interface=defs.ADVERTISEMENT_MONITOR_MANAGER_INTERFACE,
+                                member="UnregisterMonitor",
+                                signature="o",
+                                body=[monitor_path],
+                            )
+                        )
+                        assert_reply(reply)
+
+                        self._advertisement_callbacks.remove(callback_and_state)
+
+                return stop
+
+            except BaseException:
+                # if starting scanning failed, don't leak the callback
+                self._advertisement_callbacks.remove(callback_and_state)
+                raise
+
+    def add_device_watcher(
+        self,
+        device_path: str,
+        on_connected_changed: DeviceConnectedChangedCallback,
+        on_characteristic_value_changed: CharacteristicValueChangedCallback,
+    ) -> DeviceWatcher:
+        """
+        Registers a device watcher to receive callbacks when device state
+        changes or events are received.
+
+        Args:
+            device_path:
+                The D-Bus object path of the device.
+            on_connected_changed:
+                A callback that is called when the device's "Connected"
+                state changes.
+            on_characteristic_value_changed:
+                A callback that is called whenever a characteristic receives
+                a notification/indication.
+
+        Returns:
+            A device watcher object that acts a token to unregister the watcher.
+        """
+        watcher = DeviceWatcher(
+            device_path, on_connected_changed, on_characteristic_value_changed
+        )
+
+        self._device_watchers.add(watcher)
+        return watcher
+
+    def remove_device_watcher(self, watcher: DeviceWatcher) -> None:
+        """
+        Unregisters a device watcher.
+
+        Args:
+            The device watcher token that was returned by
+            :meth:`add_device_watcher`.
+        """
+        self._device_watchers.remove(watcher)
+
+    async def get_services(self, device_path: str) -> BleakGATTServiceCollection:
+        """
+        Builds a new :class:`BleakGATTServiceCollection` from the current state.
+
+        Args:
+            device_path: The D-Bus object path of the Bluetooth device.
+
+        Returns:
+            A new :class:`BleakGATTServiceCollection`.
+        """
+        await self._wait_condition(device_path, "ServicesResolved", True)
+
+        services = BleakGATTServiceCollection()
+
+        for service_path, service_ifaces in self._properties.items():
+            if (
+                not service_path.startswith(device_path)
+                or defs.GATT_SERVICE_INTERFACE not in service_ifaces
+            ):
+                continue
+
+            service = BleakGATTServiceBlueZDBus(
+                service_ifaces[defs.GATT_SERVICE_INTERFACE], service_path
+            )
+
+            services.add_service(service)
+
+            for char_path, char_ifaces in self._properties.items():
+                if (
+                    not char_path.startswith(service_path)
+                    or defs.GATT_CHARACTERISTIC_INTERFACE not in char_ifaces
+                ):
+                    continue
+
+                char = BleakGATTCharacteristicBlueZDBus(
+                    char_ifaces[defs.GATT_CHARACTERISTIC_INTERFACE],
+                    char_path,
+                    service.uuid,
+                    service.handle,
+                )
+
+                services.add_characteristic(char)
+
+                for desc_path, desc_ifaces in self._properties.items():
+                    if (
+                        not desc_path.startswith(char_path)
+                        or defs.GATT_DESCRIPTOR_INTERFACE not in desc_ifaces
+                    ):
+                        continue
+
+                    desc = BleakGATTDescriptorBlueZDBus(
+                        desc_ifaces[defs.GATT_DESCRIPTOR_INTERFACE],
+                        desc_path,
+                        char.uuid,
+                        char.handle,
+                    )
+
+                    services.add_descriptor(desc)
+
+        return services
+
+    def get_device_name(self, device_path: str) -> str:
+        """
+        Gets the value of the "Name" property for a device.
+
+        Args:
+            device_path: The D-Bus object path of the device.
+
+        Returns:
+            The current property value.
+        """
+        return self._properties[device_path][defs.DEVICE_INTERFACE]["Name"]
+
+    async def _wait_condition(
+        self, device_path: str, property_name: str, property_value: Any
+    ) -> None:
+        """
+        Waits for a condition to become true.
+
+        Args:
+            device_path: The D-Bus object path of a Bluetooth device.
+            property_name: The name of the property to test.
+            property_value: A value to compare the current property value to.
+        """
+        if (
+            self._properties[device_path][defs.DEVICE_INTERFACE][property_name]
+            == property_value
+        ):
+            return
+
+        event = asyncio.Event()
+
+        def callback():
+            if (
+                self._properties[device_path][defs.DEVICE_INTERFACE][property_name]
+                == property_value
+            ):
+                event.set()
+
+        self._condition_callbacks.add(callback)
+
+        try:
+            # can be canceled
+            await event.wait()
+        finally:
+            self._condition_callbacks.remove(callback)
+
+    def _parse_msg(self, message: Message):
+        """
+        Handles callbacks from dbus_next.
+        """
+
+        if message.message_type != MessageType.SIGNAL:
+            return
+
+        logger.debug(
+            f"received D-Bus signal: {message.interface}.{message.member} ({message.path}): {message.body}"
+        )
+
+        # type hints
+        obj_path: str
+        interfaces_and_props: Dict[str, Dict[str, Variant]]
+        interfaces: List[str]
+        interface: str
+        changed: Dict[str, Variant]
+        invalidated: List[str]
+
+        if message.member == "InterfacesAdded":
+            obj_path, interfaces_and_props = message.body
+
+            for interface, props in interfaces_and_props.items():
+                unpacked_props = unpack_variants(props)
+                self._properties.setdefault(obj_path, {})[interface] = unpacked_props
+
+                # If this is a device and it has advertising data properties,
+                # then it should mean that this device just started advertising.
+                # Previously, we just relied on RSSI updates to determine if
+                # a device was actually advertising, but we were missing "slow"
+                # devices that only advertise once and then go to sleep for a while.
+                if interface == defs.DEVICE_INTERFACE:
+                    self._run_advertisement_callbacks(
+                        obj_path, cast(Device1, unpacked_props), unpacked_props.keys()
+                    )
+        elif message.member == "InterfacesRemoved":
+            obj_path, interfaces = message.body
+
+            for interface in interfaces:
+                del self._properties[obj_path][interface]
+        elif message.member == "PropertiesChanged":
+            assert message.path is not None
+
+            interface, changed, invalidated = message.body
+
+            try:
+                self_interface = self._properties[message.path][interface]
+            except KeyError:
+                # This can happen during initialization. The "PropertyChanged"
+                # handler is attached before "GetManagedObjects" is called
+                # and so self._properties may not yet be populated.
+                # This is not a problem. We just discard the property value
+                # since "GetManagedObjects" will return a newer value.
+                pass
+            else:
+                # update self._properties first
+
+                self_interface.update(unpack_variants(changed))
+
+                for name in invalidated:
+                    del self_interface[name]
+
+                # then call any callbacks so they will be called with the
+                # updated state
+
+                if interface == defs.DEVICE_INTERFACE:
+                    # handle advertisement watchers
+
+                    self._run_advertisement_callbacks(
+                        message.path, cast(Device1, self_interface), changed.keys()
+                    )
+
+                    # handle device condition watchers
+                    for condition_callback in self._condition_callbacks:
+                        condition_callback()
+
+                    # handle device connection change watchers
+
+                    if "Connected" in changed:
+                        for (
+                            device_path,
+                            on_connected_changed,
+                            _,
+                        ) in self._device_watchers.copy():
+                            # callbacks may remove the watcher, hence the copy() above
+                            if message.path == device_path:
+                                on_connected_changed(self_interface["Connected"])
+
+                elif interface == defs.GATT_CHARACTERISTIC_INTERFACE:
+                    # handle characteristic value change watchers
+
+                    if "Value" in changed:
+                        for device_path, _, on_value_changed in self._device_watchers:
+                            if message.path.startswith(device_path):
+                                on_value_changed(message.path, self_interface["Value"])
+
+    def _run_advertisement_callbacks(
+        self, device_path: str, device: Device1, changed: Iterable[str]
+    ) -> None:
+        """
+        Runs any registered advertisement callbacks.
+
+        Args:
+            device_path: The D-Bus object path of the remote device.
+            device: The current D-Bus properties of the device.
+            changed: A list of properties that have changed since the last call.
+        """
+        for (
+            callback,
+            adapter_path,
+            seen_devices,
+        ) in self._advertisement_callbacks:
+            # filter messages from other adapters
+            if not device_path.startswith(adapter_path):
+                continue
+
+            first_time_seen = False
+
+            if device_path not in seen_devices:
+                first_time_seen = True
+                seen_devices.add(device_path)
+
+            # Only do advertising data callback if this is the first time the
+            # device has been seen or if an advertising data property changed.
+            # Otherwise we get a flood of callbacks from RSSI changing.
+            if first_time_seen or not _ADVERTISING_DATA_PROPERTIES.isdisjoint(changed):
+                # TODO: this should be deep copy, not shallow
+                callback(device_path, cast(Device1, device.copy()))
+
+
+async def get_global_bluez_manager() -> BlueZManager:
+    """
+    Gets the initialized global BlueZ manager instance.
+    """
+
+    if not hasattr(get_global_bluez_manager, "instance"):
+        setattr(get_global_bluez_manager, "instance", BlueZManager())
+
+    instance: BlueZManager = getattr(get_global_bluez_manager, "instance")
+
+    await instance.async_init()
+
+    return instance
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/scanner.py 0.15.1-1/bleak/backends/bluezdbus/scanner.py
--- 0.14.3-1/bleak/backends/bluezdbus/scanner.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/scanner.py	2022-08-03 16:15:28.000000000 +0000
@@ -1,49 +1,47 @@
 import logging
-from typing import Any, Dict, List, Optional
+from typing import Callable, Coroutine, Dict, List, Optional
+from warnings import warn
 
-from dbus_next.aio import MessageBus
-from dbus_next.constants import BusType, MessageType
-from dbus_next.message import Message
-from dbus_next.signature import Variant
-
-from bleak.backends.bluezdbus import defs
-from bleak.backends.bluezdbus.signals import MatchRules, add_match, remove_match
-from bleak.backends.bluezdbus.utils import (
-    assert_reply,
-    unpack_variants,
-    validate_address,
-)
-from bleak.backends.device import BLEDevice
-from bleak.backends.scanner import BaseBleakScanner, AdvertisementData
+from dbus_next import Variant
+from typing_extensions import TypedDict, Literal
+
+from ...exc import BleakError
+from ..device import BLEDevice
+from ..scanner import AdvertisementData, AdvertisementDataCallback, BaseBleakScanner
+from .advertisement_monitor import OrPatternLike
+from .manager import Device1, get_global_bluez_manager
 
 logger = logging.getLogger(__name__)
 
-# set of org.bluez.Device1 property names that come from advertising data
-_ADVERTISING_DATA_PROPERTIES = {
-    "AdvertisingData",
-    "AdvertisingFlags",
-    "ManufacturerData",
-    "Name",
-    "ServiceData",
-    "UUIDs",
-}
-
-
-def _device_info(path, props):
-    try:
-        name = props.get("Alias", "Unknown")
-        address = props.get("Address", None)
-        if address is None:
-            try:
-                address = path[-17:].replace("_", ":")
-                if not validate_address(address):
-                    address = None
-            except Exception:
-                address = None
-        rssi = props.get("RSSI", "?")
-        return name, address, rssi, path
-    except Exception:
-        return None, None, None, None
+
+class BlueZDiscoveryFilters(TypedDict, total=False):
+    UUIDs: List[str]
+    RSSI: int
+    Pathloss: int
+    Transport: str
+    DuplicateData: bool
+    Discoverable: bool
+    Pattern: str
+
+
+class BlueZScannerArgs(TypedDict, total=False):
+    """
+    :class:`BleakScanner` args that are specific to the BlueZ backend.
+    """
+
+    filters: BlueZDiscoveryFilters
+    """
+    Filters to pass to the adapter SetDiscoveryFilter D-Bus method.
+
+    Only used for active scanning.
+    """
+
+    or_patterns: List[OrPatternLike]
+    """
+    Or patterns to pass to the AdvertisementMonitor1 D-Bus interface.
+
+    Only used for passive scanning.
+    """
 
 
 class BleakScannerBlueZDBus(BaseBleakScanner):
@@ -54,140 +52,98 @@ class BleakScannerBlueZDBus(BaseBleakSca
     <https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt?h=5.48&id=0d1e3b9c5754022c779da129025d493a198d49cf>`_
 
     Args:
-        **detection_callback (callable or coroutine):
+        detection_callback:
             Optional function that will be called each time a device is
             discovered or advertising data has changed.
-        **service_uuids (List[str]):
+        service_uuids:
             Optional list of service UUIDs to filter on. Only advertisements
             containing this advertising data will be received. Specifying this
             also enables scanning while the screen is off on Android.
+        scanning_mode:
+            Set to ``"passive"`` to avoid the ``"active"`` scanning mode.
+        **bluez:
+            Dictionary of arguments specific to the BlueZ backend.
         **adapter (str):
             Bluetooth adapter to use for discovery.
-        **filters (dict):
-            A dict of filters to be applied on discovery.
     """
 
-    def __init__(self, **kwargs):
-        super(BleakScannerBlueZDBus, self).__init__(**kwargs)
+    def __init__(
+        self,
+        detection_callback: Optional[AdvertisementDataCallback] = None,
+        service_uuids: Optional[List[str]] = None,
+        scanning_mode: Literal["active", "passive"] = "active",
+        *,
+        bluez: BlueZScannerArgs = {},
+        **kwargs,
+    ):
+        super(BleakScannerBlueZDBus, self).__init__(detection_callback, service_uuids)
+
+        self._scanning_mode = scanning_mode
+
         # kwarg "device" is for backwards compatibility
         self._adapter = kwargs.get("adapter", kwargs.get("device", "hci0"))
-
-        self._bus: Optional[MessageBus] = None
-        self._cached_devices: Dict[str, Variant] = {}
-        self._devices: Dict[str, Dict[str, Any]] = {}
-        self._rules: List[MatchRules] = []
         self._adapter_path: str = f"/org/bluez/{self._adapter}"
 
+        # map of d-bus object path to d-bus object properties
+        self._devices: Dict[str, Device1] = {}
+
+        # callback from manager for stopping scanning if it has been started
+        self._stop: Optional[Callable[[], Coroutine]] = None
+
         # Discovery filters
+
         self._filters: Dict[str, Variant] = {}
+
+        self._filters["Transport"] = Variant("s", "le")
+        self._filters["DuplicateData"] = Variant("b", False)
+
         if self._service_uuids:
             self._filters["UUIDs"] = Variant("as", self._service_uuids)
-        self.set_scanning_filter(**kwargs)
 
-    async def start(self):
-        self._bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
+        filters = kwargs.get("filters")
 
-        self._devices.clear()
-        self._cached_devices.clear()
+        if filters is None:
+            filters = bluez.get("filters")
+        else:
+            warn(
+                "the 'filters' kwarg is deprecated, use 'bluez' kwarg instead",
+                FutureWarning,
+                stacklevel=2,
+            )
 
-        # Add signal listeners
+        if filters is not None:
+            self.set_scanning_filter(filters=filters)
 
-        self._bus.add_message_handler(self._parse_msg)
+        self._or_patterns = bluez.get("or_patterns")
 
-        rules = MatchRules(
-            interface=defs.OBJECT_MANAGER_INTERFACE,
-            member="InterfacesAdded",
-            arg0path=f"{self._adapter_path}/",
-        )
-        reply = await add_match(self._bus, rules)
-        assert_reply(reply)
-        self._rules.append(rules)
-
-        rules = MatchRules(
-            interface=defs.OBJECT_MANAGER_INTERFACE,
-            member="InterfacesRemoved",
-            arg0path=f"{self._adapter_path}/",
-        )
-        reply = await add_match(self._bus, rules)
-        assert_reply(reply)
-        self._rules.append(rules)
-
-        rules = MatchRules(
-            interface=defs.PROPERTIES_INTERFACE,
-            member="PropertiesChanged",
-            path_namespace=self._adapter_path,
-        )
-        reply = await add_match(self._bus, rules)
-        assert_reply(reply)
-        self._rules.append(rules)
-
-        # Find the HCI device to use for scanning and get cached device properties
-        reply = await self._bus.call(
-            Message(
-                destination=defs.BLUEZ_SERVICE,
-                path="/",
-                member="GetManagedObjects",
-                interface=defs.OBJECT_MANAGER_INTERFACE,
+        if self._scanning_mode == "passive" and service_uuids:
+            logger.warning(
+                "service uuid filtering is not implemented for passive scanning, use bluez or_patterns as a workaround"
             )
-        )
-        assert_reply(reply)
 
-        # get only the device interface
-        self._cached_devices = {
-            path: unpack_variants(interfaces[defs.DEVICE_INTERFACE])
-            for path, interfaces in reply.body[0].items()
-            if defs.DEVICE_INTERFACE in interfaces
-        }
+        if self._scanning_mode == "passive" and not self._or_patterns:
+            raise BleakError("passive scanning mode requires bluez or_patterns")
 
-        logger.debug(f"cached devices: {self._cached_devices}")
+    async def start(self):
+        manager = await get_global_bluez_manager()
 
-        # Apply the filters
-        reply = await self._bus.call(
-            Message(
-                destination=defs.BLUEZ_SERVICE,
-                path=self._adapter_path,
-                interface=defs.ADAPTER_INTERFACE,
-                member="SetDiscoveryFilter",
-                signature="a{sv}",
-                body=[self._filters],
-            )
-        )
-        assert_reply(reply)
+        self._devices.clear()
 
-        # Start scanning
-        reply = await self._bus.call(
-            Message(
-                destination=defs.BLUEZ_SERVICE,
-                path=self._adapter_path,
-                interface=defs.ADAPTER_INTERFACE,
-                member="StartDiscovery",
+        if self._scanning_mode == "passive":
+            self._stop = await manager.passive_scan(
+                self._adapter_path, self._or_patterns, self._handle_advertising_data
+            )
+        else:
+            self._stop = await manager.active_scan(
+                self._adapter_path, self._filters, self._handle_advertising_data
             )
-        )
-        assert_reply(reply)
 
     async def stop(self):
-        reply = await self._bus.call(
-            Message(
-                destination=defs.BLUEZ_SERVICE,
-                path=self._adapter_path,
-                interface=defs.ADAPTER_INTERFACE,
-                member="StopDiscovery",
-            )
-        )
-        assert_reply(reply)
-
-        for rule in self._rules:
-            await remove_match(self._bus, rule)
-        self._rules.clear()
-        self._bus.remove_message_handler(self._parse_msg)
-
-        # Try to disconnect the System Bus.
-        try:
-            self._bus.disconnect()
-        except Exception as e:
-            logger.error("Attempt to disconnect system bus failed: {0}".format(e))
+        if self._stop:
+            # avoid reentrancy
+            stop, self._stop = self._stop, None
 
-        self._bus = None
+            await stop()
 
     def set_scanning_filter(self, **kwargs):
         """Sets OS level scanning filters for the BleakScanner.
@@ -220,9 +176,6 @@ class BleakScannerBlueZDBus(BaseBleakSca
             else:
                 logger.warning("Filter '%s' is not currently supported." % k)
 
-        if "Transport" not in self._filters:
-            self._filters["Transport"] = Variant("s", "le")
-
     @property
     def discovered_devices(self) -> List[BLEDevice]:
         # Reduce output.
@@ -233,15 +186,13 @@ class BleakScannerBlueZDBus(BaseBleakSca
                     "Disregarding %s since no properties could be obtained." % path
                 )
                 continue
-            name, address, _, path = _device_info(path, props)
-            if address is None:
-                continue
+
             uuids = props.get("UUIDs", [])
             manufacturer_data = props.get("ManufacturerData", {})
             discovered_devices.append(
                 BLEDevice(
-                    address,
-                    name,
+                    props["Address"],
+                    props["Alias"],
                     {"path": path, "props": props},
                     props.get("RSSI", 0),
                     uuids=uuids,
@@ -252,17 +203,20 @@ class BleakScannerBlueZDBus(BaseBleakSca
 
     # Helper methods
 
-    def _invoke_callback(self, path: str, message: Message) -> None:
-        """Invokes the advertising data callback.
+    def _handle_advertising_data(self, path: str, props: Device1) -> None:
+        """
+        Handles advertising data received from the BlueZ manager instance.
 
         Args:
-            message: The D-Bus message that triggered the callback.
+            path: The D-Bus object path of the device.
+            props: The D-Bus object properties of the device.
         """
+
+        self._devices[path] = props
+
         if self._callback is None:
             return
 
-        props = self._devices[path]
-
         # Get all the information wanted to pack in the advertisement data
         _local_name = props.get("Name")
         _manufacturer_data = {
@@ -277,7 +231,7 @@ class BleakScannerBlueZDBus(BaseBleakSca
             manufacturer_data=_manufacturer_data,
             service_data=_service_data,
             service_uuids=_service_uuids,
-            platform_data=(props, message),
+            platform_data=props,
         )
 
         device = BLEDevice(
@@ -290,76 +244,3 @@ class BleakScannerBlueZDBus(BaseBleakSca
         )
 
         self._callback(device, advertisement_data)
-
-    def _parse_msg(self, message: Message):
-        if message.message_type != MessageType.SIGNAL:
-            return
-
-        logger.debug(
-            "received D-Bus signal: {0}.{1} ({2}): {3}".format(
-                message.interface, message.member, message.path, message.body
-            )
-        )
-
-        if message.member == "InterfacesAdded":
-            # if a new device is discovered while we are scanning, add it to
-            # the discovered devices list
-
-            obj_path: str
-            interfaces_and_props: Dict[str, Dict[str, Variant]]
-            obj_path, interfaces_and_props = message.body
-            device_props = unpack_variants(
-                interfaces_and_props.get(defs.DEVICE_INTERFACE, {})
-            )
-            if device_props:
-                self._devices[obj_path] = device_props
-                self._invoke_callback(obj_path, message)
-        elif message.member == "InterfacesRemoved":
-            # if a device disappears while we are scanning, remove it from the
-            # discovered devices list
-
-            obj_path: str
-            interfaces: List[str]
-            obj_path, interfaces = message.body
-
-            if defs.DEVICE_INTERFACE in interfaces:
-                # Using pop to avoid KeyError if obj_path does not exist
-                self._devices.pop(obj_path, None)
-        elif message.member == "PropertiesChanged":
-            # Property change events basically mean that new advertising data
-            # was received or the RSSI changed. Either way, it lets us know
-            # that the device is active and we can add it to the discovered
-            # devices list.
-
-            interface: str
-            changed: Dict[str, Variant]
-            invalidated: List[str]
-            interface, changed, invalidated = message.body
-
-            if interface != defs.DEVICE_INTERFACE:
-                return
-
-            first_time_seen = False
-
-            if message.path not in self._devices:
-                if message.path not in self._cached_devices:
-                    # This can happen when we start scanning. The "PropertyChanged"
-                    # handler is attached before "GetManagedObjects" is called
-                    # and so self._cached_devices is not assigned yet.
-                    # This is not a problem. We just discard the property value
-                    # since "GetManagedObjects" will return a newer value.
-                    return
-
-                first_time_seen = True
-                self._devices[message.path] = self._cached_devices[message.path]
-
-            changed = unpack_variants(changed)
-            self._devices[message.path].update(changed)
-
-            # Only do advertising data callback if this is the first time the
-            # device has been seen or if an advertising data property changed.
-            # Otherwise we get a flood of callbacks from RSSI changing.
-            if first_time_seen or not _ADVERTISING_DATA_PROPERTIES.isdisjoint(
-                changed.keys()
-            ):
-                self._invoke_callback(message.path, message)
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/signals.py 0.15.1-1/bleak/backends/bluezdbus/signals.py
--- 0.14.3-1/bleak/backends/bluezdbus/signals.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/signals.py	2022-08-03 16:15:28.000000000 +0000
@@ -46,7 +46,7 @@ def assert_bus_name_valid(type: str):
     :type name: str
 
     :raises:
-        - :class:`InvalidBusNameError` - If this is not a valid message type.
+        - :class:`InvalidMessageTypeError` - If this is not a valid message type.
     """
     if not is_message_type_valid(type):
         raise InvalidMessageTypeError(type)
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/utils.py 0.15.1-1/bleak/backends/bluezdbus/utils.py
--- 0.14.3-1/bleak/backends/bluezdbus/utils.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/utils.py	2022-08-03 16:15:28.000000000 +0000
@@ -6,8 +6,7 @@ from dbus_next.constants import MessageT
 from dbus_next.message import Message
 from dbus_next.signature import Variant
 
-from bleak import BleakError
-from bleak.exc import BleakDBusError
+from ...exc import BleakError, BleakDBusError
 
 _address_regex = re.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
 
@@ -17,7 +16,7 @@ def assert_reply(reply: Message):
 
     Raises:
         BleakDBusError: if the message type is ``MessageType.ERROR``
-        AssentationError: if the message type is not ``MessageType.METHOD_RETURN``
+        AssertionError: if the message type is not ``MessageType.METHOD_RETURN``
     """
     if reply.message_type == MessageType.ERROR:
         raise BleakDBusError(reply.error_name, reply.body)
diff -pruN 0.14.3-1/bleak/backends/bluezdbus/version.py 0.15.1-1/bleak/backends/bluezdbus/version.py
--- 0.14.3-1/bleak/backends/bluezdbus/version.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.15.1-1/bleak/backends/bluezdbus/version.py	2022-08-03 16:15:28.000000000 +0000
@@ -0,0 +1,62 @@
+import asyncio
+import contextlib
+import logging
+import re
+from typing import Optional
+
+logger = logging.getLogger(__name__)
+
+
+async def _get_bluetoothctl_version():
+    """Get the version of bluetoothctl."""
+    with contextlib.suppress(Exception):
+        proc = await asyncio.create_subprocess_exec(
+            "bluetoothctl", "--version", stdout=asyncio.subprocess.PIPE
+        )
+        out = await proc.stdout.read()
+        version = re.search(b"(\\d+).(\\d+)", out.strip(b"'"))
+        await proc.wait()
+        return version
+    return None
+
+
+class BlueZFeatures:
+    """Check which features are supported by the BlueZ backend."""
+
+    checked_bluez_version = False
+    supported_version = True
+    can_write_without_response = True
+    write_without_response_workaround_needed = False
+    hides_battery_characteristic = True
+    hides_device_name_characteristic = True
+    _check_bluez_event: Optional[asyncio.Event] = None
+
+    @classmethod
+    async def check_bluez_version(cls) -> None:
+        """Check the bluez version."""
+        if cls._check_bluez_event:
+            # If there is already a check in progress
+            # it wins, wait for it instead
+            await cls._check_bluez_event.wait()
+            return
+        cls._check_bluez_event = asyncio.Event()
+        version_output = await _get_bluetoothctl_version()
+        if version_output:
+            major, minor = tuple(map(int, version_output.groups()))
+            cls.supported_version = major == 5 and minor >= 34
+            cls.can_write_without_response = major == 5 and minor >= 46
+            cls.write_without_response_workaround_needed = not (
+                major == 5 and minor >= 51
+            )
+            cls.hides_battery_characteristic = major == 5 and minor >= 48
+            cls.hides_device_name_characteristic = major == 5 and minor >= 48
+        else:
+            # Its possible they may be running inside a container where
+            # bluetoothctl is not available and they only have access to the
+            # BlueZ D-Bus API.
+            logging.warning(
+                "Could not determine BlueZ version, bluetoothctl not available, assuming 5.51+"
+            )
+
+        cls._check_bluez_event.set()
+        cls.checked_bluez_version = True
diff -pruN 0.14.3-1/bleak/backends/corebluetooth/PeripheralDelegate.py 0.15.1-1/bleak/backends/corebluetooth/PeripheralDelegate.py
--- 0.14.3-1/bleak/backends/corebluetooth/PeripheralDelegate.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/corebluetooth/PeripheralDelegate.py	2022-08-03 16:15:28.000000000 +0000
@@ -131,7 +131,10 @@ class PeripheralDelegate(NSObject):
 
     @objc.python_method
     async def read_characteristic(
-        self, characteristic: CBCharacteristic, use_cached: bool = True
+        self,
+        characteristic: CBCharacteristic,
+        use_cached: bool = True,
+        timeout: int = 10,
     ) -> NSData:
         if characteristic.value() is not None and use_cached:
             return characteristic.value()
@@ -141,7 +144,7 @@ class PeripheralDelegate(NSObject):
         self._characteristic_read_futures[characteristic.handle()] = future
         try:
             self.peripheral.readValueForCharacteristic_(characteristic)
-            return await asyncio.wait_for(future, timeout=5)
+            return await asyncio.wait_for(future, timeout=timeout)
         finally:
             del self._characteristic_read_futures[characteristic.handle()]
 
diff -pruN 0.14.3-1/bleak/backends/corebluetooth/scanner.py 0.15.1-1/bleak/backends/corebluetooth/scanner.py
--- 0.14.3-1/bleak/backends/corebluetooth/scanner.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/corebluetooth/scanner.py	2022-08-03 16:15:28.000000000 +0000
@@ -1,18 +1,22 @@
 import logging
-import pathlib
 from typing import Any, Dict, List, Optional
 
 import objc
 from Foundation import NSArray, NSUUID, NSBundle
 from CoreBluetooth import CBPeripheral
+from typing_extensions import Literal
 
 from bleak.backends.corebluetooth.CentralManagerDelegate import CentralManagerDelegate
 from bleak.backends.corebluetooth.utils import cb_uuid_to_str
 from bleak.backends.device import BLEDevice
-from bleak.backends.scanner import BaseBleakScanner, AdvertisementData
+from bleak.backends.scanner import (
+    AdvertisementDataCallback,
+    BaseBleakScanner,
+    AdvertisementData,
+)
+from bleak.exc import BleakError
 
 logger = logging.getLogger(__name__)
-_here = pathlib.Path(__file__).parent
 
 
 class BleakScannerCoreBluetooth(BaseBleakScanner):
@@ -27,19 +31,36 @@ class BleakScannerCoreBluetooth(BaseBlea
     this for the BLEDevice address on macOS.
 
     Args:
-        **timeout (double): The scanning timeout to be used, in case of missing
-          ``stopScan_`` method.
-        **detection_callback (callable or coroutine):
+        detection_callback:
             Optional function that will be called each time a device is
             discovered or advertising data has changed.
-        **service_uuids (List[str]):
+        service_uuids:
             Optional list of service UUIDs to filter on. Only advertisements
             containing this advertising data will be received. Required on
-            macOS 12 and later (unless you create an app with ``py2app``).
+            macOS >= 12.0, < 12.3 (unless you create an app with ``py2app``).
+        scanning_mode:
+            Set to ``"passive"`` to avoid the ``"active"`` scanning mode. Not
+            supported on macOS! Will raise :class:`BleakError` if set to
+            ``"passive"``
+        **timeout (float):
+             The scanning timeout to be used, in case of missing
+            ``stopScan_`` method.
     """
 
-    def __init__(self, **kwargs):
-        super(BleakScannerCoreBluetooth, self).__init__(**kwargs)
+    def __init__(
+        self,
+        detection_callback: Optional[AdvertisementDataCallback] = None,
+        service_uuids: Optional[List[str]] = None,
+        scanning_mode: Literal["active", "passive"] = "active",
+        **kwargs
+    ):
+        super(BleakScannerCoreBluetooth, self).__init__(
+            detection_callback, service_uuids
+        )
+
+        if scanning_mode == "passive":
+            raise BleakError("macOS does not support passive scanning")
+
         self._identifiers: Optional[Dict[NSUUID, Dict[str, Any]]] = None
         self._manager = CentralManagerDelegate.alloc().init()
         self._timeout: float = kwargs.get("timeout", 5.0)
diff -pruN 0.14.3-1/bleak/backends/p4android/client.py 0.15.1-1/bleak/backends/p4android/client.py
--- 0.14.3-1/bleak/backends/p4android/client.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/p4android/client.py	2022-08-03 16:15:28.000000000 +0000
@@ -59,7 +59,7 @@ class BleakClientP4Android(BaseBleakClie
             Boolean representing connection status.
 
         """
-        loop = asyncio.get_event_loop()
+        loop = asyncio.get_running_loop()
 
         self.__adapter = defs.BluetoothAdapter.getDefaultAdapter()
         if self.__adapter is None:
@@ -167,7 +167,7 @@ class BleakClientP4Android(BaseBleakClie
             Boolean regarding success of pairing.
 
         """
-        loop = asyncio.get_event_loop()
+        loop = asyncio.get_running_loop()
 
         bondedFuture = loop.create_future()
 
diff -pruN 0.14.3-1/bleak/backends/p4android/scanner.py 0.15.1-1/bleak/backends/p4android/scanner.py
--- 0.14.3-1/bleak/backends/p4android/scanner.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/p4android/scanner.py	2022-08-03 16:15:28.000000000 +0000
@@ -2,10 +2,16 @@
 
 import asyncio
 import logging
-from typing import List
+from typing import List, Optional
 import warnings
 
-from bleak.backends.scanner import BaseBleakScanner, AdvertisementData
+from typing_extensions import Literal
+
+from bleak.backends.scanner import (
+    AdvertisementDataCallback,
+    BaseBleakScanner,
+    AdvertisementData,
+)
 from bleak.backends.device import BLEDevice
 from bleak.exc import BleakError
 
@@ -24,19 +30,32 @@ class BleakScannerP4Android(BaseBleakSca
     The python-for-android Bleak BLE Scanner.
 
     Args:
-        **detection_callback (callable or coroutine):
+        detection_callback:
             Optional function that will be called each time a device is
             discovered or advertising data has changed.
-        **service_uuids (List[str]):
+        service_uuids:
             Optional list of service UUIDs to filter on. Only advertisements
             containing this advertising data will be received. Specifying this
             also enables scanning while the screen is off on Android.
+        scanning_mode:
+            Set to ``"passive"`` to avoid the ``"active"`` scanning mode.
     """
 
     __scanner = None
 
-    def __init__(self, **kwargs):
-        super(BleakScannerP4Android, self).__init__(**kwargs)
+    def __init__(
+        self,
+        detection_callback: Optional[AdvertisementDataCallback] = None,
+        service_uuids: Optional[List[str]] = None,
+        scanning_mode: Literal["active", "passive"] = "active",
+        **kwargs,
+    ):
+        super(BleakScannerP4Android, self).__init__(detection_callback, service_uuids)
+
+        if scanning_mode == "passive":
+            self.__scan_mode = defs.ScanSettings.SCAN_MODE_OPPORTUNISTIC
+        else:
+            self.__scan_mode = defs.ScanSettings.SCAN_MODE_LOW_LATENCY
 
         self._devices = {}
         self.__adapter = None
@@ -52,7 +71,7 @@ class BleakScannerP4Android(BaseBleakSca
 
         logger.debug("Starting BTLE scan")
 
-        loop = asyncio.get_event_loop()
+        loop = asyncio.get_running_loop()
 
         if self.__javascanner is None:
             if self.__callback is None:
@@ -106,7 +125,7 @@ class BleakScannerP4Android(BaseBleakSca
             dispatchParams=(
                 filters,
                 defs.ScanSettingsBuilder()
-                .setScanMode(defs.ScanSettings.SCAN_MODE_LOW_LATENCY)
+                .setScanMode(self.__scan_mode)
                 .setReportDelay(0)
                 .setPhy(defs.ScanSettings.PHY_LE_ALL_SUPPORTED)
                 .setNumOfMatches(defs.ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
diff -pruN 0.14.3-1/bleak/backends/scanner.py 0.15.1-1/bleak/backends/scanner.py
--- 0.14.3-1/bleak/backends/scanner.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/scanner.py	2022-08-03 16:15:28.000000000 +0000
@@ -72,23 +72,24 @@ class BaseBleakScanner(abc.ABC):
     Interface for Bleak Bluetooth LE Scanners
 
     Args:
-        **detection_callback (callable or coroutine):
+        detection_callback:
             Optional function that will be called each time a device is
             discovered or advertising data has changed.
-        **service_uuids (List[str]):
+        service_uuids:
             Optional list of service UUIDs to filter on. Only advertisements
-            containing this advertising data will be received. Required on
-            macOS 12 and later.
+            containing this advertising data will be received.
     """
 
-    def __init__(self, *args, **kwargs):
+    def __init__(
+        self,
+        detection_callback: Optional[AdvertisementDataCallback],
+        service_uuids: Optional[List[str]],
+    ):
         super(BaseBleakScanner, self).__init__()
         self._callback: Optional[AdvertisementDataCallback] = None
-        self.register_detection_callback(kwargs.get("detection_callback"))
+        self.register_detection_callback(detection_callback)
         self._service_uuids: Optional[List[str]] = (
-            [u.lower() for u in kwargs["service_uuids"]]
-            if "service_uuids" in kwargs
-            else None
+            [u.lower() for u in service_uuids] if service_uuids is not None else None
         )
 
     async def __aenter__(self):
diff -pruN 0.14.3-1/bleak/backends/service.py 0.15.1-1/bleak/backends/service.py
--- 0.14.3-1/bleak/backends/service.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/service.py	2022-08-03 16:15:28.000000000 +0000
@@ -133,28 +133,33 @@ class BleakGATTServiceCollection(object)
                 service.handle,
             )
 
-    def get_service(self, specifier: Union[int, str, UUID]) -> BleakGATTService:
+    def get_service(
+        self, specifier: Union[int, str, UUID]
+    ) -> Optional[BleakGATTService]:
         """Get a service by handle (int) or UUID (str or uuid.UUID)"""
         if isinstance(specifier, int):
-            return self.services.get(specifier, None)
-        else:
-            _specifier = str(specifier).lower()
-            # Assume uuid usage.
-            # Convert 16-bit uuid to 128-bit uuid
-            if len(_specifier) == 4:
-                _specifier = f"0000{_specifier}-0000-1000-8000-00805f9b34fb"
-            x = list(
-                filter(
-                    lambda x: x.uuid.lower() == _specifier,
-                    self.services.values(),
-                )
-            )
-            if len(x) > 1:
-                raise BleakError(
-                    "Multiple Services with this UUID, refer to your desired service by the `handle` attribute instead."
-                )
-            else:
-                return x[0] if x else None
+            return self.services.get(specifier)
+
+        _specifier = str(specifier).lower()
+
+        # Assume uuid usage.
+        # Convert 16-bit uuid to 128-bit uuid
+        if len(_specifier) == 4:
+            _specifier = f"0000{_specifier}-0000-1000-8000-00805f9b34fb"
+
+        x = list(
+            filter(
+                lambda x: x.uuid.lower() == _specifier,
+                self.services.values(),
+            )
+        )
+
+        if len(x) > 1:
+            raise BleakError(
+                "Multiple Services with this UUID, refer to your desired service by the `handle` attribute instead."
+            )
+
+        return x[0] if x else None
 
     def add_characteristic(self, characteristic: BleakGATTCharacteristic):
         """Add a :py:class:`~BleakGATTCharacteristic` to the service collection.
@@ -174,24 +179,25 @@ class BleakGATTServiceCollection(object)
 
     def get_characteristic(
         self, specifier: Union[int, str, UUID]
-    ) -> BleakGATTCharacteristic:
+    ) -> Optional[BleakGATTCharacteristic]:
         """Get a characteristic by handle (int) or UUID (str or uuid.UUID)"""
         if isinstance(specifier, int):
-            return self.characteristics.get(specifier, None)
-        else:
-            # Assume uuid usage.
-            x = list(
-                filter(
-                    lambda x: x.uuid == str(specifier).lower(),
-                    self.characteristics.values(),
-                )
-            )
-            if len(x) > 1:
-                raise BleakError(
-                    "Multiple Characteristics with this UUID, refer to your desired characteristic by the `handle` attribute instead."
-                )
-            else:
-                return x[0] if x else None
+            return self.characteristics.get(specifier)
+
+        # Assume uuid usage.
+        x = list(
+            filter(
+                lambda x: x.uuid == str(specifier).lower(),
+                self.characteristics.values(),
+            )
+        )
+
+        if len(x) > 1:
+            raise BleakError(
+                "Multiple Characteristics with this UUID, refer to your desired characteristic by the `handle` attribute instead."
+            )
+
+        return x[0] if x else None
 
     def add_descriptor(self, descriptor: BleakGATTDescriptor):
         """Add a :py:class:`~BleakGATTDescriptor` to the service collection.
@@ -209,6 +215,6 @@ class BleakGATTServiceCollection(object)
                 descriptor.handle,
             )
 
-    def get_descriptor(self, handle: int) -> BleakGATTDescriptor:
+    def get_descriptor(self, handle: int) -> Optional[BleakGATTDescriptor]:
         """Get a descriptor by integer handle"""
-        return self.descriptors.get(handle, None)
+        return self.descriptors.get(handle)
diff -pruN 0.14.3-1/bleak/backends/winrt/characteristic.py 0.15.1-1/bleak/backends/winrt/characteristic.py
--- 0.14.3-1/bleak/backends/winrt/characteristic.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/winrt/characteristic.py	2022-08-03 16:15:28.000000000 +0000
@@ -94,7 +94,11 @@ class BleakGATTCharacteristicWinRT(Bleak
     @property
     def description(self) -> str:
         """Description for this characteristic"""
-        return self.obj.user_description
+        return (
+            self.obj.user_description
+            if self.obj.user_description
+            else super().description
+        )
 
     @property
     def properties(self) -> List[str]:
diff -pruN 0.14.3-1/bleak/backends/winrt/client.py 0.15.1-1/bleak/backends/winrt/client.py
--- 0.14.3-1/bleak/backends/winrt/client.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/winrt/client.py	2022-08-03 16:15:28.000000000 +0000
@@ -8,7 +8,9 @@ Created on 2020-08-19 by hbldh <henrik.b
 import inspect
 import logging
 import asyncio
+from typing_extensions import Literal, TypedDict
 import uuid
+import warnings
 from functools import wraps
 from typing import Callable, Any, List, Optional, Sequence, Union
 
@@ -107,6 +109,31 @@ def _ensure_success(result: Any, attr: O
     raise BleakError(f"{fail_msg}: Unexpected status code 0x{result.status:02X}")
 
 
+class WinRTClientArgs(TypedDict, total=False):
+    """
+    Windows-specific arguments for :class:`BleakClient`.
+    """
+
+    address_type: Literal["public", "random"]
+    """
+    Can either be ``"public"`` or ``"random"``, depending on the required address
+    type needed to connect to your device.
+    """
+
+    use_cached_services: bool
+    """
+    ``True`` allows Windows to fetch the services, characteristics and descriptors
+    from the Windows cache instead of reading them from the device. Can be very
+    much faster for known, unchanging devices, but not recommended for DIY peripherals
+    where the GATT layout can change between connections.
+
+    ``False`` will force the attribute database to be read from the remote device
+    instead of using the OS cache.
+
+    If omitted, the OS Bluetooth stack will do what it thinks is best.
+    """
+
+
 class BleakClientWinRT(BaseBleakClient):
     """Native Windows Bleak Client.
 
@@ -114,14 +141,22 @@ class BleakClientWinRT(BaseBleakClient):
     a package that enables Python developers to access Windows Runtime APIs directly from Python.
 
     Args:
-        address_or_ble_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it.
-
-    Keyword Args:
-        timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0.
-
+        address_or_ble_device (str or BLEDevice): The Bluetooth address of the BLE peripheral
+            to connect to or the ``BLEDevice`` object representing it.
+        winrt (dict): A dictionary of Windows-specific configuration values.
+        **timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0.
+        **disconnected_callback (callable): Callback that will be scheduled in the
+            event loop when the client is disconnected. The callable must take one
+            argument, which will be this client object.
     """
 
-    def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs):
+    def __init__(
+        self,
+        address_or_ble_device: Union[BLEDevice, str],
+        *,
+        winrt: WinRTClientArgs = {},
+        **kwargs,
+    ):
         super(BleakClientWinRT, self).__init__(address_or_ble_device, **kwargs)
 
         # Backend specific. WinRT objects.
@@ -134,12 +169,16 @@ class BleakClientWinRT(BaseBleakClient):
         self._session_closed_events: List[asyncio.Event] = []
         self._session: GattSession = None
 
-        self._address_type = (
-            kwargs["address_type"]
-            if "address_type" in kwargs
-            and kwargs["address_type"] in ("public", "random")
-            else None
-        )
+        if "address_type" in kwargs:
+            warnings.warn(
+                "The address_type keyword arg will in a future version be moved into the win dict input instead.",
+                PendingDeprecationWarning,
+                stacklevel=2,
+            )
+
+        # os-specific options
+        self._use_cached_services = winrt.get("use_cached_services")
+        self._address_type = winrt.get("address_type", kwargs.get("address_type"))
 
         self._session_status_changed_token: Optional[EventRegistrationToken] = None
 
@@ -158,7 +197,6 @@ class BleakClientWinRT(BaseBleakClient):
             Boolean representing connection status.
 
         """
-
         # Try to find the desired device.
         timeout = kwargs.get("timeout", self._timeout)
         if self._device_info is None:
@@ -340,12 +378,12 @@ class BleakClientWinRT(BaseBleakClient):
         """Attempts to pair with the device.
 
         Keyword Args:
-            protection_level:
-                ``Windows.Devices.Enumeration.DevicePairingProtectionLevel``
-                1: None - Pair the device using no levels of protection.
-                2: Encryption - Pair the device using encryption.
-                3: EncryptionAndAuthentication - Pair the device using
-                encryption and authentication. (This will not work in Bleak...)
+            protection_level (int): A ``DevicePairingProtectionLevel`` enum value:
+
+                1. None - Pair the device using no levels of protection.
+                2. Encryption - Pair the device using encryption.
+                3. EncryptionAndAuthentication - Pair the device using
+                   encryption and authentication. (This will not work in Bleak...)
 
         Returns:
             Boolean regarding success of pairing.
@@ -448,17 +486,31 @@ class BleakClientWinRT(BaseBleakClient):
             return self.services
         else:
             logger.debug("Get Services...")
+
+            # Each of the get_serv/char/desc_async() methods has two forms, one
+            # with no args and one with a cache_mode argument
+            args = []
+
+            # If the os-specific use_cached_services arg was given when BleakClient
+            # was created, the we use the second form with explicit cache mode.
+            # Otherwise we use the first form with no explicit cache mode which
+            # allows the OS Bluetooth stack to decide what is best.
+            if self._use_cached_services is not None:
+                args.append(
+                    BluetoothCacheMode.CACHED
+                    if self._use_cached_services
+                    else BluetoothCacheMode.UNCACHED
+                )
+
             services: Sequence[GattDeviceService] = _ensure_success(
-                await self._requester.get_gatt_services_async(
-                    BluetoothCacheMode.UNCACHED
-                ),
+                await self._requester.get_gatt_services_async(*args),
                 "services",
                 "Could not get GATT services",
             )
 
             for service in services:
                 # Windows returns an ACCESS_DENIED error when trying to enumerate
-                # characterstics of services used by the OS, like the HID service
+                # characteristics of services used by the OS, like the HID service
                 # so we have to exclude those services.
                 if service.uuid in _ACCESS_DENIED_SERVICES:
                     continue
@@ -466,9 +518,7 @@ class BleakClientWinRT(BaseBleakClient):
                 self.services.add_service(BleakGATTServiceWinRT(service))
 
                 characteristics: Sequence[GattCharacteristic] = _ensure_success(
-                    await service.get_characteristics_async(
-                        BluetoothCacheMode.UNCACHED
-                    ),
+                    await service.get_characteristics_async(*args),
                     "characteristics",
                     f"Could not get GATT characteristics for {service}",
                 )
@@ -479,9 +529,7 @@ class BleakClientWinRT(BaseBleakClient):
                     )
 
                     descriptors: Sequence[GattDescriptor] = _ensure_success(
-                        await characteristic.get_descriptors_async(
-                            BluetoothCacheMode.UNCACHED
-                        ),
+                        await characteristic.get_descriptors_async(*args),
                         "descriptors",
                         f"Could not get GATT descriptors for {service}",
                     )
diff -pruN 0.14.3-1/bleak/backends/winrt/scanner.py 0.15.1-1/bleak/backends/winrt/scanner.py
--- 0.14.3-1/bleak/backends/winrt/scanner.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/winrt/scanner.py	2022-08-03 16:15:28.000000000 +0000
@@ -1,6 +1,5 @@
 import asyncio
 import logging
-import pathlib
 from typing import Dict, List, NamedTuple, Optional
 from uuid import UUID
 
@@ -10,13 +9,14 @@ from bleak_winrt.windows.devices.bluetoo
     BluetoothLEAdvertisementReceivedEventArgs,
     BluetoothLEAdvertisementType,
 )
+from typing_extensions import Literal
 
-from bleak.backends.device import BLEDevice
-from bleak.backends.scanner import BaseBleakScanner, AdvertisementData
+from ..device import BLEDevice
+from ..scanner import AdvertisementDataCallback, BaseBleakScanner, AdvertisementData
+from ...assigned_numbers import AdvertisementDataType
 
 
 logger = logging.getLogger(__name__)
-_here = pathlib.Path(__file__).parent
 
 
 def _format_bdaddr(a):
@@ -55,19 +55,33 @@ class BleakScannerWinRT(BaseBleakScanner
 
     Implemented using `Python/WinRT <https://github.com/Microsoft/xlang/tree/master/src/package/pywinrt/projection/>`_.
 
-    Keyword Args:
-        scanning mode (str): Set to "Passive" to avoid the "Active" scanning mode.
+    Args:
+        detection_callback:
+            Optional function that will be called each time a device is
+            discovered or advertising data has changed.
+        service_uuids:
+            Optional list of service UUIDs to filter on. Only advertisements
+            containing this advertising data will be received.
+        scanning_mode:
+            Set to ``"passive"`` to avoid the ``"active"`` scanning mode.
 
     """
 
-    def __init__(self, **kwargs):
-        super(BleakScannerWinRT, self).__init__(**kwargs)
+    def __init__(
+        self,
+        detection_callback: Optional[AdvertisementDataCallback] = None,
+        service_uuids: Optional[List[str]] = None,
+        scanning_mode: Literal["active", "passive"] = "active",
+        **kwargs,
+    ):
+        super(BleakScannerWinRT, self).__init__(detection_callback, service_uuids)
 
         self.watcher = None
         self._stopped_event = None
         self._discovered_devices: Dict[int, _RawAdvData] = {}
 
-        if "scanning_mode" in kwargs and kwargs["scanning_mode"].lower() == "passive":
+        # case insensitivity is for backwards compatibility on Windows only
+        if scanning_mode.lower() == "passive":
             self._scanning_mode = BluetoothLEScanningMode.PASSIVE
         else:
             self._scanning_mode = BluetoothLEScanningMode.ACTIVE
@@ -124,20 +138,23 @@ class BleakScannerWinRT(BaseBleakScanner
 
         # Decode service data
         for args in filter(lambda d: d is not None, raw_data):
-            # 0x16 is service data with 16-bit UUID
-            for section in args.advertisement.get_sections_by_type(0x16):
+            for section in args.advertisement.get_sections_by_type(
+                AdvertisementDataType.SERVICE_DATA_UUID16
+            ):
                 data = bytes(section.data)
                 service_data[
                     f"0000{data[1]:02x}{data[0]:02x}-0000-1000-8000-00805f9b34fb"
                 ] = data[2:]
-            # 0x20 is service data with 32-bit UUID
-            for section in args.advertisement.get_sections_by_type(0x20):
+            for section in args.advertisement.get_sections_by_type(
+                AdvertisementDataType.SERVICE_DATA_UUID32
+            ):
                 data = bytes(section.data)
                 service_data[
                     f"{data[3]:02x}{data[2]:02x}{data[1]:02x}{data[0]:02x}-0000-1000-8000-00805f9b34fb"
                 ] = data[4:]
-            # 0x21 is service data with 128-bit UUID
-            for section in args.advertisement.get_sections_by_type(0x21):
+            for section in args.advertisement.get_sections_by_type(
+                AdvertisementDataType.SERVICE_DATA_UUID128
+            ):
                 data = bytes(section.data)
                 service_data[str(UUID(bytes=bytes(data[15::-1])))] = data[16:]
 
diff -pruN 0.14.3-1/bleak/backends/winrt/service.py 0.15.1-1/bleak/backends/winrt/service.py
--- 0.14.3-1/bleak/backends/winrt/service.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/backends/winrt/service.py	2022-08-03 16:15:28.000000000 +0000
@@ -1,5 +1,4 @@
-from uuid import UUID
-from typing import List, Union
+from typing import List
 
 from bleak_winrt.windows.devices.bluetooth.genericattributeprofile import (
     GattDeviceService,
diff -pruN 0.14.3-1/bleak/__init__.py 0.15.1-1/bleak/__init__.py
--- 0.14.3-1/bleak/__init__.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/__init__.py	2022-08-03 16:15:28.000000000 +0000
@@ -12,11 +12,9 @@ import platform
 import asyncio
 
 from bleak.__version__ import __version__  # noqa: F401
-from bleak.backends.bluezdbus import check_bluez_version
 from bleak.exc import BleakError
 
 _on_rtd = os.environ.get("READTHEDOCS") == "True"
-_on_ci = "CI" in os.environ
 
 _logger = logging.getLogger(__name__)
 _logger.addHandler(logging.NullHandler())
@@ -38,9 +36,6 @@ elif os.environ.get("P4A_BOOTSTRAP") is
         BleakClientP4Android as BleakClient,
     )  # noqa: F401
 elif platform.system() == "Linux":
-    if not _on_ci and not check_bluez_version(5, 43):
-        raise BleakError("Bleak requires BlueZ >= 5.43.")
-
     from bleak.backends.bluezdbus.scanner import (
         BleakScannerBlueZDBus as BleakScanner,
     )  # noqa: F401
diff -pruN 0.14.3-1/bleak/uuids.py 0.15.1-1/bleak/uuids.py
--- 0.14.3-1/bleak/uuids.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/uuids.py	2022-08-03 16:15:28.000000000 +0000
@@ -82,7 +82,10 @@ uuid16_dict = {
     0x1139: "3D Synchronization",
     0x113A: "MPS Profile",
     0x113B: "MPS Service",
-    # 0x113c to 0x11ff undefined */
+    0x113C: "CTN Access Service",
+    0x113D: "CTN Notification Service",
+    0x113E: "CTN Profile",
+    # 0x113f to 0x11ff undefined */
     0x1200: "PnP Information",
     0x1201: "Generic Networking",
     0x1202: "Generic File Transfer",
@@ -141,7 +144,155 @@ uuid16_dict = {
     0x1826: "Fitness Machine",
     0x1827: "Mesh Provisioning",
     0x1828: "Mesh Proxy",
-    # 0x1829 to 0x27ff undefined */
+    0x1829: "Reconnection Configuration",
+    # 0x182a-0x1839 undefined
+    0x183A: "Insulin Delivery",
+    0x183B: "Binary Sensor",
+    0x183C: "Emergency Configuration",
+    # 0x183D undefined
+    0x183E: "Physical Activity Monitor",
+    # 0x183F-0x1842 undefined
+    0x1843: "Audio Input Control",
+    0x1844: "Volume Control",
+    0x1845: "Volume Offset Control",
+    0x1846: "Coordinated Set Identification Service",
+    0x1847: "Device Time",
+    0x1848: "Media Control Service",
+    0x1849: "Generic Media Control Service",
+    0x184A: "Constant Tone Extension",
+    0x184B: "Telephone Bearer Service",
+    0x184C: "Generic Telephone Bearer Service",
+    0x184D: "Microphone Control",
+    0x184E: "Audio Stream Control Service",
+    0x184F: "Broadcast Audio Scan Service",
+    0x1850: "Published Audio Capabilities Service",
+    0x1851: "Basic Audio Announcement Service",
+    0x1852: "Broadcast Audio Announcement Service",
+    # 0x1853 to 0x26ff undefined */
+    # 0x2700.. GATT Units
+    0x2700: "unitless",
+    0x2701: "length (metre)",
+    0x2702: "mass (kilogram)",
+    0x2703: "time (second)",
+    0x2704: "electric current (ampere)",
+    0x2705: "thermodynamic temperature (kelvin)",
+    0x2706: "amount of substance (mole)",
+    0x2707: "luminous intensity (candela)",
+    0x2710: "area (square metres)",
+    0x2711: "volume (cubic metres)",
+    0x2712: "velocity (metres per second)",
+    0x2713: "acceleration (metres per second squared)",
+    0x2714: "wavenumber (reciprocal metre)",
+    0x2715: "density (kilogram per cubic metre)",
+    0x2716: "surface density (kilogram per square metre)",
+    0x2717: "specific volume (cubic metre per kilogram)",
+    0x2718: "current density (ampere per square metre)",
+    0x2719: "magnetic field strength (ampere per metre)",
+    0x271A: "amount concentration (mole per cubic metre)",
+    0x271B: "mass concentration (kilogram per cubic metre)",
+    0x271C: "luminance (candela per square metre)",
+    0x271D: "refractive index",
+    0x271E: "relative permeability",
+    0x2720: "plane angle (radian)",
+    0x2721: "solid angle (steradian)",
+    0x2722: "frequency (hertz)",
+    0x2723: "force (newton)",
+    0x2724: "pressure (pascal)",
+    0x2725: "energy (joule)",
+    0x2726: "power (watt)",
+    0x2727: "electric charge (coulomb)",
+    0x2728: "electric potential difference (volt)",
+    0x2729: "capacitance (farad)",
+    0x272A: "electric resistance (ohm)",
+    0x272B: "electric conductance (siemens)",
+    0x272C: "magnetic flux (weber)",
+    0x272D: "magnetic flux density (tesla)",
+    0x272E: "inductance (henry)",
+    0x272F: "Celsius temperature (degree Celsius)",
+    0x2730: "luminous flux (lumen)",
+    0x2731: "illuminance (lux)",
+    0x2732: "activity referred to a radionuclide (becquerel)",
+    0x2733: "absorbed dose (gray)",
+    0x2734: "dose equivalent (sievert)",
+    0x2735: "catalytic activity (katal)",
+    0x2740: "dynamic viscosity (pascal second)",
+    0x2741: "moment of force (newton metre)",
+    0x2742: "surface tension (newton per metre)",
+    0x2743: "angular velocity (radian per second)",
+    0x2744: "angular acceleration (radian per second squared)",
+    0x2745: "heat flux density (watt per square metre)",
+    0x2746: "heat capacity (joule per kelvin)",
+    0x2747: "specific heat capacity (joule per kilogram kelvin)",
+    0x2748: "specific energy (joule per kilogram)",
+    0x2749: "thermal conductivity (watt per metre kelvin)",
+    0x274A: "energy density (joule per cubic metre)",
+    0x274B: "electric field strength (volt per metre)",
+    0x274C: "electric charge density (coulomb per cubic metre)",
+    0x274D: "surface charge density (coulomb per square metre)",
+    0x274E: "electric flux density (coulomb per square metre)",
+    0x274F: "permittivity (farad per metre)",
+    0x2750: "permeability (henry per metre)",
+    0x2751: "molar energy (joule per mole)",
+    0x2752: "molar entropy (joule per mole kelvin)",
+    0x2753: "exposure (coulomb per kilogram)",
+    0x2754: "absorbed dose rate (gray per second)",
+    0x2755: "radiant intensity (watt per steradian)",
+    0x2756: "radiance (watt per square metre steradian)",
+    0x2757: "catalytic activity concentration (katal per cubic metre)",
+    0x2760: "time (minute)",
+    0x2761: "time (hour)",
+    0x2762: "time (day)",
+    0x2763: "plane angle (degree)",
+    0x2764: "plane angle (minute)",
+    0x2765: "plane angle (second)",
+    0x2766: "area (hectare)",
+    0x2767: "volume (litre)",
+    0x2768: "mass (tonne)",
+    0x2780: "pressure (bar)",
+    0x2781: "pressure (millimetre of mercury)",
+    0x2782: "length (ångström)",
+    0x2783: "length (nautical mile)",
+    0x2784: "area (barn)",
+    0x2785: "velocity (knot)",
+    0x2786: "logarithmic radio quantity (neper)",
+    0x2787: "logarithmic radio quantity (bel)",
+    0x27A0: "length (yard)",
+    0x27A1: "length (parsec)",
+    0x27A2: "length (inch)",
+    0x27A3: "length (foot)",
+    0x27A4: "length (mile)",
+    0x27A5: "pressure (pound-force per square inch)",
+    0x27A6: "velocity (kilometre per hour)",
+    0x27A7: "velocity (mile per hour)",
+    0x27A8: "angular velocity (revolution per minute)",
+    0x27A9: "energy (gram calorie)",
+    0x27AA: "energy (kilogram calorie)",
+    0x27AB: "energy (kilowatt hour)",
+    0x27AC: "thermodynamic temperature (degree Fahrenheit)",
+    0x27AD: "percentage",
+    0x27AE: "per mille",
+    0x27AF: "period (beats per minute)",
+    0x27B0: "electric charge (ampere hours)",
+    0x27B1: "mass density (milligram per decilitre)",
+    0x27B2: "mass density (millimole per litre)",
+    0x27B3: "time (year)",
+    0x27B4: "time (month)",
+    0x27B5: "concentration (count per cubic metre)",
+    0x27B6: "irradiance (watt per square metre)",
+    0x27B7: "milliliter (per kilogram per minute)",
+    0x27B8: "mass (pound)",
+    0x27B9: "metabolic equivalent",
+    0x27BA: "step (per minute)",
+    0x27BC: "stroke (per minute)",
+    0x27BD: "pace (kilometre per minute)",
+    0x27BE: "luminous efficacy (lumen per watt)",
+    0x27BF: "luminous energy (lumen hour)",
+    0x27C0: "luminous exposure (lux hour)",
+    0x27C1: "mass flow (gram per second)",
+    0x27C2: "volume flow (litre per second)",
+    0x27C3: "sound pressure (decible)",
+    0x27C4: "parts per million",
+    0x27C5: "parts per billion",
     0x2800: "Primary Service",
     0x2801: "Secondary Service",
     0x2802: "Include",
@@ -163,7 +314,9 @@ uuid16_dict = {
     0x290C: "Environmental Sensing Measurement",
     0x290D: "Environmental Sensing Trigger Setting",
     0x290E: "Time Trigger Setting",
-    # 0x290f to 0x29ff undefined */
+    0x290F: "Complete BR-EDR Transport Block Data",
+    # 0x2910 to 0x29ff undefined */
+    # 0x2a00.. GATT characteristic and Object Types
     0x2A00: "Device Name",
     0x2A01: "Appearance",
     0x2A02: "Peripheral Privacy Flag",
@@ -180,12 +333,12 @@ uuid16_dict = {
     0x2A0D: "DST Offset",
     0x2A0E: "Time Zone",
     0x2A0F: "Local Time Information",
-    # 0x2a10 undefined */
+    0x2A10: "Secondary Time Zone",
     0x2A11: "Time with DST",
     0x2A12: "Time Accuracy",
     0x2A13: "Time Source",
     0x2A14: "Reference Time Information",
-    # 0x2a15 undefined */
+    0x2A15: "Time Broadcast",
     0x2A16: "Time Update Control Point",
     0x2A17: "Time Update State",
     0x2A18: "Glucose Measurement",
@@ -209,7 +362,9 @@ uuid16_dict = {
     0x2A2A: "IEEE 11073-20601 Regulatory Cert. Data List",
     0x2A2B: "Current Time",
     0x2A2C: "Magnetic Declination",
-    # 0x2a2d to 0x2a30 undefined */
+    # 0x2a2d to 0x2a2e undefined */
+    0x2A2F: "Position 2D",
+    0x2A30: "Position 3D",
     0x2A31: "Scan Refresh",
     0x2A32: "Boot Keyboard Output Report",
     0x2A33: "Boot Mouse Input Report",
@@ -219,7 +374,11 @@ uuid16_dict = {
     0x2A37: "Heart Rate Measurement",
     0x2A38: "Body Sensor Location",
     0x2A39: "Heart Rate Control Point",
-    # 0x2a3a to 0x2a3e undefined */
+    0x2A3A: "Removable",
+    0x2A3B: "Service Required",
+    0x2A3C: "Scientific Temperature Celsius",
+    0x2A3D: "String",
+    0x2A3E: "Network Availability",
     0x2A3F: "Alert Status",
     0x2A40: "Ringer Control Point",
     0x2A41: "Ringer Setting",
@@ -244,14 +403,17 @@ uuid16_dict = {
     0x2A54: "RSC Feature",
     0x2A55: "SC Control Point",
     0x2A56: "Digital",
-    # 0x2a57 undefined */
+    0x2A57: "Digital Output",
     0x2A58: "Analog",
-    # 0x2a59 undefined */
+    0x2A59: "Analog Output",
     0x2A5A: "Aggregate",
     0x2A5B: "CSC Measurement",
     0x2A5C: "CSC Feature",
     0x2A5D: "Sensor Location",
-    # 0x2a5e to 0x2a62 undefined */
+    0x2A5E: "PLX Spot-Check Measurement",
+    0x2A5F: "PLX Continuous Measurement Characteristic",
+    0x2A60: "PLX Features",
+    0x2A62: "Pulse Oximetry Control Point",
     0x2A63: "Cycling Power Measurement",
     0x2A64: "Cycling Power Vector",
     0x2A65: "Cycling Power Feature",
@@ -375,335 +537,437 @@ uuid16_dict = {
     0x2ADC: "Mesh Provisioning Data Out",
     0x2ADD: "Mesh Proxy Data In",
     0x2ADE: "Mesh Proxy Data Out",
-    0x2A59: "Analog Output",
-    0x2B29: "Client Supported Features",
-    0x2B2A: "Database Hash",
+    0x2AE0: "Average Current",
+    0x2AE1: "Average Voltage",
+    0x2AE2: "Boolean",
+    0x2AE3: "Chromatic Distance From Planckian",
+    0x2AE4: "Chromaticity Coordinates",
+    0x2AE5: "Chromaticity In CCT And Duv Values",
+    0x2AE6: "Chromaticity Tolerance",
+    0x2AE7: "CIE 13.3-1995 Color Rendering Index",
+    0x2AE8: "Coefficient",
+    0x2AE9: "Correlated Color Temperature",
+    0x2AEA: "Count 16",
+    0x2AEB: "Count 24",
+    0x2AEC: "Country Code",
     0x2AED: "Date UTC",
-    0x2A57: "Digital Output",
+    0x2AEE: "Electric Current",
+    0x2AEF: "Electric Current Range",
+    0x2AF0: "Electric Current Specification",
+    0x2AF1: "Electric Current Statistics",
+    0x2AF2: "Energy",
+    0x2AF3: "Energy In A Period Of Day",
+    0x2AF4: "Event Statistics",
+    0x2AF5: "Fixed String 16",
+    0x2AF6: "Fixed String 24",
+    0x2AF7: "Fixed String 36",
+    0x2AF8: "Fixed String 8",
+    0x2AF9: "Generic Level",
+    0x2AFA: "Global Trade Item Number",
+    0x2AFB: "Illuminance",
+    0x2AFC: "Luminous Efficacy",
+    0x2AFD: "Luminous Energy",
+    0x2AFE: "Luminous Exposure",
+    0x2AFF: "Luminous Flux",
+    0x2B00: "Luminous Flux Range",
+    0x2B01: "Luminous Intensity",
+    0x2B02: "Mass Flow",
+    0x2B03: "Perceived Lightness",
+    0x2B04: "Percentage 8",
+    0x2B05: "Power",
+    0x2B06: "Power Specification",
+    0x2B07: "Relative Runtime In A Current Range",
+    0x2B08: "Relative Runtime In A Generic Level Range",
+    0x2B09: "Relative Value In A Voltage Range",
+    0x2B0A: "Relative Value In An Illuminance Range",
+    0x2B0B: "Relative Value In A Period of Day",
+    0x2B0C: "Relative Value In A Temperature Range",
+    0x2B0D: "Temperature 8",
+    0x2B0E: "Temperature 8 In A Period Of Day",
+    0x2B0F: "Temperature 8 Statistics",
+    0x2B10: "Temperature Range",
+    0x2B11: "Temperature Statistics",
+    0x2B12: "Time Decihour 8",
+    0x2B13: "Time Exponential 8",
+    0x2B14: "Time Hour 24",
+    0x2B15: "Time Millisecond 24",
+    0x2B16: "Time Second 16",
+    0x2B17: "Time Second 8",
+    0x2B18: "Voltage",
+    0x2B19: "Voltage Specification",
+    0x2B1A: "Voltage Statistics",
+    0x2B1B: "Volume Flow",
+    0x2B1C: "Chromaticity Coordinate",
+    0x2B1D: "RC Feature",
+    0x2B1E: "RC Settings",
+    0x2B1F: "Reconnection Configuration Control Point",
+    0x2B20: "IDD Status Changed",
+    0x2B21: "IDD Status",
     0x2B22: "IDD Annunciation Status",
+    0x2B23: "IDD Features",
+    0x2B24: "IDD Status Reader Control Point",
     0x2B25: "IDD Command Control Point",
     0x2B26: "IDD Command Data",
-    0x2B23: "IDD Features",
-    0x2B28: "IDD History Data",
     0x2B27: "IDD Record Access Control Point",
-    0x2B21: "IDD Status",
-    0x2B20: "IDD Status Changed",
-    0x2B24: "IDD Status Reader Control Point",
-    0x2A3E: "Network Availability",
-    0x2A5F: "PLX Continuous Measurement Characteristic",
-    0x2A60: "PLX Features",
-    0x2A5E: "PLX Spot-Check Measurement",
-    0x2A2F: "Position 2D",
-    0x2A30: "Position 3D",
-    0x2A62: "Pulse Oximetry Control Point",
-    0x2B1D: "RC Feature",
-    0x2B1E: "RC Settings",
-    0x2B1F: "Reconnection Configuration Control Point",
-    0x2A3A: "Removable",
-    0x2A3C: "Scientific Temperature Celsius",
-    0x2A10: "Secondary Time Zone",
-    0x2A3B: "Service Required",
-    0x2A3D: "String",
-    0x2A15: "Time Broadcast",
-    # vendor defined */
-    0xFEFF: "GN Netcom",
-    0xFEFE: "GN ReSound A/S",
-    0xFEFD: "Gimbal: Inc.",
-    0xFEFC: "Gimbal: Inc.",
-    0xFEFB: "Stollmann E+V GmbH",
-    0xFEFA: "PayPal: Inc.",
-    0xFEF9: "PayPal: Inc.",
-    0xFEF8: "Aplix Corporation",
-    0xFEF7: "Aplix Corporation",
-    0xFEF6: "Wicentric: Inc.",
-    0xFEF5: "Dialog Semiconductor GmbH",
-    0xFEF4: "Google",
-    0xFEF3: "Google",
-    0xFEF2: "CSR",
-    0xFEF1: "CSR",
-    0xFEF0: "Intel",
-    0xFEEF: "Polar Electro Oy",
-    0xFEEE: "Polar Electro Oy",
-    0xFEED: "Tile: Inc.",
-    0xFEEC: "Tile: Inc.",
-    0xFEEB: "Swirl Networks: Inc.",
-    0xFEEA: "Swirl Networks: Inc.",
-    0xFEE9: "Quintic Corp.",
-    0xFEE8: "Quintic Corp.",
-    0xFEE7: "Tencent Holdings Limited",
-    0xFEE6: "Seed Labs: Inc.",
-    0xFEE5: "Nordic Semiconductor ASA",
-    0xFEE4: "Nordic Semiconductor ASA",
-    0xFEE3: "Anki: Inc.",
-    0xFEE2: "Anki: Inc.",
-    0xFEE1: "Anhui Huami Information Technology Co.",
-    0xFEE0: "Anhui Huami Information Technology Co.",
-    0xFEDF: "Design SHIFT",
-    0xFEDE: "Coin: Inc.",
-    0xFEDD: "Jawbone",
-    0xFEDC: "Jawbone",
-    0xFEDB: "Perka: Inc.",
-    0xFEDA: "ISSC Technologies Corporation",
-    0xFED9: "Pebble Technology Corporation",
-    0xFED8: "Google",
-    0xFED7: "Broadcom Corporation",
-    0xFED6: "Broadcom Corporation",
-    0xFED5: "Plantronics Inc.",
-    0xFED4: "Apple: Inc.",
-    0xFED3: "Apple: Inc.",
-    0xFED2: "Apple: Inc.",
-    0xFED1: "Apple: Inc.",
-    0xFED0: "Apple: Inc.",
-    0xFECF: "Apple: Inc.",
-    0xFECE: "Apple: Inc.",
-    0xFECD: "Apple: Inc.",
-    0xFECC: "Apple: Inc.",
-    0xFECB: "Apple: Inc.",
-    0xFECA: "Apple: Inc.",
-    0xFEC9: "Apple: Inc.",
-    0xFEC8: "Apple: Inc.",
-    0xFEC7: "Apple: Inc.",
-    0xFEC6: "Kocomojo: LLC",
-    0xFEC5: "Realtek Semiconductor Corp.",
-    0xFEC4: "PLUS Location Systems",
-    0xFEC3: "360fly: Inc.",
-    0xFEC2: "Blue Spark Technologies: Inc.",
-    0xFEC1: "KDDI Corporation",
-    0xFEC0: "KDDI Corporation",
-    0xFEBF: "Nod: Inc.",
-    0xFEBE: "Bose Corporation",
-    0xFEBD: "Clover Network: Inc.",
-    0xFEBC: "Dexcom: Inc.",
-    0xFEBB: "adafruit industries",
-    0xFEBA: "Tencent Holdings Limited",
-    0xFEB9: "LG Electronics",
-    0xFEB8: "Facebook: Inc.",
-    0xFEB7: "Facebook: Inc.",
-    0xFEB6: "Vencer Co: Ltd",
-    0xFEB5: "WiSilica Inc.",
-    0xFEB4: "WiSilica Inc.",
-    0xFEB3: "Taobao",
-    0xFEB2: "Microsoft Corporation",
-    0xFEB1: "Electronics Tomorrow Limited",
-    0xFEB0: "Nest Labs Inc.",
-    0xFEAF: "Nest Labs Inc.",
-    0xFEAE: "Nokia Corporation",
-    0xFEAD: "Nokia Corporation",
-    0xFEAC: "Nokia Corporation",
-    0xFEAB: "Nokia Corporation",
-    0xFEAA: "Google",
-    0xFEA9: "Savant Systems LLC",
-    0xFEA8: "Savant Systems LLC",
-    0xFEA7: "UTC Fire and Security",
-    0xFEA6: "GoPro: Inc.",
-    0xFEA5: "GoPro: Inc.",
-    0xFEA4: "Paxton Access Ltd",
-    0xFEA3: "ITT Industries",
-    0xFEA2: "Intrepid Control Systems: Inc.",
-    0xFEA1: "Intrepid Control Systems: Inc.",
-    0xFEA0: "Google",
-    0xFE9F: "Google",
-    0xFE9E: "Dialog Semiconductor B.V.",
-    0xFE9D: "Mobiquity Networks Inc",
-    0xFE9C: "GSI Laboratories: Inc.",
-    0xFE9B: "Samsara Networks: Inc",
-    0xFE9A: "Estimote",
-    0xFE99: "Currant: Inc.",
-    0xFE98: "Currant: Inc.",
-    0xFE97: "Tesla Motor Inc.",
-    0xFE96: "Tesla Motor Inc.",
-    0xFE95: "Xiaomi Inc.",
-    0xFE94: "OttoQ Inc.",
-    0xFE93: "OttoQ Inc.",
-    0xFE92: "Jarden Safety & Security",
-    0xFE91: "Shanghai Imilab Technology Co.,Ltd",
-    0xFE90: "JUMA",
-    0xFE8F: "CSR",
-    0xFE8E: "ARM Ltd",
-    0xFE8D: "Interaxon Inc.",
-    0xFE8C: "TRON Forum",
-    0xFE8B: "Apple: Inc.",
-    0xFE8A: "Apple: Inc.",
-    0xFE89: "B&O Play A/S",
-    0xFE88: "SALTO SYSTEMS S.L.",
-    0xFE87: "Qingdao Yeelink Information Technology Co.: Ltd. ( 青岛亿联客信息技术有限公司 )",
-    0xFE86: "HUAWEI Technologies Co.: Ltd. ( 华为技术有限公司 )",
-    0xFE85: "RF Digital Corp",
-    0xFE84: "RF Digital Corp",
-    0xFE83: "Blue Bite",
-    0xFE82: "Medtronic Inc.",
-    0xFE81: "Medtronic Inc.",
-    0xFE80: "Doppler Lab",
-    0xFE7F: "Doppler Lab",
-    0xFE7E: "Awear Solutions Ltd",
-    0xFE7D: "Aterica Health Inc.",
-    0xFE7C: "Stollmann E+V GmbH",
-    0xFE7B: "Orion Labs: Inc.",
-    0xFE7A: "Bragi GmbH",
-    0xFE79: "Zebra Technologies",
-    0xFE78: "Hewlett-Packard Company",
-    0xFE77: "Hewlett-Packard Company",
-    0xFE76: "TangoMe",
-    0xFE75: "TangoMe",
-    0xFE74: "unwire",
-    0xFE73: "St. Jude Medical: Inc.",
-    0xFE72: "St. Jude Medical: Inc.",
-    0xFE71: "Plume Design Inc",
-    0xFE70: "Beijing Jingdong Century Trading Co.: Ltd.",
-    0xFE6F: "LINE Corporation",
-    0xFE6E: "The University of Tokyo",
-    0xFE6D: "The University of Tokyo",
-    0xFE6C: "TASER International: Inc.",
-    0xFE6B: "TASER International: Inc.",
-    0xFE6A: "Kontakt Micro-Location Sp. z o.o.",
-    0xFE69: "Qualcomm Life Inc",
-    0xFE68: "Qualcomm Life Inc",
-    0xFE67: "Lab Sensor Solutions",
-    0xFE66: "Intel Corporation",
-    0xFE65: "CHIPOLO d.o.o.",
-    0xFE64: "Siemens AG",
-    0xFE63: "Connected Yard: Inc.",
-    0xFE62: "Indagem Tech LLC",
-    0xFE61: "Logitech International SA",
-    0xFE60: "Lierda Science & Technology Group Co.: Ltd.",
-    0xFE5F: "Eyefi: Inc.",
-    0xFE5E: "Plastc Corporation",
-    0xFE5D: "Grundfos A/S",
-    0xFE5C: "million hunters GmbH",
-    0xFE5B: "GT-tronics HK Ltd",
-    0xFE5A: "Chronologics Corporation",
-    0xFE59: "Nordic Semiconductor ASA",
-    0xFE58: "Nordic Semiconductor ASA",
-    0xFE57: "Dotted Labs",
-    0xFE56: "Google Inc.",
-    0xFE55: "Google Inc.",
-    0xFE54: "Motiv: Inc.",
-    0xFE53: "3M",
-    0xFE52: "SetPoint Medical",
-    0xFE51: "SRAM",
-    0xFE50: "Google Inc.",
-    0xFE4F: "Molekule: Inc.",
-    0xFE4E: "NTT docomo",
-    0xFE4D: "Casambi Technologies Oy",
-    0xFE4C: "Volkswagen AG",
-    0xFE4B: "Koninklijke Philips N.V.",
-    0xFE4A: "OMRON HEALTHCARE Co.: Ltd.",
-    0xFE49: "SenionLab AB",
-    0xFE48: "General Motors",
-    0xFE47: "General Motors",
-    0xFE46: "B&O Play A/S",
-    0xFE45: "Snapchat Inc",
-    0xFE44: "SK Telecom",
-    0xFE43: "Andreas Stihl AG & Co. KG",
-    0xFE42: "Nets A/S",
-    0xFE41: "Inugo Systems Limited",
-    0xFE40: "Inugo Systems Limited",
-    0xFE3F: "Friday Labs Limited",
-    0xFE3E: "BD Medical",
-    0xFE3D: "BD Medical",
-    0xFE3C: "Alibaba",
-    0xFE3B: "Dolby Laboratories",
-    0xFE3A: "TTS Tooltechnic Systems AG & Co. KG",
-    0xFE39: "TTS Tooltechnic Systems AG & Co. KG",
-    0xFE38: "Spaceek LTD",
-    0xFE37: "Spaceek LTD",
-    0xFE36: "HUAWEI Technologies Co.: Ltd",
-    0xFE35: "HUAWEI Technologies Co.: Ltd",
-    0xFE34: "SmallLoop LLC",
-    0xFE33: "CHIPOLO d.o.o.",
-    0xFE32: "Pro-Mark: Inc.",
-    0xFE31: "Volkswagen AG",
-    0xFE30: "Volkswagen AG",
-    0xFE2F: "CRESCO Wireless: Inc",
-    0xFE2E: "ERi,Inc.",
-    0xFE2D: "SMART INNOVATION Co.,Ltd",
-    0xFE2C: "Google Inc.",
-    0xFE2B: "ITT Industries",
-    0xFE2A: "DaisyWorks: Inc.",
-    0xFE29: "Gibson Innovations",
-    0xFE28: "Ayla Network",
-    0xFE27: "Google Inc.",
-    0xFE26: "Google Inc.",
-    0xFE25: "Apple: Inc.",
-    0xFE24: "August Home Inc",
-    0xFE23: "Zoll Medical Corporation",
-    0xFE22: "Zoll Medical Corporation",
-    0xFE21: "Bose Corporation",
-    0xFE20: "Emerson",
-    0xFE1F: "Garmin International: Inc.",
-    0xFE1E: "Smart Innovations Co.: Ltd",
-    0xFE1D: "Illuminati Instrument Corporation",
+    0x2B28: "IDD History Data",
+    0x2B29: "Client Supported Features",
+    0x2B2A: "Database Hash",
+    0x2B2B: "BSS Control Point",
+    0x2B2C: "BSS Response",
+    0x2B2D: "Emergency ID",
+    0x2B2E: "Emergency Text",
+    0x2B34: "Enhanced Blood Pressure Measurement",
+    0x2B35: "Enhanced Intermediate Cuff Pressure",
+    0x2B36: "Blood Pressure Record",
+    # 0x2B37 undefined
+    0x2B38: "BR-EDR Handover Data",
+    0x2B39: "Bluetooth SIG Data",
+    0x2B3A: "Server Supported Features",
+    0x2B3B: "Physical Activity Monitor Features",
+    0x2B3C: "General Activity Instantaneous Data",
+    0x2B3D: "General Activity Summary Data",
+    0x2B3E: "CardioRespiratory Activity Instantaneous Data",
+    0x2B3F: "CardioRespiratory Activity Summary Data",
+    0x2B40: "Step Counter Activity Summary Data",
+    0x2B41: "Sleep Activity Instantaneous Data",
+    0x2B42: "Sleep Activity Summary Data",
+    0x2B43: "Physical Activity Monitor Control Point",
+    0x2B44: "Current Session",
+    0x2B45: "Session",
+    0x2B46: "Preferred Units",
+    0x2B47: "High Resolution Height",
+    0x2B48: "Middle Name",
+    0x2B49: "Stride Length",
+    0x2B4A: "Handedness",
+    0x2B4B: "Device Wearing Position",
+    0x2B4C: "Four Zone Heart Rate Limits",
+    0x2B4D: "High Intensity Exercise Threshold",
+    0x2B4E: "Activity Goal",
+    0x2B4F: "Sedentary Interval Notification",
+    0x2B50: "Caloric Intake",
+    0x2B77: "Audio Input State",
+    0x2B78: "Gain Settings Attribute",
+    0x2B79: "Audio Input Type",
+    0x2B7A: "Audio Input Status",
+    0x2B7B: "Audio Input Control Point",
+    0x2B7C: "Audio Input Description",
+    0x2B7D: "Volume State",
+    0x2B7E: "Volume Control Point",
+    0x2B7F: "Volume Flags",
+    0x2B80: "Offset State",
+    0x2B81: "Audio Location",
+    0x2B82: "Volume Offset Control Point",
+    0x2B83: "Audio Output Description",
+    0x2B84: "Set Identity Resolving Key Characteristic",
+    0x2B85: "Size Characteristic",
+    0x2B86: "Lock Characteristic",
+    0x2B87: "Rank Characteristic",
+    0x2B8E: "Device Time Feature",
+    0x2B8F: "Device Time Parameters",
+    0x2B90: "Device Time",
+    0x2B91: "Device Time Control Point",
+    0x2B92: "Time Change Log Data",
+    0x2B93: "Media Player Name",
+    0x2B94: "Media Player Icon Object ID",
+    0x2B95: "Media Player Icon URL",
+    0x2B96: "Track Changed",
+    0x2B97: "Track Title",
+    0x2B98: "Track Duration",
+    0x2B99: "Track Position",
+    0x2B9A: "Playback Speed",
+    0x2B9B: "Seeking Speed",
+    0x2B9C: "Current Track Segments Object ID",
+    0x2B9D: "Current Track Object ID",
+    0x2B9E: "Next Track Object ID",
+    0x2B9F: "Parent Group Object ID",
+    0x2BA0: "Current Group Object ID",
+    0x2BA1: "Playing Order",
+    0x2BA2: "Playing Orders Supported",
+    0x2BA3: "Media State",
+    0x2BA4: "Media Control Point",
+    0x2BA5: "Media Control Point Opcodes Supported",
+    0x2BA6: "Search Results Object ID",
+    0x2BA7: "Search Control Point",
+    0x2BA9: "Media Player Icon Object Type",
+    0x2BAA: "Track Segments Object Type",
+    0x2BAB: "Track Object Type",
+    0x2BAC: "Group Object Type",
+    0x2BAD: "Constant Tone Extension Enable",
+    0x2BAE: "Advertising Constant Tone Extension Minimum Length",
+    0x2BAF: "Advertising Constant Tone Extension Minimum Transmit Count",
+    0x2BB0: "Advertising Constant Tone Extension Transmit Duration",
+    0x2BB1: "Advertising Constant Tone Extension Interval",
+    0x2BB2: "Advertising Constant Tone Extension PHY",
+    0x2BB3: "Bearer Provider Name",
+    0x2BB4: "Bearer UCI",
+    0x2BB5: "Bearer Technology",
+    0x2BB6: "Bearer URI Schemes Supported List",
+    0x2BB7: "Bearer Signal Strength",
+    0x2BB8: "Bearer Signal Strength Reporting Interval",
+    0x2BB9: "Bearer List Current Calls",
+    0x2BBA: "Content Control ID",
+    0x2BBB: "Status Flags",
+    0x2BBC: "Incoming Call Target Bearer URI",
+    0x2BBD: "Call State",
+    0x2BBE: "Call Control Point",
+    0x2BBF: "Call Control Point Optional Opcodes",
+    0x2BC0: "Termination Reason",
+    0x2BC1: "Incoming Call",
+    0x2BC2: "Call Friendly Name",
+    0x2BC3: "Mute",
+    0x2BC4: "Sink ASE",
+    0x2BC5: "Source ASE",
+    0x2BC6: "ASE Control Point",
+    0x2BC7: "Broadcast Audio Scan Control Point",
+    0x2BC8: "Broadcast Receive State",
+    0x2BC9: "Sink PAC",
+    0x2BCA: "Sink Audio Locations",
+    0x2BCB: "Source PAC",
+    0x2BCC: "Source Audio Locations",
+    0x2BCD: "Available Audio Contexts",
+    0x2BCE: "Supported Audio Contexts",
+    0x2BCF: "Ammonia Concentration",
+    0x2BD0: "Carbon Monoxide Concentration",
+    0x2BD1: "Methane Concentration",
+    0x2BD2: "Nitrogen Dioxide Concentration",
+    0x2BD3: "Non-Methane Volatile Organic Compounds Concentration",
+    0x2BD4: "Ozone Concentration",
+    0x2BD5: "Particulate Matter - PM1 Concentration",
+    0x2BD6: "Particulate Matter - PM2.5 Concentration",
+    0x2BD7: "Particulate Matter - PM10 Concentration",
+    0x2BD8: "Sulfur Dioxide Concentration",
+    0x2BD9: "Sulfur Hexafluoride Concentration",
     0xFE1C: "NetMedia: Inc.",
-    # SDO defined */
+    0xFE1D: "Illuminati Instrument Corporation",
+    0xFE1E: "Smart Innovations Co.: Ltd",
+    0xFE1F: "Garmin International: Inc.",
+    0xFE20: "Emerson",
+    0xFE21: "Bose Corporation",
+    0xFE22: "Zoll Medical Corporation",
+    0xFE23: "Zoll Medical Corporation",
+    0xFE24: "August Home Inc",
+    0xFE25: "Apple: Inc.",
+    0xFE26: "Google Inc.",
+    0xFE27: "Google Inc.",
+    0xFE28: "Ayla Network",
+    0xFE29: "Gibson Innovations",
+    0xFE2A: "DaisyWorks: Inc.",
+    0xFE2B: "ITT Industries",
+    0xFE2C: "Google Inc.",
+    0xFE2D: "SMART INNOVATION Co.,Ltd",
+    0xFE2E: "ERi,Inc.",
+    0xFE2F: "CRESCO Wireless: Inc",
+    0xFE30: "Volkswagen AG",
+    0xFE31: "Volkswagen AG",
+    0xFE32: "Pro-Mark: Inc.",
+    0xFE33: "CHIPOLO d.o.o.",
+    0xFE34: "SmallLoop LLC",
+    0xFE35: "HUAWEI Technologies Co.: Ltd",
+    0xFE36: "HUAWEI Technologies Co.: Ltd",
+    0xFE37: "Spaceek LTD",
+    0xFE38: "Spaceek LTD",
+    0xFE39: "TTS Tooltechnic Systems AG & Co. KG",
+    0xFE3A: "TTS Tooltechnic Systems AG & Co. KG",
+    0xFE3B: "Dolby Laboratories",
+    0xFE3C: "Alibaba",
+    0xFE3D: "BD Medical",
+    0xFE3E: "BD Medical",
+    0xFE3F: "Friday Labs Limited",
+    0xFE40: "Inugo Systems Limited",
+    0xFE41: "Inugo Systems Limited",
+    0xFE42: "Nets A/S",
+    0xFE43: "Andreas Stihl AG & Co. KG",
+    0xFE44: "SK Telecom",
+    0xFE45: "Snapchat Inc",
+    0xFE46: "B&O Play A/S",
+    0xFE47: "General Motors",
+    0xFE48: "General Motors",
+    0xFE49: "SenionLab AB",
+    0xFE4A: "OMRON HEALTHCARE Co.: Ltd.",
+    0xFE4B: "Koninklijke Philips N.V.",
+    0xFE4C: "Volkswagen AG",
+    0xFE4D: "Casambi Technologies Oy",
+    0xFE4E: "NTT docomo",
+    0xFE4F: "Molekule: Inc.",
+    0xFE50: "Google Inc.",
+    0xFE51: "SRAM",
+    0xFE52: "SetPoint Medical",
+    0xFE53: "3M",
+    0xFE54: "Motiv: Inc.",
+    0xFE55: "Google Inc.",
+    0xFE56: "Google Inc.",
+    0xFE57: "Dotted Labs",
+    0xFE58: "Nordic Semiconductor ASA",
+    0xFE59: "Nordic Semiconductor ASA",
+    0xFE5A: "Chronologics Corporation",
+    0xFE5B: "GT-tronics HK Ltd",
+    0xFE5C: "million hunters GmbH",
+    0xFE5D: "Grundfos A/S",
+    0xFE5E: "Plastc Corporation",
+    0xFE5F: "Eyefi: Inc.",
+    0xFE60: "Lierda Science & Technology Group Co.: Ltd.",
+    0xFE61: "Logitech International SA",
+    0xFE62: "Indagem Tech LLC",
+    0xFE63: "Connected Yard: Inc.",
+    0xFE64: "Siemens AG",
+    0xFE65: "CHIPOLO d.o.o.",
+    0xFE66: "Intel Corporation",
+    0xFE67: "Lab Sensor Solutions",
+    0xFE68: "Qualcomm Life Inc",
+    0xFE69: "Qualcomm Life Inc",
+    0xFE6A: "Kontakt Micro-Location Sp. z o.o.",
+    0xFE6B: "TASER International: Inc.",
+    0xFE6C: "TASER International: Inc.",
+    0xFE6D: "The University of Tokyo",
+    0xFE6E: "The University of Tokyo",
+    0xFE6F: "LINE Corporation",
+    0xFE70: "Beijing Jingdong Century Trading Co.: Ltd.",
+    0xFE71: "Plume Design Inc",
+    0xFE72: "St. Jude Medical: Inc.",
+    0xFE73: "St. Jude Medical: Inc.",
+    0xFE74: "unwire",
+    0xFE75: "TangoMe",
+    0xFE76: "TangoMe",
+    0xFE77: "Hewlett-Packard Company",
+    0xFE78: "Hewlett-Packard Company",
+    0xFE79: "Zebra Technologies",
+    0xFE7A: "Bragi GmbH",
+    0xFE7B: "Orion Labs: Inc.",
+    0xFE7C: "Stollmann E+V GmbH",
+    0xFE7D: "Aterica Health Inc.",
+    0xFE7E: "Awear Solutions Ltd",
+    0xFE7F: "Doppler Lab",
+    0xFE80: "Doppler Lab",
+    0xFE81: "Medtronic Inc.",
+    0xFE82: "Medtronic Inc.",
+    0xFE83: "Blue Bite",
+    0xFE84: "RF Digital Corp",
+    0xFE85: "RF Digital Corp",
+    0xFE86: "HUAWEI Technologies Co.: Ltd. ( 华为技术有限公司 )",
+    0xFE87: "Qingdao Yeelink Information Technology Co.: Ltd. ( 青岛亿联客信息技术有限公司 )",
+    0xFE88: "SALTO SYSTEMS S.L.",
+    0xFE89: "B&O Play A/S",
+    0xFE8A: "Apple: Inc.",
+    0xFE8B: "Apple: Inc.",
+    0xFE8C: "TRON Forum",
+    0xFE8D: "Interaxon Inc.",
+    0xFE8E: "ARM Ltd",
+    0xFE8F: "CSR",
+    0xFE90: "JUMA",
+    0xFE91: "Shanghai Imilab Technology Co.,Ltd",
+    0xFE92: "Jarden Safety & Security",
+    0xFE93: "OttoQ Inc.",
+    0xFE94: "OttoQ Inc.",
+    0xFE95: "Xiaomi Inc.",
+    0xFE96: "Tesla Motor Inc.",
+    0xFE97: "Tesla Motor Inc.",
+    0xFE98: "Currant: Inc.",
+    0xFE99: "Currant: Inc.",
+    0xFE9A: "Estimote",
+    0xFE9B: "Samsara Networks: Inc",
+    0xFE9C: "GSI Laboratories: Inc.",
+    0xFE9D: "Mobiquity Networks Inc",
+    0xFE9E: "Dialog Semiconductor B.V.",
+    0xFE9F: "Google",
+    0xFEA0: "Google",
+    0xFEA1: "Intrepid Control Systems: Inc.",
+    0xFEA2: "Intrepid Control Systems: Inc.",
+    0xFEA3: "ITT Industries",
+    0xFEA4: "Paxton Access Ltd",
+    0xFEA5: "GoPro: Inc.",
+    0xFEA6: "GoPro: Inc.",
+    0xFEA7: "UTC Fire and Security",
+    0xFEA8: "Savant Systems LLC",
+    0xFEA9: "Savant Systems LLC",
+    0xFEAA: "Google",
+    0xFEAB: "Nokia Corporation",
+    0xFEAC: "Nokia Corporation",
+    0xFEAD: "Nokia Corporation",
+    0xFEAE: "Nokia Corporation",
+    0xFEAF: "Nest Labs Inc.",
+    0xFEB0: "Nest Labs Inc.",
+    0xFEB1: "Electronics Tomorrow Limited",
+    0xFEB2: "Microsoft Corporation",
+    0xFEB3: "Taobao",
+    0xFEB4: "WiSilica Inc.",
+    0xFEB5: "WiSilica Inc.",
+    0xFEB6: "Vencer Co: Ltd",
+    0xFEB7: "Facebook: Inc.",
+    0xFEB8: "Facebook: Inc.",
+    0xFEB9: "LG Electronics",
+    0xFEBA: "Tencent Holdings Limited",
+    0xFEBB: "adafruit industries",
+    0xFEBC: "Dexcom: Inc.",
+    0xFEBD: "Clover Network: Inc.",
+    0xFEBE: "Bose Corporation",
+    0xFEBF: "Nod: Inc.",
+    0xFEC0: "KDDI Corporation",
+    0xFEC1: "KDDI Corporation",
+    0xFEC2: "Blue Spark Technologies: Inc.",
+    0xFEC3: "360fly: Inc.",
+    0xFEC4: "PLUS Location Systems",
+    0xFEC5: "Realtek Semiconductor Corp.",
+    0xFEC6: "Kocomojo: LLC",
+    0xFEC7: "Apple: Inc.",
+    0xFEC8: "Apple: Inc.",
+    0xFEC9: "Apple: Inc.",
+    0xFECA: "Apple: Inc.",
+    0xFECB: "Apple: Inc.",
+    0xFECC: "Apple: Inc.",
+    0xFECD: "Apple: Inc.",
+    0xFECE: "Apple: Inc.",
+    0xFECF: "Apple: Inc.",
+    0xFED0: "Apple: Inc.",
+    0xFED1: "Apple: Inc.",
+    0xFED2: "Apple: Inc.",
+    0xFED3: "Apple: Inc.",
+    0xFED4: "Apple: Inc.",
+    0xFED5: "Plantronics Inc.",
+    0xFED6: "Broadcom Corporation",
+    0xFED7: "Broadcom Corporation",
+    0xFED8: "Google",
+    0xFED9: "Pebble Technology Corporation",
+    0xFEDA: "ISSC Technologies Corporation",
+    0xFEDB: "Perka: Inc.",
+    0xFEDC: "Jawbone",
+    0xFEDD: "Jawbone",
+    0xFEDE: "Coin: Inc.",
+    0xFEDF: "Design SHIFT",
+    0xFEE0: "Anhui Huami Information Technology Co.",
+    0xFEE1: "Anhui Huami Information Technology Co.",
+    0xFEE2: "Anki: Inc.",
+    0xFEE3: "Anki: Inc.",
+    0xFEE4: "Nordic Semiconductor ASA",
+    0xFEE5: "Nordic Semiconductor ASA",
+    0xFEE6: "Seed Labs: Inc.",
+    0xFEE7: "Tencent Holdings Limited",
+    0xFEE8: "Quintic Corp.",
+    0xFEE9: "Quintic Corp.",
+    0xFEEA: "Swirl Networks: Inc.",
+    0xFEEB: "Swirl Networks: Inc.",
+    0xFEEC: "Tile: Inc.",
+    0xFEED: "Tile: Inc.",
+    0xFEEE: "Polar Electro Oy",
+    0xFEEF: "Polar Electro Oy",
+    0xFEF0: "Intel",
+    0xFEF1: "CSR",
+    0xFEF2: "CSR",
+    0xFEF3: "Google",
+    0xFEF4: "Google",
+    0xFEF5: "Dialog Semiconductor GmbH",
+    0xFEF6: "Wicentric: Inc.",
+    0xFEF7: "Aplix Corporation",
+    0xFEF8: "Aplix Corporation",
+    0xFEF9: "PayPal: Inc.",
+    0xFEFA: "PayPal: Inc.",
+    0xFEFB: "Stollmann E+V GmbH",
+    0xFEFC: "Gimbal: Inc.",
+    0xFEFD: "Gimbal: Inc.",
+    0xFEFE: "GN ReSound A/S",
+    0xFEFF: "GN Netcom",
     0xFFFC: "AirFuel Alliance",
-    0xFFFE: "Alliance for Wireless Power (A4WP)",
     0xFFFD: "Fast IDentity Online Alliance (FIDO)",
-    # Mesh Characteristics
-    0x2AE0: "Average Current",
-    0x2AE1: "Average Voltage",
-    0x2AE2: "Boolean",
-    0x2AE3: "Chromatic Distance From Planckian",
-    0x2B1C: "Chromaticity Coordinate",
-    0x2AE4: "Chromaticity Coordinates",
-    0x2AE5: "Chromaticity In CCT And Duv Values",
-    0x2AE6: "Chromaticity Tolerance",
-    0x2AE7: "CIE 13.3-1995 Color Rendering Index",
-    0x2AE8: "Coefficient",
-    0x2AE9: "Correlated Color Temperature",
-    0x2AEA: "Count 16",
-    0x2AEB: "Count 24",
-    0x2AEC: "Country Code",
-    0x2AED: "Date UTC",
-    0x2AEE: "Electric Current",
-    0x2AEF: "Electric Current Range",
-    0x2AF0: "Electric Current Specification",
-    0x2AF1: "Electric Current Statistics",
-    0x2AF2: "Energy",
-    0x2AF3: "Energy In A Period Of Day",
-    0x2AF4: "Event Statistics",
-    0x2AF5: "Fixed String 16",
-    0x2AF6: "Fixed String 24",
-    0x2AF7: "Fixed String 36",
-    0x2AF8: "Fixed String 8",
-    0x2AF9: "Generic Level",
-    0x2AFA: "Global Trade Item Number",
-    0x2AFB: "Illuminance",
-    0x2AFC: "Luminous Efficacy",
-    0x2AFD: "Luminous Energy",
-    0x2AFE: "Luminous Exposure",
-    0x2AFF: "Luminous Flux",
-    0x2B00: "Luminous Flux Range",
-    0x2B01: "Luminous Intensity",
-    0x2B02: "Mass Flow",
-    0x2ADB: "Mesh Provisioning Data In",
-    0x2ADC: "Mesh Provisioning Data Out",
-    0x2ADD: "Mesh Proxy Data In",
-    0x2ADE: "Mesh Proxy Data Out",
-    0x2B03: "Perceived Lightness",
-    0x2B04: "Percentage 8",
-    0x2B05: "Power",
-    0x2B06: "Power Specification",
-    0x2B07: "Relative Runtime In A Current Range",
-    0x2B08: "Relative Runtime In A Generic Level Range",
-    0x2B0B: "Relative Value In A Period of Day",
-    0x2B0C: "Relative Value In A Temperature Range",
-    0x2B09: "Relative Value In A Voltage Range",
-    0x2B0A: "Relative Value In An Illuminance Range",
-    0x2B0D: "Temperature 8",
-    0x2B0E: "Temperature 8 In A Period Of Day",
-    0x2B0F: "Temperature 8 Statistics",
-    0x2B10: "Temperature Range",
-    0x2B11: "Temperature Statistics",
-    0x2B12: "Time Decihour 8",
-    0x2B13: "Time Exponential 8",
-    0x2B14: "Time Hour 24",
-    0x2B15: "Time Millisecond 24",
-    0x2B16: "Time Second 16",
-    0x2B17: "Time Second 8",
-    0x2B18: "Voltage",
-    0x2B19: "Voltage Specification",
-    0x2B1A: "Voltage Statistics",
-    0x2B1B: "Volume Flow",
+    0xFFFE: "Alliance for Wireless Power (A4WP)",
 }
 
 uuid128_dict = {
diff -pruN 0.14.3-1/bleak/__version__.py 0.15.1-1/bleak/__version__.py
--- 0.14.3-1/bleak/__version__.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/bleak/__version__.py	2022-08-03 16:15:28.000000000 +0000
@@ -1,3 +1,3 @@
 # -*- coding: utf-8 -*-
 
-__version__ = "0.14.3"
+__version__ = "0.15.1"
diff -pruN 0.14.3-1/CHANGELOG.rst 0.15.1-1/CHANGELOG.rst
--- 0.14.3-1/CHANGELOG.rst	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/CHANGELOG.rst	2022-08-03 16:15:28.000000000 +0000
@@ -7,6 +7,63 @@ All notable changes to this project will
 The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_,
 and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
 
+`Unreleased`_
+=============
+
+`0.15.1`_ (2022-08-03)
+======================
+
+Fixed
+-----
+* The global BlueZ manager now disconnects correctly on exception. Merged #918.
+* Handle the race in the BlueZ D-Bus backend where the device disconnects during
+  the connection process which presented as ``Failed to cancel connection``. Merged #919.
+* Ensure the BlueZ D-Bus scanner can reconnect after DBus disconnection. Merged #920.
+
+
+`0.15.0`_ (2022-07-29)
+======================
+
+Added
+-----
+
+* Added new ``assigned_numbers`` module and ``AdvertisementDataType`` enum.
+* Added new ``bluez`` kwarg to ``BleakScanner`` in BlueZ backend.
+* Added support for passive scanning in the BlueZ backend. Fixes #606.
+* Added option to use cached services, characteristics and descriptors in WinRT backend. Fixes #686.
+* Added ``PendingDeprecationWarning`` to use of ``address_type`` as keyword argument. It will be moved into the
+  ``winrt`` keyword instead according to #623.
+* Added better error message when adapter is not present in BlueZ backend. Fixes #889.
+
+Changed
+-------
+
+* Add ``py.typed`` file so mypy discovers Bleak's type annotations.
+* UUID descriptions updated to 2022-03-16 assigned numbers document.
+* Replace use of deprecated ``asyncio.get_event_loop()`` in Android backend.
+* Adjust default timeout for ``read_gatt_char()`` with CoreBluetooth to 10s. Merged #891.
+* ``BleakScanner()`` args ``detection_callback`` and ``service_uuids`` are no longer keyword-only.
+* ``BleakScanner()`` arg ``scanning_mode`` is no longer Windows-only and is no longer keyword-only.
+* All ``BleakScanner()`` instances in BlueZ backend now use common D-Bus object manager.
+* Deprecated ``filters`` kwarg in ``BleakScanner`` in BlueZ backend.
+* BlueZ version is now checked on first connection instead of import to avoid import side effects. Merged #907.
+
+Fixed
+-----
+
+* Documentation fixes.
+* On empty characteristic description from WinRT, use the lookup table instead of returning empty string.
+* Fixed detection of first advertisement in BlueZ backend. Merged #903.
+* Fixed performance issues in BlueZ backend caused by calling "GetManagedObjects" each time a
+  ``BleakScanner`` scans or ``BleakClient`` is connected. Fixes #500.
+* Fixed not handling "InterfacesRemoved" in ``BleakClient`` in BlueZ backend. Fixes #882.
+* Fixed leaking D-Bus socket file descriptors in BlueZ backend. Fixes #805.
+
+Removed
+-------
+
+* Removed fallback to call "ConnectDevice" when "Connect" fails in Bluez backend. Fixes #806.
+
 `0.14.3`_ (2022-04-29)
 ======================
 
@@ -665,7 +722,9 @@ Fixed
 * Bleak created.
 
 
-.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.14.3...develop
+.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.15.1...develop
+.. _0.15.1: https://github.com/hbldh/bleak/compare/v0.15.0...v0.15.1
+.. _0.15.0: https://github.com/hbldh/bleak/compare/v0.14.3...v0.15.0
 .. _0.14.3: https://github.com/hbldh/bleak/compare/v0.14.2...v0.14.3
 .. _0.14.2: https://github.com/hbldh/bleak/compare/v0.14.1...v0.14.2
 .. _0.14.1: https://github.com/hbldh/bleak/compare/v0.14.0...v0.14.1
diff -pruN 0.14.3-1/debian/changelog 0.15.1-1/debian/changelog
--- 0.14.3-1/debian/changelog	2022-07-26 08:44:53.000000000 +0000
+++ 0.15.1-1/debian/changelog	2022-08-05 14:35:41.000000000 +0000
@@ -1,3 +1,13 @@
+bleak (0.15.1-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Install AUTHORS.rst.
+  * Add python3-typing-extensions and python3-pytest-asyncio to Build-Depends.
+  * Add missing dependency on the bluez package.
+  * Add python3-pytest-asyncio as a dependency for running autopkgtest.
+
+ -- Edward Betts <edward@4angle.com>  Fri, 05 Aug 2022 15:35:41 +0100
+
 bleak (0.14.3-1) unstable; urgency=medium
 
   * Initial release. (Closes: #1015988)
diff -pruN 0.14.3-1/debian/control 0.15.1-1/debian/control
--- 0.14.3-1/debian/control	2022-07-26 08:44:53.000000000 +0000
+++ 0.15.1-1/debian/control	2022-08-05 14:35:41.000000000 +0000
@@ -6,13 +6,15 @@ Priority: optional
 Build-Depends: bluez,
                debhelper-compat (= 13),
                dh-sequence-python3,
-               dh-sequence-sphinxdoc,
+               dh-sequence-sphinxdoc <!nodoc>,
                python3-all,
                python3-dbus-next,
                python3-pytest <!nocheck>,
+               python3-pytest-asyncio <!nocheck>,
                python3-setuptools,
-               python3-sphinx,
-               python3-sphinx-rtd-theme
+               python3-sphinx <!nodoc>,
+               python3-sphinx-rtd-theme <!nodoc>,
+               python3-typing-extensions
 Rules-Requires-Root: no
 Standards-Version: 4.6.1
 Homepage: https://github.com/hbldh/bleak
@@ -21,7 +23,7 @@ Vcs-Git: https://salsa.debian.org/python
 
 Package: python3-bleak
 Architecture: all
-Depends: ${misc:Depends}, ${python3:Depends}
+Depends: bluez, ${misc:Depends}, ${python3:Depends}
 Description: Bluetooth Low Energy platform agnostic client
  Bleak is an acronym for Bluetooth Low Energy platform Agnostic Klient.
  .
diff -pruN 0.14.3-1/debian/docs 0.15.1-1/debian/docs
--- 0.14.3-1/debian/docs	2022-07-26 08:44:53.000000000 +0000
+++ 0.15.1-1/debian/docs	2022-08-05 14:35:41.000000000 +0000
@@ -1 +1,2 @@
+AUTHORS.rst
 README.rst
diff -pruN 0.14.3-1/debian/tests/control 0.15.1-1/debian/tests/control
--- 0.14.3-1/debian/tests/control	2022-07-26 08:39:17.000000000 +0000
+++ 0.15.1-1/debian/tests/control	2022-08-05 14:35:41.000000000 +0000
@@ -1,2 +1,2 @@
 Tests: run-tests
-Depends: bluez, python3-all, python3-pytest, @
+Depends: python3-all, python3-pytest, python3-pytest-asyncio, @
diff -pruN 0.14.3-1/docs/scanning.rst 0.15.1-1/docs/scanning.rst
--- 0.14.3-1/docs/scanning.rst	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/docs/scanning.rst	2022-08-03 16:15:28.000000000 +0000
@@ -24,6 +24,10 @@ To discover Bluetooth devices that can b
 
 .. warning:: Do not name your script `bleak.py`! It will cause a circular import error.
 
+.. warning:: On macOS you may need to give your terminal permission to access Bluetooth.
+    See `this troubleshooting message <https://bleak.readthedocs.io/en/latest/troubleshooting.html#bleak-crashes-with-sigabrt-on-macos>`_
+
+
 This will scan for 5 seconds and then produce a printed list of detected devices::
 
     24:71:89:CC:09:05: CC2650 SensorTag
@@ -62,8 +66,7 @@ or separately, calling ``start`` and ``s
         print(device.address, "RSSI:", device.rssi, advertisement_data)
 
     async def main():
-        scanner = BleakScanner()
-        scanner.register_detection_callback(detection_callback)
+        scanner = BleakScanner(detection_callback)
         await scanner.start()
         await asyncio.sleep(5.0)
         await scanner.stop()
diff -pruN 0.14.3-1/docs/troubleshooting.rst 0.15.1-1/docs/troubleshooting.rst
--- 0.14.3-1/docs/troubleshooting.rst	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/docs/troubleshooting.rst	2022-08-03 16:15:28.000000000 +0000
@@ -8,7 +8,7 @@ When things don't seem to be working rig
 Common Mistakes
 ---------------
 
-Many people name their first script ```bleak.py``. This causes the script to
+Many people name their first script ``bleak.py``. This causes the script to
 crash with an ``ImportError`` similar to::
 
     ImportError: cannot import name 'BleakClient' from partially initialized module 'bleak' (most likely due to a circular import) (bleak.py)`
diff -pruN 0.14.3-1/examples/detection_callback.py 0.15.1-1/examples/detection_callback.py
--- 0.14.3-1/examples/detection_callback.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/examples/detection_callback.py	2022-08-03 16:15:28.000000000 +0000
@@ -24,10 +24,10 @@ def simple_callback(device: BLEDevice, a
 
 
 async def main(service_uuids):
-    scanner = BleakScanner(service_uuids=service_uuids)
-    scanner.register_detection_callback(simple_callback)
+    scanner = BleakScanner(simple_callback, service_uuids)
 
     while True:
+        print("(re)starting scanner")
         await scanner.start()
         await asyncio.sleep(5.0)
         await scanner.stop()
diff -pruN 0.14.3-1/examples/mtu_size.py 0.15.1-1/examples/mtu_size.py
--- 0.14.3-1/examples/mtu_size.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/examples/mtu_size.py	2022-08-03 16:15:28.000000000 +0000
@@ -18,7 +18,7 @@ async def main():
         # can use advertising data to filter here
         queue.put_nowait(device)
 
-    async with BleakScanner(detection_callback=callback):
+    async with BleakScanner(callback):
         # get the first matching device
         device = await queue.get()
 
diff -pruN 0.14.3-1/examples/sensortag.py 0.15.1-1/examples/sensortag.py
--- 0.14.3-1/examples/sensortag.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/examples/sensortag.py	2022-08-03 16:15:28.000000000 +0000
@@ -98,7 +98,7 @@ IO_CONFIG_CHAR_UUID = "f000aa66-0451-400
 
 
 async def main(address):
-    async with BleakClient(address) as client:
+    async with BleakClient(address, winrt=dict(use_cached_services=True)) as client:
         print(f"Connected: {client.is_connected}")
 
         system_id = await client.read_gatt_char(SYSTEM_ID_UUID)
@@ -132,10 +132,11 @@ async def main(address):
         battery_level = await client.read_gatt_char(BATTERY_LEVEL_UUID)
         print("Battery Level: {0}%".format(int(battery_level[0])))
 
-        async def keypress_handler(sender, data):
+        async def notification_handler(sender, data):
             print("{0}: {1}".format(sender, data))
 
-        write_value = bytearray([0xA0])
+        # Turn on the red light on the Sensor Tag by writing to I/O Data and I/O Config.
+        write_value = bytearray([0x01])
         value = await client.read_gatt_char(IO_DATA_CHAR_UUID)
         print("I/O Data Pre-Write Value: {0}".format(value))
 
@@ -145,7 +146,19 @@ async def main(address):
         print("I/O Data Post-Write Value: {0}".format(value))
         assert value == write_value
 
-        await client.start_notify(KEY_PRESS_UUID, keypress_handler)
+        write_value = bytearray([0x01])
+        value = await client.read_gatt_char(IO_CONFIG_CHAR_UUID)
+        print("I/O Config Pre-Write Value: {0}".format(value))
+
+        await client.write_gatt_char(IO_CONFIG_CHAR_UUID, write_value)
+
+        value = await client.read_gatt_char(IO_CONFIG_CHAR_UUID)
+        print("I/O Config Post-Write Value: {0}".format(value))
+        assert value == write_value
+
+        # Try notifications with key presses.
+
+        await client.start_notify(KEY_PRESS_UUID, notification_handler)
         await asyncio.sleep(5.0)
         await client.stop_notify(KEY_PRESS_UUID)
 
diff -pruN 0.14.3-1/MANIFEST.in 0.15.1-1/MANIFEST.in
--- 0.14.3-1/MANIFEST.in	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/MANIFEST.in	2022-08-03 16:15:28.000000000 +0000
@@ -3,6 +3,7 @@ include CHANGELOG.rst
 include CONTRIBUTING.rst
 include LICENSE
 include README.rst
+include bleak/py.typed
 
 recursive-include tests *
 recursive-exclude * __pycache__
diff -pruN 0.14.3-1/Pipfile 0.15.1-1/Pipfile
--- 0.14.3-1/Pipfile	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/Pipfile	2022-08-03 16:15:28.000000000 +0000
@@ -4,6 +4,7 @@ verify_ssl = true
 name = "pypi"
 
 [packages]
+typing-extensions = ">=4.2.0"
 dbus-next = {version = ">=0.2.2", sys_platform = "== 'linux'"}
 pyobjc-core = {version = ">=7.0.1", sys_platform = "== 'darwin'"}
 pyobjc-framework-CoreBluetooth = {version = ">=7.0.1", sys_platform = "== 'darwin'"}
diff -pruN 0.14.3-1/requirements_dev.txt 0.15.1-1/requirements_dev.txt
--- 0.14.3-1/requirements_dev.txt	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/requirements_dev.txt	2022-08-03 16:15:28.000000000 +0000
@@ -1,3 +1,4 @@
+asynctest>=0.13.0
 pip>=18.0
 bump2version==1.0.1
 wheel>=0.32.2
@@ -11,4 +12,6 @@ Sphinx>=1.7.7
 sphinx-rtd-theme>=1.0.0
 PyYAML>=3.13
 pytest>=3.9.2
+pytest-asyncio>=0.19.0
 pytest-cov>=2.5.1
+typing-extensions>=4.2.0
diff -pruN 0.14.3-1/requirements.txt 0.15.1-1/requirements.txt
--- 0.14.3-1/requirements.txt	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/requirements.txt	2022-08-03 16:15:28.000000000 +0000
@@ -1,3 +1,4 @@
+typing-extensions>=4.2.0
 dbus-next>=0.2.2; sys_platform=="linux"
 pyobjc-core>=7.0.1;sys_platform == 'darwin'
 pyobjc-framework-CoreBluetooth>=7.0.1;sys_platform == 'darwin'
diff -pruN 0.14.3-1/setup.py 0.15.1-1/setup.py
--- 0.14.3-1/setup.py	2022-04-29 18:52:31.000000000 +0000
+++ 0.15.1-1/setup.py	2022-08-03 16:15:28.000000000 +0000
@@ -19,6 +19,7 @@ EMAIL = "henrik.blidh@nedomkull.com"
 AUTHOR = "Henrik Blidh"
 
 REQUIRED = [
+    "typing-extensions>=4.2.0",
     # Linux reqs
     'dbus-next;platform_system=="Linux"',
     # macOS reqs
diff -pruN 0.14.3-1/tests/bleak/backends/bluezdbus/test_version.py 0.15.1-1/tests/bleak/backends/bluezdbus/test_version.py
--- 0.14.3-1/tests/bleak/backends/bluezdbus/test_version.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.15.1-1/tests/bleak/backends/bluezdbus/test_version.py	2022-08-03 16:15:28.000000000 +0000
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+
+"""Tests for `bleak.backends.bluezdbus.version` package."""
+
+import sys
+from unittest.mock import Mock, patch
+
+import pytest
+
+if sys.version_info[:2] < (3, 8):
+    from asynctest.mock import CoroutineMock as AsyncMock
+else:
+    from unittest.mock import AsyncMock
+
+from bleak.backends.bluezdbus.version import BlueZFeatures
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "version,can_write_without_response,write_without_response_workaround_needed,hides_battery_characteristic,hides_device_name_characteristic",
+    [
+        (b"bluetoothctl: 5.34", False, False, False, False),
+        (b"bluetoothctl: 5.46", True, False, False, False),
+        (b"bluetoothctl: 5.48", True, False, True, True),
+        (b"bluetoothctl: 5.51", True, True, True, True),
+        (b"bluetoothctl: 5.63", True, True, True, True),
+        (b"", True, True, True, True),
+    ],
+)
+async def test_bluez_version(
+    version,
+    can_write_without_response,
+    write_without_response_workaround_needed,
+    hides_battery_characteristic,
+    hides_device_name_characteristic,
+):
+    """Test we can determine supported feature from bluetoothctl."""
+    mock_proc = Mock(
+        wait=AsyncMock(), stdout=Mock(read=AsyncMock(return_value=version))
+    )
+    with patch(
+        "bleak.backends.bluezdbus.version.asyncio.create_subprocess_exec",
+        AsyncMock(return_value=mock_proc),
+    ):
+        BlueZFeatures._check_bluez_event = None
+        await BlueZFeatures.check_bluez_version()
+    assert BlueZFeatures.checked_bluez_version is True
+    assert BlueZFeatures.can_write_without_response == can_write_without_response
+    assert (
+        not BlueZFeatures.write_without_response_workaround_needed
+        == write_without_response_workaround_needed
+    )
+    assert BlueZFeatures.hides_battery_characteristic == hides_battery_characteristic
+    assert (
+        BlueZFeatures.hides_device_name_characteristic
+        == hides_device_name_characteristic
+    )
+
+
+@pytest.mark.asyncio
+async def test_bluez_version_only_happens_once():
+    """Test we can determine supported feature from bluetoothctl."""
+    BlueZFeatures.checked_bluez_version = False
+    BlueZFeatures._check_bluez_event = None
+    mock_proc = Mock(
+        wait=AsyncMock(),
+        stdout=Mock(read=AsyncMock(return_value=b"bluetoothctl: 5.46")),
+    )
+    with patch(
+        "bleak.backends.bluezdbus.version.asyncio.create_subprocess_exec",
+        AsyncMock(return_value=mock_proc),
+    ):
+        await BlueZFeatures.check_bluez_version()
+
+    assert BlueZFeatures.checked_bluez_version is True
+
+    with patch(
+        "bleak.backends.bluezdbus.version.asyncio.create_subprocess_exec",
+        side_effect=Exception,
+    ):
+        await BlueZFeatures.check_bluez_version()
+
+    assert BlueZFeatures.checked_bluez_version is True
+
+
+@pytest.mark.asyncio
+async def test_exception_checking_bluez_features_does_not_block_forever():
+    """Test an exception while checking BlueZ features does not stall a second check."""
+    BlueZFeatures.checked_bluez_version = False
+    BlueZFeatures._check_bluez_event = None
+    with patch(
+        "bleak.backends.bluezdbus.version.asyncio.create_subprocess_exec",
+        side_effect=OSError,
+    ):
+        await BlueZFeatures.check_bluez_version()
+
+    assert BlueZFeatures.checked_bluez_version is True
+
+    with patch(
+        "bleak.backends.bluezdbus.version.asyncio.create_subprocess_exec",
+        side_effect=OSError,
+    ):
+        await BlueZFeatures.check_bluez_version()
+
+    assert BlueZFeatures.checked_bluez_version is True
