From 6fcd1c92433517df78ae467ac566eca286379c7d Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Thu, 10 Sep 2020 13:42:57 +0200 Subject: [PATCH 01/21] Added connection possibility by BLEDevice --- CHANGELOG.rst | 3 +++ bleak/backends/bluezdbus/client.py | 33 +++++++++++++++----------- bleak/backends/client.py | 12 +++++++--- bleak/backends/corebluetooth/client.py | 32 +++++++++++++++---------- bleak/backends/dotnet/client.py | 33 +++++++++++++++----------- 5 files changed, 69 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 92de7f93..cda21bff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -26,12 +26,15 @@ Added * Implemented pairing method in .NET backend. * Implemented pairing method in the BlueZ backend. * Added stumps and ``NotImplementedError`` on pairing in macOS backend. +* Added the possibility to connect using ``BLEDevice`` instead of a string adress. This + allows for skipping the discovery call when connecting. Changed ~~~~~~~ * **BREAKING CHANGE** All notifications now have the characteristic's integer **handle** instead of its UUID as a string as the first argument ``sender`` sent to notification callbacks. This provides the uniqueness of sender in notifications as well. +* Renamed ``BleakClient`` argument ``address`` to ``address_or_device``. * Version 0.5.0 of BleakUWPBridge, with some modified methods and implementing ``IDisposable``. * Merged #224. All storing and passing of event loops in bleak is removed. * Removed Objective C delegate compliance checks. Merged #253. diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 4742ebaf..9be8a282 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -12,6 +12,7 @@ from twisted.internet.error import ConnectionDone +from bleak.backends.device import BLEDevice from bleak.backends.service import BleakGATTServiceCollection from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.exc import BleakError @@ -36,20 +37,23 @@ class BleakClientBlueZDBus(BaseBleakClient): Implemented by using the `BlueZ DBUS API `_. Args: - address (str): The address of the BLE peripheral to connect to. + address_or_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. """ - def __init__(self, address, **kwargs): - super(BleakClientBlueZDBus, self).__init__(address, **kwargs) + def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): + super(BleakClientBlueZDBus, self).__init__(address_or_device, **kwargs) self.device = kwargs.get("device") if kwargs.get("device") else "hci0" - self.address = address + self.address = address_or_device # Backend specific, TXDBus objects and data - self._device_path = None + if isinstance(address_or_device, BLEDevice): + self._device_path = address_or_device.details["path"] + else: + self._device_path = None self._bus = None self._reactor = None self._rules = {} @@ -109,16 +113,17 @@ async def connect(self, **kwargs) -> bool: """ # 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) - device = await BleakScannerBlueZDBus.find_device_by_address( - self.address, timeout=timeout, device=self.device) + if self._device_path is None: + timeout = kwargs.get("timeout", self._timeout) + device = await BleakScannerBlueZDBus.find_device_by_address( + self.address, timeout=timeout, device=self.device) - if device: - self._device_path = device.details["path"] - else: - raise BleakError( - "Device with address {0} was not found.".format(self.address) - ) + if device: + self._device_path = device.details["path"] + else: + raise BleakError( + "Device with address {0} was not found.".format(self.address) + ) loop = asyncio.get_event_loop() self._reactor = get_reactor(loop) diff --git a/bleak/backends/client.py b/bleak/backends/client.py index 9933aab0..d75312c4 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -12,20 +12,26 @@ from bleak.backends.service import BleakGATTServiceCollection from bleak.backends.characteristic import BleakGATTCharacteristic - +from bleak.backends.device import BLEDevice class BaseBleakClient(abc.ABC): """The Client Interface for Bleak Backend implementations to implement. The documentation of this interface should thus be safe to use as a reference for your implementation. + Args: + address_or_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 ``discover`` call. Defaults to 2.0. """ - def __init__(self, address, **kwargs): - self.address = address + def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): + if isinstance(address_or_device, BLEDevice): + self.address = address_or_device.address + else: + self.address = address_or_device self.services = BleakGATTServiceCollection() diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 302fa840..83202c44 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -20,9 +20,9 @@ BleakGATTCharacteristicCoreBluetooth, ) from bleak.backends.corebluetooth.descriptor import BleakGATTDescriptorCoreBluetooth -from bleak.backends.corebluetooth.discovery import discover from bleak.backends.corebluetooth.scanner import BleakScannerCoreBluetooth from bleak.backends.corebluetooth.service import BleakGATTServiceCoreBluetooth +from bleak.backends.device import BLEDevice from bleak.backends.service import BleakGATTServiceCollection from bleak.backends.characteristic import BleakGATTCharacteristic @@ -35,14 +35,19 @@ class BleakClientCoreBluetooth(BaseBleakClient): """CoreBluetooth class interface for BleakClient Args: - address (str): The uuid of the BLE peripheral to connect to. + address_or_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 ``discover`` call during connect. Defaults to 10.0. """ - def __init__(self, address: str, **kwargs): - super(BleakClientCoreBluetooth, self).__init__(address, **kwargs) + def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): + super(BleakClientCoreBluetooth, self).__init__(address_or_device, **kwargs) + + if isinstance(address_or_device, BLEDevice): + self._device_info = address_or_device.details + else: + self._device_info = None self._device_info = None self._requester = None @@ -64,16 +69,17 @@ async def connect(self, **kwargs) -> bool: Boolean representing connection status. """ - timeout = kwargs.get("timeout", self._timeout) - device = await BleakScannerCoreBluetooth.find_device_by_address( - self.address, timeout=timeout) + if self._device_info is None: + timeout = kwargs.get("timeout", self._timeout) + device = await BleakScannerCoreBluetooth.find_device_by_address( + self.address, timeout=timeout) - if device: - self._device_info = device.details - else: - raise BleakError( - "Device with address {} was not found".format(self.address) - ) + if device: + self._device_info = device.details + else: + raise BleakError( + "Device with address {} was not found".format(self.address) + ) logger.debug("Connecting to BLE device @ {}".format(self.address)) diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index 11258aa5..a32f5afe 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -11,6 +11,7 @@ from functools import wraps from typing import Callable, Any, Union +from bleak.backends.device import BLEDevice from bleak.backends.dotnet.scanner import BleakScannerDotNet from bleak.exc import BleakError, BleakDotNetTaskError, CONTROLLER_ERROR_CODES from bleak.backends.client import BaseBleakClient @@ -91,18 +92,21 @@ class BleakClientDotNet(BaseBleakClient): Common Language Runtime (CLR). Therefore, much of the code below has a distinct C# feel. Args: - address (str): The Bluetooth address of the BLE peripheral to connect to. + address_or_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 ``discover`` call. Defaults to 2.0. + timeout (float): Timeout for required ``discover`` call. Defaults to 2.0. """ - def __init__(self, address: str, **kwargs): - super(BleakClientDotNet, self).__init__(address, **kwargs) + def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): + super(BleakClientDotNet, self).__init__(address_or_device, **kwargs) # Backend specific. Python.NET objects. - self._device_info = None + if isinstance(address_or_device, BLEDevice): + self._device_info = address_or_device.details.BluetoothAddress + else: + self._device_info = None self._requester = None self._bridge = None @@ -132,16 +136,17 @@ async def connect(self, **kwargs) -> bool: self._bridge = Bridge() # Try to find the desired device. - timeout = kwargs.get("timeout", self._timeout) - device = await BleakScannerDotNet.find_device_by_address( - self.address, timeout=timeout) + if self._device_info is not None: + timeout = kwargs.get("timeout", self._timeout) + device = await BleakScannerDotNet.find_device_by_address( + self.address, timeout=timeout) - if device: - self._device_info = device.details.BluetoothAddress - else: - raise BleakError( - "Device with address {0} was not found.".format(self.address) - ) + if device: + self._device_info = device.details.BluetoothAddress + else: + raise BleakError( + "Device with address {0} was not found.".format(self.address) + ) logger.debug("Connecting to BLE device @ {0}".format(self.address)) From 6fc051bf161c1b200f5ebcfab263ddc715322bfc Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Thu, 10 Sep 2020 13:47:59 +0200 Subject: [PATCH 02/21] Added example for connection by BLEDevice to use for testing on all platforms. --- examples/connect_by_bledevice.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 examples/connect_by_bledevice.py diff --git a/examples/connect_by_bledevice.py b/examples/connect_by_bledevice.py new file mode 100644 index 00000000..fe9978e5 --- /dev/null +++ b/examples/connect_by_bledevice.py @@ -0,0 +1,24 @@ +""" +Connect by BLEDevice +""" + +import asyncio +import platform + +from bleak import BleakClient, BleakScanner + + +async def print_services(mac_addr: str): + device = await BleakScanner.find_device_by_address(mac_addr) + async with BleakClient(device) as client: + svcs = await client.get_services() + print("Services:", svcs) + + +mac_addr = ( + "24:71:89:cc:09:05" + if platform.system() != "Darwin" + else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" +) +loop = asyncio.get_event_loop() +loop.run_until_complete(print_services(mac_addr)) From 03e9a4ce128eb99e0d0c3a923188ddeb8a991dc9 Mon Sep 17 00:00:00 2001 From: Jack Jansen Date: Fri, 11 Sep 2020 14:24:17 +0200 Subject: [PATCH 03/21] Fixes for OSX two-callback discovery. Device name may not be known until second callback, and service UUIDs of the two callbacks must be combined. --- bleak/backends/corebluetooth/CentralManagerDelegate.py | 5 +++++ bleak/backends/corebluetooth/device.py | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index c09615b6..ba5c8db5 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -201,6 +201,8 @@ def did_discover_peripheral( RSSI: NSNumber, ): # Note: this function might be called several times for same device. + # This can happen for instance when an active scan is done, and the + # second call with contain the data from the BLE scan response. # Example a first time with the following keys in advertisementData: # ['kCBAdvDataLocalName', 'kCBAdvDataIsConnectable', 'kCBAdvDataChannel'] # ... and later a second time with other keys (and values) such as: @@ -216,6 +218,9 @@ def did_discover_peripheral( if uuid_string in self.devices: device = self.devices[uuid_string] + # It could be the device did not have a name previously but now it does. + if peripheral.name(): + device.name = peripheral.name() else: address = uuid_string name = peripheral.name() or None diff --git a/bleak/backends/corebluetooth/device.py b/bleak/backends/corebluetooth/device.py index 453c437b..a7e924cb 100644 --- a/bleak/backends/corebluetooth/device.py +++ b/bleak/backends/corebluetooth/device.py @@ -47,7 +47,13 @@ def _update_uuids(self, advertisementData: NSDictionary): if not cbuuids: return # converting to lower case to match other platforms - self.metadata["uuids"] = [str(u).lower() for u in cbuuids] + chuuids = [str(u).lower() for u in cbuuids] + if 'uuids' in self.metadata: + for uuid in chuuids: + if not uuid in self.metadata['uuids']: + self.metadata['uuids'].append(uuid) + else: + self.metadata["uuids"] = chuuids def _update_manufacturer(self, advertisementData: NSDictionary): mfg_bytes = advertisementData.get("kCBAdvDataManufacturerData") From 87423025fc792abf31f201f47ecadd72b336b282 Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Mon, 14 Sep 2020 12:13:56 +0200 Subject: [PATCH 04/21] Rename argument to address_to_ble_device --- CHANGELOG.rst | 2 +- bleak/backends/bluezdbus/client.py | 12 ++++++------ bleak/backends/client.py | 10 +++++----- bleak/backends/corebluetooth/client.py | 10 +++++----- bleak/backends/dotnet/client.py | 10 +++++----- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cda21bff..fbaac05d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -34,7 +34,7 @@ Changed * **BREAKING CHANGE** All notifications now have the characteristic's integer **handle** instead of its UUID as a string as the first argument ``sender`` sent to notification callbacks. This provides the uniqueness of sender in notifications as well. -* Renamed ``BleakClient`` argument ``address`` to ``address_or_device``. +* Renamed ``BleakClient`` argument ``address`` to ``address_or_ble_device``. * Version 0.5.0 of BleakUWPBridge, with some modified methods and implementing ``IDisposable``. * Merged #224. All storing and passing of event loops in bleak is removed. * Removed Objective C delegate compliance checks. Merged #253. diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 9be8a282..54dfc7b8 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -37,21 +37,21 @@ class BleakClientBlueZDBus(BaseBleakClient): Implemented by using the `BlueZ DBUS API `_. Args: - address_or_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. + 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. """ - def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): - super(BleakClientBlueZDBus, self).__init__(address_or_device, **kwargs) + def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + super(BleakClientBlueZDBus, self).__init__(address_or_ble_device, **kwargs) self.device = kwargs.get("device") if kwargs.get("device") else "hci0" - self.address = address_or_device + self.address = address_or_ble_device # Backend specific, TXDBus objects and data - if isinstance(address_or_device, BLEDevice): - self._device_path = address_or_device.details["path"] + if isinstance(address_or_ble_device, BLEDevice): + self._device_path = address_or_ble_device.details["path"] else: self._device_path = None self._bus = None diff --git a/bleak/backends/client.py b/bleak/backends/client.py index d75312c4..1ee2abf7 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -20,18 +20,18 @@ class BaseBleakClient(abc.ABC): The documentation of this interface should thus be safe to use as a reference for your implementation. Args: - address_or_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. + 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 ``discover`` call. Defaults to 2.0. """ - def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): - if isinstance(address_or_device, BLEDevice): - self.address = address_or_device.address + def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + if isinstance(address_or_ble_device, BLEDevice): + self.address = address_or_ble_device.address else: - self.address = address_or_device + self.address = address_or_ble_device self.services = BleakGATTServiceCollection() diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 83202c44..318854f3 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -35,17 +35,17 @@ class BleakClientCoreBluetooth(BaseBleakClient): """CoreBluetooth class interface for BleakClient Args: - address_or_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. + 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 ``discover`` call during connect. Defaults to 10.0. """ - def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): - super(BleakClientCoreBluetooth, self).__init__(address_or_device, **kwargs) + def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + super(BleakClientCoreBluetooth, self).__init__(address_or_ble_device, **kwargs) - if isinstance(address_or_device, BLEDevice): - self._device_info = address_or_device.details + if isinstance(address_or_ble_device, BLEDevice): + self._device_info = address_or_ble_device.details else: self._device_info = None diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index a32f5afe..2e7398c3 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -92,19 +92,19 @@ class BleakClientDotNet(BaseBleakClient): Common Language Runtime (CLR). Therefore, much of the code below has a distinct C# feel. Args: - address_or_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. + 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 ``discover`` call. Defaults to 2.0. """ - def __init__(self, address_or_device: Union[BLEDevice, str], **kwargs): - super(BleakClientDotNet, self).__init__(address_or_device, **kwargs) + def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + super(BleakClientDotNet, self).__init__(address_or_ble_device, **kwargs) # Backend specific. Python.NET objects. - if isinstance(address_or_device, BLEDevice): - self._device_info = address_or_device.details.BluetoothAddress + if isinstance(address_or_ble_device, BLEDevice): + self._device_info = address_or_ble_device.details.BluetoothAddress else: self._device_info = None self._requester = None From 07d07a045aaca88107724a657a49a6e875b5d77d Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Mon, 14 Sep 2020 15:31:39 +0200 Subject: [PATCH 05/21] README improvements --- README.rst | 3 +- bleak/backends/bluezdbus/client.py | 21 ++++--- bleak/backends/client.py | 2 +- .../corebluetooth/CentralManagerDelegate.py | 8 ++- bleak/backends/corebluetooth/client.py | 14 ++--- bleak/backends/corebluetooth/scanner.py | 11 ++++ bleak/backends/device.py | 18 +++--- bleak/backends/dotnet/client.py | 4 +- bleak/backends/dotnet/scanner.py | 60 +++++++++++-------- docs/api.rst | 51 ++++++++++++---- docs/conf.py | 26 +++++++- docs/index.rst | 2 +- docs/scanning.rst | 2 +- 13 files changed, 152 insertions(+), 70 deletions(-) diff --git a/README.rst b/README.rst index dfbcf46b..d765fdac 100644 --- a/README.rst +++ b/README.rst @@ -2,13 +2,12 @@ bleak ===== -.. image:: https://raw.githubusercontent.com/hbldh/bleak/master/Bleak_logo.png +.. figure:: https://raw.githubusercontent.com/hbldh/bleak/master/Bleak_logo.png :target: https://github.com/hbldh/bleak :alt: Bleak Logo :scale: 50% - .. image:: https://github.com/hbldh/bleak/workflows/Build%20and%20Test/badge.svg :target: https://github.com/hbldh/bleak/actions?query=workflow%3A%22Build+and+Test%22 :alt: Build and Test diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 4742ebaf..92f41284 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +""" +BLE Client for BlueZ on Linux +""" import logging import asyncio import os @@ -569,14 +572,16 @@ async def write_gatt_char( ) -> None: """Perform a write operation on the specified GATT characteristic. - NB: the version check below is for the "type" option to the - "Characteristic.WriteValue" method that was added to Bluez in 5.51 - https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit?id=fa9473bcc48417d69cc9ef81d41a72b18e34a55a - Before that commit, "Characteristic.WriteValue" was only "Write with - response". "Characteristic.AcquireWrite" was added in Bluez 5.46 - https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/doc/gatt-api.txt?id=f59f3dedb2c79a75e51a3a0d27e2ae06fefc603e - which can be used to "Write without response", but for older versions - of Bluez, it is not possible to "Write without response". + .. note:: + + The version check below is for the "type" option to the + "Characteristic.WriteValue" method that was added to `Bluez in 5.51 + `_ + Before that commit, ``Characteristic.WriteValue`` was only "Write with + response". ``Characteristic.AcquireWrite`` was `added in Bluez 5.46 + `_ + which can be used to "Write without response", but for older versions + of Bluez, it is not possible to "Write without response". Args: char_specifier (BleakGATTCharacteristicBlueZDBus, int, str or UUID): The characteristic to write diff --git a/bleak/backends/client.py b/bleak/backends/client.py index 9933aab0..72351581 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -20,7 +20,7 @@ class BaseBleakClient(abc.ABC): The documentation of this interface should thus be safe to use as a reference for your implementation. Keyword Args: - timeout (float): Timeout for required ``discover`` call. Defaults to 2.0. + timeout (float): Timeout for required ``discover`` call. Defaults to 10.0. """ diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index 62b37417..d80ade4f 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -41,8 +41,12 @@ CBCentralManagerDelegate = objc.protocolNamed("CBCentralManagerDelegate") -_mac_version = list(map(int, platform.mac_ver()[0].split("."))) -_IS_PRE_10_13 = _mac_version[0] == 10 and _mac_version[1] < 13 +try: + _mac_version = list(map(int, platform.mac_ver()[0].split("."))) + _IS_PRE_10_13 = _mac_version[0] == 10 and _mac_version[1] < 13 +except: # noqa For building docs + _mac_version = "" + _IS_PRE_10_13 = False class CMDConnectionState(Enum): diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 302fa840..9befd167 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -1,7 +1,7 @@ """ BLE Client for CoreBluetooth on macOS -Created on 2019-6-26 by kevincar +Created on 2019-06-26 by kevincar """ import logging @@ -20,7 +20,6 @@ BleakGATTCharacteristicCoreBluetooth, ) from bleak.backends.corebluetooth.descriptor import BleakGATTDescriptorCoreBluetooth -from bleak.backends.corebluetooth.discovery import discover from bleak.backends.corebluetooth.scanner import BleakScannerCoreBluetooth from bleak.backends.corebluetooth.service import BleakGATTServiceCoreBluetooth from bleak.backends.service import BleakGATTServiceCollection @@ -38,7 +37,7 @@ class BleakClientCoreBluetooth(BaseBleakClient): address (str): The uuid of the BLE peripheral to connect to. Keyword Args: - timeout (float): Timeout for required ``discover`` call during connect. Defaults to 10.0. + timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. """ def __init__(self, address: str, **kwargs): @@ -58,7 +57,7 @@ async def connect(self, **kwargs) -> bool: """Connect to a specified Peripheral Keyword Args: - timeout (float): Timeout for required ``discover`` call. Defaults to 10.0. + timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. Returns: Boolean representing connection status. @@ -132,10 +131,9 @@ async def pair(self, *args, **kwargs) -> bool: Reference: - - https://stackoverflow.com/questions/25254932/can-you-pair-a-bluetooth-le-device-in-an-ios-app - - https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/BestPracticesForSettingUpYourIOSDeviceAsAPeripheral/BestPracticesForSettingUpYourIOSDeviceAsAPeripheral.html#//apple_ref/doc/uid/TP40013257-CH5-SW1 - - https://stackoverflow.com/questions/47546690/ios-bluetooth-pairing-request-dialog-can-i-know-the-users-choice - + - `Apple Docs `_ + - `Stack Overflow post #1 `_ + - `Stack Overflow post #2 `_ Returns: Boolean regarding success of pairing. diff --git a/bleak/backends/corebluetooth/scanner.py b/bleak/backends/corebluetooth/scanner.py index 6951803b..d03e7842 100644 --- a/bleak/backends/corebluetooth/scanner.py +++ b/bleak/backends/corebluetooth/scanner.py @@ -62,6 +62,17 @@ async def stop(self): logger.warning("stopScan method could not be called: {0}".format(e)) async def set_scanning_filter(self, **kwargs): + """Set scanning filter for the scanner. + + .. note:: + + This is not implemented for macOS yet. + + Raises: + + ``NotImplementedError`` + + """ raise NotImplementedError( "Need to evaluate which macOS versions to support first..." ) diff --git a/bleak/backends/device.py b/bleak/backends/device.py index 275bd12f..9838ec17 100644 --- a/bleak/backends/device.py +++ b/bleak/backends/device.py @@ -14,20 +14,24 @@ class BLEDevice(object): a `discover` call. - When using Windows backend, `details` attribute is a - `Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisement` object, unless + ``Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisement`` object, unless it is created with the Windows.Devices.Enumeration discovery method, then is is a - `Windows.Devices.Enumeration.DeviceInformation` - - When using Linux backend, `details` attribute is a - dict with keys `path` which has the string path to the DBus device object and `props` + ``Windows.Devices.Enumeration.DeviceInformation``. + - When using Linux backend, ``details`` attribute is a + dict with keys ``path`` which has the string path to the DBus device object and ``props`` which houses the properties dictionary of the D-Bus Device. - - When using macOS backend, `details` attribute will be a CBPeripheral object + - When using macOS backend, ``details`` attribute will be a CBPeripheral object. """ def __init__(self, address, name, details=None, **kwargs): + #: The Bluetooth address of the device on this machine. self.address = address - self.name = name if name else "Unknown" + #: The advertised name of the device. + self.name= name if name else "Unknown" + #: The OS native details required for connecting to the device. self.details = details - self.metadata = kwargs + #: Device specific details. Contains a ``uuids`` key which is a list of service UUIDs and a ``manufacturer_data`` field with a bytes-object from the advertised data. + self.metadata= kwargs @property def rssi(self): diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index 11258aa5..c026b7e0 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -94,7 +94,7 @@ class BleakClientDotNet(BaseBleakClient): address (str): The Bluetooth address of the BLE peripheral to connect to. Keyword Args: - timeout (float): Timeout for required ``discover`` call. Defaults to 2.0. + timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. """ @@ -122,7 +122,7 @@ async def connect(self, **kwargs) -> bool: """Connect to the specified GATT server. Keyword Args: - timeout (float): Timeout for required ``find_device_by_address`` call. Defaults to maximally 10.0 seconds. + timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. Returns: Boolean representing connection status. diff --git a/bleak/backends/dotnet/scanner.py b/bleak/backends/dotnet/scanner.py index a63b3c32..92d77c85 100644 --- a/bleak/backends/dotnet/scanner.py +++ b/bleak/backends/dotnet/scanner.py @@ -41,17 +41,20 @@ def _format_event_args(e): class BleakScannerDotNet(BaseBleakScanner): """The native Windows Bleak BLE Scanner. - Implemented using `pythonnet `_, a package that provides an integration to the .NET - Common Language Runtime (CLR). Therefore, much of the code below has a distinct C# feel. + Implemented using `pythonnet `_, a package that provides an integration to + the .NET Common Language Runtime (CLR). Therefore, much of the code below has a distinct C# feel. Keyword Args: - scanning mode (str): Set to "Passive" to avoid the "Active" scanning mode. - SignalStrengthFilter (Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter): A - BluetoothSignalStrengthFilter object used for configuration of Bluetooth - LE advertisement filtering that uses signal strength-based filtering. - AdvertisementFilter (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter): A - BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE - advertisement filtering that uses payload section-based filtering. + + scanning mode (str): Set to ``Passive`` to avoid the ``Active`` scanning mode. + + SignalStrengthFilter (``Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter``): A + BluetoothSignalStrengthFilter object used for configuration of Bluetooth LE advertisement + filtering that uses signal strength-based filtering. + + AdvertisementFilter (``Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter``): A + BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE advertisement + filtering that uses payload section-based filtering. """ @@ -120,12 +123,12 @@ async def set_scanning_filter(self, **kwargs): """Set a scanning filter for the BleakScanner. Keyword Args: - SignalStrengthFilter (Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter): A - BluetoothSignalStrengthFilter object used for configuration of Bluetooth - LE advertisement filtering that uses signal strength-based filtering. - AdvertisementFilter (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter): A - BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE - advertisement filtering that uses payload section-based filtering. + SignalStrengthFilter (``Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter``): A + BluetoothSignalStrengthFilter object used for configuration of Bluetooth + LE advertisement filtering that uses signal strength-based filtering. + AdvertisementFilter (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter): A + BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE + advertisement filtering that uses payload section-based filtering. """ if "SignalStrengthFilter" in kwargs: @@ -176,8 +179,8 @@ def register_detection_callback(self, callback: Callable): Args: callback: Function accepting two arguments: - sender (Windows.Devices.Bluetooth.AdvertisementBluetoothLEAdvertisementWatcher) and - eventargs (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementReceivedEventArgs) + sender (``Windows.Devices.Bluetooth.AdvertisementBluetoothLEAdvertisementWatcher``) and + eventargs (``Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementReceivedEventArgs``) """ self._callback = callback @@ -215,19 +218,26 @@ async def find_device_by_address( """A convenience method for obtaining a ``BLEDevice`` object specified by Bluetooth address. Args: + device_identifier (str): The Bluetooth address of the Bluetooth peripheral. - timeout (float): Optional timeout to wait for detection of specified peripheral before giving up. Defaults to 10.0 seconds. + + timeout (float): Optional timeout to wait for detection of specified peripheral + before giving up. Defaults to 10.0 seconds. Keyword Args: - scanning mode (str): Set to "Passive" to avoid the "Active" scanning mode. - SignalStrengthFilter (Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter): A - BluetoothSignalStrengthFilter object used for configuration of Bluetooth - LE advertisement filtering that uses signal strength-based filtering. - AdvertisementFilter (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter): A - BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE - advertisement filtering that uses payload section-based filtering. + + scanning mode (str): Set to ``Passive`` to avoid the ``Active`` scanning mode. + + SignalStrengthFilter (``Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter``): A + BluetoothSignalStrengthFilter object used for configuration of Bluetooth LE advertisement + filtering that uses signal strength-based filtering. + + AdvertisementFilter (``Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter``): A + BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE + advertisement filtering that uses payload section-based filtering. Returns: + The ``BLEDevice`` sought or ``None`` if not detected. """ diff --git a/docs/api.rst b/docs/api.rst index 8dbbdb47..8bfa4fd2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,28 +1,59 @@ Interfaces, exceptions and utils ================================ -Connection Client Interface ---------------------------- +Connection Clients +------------------ -.. automodule:: bleak.backends.client +Windows +~~~~~~~ + +.. automodule:: bleak.backends.dotnet.client + :members: + +macOS +~~~~~ + +.. automodule:: bleak.backends.corebluetooth.client :members: -Scanning Client Interface -------------------------- +Linux Distributions with BlueZ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: bleak.backends.bluezdbus.client + :members: + +Scanning Clients +---------------- + +Windows +~~~~~~~ -.. automodule:: bleak.backends.scanner +.. automodule:: bleak.backends.dotnet.scanner :members: -Interface for BLE devices -------------------------- +macOS +~~~~~ + +.. automodule:: bleak.backends.corebluetooth.scanner + :members: + +Linux Distributions with BlueZ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: bleak.backends.bluezdbus.scanner + :members: + + +Class representing BLE devices +------------------------------ Generated by :py:meth:`bleak.discover` and :py:class:`bleak.backends.scanning.BaseBleakScanner`. .. automodule:: bleak.backends.device :members: -Interfaces for GATT objects ---------------------------- +GATT objects +------------ .. automodule:: bleak.backends.service :members: diff --git a/docs/conf.py b/docs/conf.py index 37ed91f0..451b463a 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,26 @@ # All configuration values have a default; values that are commented out # serve to show the default. + +windows_autodoc_mock_import = ["clr", "Windows", "System"] +linux_autodoc_mock_import = [ + "twisted", + "txdbus", +] +macos_autodoc_mock_import = [ + "objc", + "Foundation", + "CoreBluetooth", + "libdispatch", +] +autodoc_mock_imports = list( + set( + windows_autodoc_mock_import + + macos_autodoc_mock_import + + linux_autodoc_mock_import + ) +) + import sys import os @@ -40,8 +60,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] - +extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -56,7 +75,7 @@ # General information about the project. project = u"bleak" -copyright = u"2018, Henrik Blidh" +copyright = u"2020, Henrik Blidh" # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout @@ -112,6 +131,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "default" +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a # theme further. For a list of options available for each theme, see the diff --git a/docs/index.rst b/docs/index.rst index 318d3054..989a49ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ bleak ===== -.. image:: https://raw.githubusercontent.com/hbldh/bleak/master/Bleak_logo.png +.. figure:: https://raw.githubusercontent.com/hbldh/bleak/master/Bleak_logo.png :target: https://github.com/hbldh/bleak :alt: Bleak Logo :width: 50% diff --git a/docs/scanning.rst b/docs/scanning.rst index 036ad602..dc3dff80 100644 --- a/docs/scanning.rst +++ b/docs/scanning.rst @@ -128,6 +128,6 @@ To be written. In the meantime, check Scanning filter examples in Core Bluetooth backend -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To be implemented. Exists in a draft in `PR #209 `_. From 0520e9df721042573a1cc6218cad7c3a55c9efb7 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 13:13:03 -0500 Subject: [PATCH 06/21] fix spelling errors in CHANGELOG --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e147458c..eeaa620a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -26,7 +26,7 @@ Added * Implemented pairing method in .NET backend. * Implemented pairing method in the BlueZ backend. * Added stumps and ``NotImplementedError`` on pairing in macOS backend. -* Added the possibility to connect using ``BLEDevice`` instead of a string adress. This +* Added the possibility to connect using ``BLEDevice`` instead of a string address. This allows for skipping the discovery call when connecting. Changed @@ -59,7 +59,7 @@ Fixed Changed ~~~~~~~ -* Improved, more explantory error on BlueZ backend when ``BleakClient`` cannot find the desired device when trying to connect. (#238) +* Improved, more explanatory error on BlueZ backend when ``BleakClient`` cannot find the desired device when trying to connect. (#238) * Better-than-nothing documentation about scanning filters added (#230). * Ran black on code which was forgotten in 0.7.0. Large diffs due to that. * Re-adding Python 3.8 CI "tests" on Windows again. From 8264517d26b3806592f56dbbf65f3fc90b81204b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 13:16:42 -0500 Subject: [PATCH 07/21] dev version bump --- bleak/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bleak/__version__.py b/bleak/__version__.py index d7743ec4..84030da1 100644 --- a/bleak/__version__.py +++ b/bleak/__version__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = "0.8.0" +__version__ = "0.8.1a1" From 430ae9e9d67aa34f44b11b9d91097b13a9486ab5 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 12:51:30 -0500 Subject: [PATCH 08/21] Format with black Some commits were made without proper formatting. Other changes are due to breaking changes in black v20. A task is added in CI to ensure that future commits are properly formatted according to black. --- .github/workflows/build_and_test.yml | 6 +- bleak/backends/bluezdbus/client.py | 7 +- bleak/backends/bluezdbus/scanner.py | 13 +- bleak/backends/bluezdbus/utils.py | 24 ++-- bleak/backends/characteristic.py | 4 +- bleak/backends/client.py | 7 +- .../corebluetooth/CentralManagerDelegate.py | 27 +++- .../corebluetooth/PeripheralDelegate.py | 30 ++-- bleak/backends/corebluetooth/client.py | 30 ++-- bleak/backends/corebluetooth/device.py | 6 +- bleak/backends/corebluetooth/scanner.py | 4 +- bleak/backends/device.py | 4 +- bleak/backends/dotnet/client.py | 31 +++-- bleak/backends/dotnet/discovery.py | 8 +- bleak/backends/dotnet/scanner.py | 4 +- bleak/backends/scanner.py | 9 +- bleak/backends/service.py | 4 +- bleak/uuids.py | 130 +++++++++--------- examples/sensortag.py | 4 +- requirements_dev.txt | 1 + 20 files changed, 212 insertions(+), 141 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bc4bac3c..8442a09e 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -32,12 +32,14 @@ jobs: run: pip install -r requirements.txt - name: Install develoment dependencies run: pip install -r requirements_dev.txt + - name: Check code formatting with black + run: black . --diff - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # exit-zero treats all errors as warnings. Default line length of black is 88 + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics - name: Test with pytest run: | pytest tests --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml --cov=com --cov-report=xml --cov-report=html diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 5b80d9a0..5fff533d 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -119,7 +119,8 @@ async def connect(self, **kwargs) -> bool: if self._device_path is None: timeout = kwargs.get("timeout", self._timeout) device = await BleakScannerBlueZDBus.find_device_by_address( - self.address, timeout=timeout, device=self.device) + self.address, timeout=timeout, device=self.device + ) if device: self._device_path = device.details["path"] @@ -349,7 +350,9 @@ async def unpair(self) -> bool: Boolean regarding success of unpairing. """ - warnings.warn("Unpairing is seemingly unavailable in the BlueZ DBus API at the moment.") + warnings.warn( + "Unpairing is seemingly unavailable in the BlueZ DBus API at the moment." + ) return False async def is_connected(self) -> bool: diff --git a/bleak/backends/bluezdbus/scanner.py b/bleak/backends/bluezdbus/scanner.py index 2a333b95..01d2e46d 100644 --- a/bleak/backends/bluezdbus/scanner.py +++ b/bleak/backends/bluezdbus/scanner.py @@ -217,7 +217,9 @@ def register_detection_callback(self, callback: Callable): self._callback = callback @classmethod - async def find_device_by_address(cls, device_identifier: str, timeout: float = 10.0, **kwargs) -> BLEDevice: + async def find_device_by_address( + cls, device_identifier: str, timeout: float = 10.0, **kwargs + ) -> BLEDevice: """A convenience method for obtaining a ``BLEDevice`` object specified by Bluetooth address. Args: @@ -237,10 +239,15 @@ async def find_device_by_address(cls, device_identifier: str, timeout: float = 1 scanner = cls(timeout=timeout) def stop_if_detected(message): - if any(device.get("Address", "").lower() == device_identifier for device in scanner._devices.values()): + if any( + device.get("Address", "").lower() == device_identifier + for device in scanner._devices.values() + ): loop.call_soon_threadsafe(stop_scanning_event.set) - return await scanner._find_device_by_address(device_identifier, stop_scanning_event, stop_if_detected, timeout) + return await scanner._find_device_by_address( + device_identifier, stop_scanning_event, stop_if_detected, timeout + ) # Helper methods diff --git a/bleak/backends/bluezdbus/utils.py b/bleak/backends/bluezdbus/utils.py index ab985f1a..997f3583 100644 --- a/bleak/backends/bluezdbus/utils.py +++ b/bleak/backends/bluezdbus/utils.py @@ -50,21 +50,21 @@ def get_device_object_path(hci_device, address): def get_gatt_service_path(hci_device, address, service_id): """Get object path for a GATT Service for a Bluetooth device. - Service org.bluez - Service org.bluez - Interface org.bluez.GattService1 - Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX + Service org.bluez + Service org.bluez + Interface org.bluez.GattService1 + Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX - Args: - hci_device (str): Which bluetooth adapter to connect with. - address (str): The Bluetooth address of the bluetooth device. - service_id (int): + Args: + hci_device (str): Which bluetooth adapter to connect with. + address (str): The Bluetooth address of the bluetooth device. + service_id (int): - Returns: - String representation of GATT service object path on format - `/org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX`. + Returns: + String representation of GATT service object path on format + `/org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX`. - """ + """ base = get_device_object_path(hci_device, address) return base + "{0}/service{1:02d}".format(base, service_id) diff --git a/bleak/backends/characteristic.py b/bleak/backends/characteristic.py index fb3c81c5..d7bfc230 100644 --- a/bleak/backends/characteristic.py +++ b/bleak/backends/characteristic.py @@ -27,9 +27,7 @@ class GattCharacteristicsFlags(enum.Enum): class BleakGATTCharacteristic(abc.ABC): - """Interface for the Bleak representation of a GATT Characteristic - - """ + """Interface for the Bleak representation of a GATT Characteristic""" def __init__(self, obj: Any): self.obj = obj diff --git a/bleak/backends/client.py b/bleak/backends/client.py index 37e6eb7c..ed43949c 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -14,6 +14,7 @@ from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.backends.device import BLEDevice + class BaseBleakClient(abc.ABC): """The Client Interface for Bleak Backend implementations to implement. @@ -44,7 +45,11 @@ def __str__(self): return "{0}, {1}".format(self.__class__.__name__, self.address) def __repr__(self): - return "<{0}, {1}, {2}>".format(self.__class__.__name__, self.address, super(BaseBleakClient, self).__repr__()) + return "<{0}, {1}, {2}>".format( + self.__class__.__name__, + self.address, + super(BaseBleakClient, self).__repr__(), + ) # Async Context managers diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index 45671165..20cb4a3b 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -187,7 +187,8 @@ def did_update_state(self, centralManager): def centralManagerDidUpdateState_(self, centralManager): logger.debug("centralManagerDidUpdateState_") self.event_loop.call_soon_threadsafe( - self.did_update_state, centralManager, + self.did_update_state, + centralManager, ) @objc.python_method @@ -248,7 +249,11 @@ def centralManager_didDiscoverPeripheral_advertisementData_RSSI_( ): logger.debug("centralManager_didDiscoverPeripheral_advertisementData_RSSI_") self.event_loop.call_soon_threadsafe( - self.did_discover_peripheral, central, peripheral, advertisementData, RSSI, + self.did_discover_peripheral, + central, + peripheral, + advertisementData, + RSSI, ) @objc.python_method @@ -259,14 +264,18 @@ def did_connect_peripheral(self, central, peripheral): ) ) if self._connection_state != CMDConnectionState.CONNECTED: - peripheralDelegate = PeripheralDelegate.alloc().initWithPeripheral_(peripheral) + peripheralDelegate = PeripheralDelegate.alloc().initWithPeripheral_( + peripheral + ) self.connected_peripheral_delegate = peripheralDelegate self._connection_state = CMDConnectionState.CONNECTED def centralManager_didConnectPeripheral_(self, central, peripheral): logger.debug("centralManager_didConnectPeripheral_") self.event_loop.call_soon_threadsafe( - self.did_connect_peripheral, central, peripheral, + self.did_connect_peripheral, + central, + peripheral, ) @objc.python_method @@ -285,7 +294,10 @@ def centralManager_didFailToConnectPeripheral_error_( ): logger.debug("centralManager_didFailToConnectPeripheral_error_") self.event_loop.call_soon_threadsafe( - self.did_fail_to_connect_peripheral, centralManager, peripheral, error, + self.did_fail_to_connect_peripheral, + centralManager, + peripheral, + error, ) @objc.python_method @@ -305,7 +317,10 @@ def centralManager_didDisconnectPeripheral_error_( ): logger.debug("centralManager_didDisconnectPeripheral_error_") self.event_loop.call_soon_threadsafe( - self.did_disconnect_peripheral, central, peripheral, error, + self.did_disconnect_peripheral, + central, + peripheral, + error, ) diff --git a/bleak/backends/corebluetooth/PeripheralDelegate.py b/bleak/backends/corebluetooth/PeripheralDelegate.py index a347b463..bcd03e0c 100644 --- a/bleak/backends/corebluetooth/PeripheralDelegate.py +++ b/bleak/backends/corebluetooth/PeripheralDelegate.py @@ -35,7 +35,7 @@ class _EventDict(dict): def get_cleared(self, xUUID) -> asyncio.Event: - """ Convenience method. + """Convenience method. Returns a cleared (False) event. Creates it if doesn't exits. """ if xUUID not in self: @@ -128,9 +128,9 @@ async def readCharacteristic_( self.peripheral.readValueForCharacteristic_(characteristic) await asyncio.wait_for(event.wait(), timeout=5) if characteristic.value(): - return characteristic.value() + return characteristic.value() else: - return b'' + return b"" async def readDescriptor_( self, descriptor: CBDescriptor, use_cached=True @@ -219,7 +219,9 @@ def peripheral_didDiscoverServices_( ) -> None: logger.debug("peripheral_didDiscoverServices_") self._event_loop.call_soon_threadsafe( - self.did_discover_services, peripheral, error, + self.did_discover_services, + peripheral, + error, ) @objc.python_method @@ -244,7 +246,10 @@ def peripheral_didDiscoverCharacteristicsForService_error_( ): logger.debug("peripheral_didDiscoverCharacteristicsForService_error_") self._event_loop.call_soon_threadsafe( - self.did_discover_characteristics_for_service, peripheral, service, error, + self.did_discover_characteristics_for_service, + peripheral, + service, + error, ) @objc.python_method @@ -336,7 +341,10 @@ def peripheral_didUpdateValueForDescriptor_error_( ): logger.debug("peripheral_didUpdateValueForDescriptor_error_") self._event_loop.call_soon_threadsafe( - self.did_update_value_for_descriptor, peripheral, descriptor, error, + self.did_update_value_for_descriptor, + peripheral, + descriptor, + error, ) @objc.python_method @@ -362,7 +370,10 @@ def peripheral_didWriteValueForCharacteristic_error_( ): logger.debug("peripheral_didWriteValueForCharacteristic_error_") self._event_loop.call_soon_threadsafe( - self.did_write_value_for_characteristic, peripheral, characteristic, error, + self.did_write_value_for_characteristic, + peripheral, + characteristic, + error, ) @objc.python_method @@ -385,7 +396,10 @@ def peripheral_didWriteValueForDescriptor_error_( ): logger.debug("peripheral_didWriteValueForDescriptor_error_") self._event_loop.call_soon_threadsafe( - self.did_write_value_for_descriptor, peripheral, descriptor, error, + self.did_write_value_for_descriptor, + peripheral, + descriptor, + error, ) @objc.python_method diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index f1d2c9d2..e02a4e6d 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -41,6 +41,7 @@ class BleakClientCoreBluetooth(BaseBleakClient): timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. """ + def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): super(BleakClientCoreBluetooth, self).__init__(address_or_ble_device, **kwargs) @@ -72,7 +73,8 @@ async def connect(self, **kwargs) -> bool: if self._device_info is None: timeout = kwargs.get("timeout", self._timeout) device = await BleakScannerCoreBluetooth.find_device_by_address( - self.address, timeout=timeout) + self.address, timeout=timeout + ) if device: self._device_info = device.details @@ -175,8 +177,10 @@ async def get_services(self) -> BleakGATTServiceCollection: logger.debug( "Retrieving characteristics for service {}".format(serviceUUID) ) - characteristics = await manager.connected_peripheral_delegate.discoverCharacteristics_( - service + characteristics = ( + await manager.connected_peripheral_delegate.discoverCharacteristics_( + service + ) ) self.services.add_service(BleakGATTServiceCoreBluetooth(service)) @@ -186,8 +190,10 @@ async def get_services(self) -> BleakGATTServiceCollection: logger.debug( "Retrieving descriptors for characteristic {}".format(cUUID) ) - descriptors = await manager.connected_peripheral_delegate.discoverDescriptors_( - characteristic + descriptors = ( + await manager.connected_peripheral_delegate.discoverDescriptors_( + characteristic + ) ) self.services.add_characteristic( @@ -298,12 +304,14 @@ async def write_gatt_char( raise BleakError("Characteristic {} was not found!".format(char_specifier)) value = NSData.alloc().initWithBytes_length_(data, len(data)) - success = await manager.connected_peripheral_delegate.writeCharacteristic_value_type_( - characteristic.obj, - value, - CBCharacteristicWriteWithResponse - if response - else CBCharacteristicWriteWithoutResponse, + success = ( + await manager.connected_peripheral_delegate.writeCharacteristic_value_type_( + characteristic.obj, + value, + CBCharacteristicWriteWithResponse + if response + else CBCharacteristicWriteWithoutResponse, + ) ) if success: logger.debug( diff --git a/bleak/backends/corebluetooth/device.py b/bleak/backends/corebluetooth/device.py index a7e924cb..2e0e022e 100644 --- a/bleak/backends/corebluetooth/device.py +++ b/bleak/backends/corebluetooth/device.py @@ -48,10 +48,10 @@ def _update_uuids(self, advertisementData: NSDictionary): return # converting to lower case to match other platforms chuuids = [str(u).lower() for u in cbuuids] - if 'uuids' in self.metadata: + if "uuids" in self.metadata: for uuid in chuuids: - if not uuid in self.metadata['uuids']: - self.metadata['uuids'].append(uuid) + if not uuid in self.metadata["uuids"]: + self.metadata["uuids"].append(uuid) else: self.metadata["uuids"] = chuuids diff --git a/bleak/backends/corebluetooth/scanner.py b/bleak/backends/corebluetooth/scanner.py index d03e7842..9faf3841 100644 --- a/bleak/backends/corebluetooth/scanner.py +++ b/bleak/backends/corebluetooth/scanner.py @@ -153,7 +153,9 @@ def stop_if_detected(peripheral, advertisement_data, rssi): if str(peripheral.identifier().UUIDString()).lower() == device_identifier: loop.call_soon_threadsafe(stop_scanning_event.set) - return await scanner._find_device_by_address(device_identifier, stop_scanning_event, stop_if_detected, timeout) + return await scanner._find_device_by_address( + device_identifier, stop_scanning_event, stop_if_detected, timeout + ) # macOS specific methods diff --git a/bleak/backends/device.py b/bleak/backends/device.py index 9838ec17..bdd45a1e 100644 --- a/bleak/backends/device.py +++ b/bleak/backends/device.py @@ -27,11 +27,11 @@ def __init__(self, address, name, details=None, **kwargs): #: The Bluetooth address of the device on this machine. self.address = address #: The advertised name of the device. - self.name= name if name else "Unknown" + self.name = name if name else "Unknown" #: The OS native details required for connecting to the device. self.details = details #: Device specific details. Contains a ``uuids`` key which is a list of service UUIDs and a ``manufacturer_data`` field with a bytes-object from the advertised data. - self.metadata= kwargs + self.metadata = kwargs @property def rssi(self): diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index 8424e9c4..605b8643 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -139,7 +139,8 @@ async def connect(self, **kwargs) -> bool: if self._device_info is not None: timeout = kwargs.get("timeout", self._timeout) device = await BleakScannerDotNet.find_device_by_address( - self.address, timeout=timeout) + self.address, timeout=timeout + ) if device: self._device_info = device.details.BluetoothAddress @@ -381,7 +382,9 @@ async def get_services(self) -> BleakGATTServiceCollection: "Could not get GATT services: {0} (Error: 0x{1:02X}: {2})".format( _communication_statues.get(services_result.Status, ""), services_result.ProtocolError, - CONTROLLER_ERROR_CODES.get(services_result.ProtocolError, "Unknown") + CONTROLLER_ERROR_CODES.get( + services_result.ProtocolError, "Unknown" + ), ) ) else: @@ -411,7 +414,9 @@ async def get_services(self) -> BleakGATTServiceCollection: characteristics_result.Status, "" ), characteristics_result.ProtocolError, - CONTROLLER_ERROR_CODES.get(characteristics_result.ProtocolError, "Unknown") + CONTROLLER_ERROR_CODES.get( + characteristics_result.ProtocolError, "Unknown" + ), ) ) else: @@ -446,8 +451,8 @@ async def get_services(self) -> BleakGATTServiceCollection: ), descriptors_result.ProtocolError, CONTROLLER_ERROR_CODES.get( - descriptors_result.ProtocolError, - "Unknown") + descriptors_result.ProtocolError, "Unknown" + ), ) ) else: @@ -527,8 +532,8 @@ async def read_gatt_char( _communication_statues.get(read_result.Status, ""), read_result.ProtocolError, CONTROLLER_ERROR_CODES.get( - read_result.ProtocolError, - "Unknown") + read_result.ProtocolError, "Unknown" + ), ) ) else: @@ -583,8 +588,8 @@ async def read_gatt_descriptor( _communication_statues.get(read_result.Status, ""), read_result.ProtocolError, CONTROLLER_ERROR_CODES.get( - read_result.ProtocolError, - "Unknown") + read_result.ProtocolError, "Unknown" + ), ) ) else: @@ -648,8 +653,8 @@ async def write_gatt_char( _communication_statues.get(write_result.Status, ""), write_result.ProtocolError, CONTROLLER_ERROR_CODES.get( - write_result.ProtocolError, - "Unknown") + write_result.ProtocolError, "Unknown" + ), ) ) else: @@ -692,8 +697,8 @@ async def write_gatt_descriptor(self, handle: int, data: bytearray) -> None: _communication_statues.get(write_result.Status, ""), write_result.ProtocolError, CONTROLLER_ERROR_CODES.get( - write_result.ProtocolError, - "Unknown") + write_result.ProtocolError, "Unknown" + ), ) ) else: diff --git a/bleak/backends/dotnet/discovery.py b/bleak/backends/dotnet/discovery.py index 5a105335..b2310427 100644 --- a/bleak/backends/dotnet/discovery.py +++ b/bleak/backends/dotnet/discovery.py @@ -126,7 +126,13 @@ def AdvertisementWatcher_Stopped(sender, e): if not local_name and d.BluetoothAddress in scan_responses: local_name = scan_responses[d.BluetoothAddress].Advertisement.LocalName found.append( - BLEDevice(bdaddr, local_name, d, uuids=uuids, manufacturer_data=data,) + BLEDevice( + bdaddr, + local_name, + d, + uuids=uuids, + manufacturer_data=data, + ) ) return found diff --git a/bleak/backends/dotnet/scanner.py b/bleak/backends/dotnet/scanner.py index 92d77c85..44835c91 100644 --- a/bleak/backends/dotnet/scanner.py +++ b/bleak/backends/dotnet/scanner.py @@ -251,4 +251,6 @@ def stop_if_detected(sender, event_args): if event_args.BluetoothAddress == ulong_id: loop.call_soon_threadsafe(stop_scanning_event.set) - return await scanner._find_device_by_address(device_identifier, stop_scanning_event, stop_if_detected, timeout) + return await scanner._find_device_by_address( + device_identifier, stop_scanning_event, stop_if_detected, timeout + ) diff --git a/bleak/backends/scanner.py b/bleak/backends/scanner.py index 8bde1f6e..cf77b3bf 100644 --- a/bleak/backends/scanner.py +++ b/bleak/backends/scanner.py @@ -74,7 +74,9 @@ async def get_discovered_devices(self) -> List[BLEDevice]: @classmethod @abc.abstractmethod - async def find_device_by_address(cls, device_identifier: str, timeout: float = 10.0) -> BLEDevice: + async def find_device_by_address( + cls, device_identifier: str, timeout: float = 10.0 + ) -> BLEDevice: """A convenience method for obtaining a ``BLEDevice`` object specified by Bluetooth address or (macOS) UUID address. Args: @@ -87,7 +89,9 @@ async def find_device_by_address(cls, device_identifier: str, timeout: float = 1 """ raise NotImplementedError() - async def _find_device_by_address(self, device_identifier, stop_scanning_event, stop_if_detected_callback, timeout): + async def _find_device_by_address( + self, device_identifier, stop_scanning_event, stop_if_detected_callback, timeout + ): """Internal method for performing find by address work.""" self.register_detection_callback(stop_if_detected_callback) @@ -107,4 +111,3 @@ async def _find_device_by_address(self, device_identifier, stop_scanning_event, await self.stop() return device - diff --git a/bleak/backends/service.py b/bleak/backends/service.py index 79c678aa..1865ff71 100644 --- a/bleak/backends/service.py +++ b/bleak/backends/service.py @@ -147,8 +147,8 @@ def get_characteristic( def add_descriptor(self, descriptor: BleakGATTDescriptor): """Add a :py:class:`~BleakGATTDescriptor` to the service collection. - Should not be used by end user, but rather by `bleak` itself. - """ + Should not be used by end user, but rather by `bleak` itself. + """ if descriptor.handle not in self.__descriptors: self.__descriptors[descriptor.handle] = descriptor self.__characteristics[descriptor.characteristic_handle].add_descriptor( diff --git a/bleak/uuids.py b/bleak/uuids.py index 8d800379..0e02bdaf 100644 --- a/bleak/uuids.py +++ b/bleak/uuids.py @@ -636,71 +636,71 @@ 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', + 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", } uuid128_dict = { diff --git a/examples/sensortag.py b/examples/sensortag.py index b8472ec5..32982313 100644 --- a/examples/sensortag.py +++ b/examples/sensortag.py @@ -87,7 +87,7 @@ BATTERY_LEVEL_UUID = "0000{0:x}-0000-1000-8000-00805f9b34fb".format( uuid16_dict.get("Battery Level") ) -KEY_PRESS_UUID = "0000{0:x}-0000-1000-8000-00805f9b34fb".format(0xffe1) +KEY_PRESS_UUID = "0000{0:x}-0000-1000-8000-00805f9b34fb".format(0xFFE1) # I/O test points on SensorTag. IO_DATA_CHAR_UUID = "f000aa65-0451-4000-b000-000000000000" IO_CONFIG_CHAR_UUID = "f000aa66-0451-4000-b000-000000000000" @@ -141,7 +141,7 @@ async def run(address, debug=False): def keypress_handler(sender, data): print("{0}: {1}".format(sender, data)) - write_value = bytearray([0xa0]) + write_value = bytearray([0xA0]) value = await client.read_gatt_char(IO_DATA_CHAR_UUID) print("I/O Data Pre-Write Value: {0}".format(value)) diff --git a/requirements_dev.txt b/requirements_dev.txt index 59ed53cd..789fe38a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,6 +2,7 @@ pip>=18.0 bump2version==1.0.0 wheel>=0.32.2 watchdog>=0.8.3 +black>=20.8b1 flake8>=3.5.0 tox>=3.1.3 coverage>=4.5.1 From 2dcfbb01e6d376fb180832541219d9d6f73da3d8 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 13:01:50 -0500 Subject: [PATCH 09/21] Drop Python 3.5 support. Python 3.5 reached end of life on September 13 2020. --- .github/workflows/build_and_test.yml | 5 +---- CHANGELOG.rst | 7 +++++++ CONTRIBUTING.rst | 2 +- azure-pipelines.yml | 8 -------- setup.py | 1 - 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 8442a09e..16008896 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -13,10 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: [3.5, 3.6, 3.7, 3.8] - exclude: - - os: macos-latest - python-version: 3.5 + python-version: [3.6, 3.7, 3.8] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eeaa620a..a89e0371 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_, and this project adheres to `Semantic Versioning `_. +`Unreleased`_ +------------- + +Removed +~~~~~~~ +Support for Python 3.5. + `0.8.0`_ (2020-09-02) --------------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d58c1e95..82e558b0 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -102,7 +102,7 @@ Before you submit a pull request, check that it meets these guidelines: 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. -3. The pull request should work for Python 3.5+ on the following platforms: +3. The pull request should work for Python 3.6+ on the following platforms: - Windows 10, version 16299 (Fall Creators Update) and greater - Linux distributions with BlueZ >= 5.43 - OS X / macOS >= 10.11 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6e8b752e..d5203fba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,8 +13,6 @@ jobs: vmImage: 'Ubuntu 16.04' strategy: matrix: - Python35-x64: - python.version: '3.5' python.architecture: 'x64' Python36-x64: python.version: '3.6' @@ -53,8 +51,6 @@ jobs: vmImage: 'windows-2019' strategy: matrix: - Python35-x64: - python.version: '3.5' python.architecture: 'x64' Python36-x64: python.version: '3.6' @@ -92,8 +88,6 @@ jobs: strategy: matrix: - Python35-x64: - python.version: '3.5' python.architecture: 'x64' Python36-x64: python.version: '3.6' @@ -131,8 +125,6 @@ jobs: strategy: matrix: - Python35-x64: - python.version: '3.5' python.architecture: 'x64' Python36-x64: python.version: '3.6' diff --git a/setup.py b/setup.py index 9dae2bd3..adcb0a1e 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,6 @@ def run(self): "Operating System :: MacOS :: MacOS X", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", From ac872c153374a39264d4f3bb63cdd3d36f92c724 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 14:00:31 -0500 Subject: [PATCH 10/21] Fix device_info logic in dotnet client The logic was inverted. If a device was given in the BleakClient constructor, then the _device_info attribute is set to the Bluetooth address, otherwise it is set to None. So we should only trying scanning for the device if _device_info is None. --- bleak/backends/dotnet/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index 8424e9c4..eaf4cb0c 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -136,7 +136,7 @@ async def connect(self, **kwargs) -> bool: self._bridge = Bridge() # Try to find the desired device. - if self._device_info is not None: + if self._device_info is None: timeout = kwargs.get("timeout", self._timeout) device = await BleakScannerDotNet.find_device_by_address( self.address, timeout=timeout) From 67d38c2879dc60b3137743c4e4284d9050a3fada Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 15:35:41 -0500 Subject: [PATCH 11/21] rename _disconnected_callback_client Rename _disconnect_callback_client to _disconnected_callback_client in the CoreBluetooth backend to be consistent with the name of "disconnected_callback" that is used everywhere else. --- bleak/backends/corebluetooth/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index f1d2c9d2..3d6e171f 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -85,7 +85,7 @@ async def connect(self, **kwargs) -> bool: manager = self._device_info.manager().delegate() await manager.connect_(self._device_info) - manager.disconnected_callback = self._disconnect_callback_client + manager.disconnected_callback = self._disconnected_callback_client # Now get services await self.get_services() @@ -117,7 +117,7 @@ def set_disconnected_callback( """ self._disconnected_callback = callback - def _disconnect_callback_client(self): + def _disconnected_callback_client(self): """ Callback for device disconnection. Bleak callback sends one argument as client. This is wrapper function that gets called from the CentralManager and call actual disconnected_callback by sending client as argument From a58467855da32c598b3df8a86c23aadafe395ca3 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 15:51:05 -0500 Subject: [PATCH 12/21] Implement set_disconnected_callback on Windows The logger.debug() statement in _ConnectionStatusChanged_Handler() was causing a silent failure that cause the rest of that function to not run. After fixing that, we can now properly implement the callback when a device is disconnected. --- CHANGELOG.rst | 8 ++++++++ bleak/backends/dotnet/client.py | 27 +++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eeaa620a..66cf3e9e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_, and this project adheres to `Semantic Versioning `_. +`Unreleased`_ +------------- + +Added +~~~~~ + +* Implemented ``set_disconnected_callback`` in the .NET backend ``BleakClient`` implementation. + `0.8.0`_ (2020-09-02) --------------------- diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index eaf4cb0c..4923ce85 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -109,6 +109,7 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._device_info = None self._requester = None self._bridge = None + self._disconnected_callback = None self._address_type = ( kwargs["address_type"] @@ -164,8 +165,17 @@ async def connect(self, **kwargs) -> bool: return_type=BluetoothLEDevice, ) + loop = asyncio.get_event_loop() + def _ConnectionStatusChanged_Handler(sender, args): - logger.debug("_ConnectionStatusChanged_Handler: " + args.ToString()) + logger.debug( + "_ConnectionStatusChanged_Handler: %d", sender.ConnectionStatus + ) + if ( + sender.ConnectionStatus == BluetoothConnectionStatus.Disconnected + and self._disconnected_callback + ): + loop.call_soon_threadsafe(self._disconnected_callback, self) self._requester.ConnectionStatusChanged += _ConnectionStatusChanged_Handler @@ -246,15 +256,24 @@ async def is_connected(self) -> bool: def set_disconnected_callback( self, callback: Callable[[BaseBleakClient], None], **kwargs ) -> None: - """Set the disconnected callback. + """Set the disconnect callback. + The callback will only be called on unsolicited disconnect event. + + Callbacks must accept one input which is the client object itself. + + .. code-block:: python + + def callback(client): + print("Client with address {} got disconnected!".format(client.address)) - N.B. This is not implemented in the .NET backend yet. + client.set_disconnected_callback(callback) + client.connect() Args: callback: callback to be called on disconnection. """ - raise NotImplementedError("This is not implemented in the .NET backend yet") + self._disconnected_callback = callback async def pair(self, protection_level=None, **kwargs) -> bool: """Attempts to pair with the device. From 4d74549921e5f49527bc5e8ab8c73e07e458c40f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 15:59:48 -0500 Subject: [PATCH 13/21] Fix disconnect_callback example This example was not working because of the extra `future` argument. Also remove depedency on a specific device address while we are touching this file. --- examples/disconnect_callback.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/disconnect_callback.py b/examples/disconnect_callback.py index 15002e56..1e5efe8d 100644 --- a/examples/disconnect_callback.py +++ b/examples/disconnect_callback.py @@ -2,7 +2,7 @@ Disconnect callback ------------------- -An example showing how the `set_disconnect_callback` can be used on BlueZ backend. +An example showing how the `set_disconnected_callback` can be used on BlueZ backend. Updated on 2019-09-07 by hbldh @@ -10,19 +10,24 @@ import asyncio -from bleak import BleakClient +from bleak import BleakClient, discover -async def show_disconnect_handling(mac_addr: str): - async with BleakClient(mac_addr) as client: +async def show_disconnect_handling(): + devs = await discover() + if not devs: + print("No devices found, try again later.") + return + + async with BleakClient(devs[0]) as client: disconnected_event = asyncio.Event() - def disconnect_callback(client, future): + def disconnected_callback(client): print("Disconnected callback called!") - asyncio.get_event_loop().call_soon_threadsafe(disconnected_event.set) + disconnected_event.set() - client.set_disconnected_callback(disconnect_callback) - print("Sleeping until device disconnects according to BlueZ...") + client.set_disconnected_callback(disconnected_callback) + print("Sleeping until device disconnects...") await disconnected_event.wait() print("Connected: {0}".format(await client.is_connected())) await asyncio.sleep( @@ -30,6 +35,5 @@ def disconnect_callback(client, future): ) # Sleep a bit longer to allow _cleanup to remove all BlueZ notifications nicely... -mac_addr = "24:71:89:cc:09:05" loop = asyncio.get_event_loop() -loop.run_until_complete(show_disconnect_handling(mac_addr)) +loop.run_until_complete(show_disconnect_handling()) From 666395e26b8ac99dbaf29cafecc43f4ab5d8347d Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 14 Sep 2020 16:25:38 -0500 Subject: [PATCH 14/21] Consolidate set_disconnected_callback() implementations All backends had the same implementation for set_disconnected_callback() so we can have a common implementation in the abstract base class instead. This also allows us to pass the disconnected callback as a keyword argument when the BleakClient object is created. This can help avoid a race condition where a device might disconnect before the callback can be set. The example program is updated accordingly to avoid such a race condition. The existing set_disconnected_callback() can also be used at any time to change or remove the current callback. --- bleak/backends/bluezdbus/client.py | 30 -------------------------- bleak/backends/client.py | 15 +++++++------ bleak/backends/corebluetooth/client.py | 12 ----------- bleak/backends/dotnet/client.py | 23 -------------------- examples/disconnect_callback.py | 13 +++++------ 5 files changed, 16 insertions(+), 77 deletions(-) diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 5b80d9a0..bf0acad1 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -62,8 +62,6 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._rules = {} self._subscriptions = list() - self._disconnected_callback = None - # This maps DBus paths of GATT Characteristics to their BLE handles. self._char_path_to_handle = {} @@ -76,34 +74,6 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): # Connectivity methods - def set_disconnected_callback( - self, callback: Callable[[BaseBleakClient, Future], None], **kwargs - ) -> None: - """Set the disconnected callback. - - The callback will be called on DBus PropChanged event with - the 'Connected' key set to False. - - A disconnect callback must accept two positional arguments, - the BleakClient and the Future that called it. - - Example: - - .. code-block::python - - async with BleakClient(mac_addr) as client: - def disconnect_callback(client, future): - print(f"Disconnected callback called on {client}!") - - client.set_disconnected_callback(disconnect_callback) - - Args: - callback: callback to be called on disconnection. - - """ - - self._disconnected_callback = callback - async def connect(self, **kwargs) -> bool: """Connect to the specified GATT server. diff --git a/bleak/backends/client.py b/bleak/backends/client.py index 37e6eb7c..be3213f7 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -24,7 +24,9 @@ class BaseBleakClient(abc.ABC): Keyword Args: timeout (float): Timeout for required ``discover`` 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): @@ -39,6 +41,7 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._notification_callbacks = {} self._timeout = kwargs.get("timeout", 10.0) + self._disconnected_callback = kwargs.get("disconnected_callback") def __str__(self): return "{0}, {1}".format(self.__class__.__name__, self.address) @@ -57,15 +60,16 @@ async def __aexit__(self, exc_type, exc_val, exc_tb): # Connectivity methods - @abc.abstractmethod - async def set_disconnected_callback( - self, callback: Callable[["BaseBleakClient"], None], **kwargs + def set_disconnected_callback( + self, callback: Union[Callable[["BaseBleakClient"], None], None], **kwargs ) -> None: """Set the disconnect callback. The callback will only be called on unsolicited disconnect event. Callbacks must accept one input which is the client object itself. + Set the callback to ``None`` to remove any existing callback. + .. code-block:: python def callback(client): @@ -78,8 +82,7 @@ def callback(client): callback: callback to be called on disconnection. """ - - raise NotImplementedError() + self._disconnected_callback = callback @abc.abstractmethod async def connect(self, **kwargs) -> bool: diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 3d6e171f..2163643f 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -54,8 +54,6 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._callbacks = {} self._services = None - self._disconnected_callback = None - def __str__(self): return "BleakClientCoreBluetooth ({})".format(self.address) @@ -117,16 +115,6 @@ def set_disconnected_callback( """ self._disconnected_callback = callback - def _disconnected_callback_client(self): - """ - Callback for device disconnection. Bleak callback sends one argument as client. This is wrapper function - that gets called from the CentralManager and call actual disconnected_callback by sending client as argument - """ - logger.debug("Received disconnection callback...") - - if self._disconnected_callback is not None: - self._disconnected_callback(self) - async def pair(self, *args, **kwargs) -> bool: """Attempt to pair with a peripheral. diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index 4923ce85..3b1fc003 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -109,7 +109,6 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._device_info = None self._requester = None self._bridge = None - self._disconnected_callback = None self._address_type = ( kwargs["address_type"] @@ -253,28 +252,6 @@ async def is_connected(self) -> bool: else: return False - def set_disconnected_callback( - self, callback: Callable[[BaseBleakClient], None], **kwargs - ) -> None: - """Set the disconnect callback. - The callback will only be called on unsolicited disconnect event. - - Callbacks must accept one input which is the client object itself. - - .. code-block:: python - - def callback(client): - print("Client with address {} got disconnected!".format(client.address)) - - client.set_disconnected_callback(callback) - client.connect() - - Args: - callback: callback to be called on disconnection. - - """ - self._disconnected_callback = callback - async def pair(self, protection_level=None, **kwargs) -> bool: """Attempts to pair with the device. diff --git a/examples/disconnect_callback.py b/examples/disconnect_callback.py index 1e5efe8d..14cbd51c 100644 --- a/examples/disconnect_callback.py +++ b/examples/disconnect_callback.py @@ -19,14 +19,15 @@ async def show_disconnect_handling(): print("No devices found, try again later.") return - async with BleakClient(devs[0]) as client: - disconnected_event = asyncio.Event() + disconnected_event = asyncio.Event() - def disconnected_callback(client): - print("Disconnected callback called!") - disconnected_event.set() + def disconnected_callback(client): + print("Disconnected callback called!") + disconnected_event.set() - client.set_disconnected_callback(disconnected_callback) + async with BleakClient( + devs[0], disconnected_callback=disconnected_callback + ) as client: print("Sleeping until device disconnects...") await disconnected_event.wait() print("Connected: {0}".format(await client.is_connected())) From dc1b5a2d675424b87e4a5efb67e88320aab2e02d Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 15 Sep 2020 16:45:56 -0500 Subject: [PATCH 15/21] Fix getting handle in BlueZ backend The "Handle" property of the org.bluez.GattCharacteristic1 D-Bus interface is server-only, so it isn't available to Bleak. Instead, we can scrape the handle from the D-Bus path of the characteristic. Full object path format for reference: [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX/charYYYY Fixes #292 --- bleak/backends/bluezdbus/characteristic.py | 16 ++-------------- bleak/backends/bluezdbus/client.py | 4 +++- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/bleak/backends/bluezdbus/characteristic.py b/bleak/backends/bluezdbus/characteristic.py index badcfcfc..46454333 100644 --- a/bleak/backends/bluezdbus/characteristic.py +++ b/bleak/backends/bluezdbus/characteristic.py @@ -1,4 +1,3 @@ -import re from uuid import UUID from typing import Union, List @@ -27,8 +26,6 @@ # "authorize" } -_handle_regex = re.compile("/char([0-9a-fA-F]*)") - class BleakGATTCharacteristicBlueZDBus(BleakGATTCharacteristic): """GATT Characteristic implementation for the BlueZ DBus backend""" @@ -39,17 +36,8 @@ def __init__(self, obj: dict, object_path: str, service_uuid: str): self.__path = object_path self.__service_uuid = service_uuid - # The `Handle` attribute is added in BlueZ Release 5.51. Empirically, - # it seems to hold true that the "/charYYYY" that is at the end of the - # DBUS path actually is the desired handle. Using regex to extract - # that and using as handle, since handle is mostly used for keeping - # track of characteristics (internally in bleak anyway). - self._handle = self.obj.get("Handle") - if not self._handle: - _handle_from_path = _handle_regex.search(self.path) - if _handle_from_path: - self._handle = int(_handle_from_path.groups()[0], 16) - self._handle = int(self._handle) + # D-Bus object path contains handle as last 4 characters of 'charYYYY' + self._handle = int(object_path[-4:], 16) @property def service_uuid(self) -> str: diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 5b80d9a0..381fc6f3 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -437,7 +437,9 @@ async def get_services(self) -> BleakGATTServiceCollection: self.services.add_characteristic( BleakGATTCharacteristicBlueZDBus(char, object_path, _service[0].uuid) ) - self._char_path_to_handle[object_path] = char.get("Handle") + + # D-Bus object path contains handle as last 4 characters of 'charYYYY' + self._char_path_to_handle[object_path] = int(object_path[-4:], 16) for desc, object_path in _descs: _characteristic = list( From 800510cdb3d3fa62d27bf00ff7a601e68116d528 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 16 Sep 2020 10:38:19 -0500 Subject: [PATCH 16/21] Fix deleting wrong method in CoreBluetooth backend In 666395e26b8a, _disconnected_callback_client() was accidentally deleted instead of set_disconnected_callback(). This restores the former and deletes the latter. Fixes #294 --- bleak/backends/corebluetooth/client.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index ab7aaf82..56209c73 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -92,6 +92,16 @@ async def connect(self, **kwargs) -> bool: return True + def _disconnected_callback_client(self): + """ + Callback for device disconnection. Bleak callback sends one argument as client. This is wrapper function + that gets called from the CentralManager and call actual disconnected_callback by sending client as argument + """ + logger.debug("Received disconnection callback...") + + if self._disconnected_callback is not None: + self._disconnected_callback(self) + async def disconnect(self) -> bool: """Disconnect from the peripheral device""" manager = self._device_info.manager().delegate() @@ -107,16 +117,6 @@ async def is_connected(self) -> bool: manager = self._device_info.manager().delegate() return manager.isConnected - def set_disconnected_callback( - self, callback: Callable[[BaseBleakClient], None], **kwargs - ) -> None: - """Set the disconnected callback. - Args: - callback: callback to be called on disconnection. - - """ - self._disconnected_callback = callback - async def pair(self, *args, **kwargs) -> bool: """Attempt to pair with a peripheral. From 243641a1b4654ae8aece393ca3471e866ac71b37 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 17 Sep 2020 12:00:11 -0500 Subject: [PATCH 17/21] Fix CHANGELOG v0.8.0 has not actually been released yet. --- CHANGELOG.rst | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2482899..f0cd3220 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,18 +14,6 @@ Added ~~~~~ * Implemented ``set_disconnected_callback`` in the .NET backend ``BleakClient`` implementation. - -Removed -~~~~~~~ - -Support for Python 3.5. - -`0.8.0`_ (2020-09-02) ---------------------- - -Added -~~~~~ - * Added ``find_device_by_address`` method to the ``BleakScanner`` interface, for stopping scanning when a desired address is found. * Implemented ``find_device_by_address`` in the .NET backend ``BleakScanner`` implementation and @@ -42,6 +30,11 @@ Added * Added the possibility to connect using ``BLEDevice`` instead of a string address. This allows for skipping the discovery call when connecting. +Removed +~~~~~~~ + +* Support for Python 3.5. + Changed ~~~~~~~ * **BREAKING CHANGE** All notifications now have the characteristic's integer **handle** instead of its UUID as a From 6cdf382ab050684316f3d02c9667648d55d5745c Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Tue, 22 Sep 2020 09:22:29 +0200 Subject: [PATCH 18/21] Made context managers for .NET ``DataReader`` and ``DataWriter`` (#302) * Context managers for DataReader and DataWriter --- CHANGELOG.rst | 2 + bleak/backends/dotnet/client.py | 71 +++++++++++++----------------- bleak/backends/dotnet/discovery.py | 11 ++--- bleak/backends/dotnet/scanner.py | 12 ++--- bleak/backends/dotnet/utils.py | 49 +++++++++++++++++++++ 5 files changed, 90 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f0cd3220..e6e8dcd8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,6 +30,7 @@ Added * Added the possibility to connect using ``BLEDevice`` instead of a string address. This allows for skipping the discovery call when connecting. + Removed ~~~~~~~ @@ -44,6 +45,7 @@ Changed * Version 0.5.0 of BleakUWPBridge, with some modified methods and implementing ``IDisposable``. * Merged #224. All storing and passing of event loops in bleak is removed. * Removed Objective C delegate compliance checks. Merged #253. +* Made context managers for .NET ``DataReader`` and ``DataWriter``. Fixed ~~~~~ diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index a2df1a66..e4046763 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -15,8 +15,7 @@ from bleak.backends.dotnet.scanner import BleakScannerDotNet from bleak.exc import BleakError, BleakDotNetTaskError, CONTROLLER_ERROR_CODES from bleak.backends.client import BaseBleakClient -from bleak.backends.dotnet.discovery import discover -from bleak.backends.dotnet.utils import wrap_IAsyncOperation +from bleak.backends.dotnet.utils import BleakDataReader, BleakDataWriter, wrap_IAsyncOperation from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.backends.service import BleakGATTServiceCollection @@ -30,7 +29,7 @@ from BleakBridge import Bridge # Import of other CLR components needed. -from System import Array, Byte, UInt64 +from System import UInt64, Object from Windows.Foundation import IAsyncOperation, TypedEventHandler from Windows.Storage.Streams import DataReader, DataWriter, IBuffer from Windows.Devices.Enumeration import ( @@ -512,11 +511,8 @@ async def read_gatt_char( return_type=GattReadResult, ) if read_result.Status == GattCommunicationStatus.Success: - reader = DataReader.FromBuffer(IBuffer(read_result.Value)) - output = Array.CreateInstance(Byte, reader.UnconsumedBufferLength) - reader.ReadBytes(output) - value = bytearray(output) - reader.Dispose() + with BleakDataReader(read_result.Value) as reader: + value = bytearray(reader.read()) logger.debug( "Read Characteristic {0} : {1}".format(characteristic.uuid, value) ) @@ -570,11 +566,8 @@ async def read_gatt_descriptor( return_type=GattReadResult, ) if read_result.Status == GattCommunicationStatus.Success: - reader = DataReader.FromBuffer(IBuffer(read_result.Value)) - output = Array.CreateInstance(Byte, reader.UnconsumedBufferLength) - reader.ReadBytes(output) - value = bytearray(output) - reader.Dispose() + with BleakDataReader(read_result.Value) as reader: + value = bytearray(reader.read()) logger.debug("Read Descriptor {0} : {1}".format(handle, value)) else: if read_result.Status == GattCommunicationStatus.ProtocolError: @@ -621,21 +614,21 @@ async def write_gatt_char( if not characteristic: raise BleakError("Characteristic {} was not found!".format(char_specifier)) - writer = DataWriter() - writer.WriteBytes(Array[Byte](data)) - response = ( - GattWriteOption.WriteWithResponse - if response - else GattWriteOption.WriteWithoutResponse - ) - write_result = await wrap_IAsyncOperation( - IAsyncOperation[GattWriteResult]( - characteristic.obj.WriteValueWithResultAsync( - writer.DetachBuffer(), response - ) - ), - return_type=GattWriteResult, - ) + with BleakDataWriter(data) as writer: + response = ( + GattWriteOption.WriteWithResponse + if response + else GattWriteOption.WriteWithoutResponse + ) + write_result = await wrap_IAsyncOperation( + IAsyncOperation[GattWriteResult]( + characteristic.obj.WriteValueWithResultAsync( + writer.detach_buffer(), response + ) + ), + return_type=GattWriteResult, + ) + if write_result.Status == GattCommunicationStatus.Success: logger.debug( "Write Characteristic {0} : {1}".format(characteristic.uuid, data) @@ -674,14 +667,14 @@ async def write_gatt_descriptor(self, handle: int, data: bytearray) -> None: if not descriptor: raise BleakError("Descriptor with handle {0} was not found!".format(handle)) - writer = DataWriter() - writer.WriteBytes(Array[Byte](data)) - write_result = await wrap_IAsyncOperation( - IAsyncOperation[GattWriteResult]( - descriptor.obj.WriteValueAsync(writer.DetachBuffer()) - ), - return_type=GattWriteResult, - ) + with BleakDataWriter(data) as writer: + write_result = await wrap_IAsyncOperation( + IAsyncOperation[GattWriteResult]( + descriptor.obj.WriteValueAsync(writer.DetachBuffer()) + ), + return_type=GattWriteResult, + ) + if write_result.Status == GattCommunicationStatus.Success: logger.debug("Write Descriptor {0} : {1}".format(handle, data)) else: @@ -862,10 +855,8 @@ def _notification_wrapper(func: Callable, loop: asyncio.AbstractEventLoop): def dotnet_notification_parser(sender: Any, args: Any): # Return only the UUID string representation as sender. # Also do a conversion from System.Bytes[] to bytearray. - reader = DataReader.FromBuffer(args.CharacteristicValue) - output = Array.CreateInstance(Byte, reader.UnconsumedBufferLength) - reader.ReadBytes(output) - reader.Dispose() + with BleakDataReader(args.CharacteristicValue) as reader: + output = reader.read() return loop.call_soon_threadsafe( func, sender.AttributeHandle, bytearray(output) diff --git a/bleak/backends/dotnet/discovery.py b/bleak/backends/dotnet/discovery.py index b2310427..e2fead0c 100644 --- a/bleak/backends/dotnet/discovery.py +++ b/bleak/backends/dotnet/discovery.py @@ -23,7 +23,8 @@ BluetoothLEScanningMode, BluetoothLEAdvertisementType, ) -from Windows.Storage.Streams import DataReader, IBuffer + +from bleak.backends.dotnet.utils import BleakDataReader logger = logging.getLogger(__name__) _here = pathlib.Path(__file__).parent @@ -116,12 +117,8 @@ def AdvertisementWatcher_Stopped(sender, e): uuids.append(u.ToString()) data = {} for m in d.Advertisement.ManufacturerData: - md = IBuffer(m.Data) - b = Array.CreateInstance(Byte, md.Length) - reader = DataReader.FromBuffer(md) - reader.ReadBytes(b) - data[m.CompanyId] = bytes(b) - reader.Dispose() + with BleakDataReader(m.Data) as reader: + data[m.CompanyId] = reader.read() local_name = d.Advertisement.LocalName if not local_name and d.BluetoothAddress in scan_responses: local_name = scan_responses[d.BluetoothAddress].Advertisement.LocalName diff --git a/bleak/backends/dotnet/scanner.py b/bleak/backends/dotnet/scanner.py index 44835c91..066097e1 100644 --- a/bleak/backends/dotnet/scanner.py +++ b/bleak/backends/dotnet/scanner.py @@ -6,20 +6,19 @@ from typing import Callable, Any, Union, List from bleak.backends.device import BLEDevice +from bleak.backends.dotnet.utils import BleakDataReader from bleak.exc import BleakError, BleakDotNetTaskError from bleak.backends.scanner import BaseBleakScanner # Import of Bleak CLR->UWP Bridge. It is not needed here, but it enables loading of Windows.Devices from BleakBridge import Bridge -from System import Array, Byte -from Windows.Devices import Enumeration from Windows.Devices.Bluetooth.Advertisement import ( BluetoothLEAdvertisementWatcher, BluetoothLEScanningMode, BluetoothLEAdvertisementType, ) -from Windows.Storage.Streams import DataReader, IBuffer +from Windows.Foundation import TypedEventHandler logger = logging.getLogger(__name__) _here = pathlib.Path(__file__).parent @@ -161,11 +160,8 @@ def parse_eventargs(event_args): uuids.append(u.ToString()) data = {} for m in event_args.Advertisement.ManufacturerData: - md = IBuffer(m.Data) - b = Array.CreateInstance(Byte, md.Length) - reader = DataReader.FromBuffer(md) - reader.ReadBytes(b) - data[m.CompanyId] = bytes(b) + with BleakDataReader(m.Data) as reader: + data[m.CompanyId] = reader.read() local_name = event_args.Advertisement.LocalName return BLEDevice( bdaddr, local_name, event_args, uuids=uuids, manufacturer_data=data diff --git a/bleak/backends/dotnet/utils.py b/bleak/backends/dotnet/utils.py index 1b43cc02..57500993 100644 --- a/bleak/backends/dotnet/utils.py +++ b/bleak/backends/dotnet/utils.py @@ -18,6 +18,8 @@ IAsyncOperation, AsyncStatus, ) +from System import Array, Byte +from Windows.Storage.Streams import DataReader, DataWriter, IBuffer async def wrap_Task(task): @@ -74,3 +76,50 @@ async def wrap_IAsyncOperation(op, return_type): else: # TODO: Handle IsCancelled. raise BleakDotNetTaskError("IAsyncOperation Status: {0}".format(op.Status)) + + +class BleakDataReader: + + def __init__(self, buffer_com_object): + + self.reader = None + self.buffer = IBuffer(buffer_com_object) + + def __enter__(self): + self.reader = DataReader.FromBuffer(self.buffer) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.reader.DetachBuffer() + self.reader.Dispose() + self.reader = None + self.buffer = None + + def read(self) -> bytes: + b = Array.CreateInstance(Byte, self.reader.UnconsumedBufferLength) + self.reader.ReadBytes(b) + py_b = bytes(b) + del b + return py_b + + +class BleakDataWriter: + + def __init__(self, data): + self.data = data + + def __enter__(self): + self.writer = DataWriter() + self.writer.WriteBytes(Array[Byte](self.data)) + return self + + def detach_buffer(self): + return self.writer.DetachBuffer() + + def __exit__(self, exc_type, exc_val, exc_tb): + try: + self.writer.Dispose() + except: + pass + del self.writer + self.writer = None From 45666637f42de13811372375d1102061db4ef387 Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Tue, 22 Sep 2020 09:24:53 +0200 Subject: [PATCH 19/21] Version bump and CHANGELOG update --- CHANGELOG.rst | 5 +++-- bleak/__version__.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e6e8dcd8..1b0649a6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,8 +7,9 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_, and this project adheres to `Semantic Versioning `_. -`Unreleased`_ -------------- + +`0.8.0`_ (2020-09-22) +--------------------- Added ~~~~~ diff --git a/bleak/__version__.py b/bleak/__version__.py index 84030da1..d7743ec4 100644 --- a/bleak/__version__.py +++ b/bleak/__version__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = "0.8.1a1" +__version__ = "0.8.0" From c69ec29a67059bfdc678cc49afb3057ddde4cf17 Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Tue, 22 Sep 2020 09:27:29 +0200 Subject: [PATCH 20/21] Blacked code --- .../corebluetooth/CentralManagerDelegate.py | 23 ++++------------ .../corebluetooth/PeripheralDelegate.py | 24 ++++------------- bleak/backends/corebluetooth/client.py | 26 +++++++------------ bleak/backends/dotnet/client.py | 6 ++++- bleak/backends/dotnet/discovery.py | 8 +----- bleak/backends/dotnet/utils.py | 2 -- 6 files changed, 26 insertions(+), 63 deletions(-) diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index 20cb4a3b..3a205c08 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -187,8 +187,7 @@ def did_update_state(self, centralManager): def centralManagerDidUpdateState_(self, centralManager): logger.debug("centralManagerDidUpdateState_") self.event_loop.call_soon_threadsafe( - self.did_update_state, - centralManager, + self.did_update_state, centralManager, ) @objc.python_method @@ -249,11 +248,7 @@ def centralManager_didDiscoverPeripheral_advertisementData_RSSI_( ): logger.debug("centralManager_didDiscoverPeripheral_advertisementData_RSSI_") self.event_loop.call_soon_threadsafe( - self.did_discover_peripheral, - central, - peripheral, - advertisementData, - RSSI, + self.did_discover_peripheral, central, peripheral, advertisementData, RSSI, ) @objc.python_method @@ -273,9 +268,7 @@ def did_connect_peripheral(self, central, peripheral): def centralManager_didConnectPeripheral_(self, central, peripheral): logger.debug("centralManager_didConnectPeripheral_") self.event_loop.call_soon_threadsafe( - self.did_connect_peripheral, - central, - peripheral, + self.did_connect_peripheral, central, peripheral, ) @objc.python_method @@ -294,10 +287,7 @@ def centralManager_didFailToConnectPeripheral_error_( ): logger.debug("centralManager_didFailToConnectPeripheral_error_") self.event_loop.call_soon_threadsafe( - self.did_fail_to_connect_peripheral, - centralManager, - peripheral, - error, + self.did_fail_to_connect_peripheral, centralManager, peripheral, error, ) @objc.python_method @@ -317,10 +307,7 @@ def centralManager_didDisconnectPeripheral_error_( ): logger.debug("centralManager_didDisconnectPeripheral_error_") self.event_loop.call_soon_threadsafe( - self.did_disconnect_peripheral, - central, - peripheral, - error, + self.did_disconnect_peripheral, central, peripheral, error, ) diff --git a/bleak/backends/corebluetooth/PeripheralDelegate.py b/bleak/backends/corebluetooth/PeripheralDelegate.py index bcd03e0c..2ba1390e 100644 --- a/bleak/backends/corebluetooth/PeripheralDelegate.py +++ b/bleak/backends/corebluetooth/PeripheralDelegate.py @@ -219,9 +219,7 @@ def peripheral_didDiscoverServices_( ) -> None: logger.debug("peripheral_didDiscoverServices_") self._event_loop.call_soon_threadsafe( - self.did_discover_services, - peripheral, - error, + self.did_discover_services, peripheral, error, ) @objc.python_method @@ -246,10 +244,7 @@ def peripheral_didDiscoverCharacteristicsForService_error_( ): logger.debug("peripheral_didDiscoverCharacteristicsForService_error_") self._event_loop.call_soon_threadsafe( - self.did_discover_characteristics_for_service, - peripheral, - service, - error, + self.did_discover_characteristics_for_service, peripheral, service, error, ) @objc.python_method @@ -341,10 +336,7 @@ def peripheral_didUpdateValueForDescriptor_error_( ): logger.debug("peripheral_didUpdateValueForDescriptor_error_") self._event_loop.call_soon_threadsafe( - self.did_update_value_for_descriptor, - peripheral, - descriptor, - error, + self.did_update_value_for_descriptor, peripheral, descriptor, error, ) @objc.python_method @@ -370,10 +362,7 @@ def peripheral_didWriteValueForCharacteristic_error_( ): logger.debug("peripheral_didWriteValueForCharacteristic_error_") self._event_loop.call_soon_threadsafe( - self.did_write_value_for_characteristic, - peripheral, - characteristic, - error, + self.did_write_value_for_characteristic, peripheral, characteristic, error, ) @objc.python_method @@ -396,10 +385,7 @@ def peripheral_didWriteValueForDescriptor_error_( ): logger.debug("peripheral_didWriteValueForDescriptor_error_") self._event_loop.call_soon_threadsafe( - self.did_write_value_for_descriptor, - peripheral, - descriptor, - error, + self.did_write_value_for_descriptor, peripheral, descriptor, error, ) @objc.python_method diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 56209c73..32cf4fed 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -165,10 +165,8 @@ async def get_services(self) -> BleakGATTServiceCollection: logger.debug( "Retrieving characteristics for service {}".format(serviceUUID) ) - characteristics = ( - await manager.connected_peripheral_delegate.discoverCharacteristics_( - service - ) + characteristics = await manager.connected_peripheral_delegate.discoverCharacteristics_( + service ) self.services.add_service(BleakGATTServiceCoreBluetooth(service)) @@ -178,10 +176,8 @@ async def get_services(self) -> BleakGATTServiceCollection: logger.debug( "Retrieving descriptors for characteristic {}".format(cUUID) ) - descriptors = ( - await manager.connected_peripheral_delegate.discoverDescriptors_( - characteristic - ) + descriptors = await manager.connected_peripheral_delegate.discoverDescriptors_( + characteristic ) self.services.add_characteristic( @@ -292,14 +288,12 @@ async def write_gatt_char( raise BleakError("Characteristic {} was not found!".format(char_specifier)) value = NSData.alloc().initWithBytes_length_(data, len(data)) - success = ( - await manager.connected_peripheral_delegate.writeCharacteristic_value_type_( - characteristic.obj, - value, - CBCharacteristicWriteWithResponse - if response - else CBCharacteristicWriteWithoutResponse, - ) + success = await manager.connected_peripheral_delegate.writeCharacteristic_value_type_( + characteristic.obj, + value, + CBCharacteristicWriteWithResponse + if response + else CBCharacteristicWriteWithoutResponse, ) if success: logger.debug( diff --git a/bleak/backends/dotnet/client.py b/bleak/backends/dotnet/client.py index e4046763..64e16a85 100644 --- a/bleak/backends/dotnet/client.py +++ b/bleak/backends/dotnet/client.py @@ -15,7 +15,11 @@ from bleak.backends.dotnet.scanner import BleakScannerDotNet from bleak.exc import BleakError, BleakDotNetTaskError, CONTROLLER_ERROR_CODES from bleak.backends.client import BaseBleakClient -from bleak.backends.dotnet.utils import BleakDataReader, BleakDataWriter, wrap_IAsyncOperation +from bleak.backends.dotnet.utils import ( + BleakDataReader, + BleakDataWriter, + wrap_IAsyncOperation, +) from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.backends.service import BleakGATTServiceCollection diff --git a/bleak/backends/dotnet/discovery.py b/bleak/backends/dotnet/discovery.py index e2fead0c..56e39c46 100644 --- a/bleak/backends/dotnet/discovery.py +++ b/bleak/backends/dotnet/discovery.py @@ -123,13 +123,7 @@ def AdvertisementWatcher_Stopped(sender, e): if not local_name and d.BluetoothAddress in scan_responses: local_name = scan_responses[d.BluetoothAddress].Advertisement.LocalName found.append( - BLEDevice( - bdaddr, - local_name, - d, - uuids=uuids, - manufacturer_data=data, - ) + BLEDevice(bdaddr, local_name, d, uuids=uuids, manufacturer_data=data,) ) return found diff --git a/bleak/backends/dotnet/utils.py b/bleak/backends/dotnet/utils.py index 57500993..f8af8acc 100644 --- a/bleak/backends/dotnet/utils.py +++ b/bleak/backends/dotnet/utils.py @@ -79,7 +79,6 @@ async def wrap_IAsyncOperation(op, return_type): class BleakDataReader: - def __init__(self, buffer_com_object): self.reader = None @@ -104,7 +103,6 @@ def read(self) -> bytes: class BleakDataWriter: - def __init__(self, data): self.data = data From e1378cf4c16908cbd93d60d0acbd5047a7e4e25a Mon Sep 17 00:00:00 2001 From: Henrik Blidh Date: Tue, 22 Sep 2020 09:28:20 +0200 Subject: [PATCH 21/21] Removed Whiteline in Changelog --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1b0649a6..34bb2f96 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,7 +31,6 @@ Added * Added the possibility to connect using ``BLEDevice`` instead of a string address. This allows for skipping the discovery call when connecting. - Removed ~~~~~~~