diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index bbe8dc2f..34d2c0d7 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@ * bleak version: * Python version: * Operating System: -* BlueZ version (`bluetoothctl -v`) in case of Linux: +* BlueZ version (`bluetoothctl -v`) in case of Linux: ### Description @@ -10,7 +10,13 @@ Tell us what happened, what went wrong, and what you expected to happen. ### What I Did +It is preferable if an issue contains a [Miminal Workable Example](https://stackoverflow.com/help/minimal-reproducible-example). +This will otherwise be one of the first questions you will get as a response. + +It is also preferable if that example is not dependent on a specific peripheral device, but can be run and +reproduced with other BLE peripherals as well. + ``` Paste the command(s) you ran and the output. -If there was a crash, please include the traceback here. +If there was a crash, please include the traceback here as well. ``` diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..32d734bb --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +Pull Request Guidelines for Bleak +--------------------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. If the pull request adds functionality, the docs should be updated. +2. Modify the `CHANGELOG.rst`, describing your changes as is specified by the + guidelines in that document. +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 +4. Squash all your commits on your PR branch, if the commits are not solving + different problems and you are committing them in the same PR. In that case, + consider making several PRs instead. +5. Feel free to add your name as a contributor to the `AUTHORS.rst` file! diff --git a/AUTHORS.rst b/AUTHORS.rst index 2325f5e3..2a411f63 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -7,7 +7,12 @@ Development Lead * Henrik Blidh +Development Team / Collaborators +-------------------------------- + +* David Lechner + Contributors ------------ -* David Lechner + diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9898f140..28a3cd42 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,34 @@ 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 `_. + +`0.9.1`_ (2020-10-22) +--------------------- + +Added +~~~~~ + +* Added new attribute ``_device_info`` on ``BleakClientBlueZDBus``. Merges #347. +* Added Pull Request Template. + +Changed +~~~~~~~ + +* Updated instructions on how to contribute, file issues and make PRs. +* Updated ``AUTHORS.rst`` file with development team. + +Fixed +~~~~~ + +* Fix well-known services not converted to UUIDs in ``BLEDevice.metadata`` in + CoreBluetooth backend. Fixes #342. +* Fix advertising data replaced instead of merged in scanner in CoreBluetooth + backend. Merged #343. +* Fix CBCentralManager not properly waited for during initialization in some + cases. +* Fix AttributeError in CoreBluetooth when using BLEDeviceCoreBluetooth object. + + `0.9.0`_ (2020-10-20) --------------------- @@ -347,7 +375,8 @@ Fixed * Bleak created. -.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.9.0...develop +.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.9.1...develop +.. _0.9.1: https://github.com/hbldh/bleak/compare/v0.9.1...v0.9.0 .. _0.9.0: https://github.com/hbldh/bleak/compare/v0.9.0...v0.8.0 .. _0.8.0: https://github.com/hbldh/bleak/compare/v0.8.0...v0.7.1 .. _0.7.1: https://github.com/hbldh/bleak/compare/v0.7.1...v0.7.0 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 82e558b0..bdb18af4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -98,13 +98,14 @@ Pull Request Guidelines Before you submit a pull request, check that it meets these guidelines: -1. The pull request should include tests. -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. +1. If the pull request adds functionality, the docs should be updated. +2. Modify the ``CHANGELOG.rst``, describing your changes as is specified by the + guidelines in that document. 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 -4. Feel free to add your name to the ``AUTHORS.rst`` in the root of the project! - +4. Squash all your commits on your PR branch, if the commits are not solving + different problems and you are committing them in the same PR. In that case, + consider making several PRs instead. +5. Feel free to add your name as a contributor to the ``AUTHORS.rst`` file! diff --git a/bleak/__version__.py b/bleak/__version__.py index a0ab76bc..0f4dc532 100644 --- a/bleak/__version__.py +++ b/bleak/__version__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = "0.9.0" +__version__ = "0.9.1" diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 48a87abf..c554be47 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -54,8 +54,10 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): # Backend specific, TXDBus objects and data if isinstance(address_or_ble_device, BLEDevice): self._device_path = address_or_ble_device.details["path"] + self._device_info = address_or_ble_device.details.get("props") else: self._device_path = None + self._device_info = None self._bus = None self._reactor = None self._rules = {} @@ -92,6 +94,7 @@ async def connect(self, **kwargs) -> bool: ) if device: + self._device_info = device.details.get("props") self._device_path = device.details["path"] else: raise BleakError( diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index 84b60b78..0d1423ea 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -9,6 +9,7 @@ import asyncio import logging import platform +import threading from enum import Enum from typing import List @@ -35,6 +36,7 @@ from bleak.backends.corebluetooth.PeripheralDelegate import PeripheralDelegate from bleak.backends.corebluetooth.device import BLEDeviceCoreBluetooth +from bleak.exc import BleakError logger = logging.getLogger(__name__) CBCentralManagerDelegate = objc.protocolNamed("CBCentralManagerDelegate") @@ -70,17 +72,26 @@ def init(self): self.connected_peripheral = None self._connection_state = CMDConnectionState.DISCONNECTED - self.powered_on_event = asyncio.Event() self.devices = {} self.callbacks = {} self.disconnected_callback = None self._connection_state_changed = asyncio.Event() + self._did_update_state_event = threading.Event() self.central_manager = CBCentralManager.alloc().initWithDelegate_queue_( self, dispatch_queue_create(b"bleak.corebluetooth", DISPATCH_QUEUE_SERIAL) ) + # according to CoreBluetooth docs, it is not valid to call CBCentral + # methods until the centralManagerDidUpdateState_() delegate method + # is called and the current state is CBManagerStatePoweredOn. + # It doesn't take long for the callback to occur, so we should be able + # to do a blocking wait here without anyone complaining. + self._did_update_state_event.wait(1) + if self.central_manager.state() != CBManagerStatePoweredOn: + raise BleakError("Bluetooth device is turned off") + return self # User defined functions @@ -89,16 +100,6 @@ def init(self): def isConnected(self) -> bool: return self._connection_state == CMDConnectionState.CONNECTED - @objc.python_method - async def wait_for_powered_on(self, timeout: float): - """ - Waits for state to be CBManagerStatePoweredOn. This must be done before - attempting to do anything else. - - Throws asyncio.TimeoutError if power on is not detected before timeout. - """ - await asyncio.wait_for(self.powered_on_event.wait(), timeout) - @objc.python_method def start_scan(self, scan_options): # remove old @@ -174,8 +175,8 @@ async def disconnect(self) -> bool: # Protocol Functions - @objc.python_method - def did_update_state(self, centralManager): + def centralManagerDidUpdateState_(self, centralManager): + logger.debug("centralManagerDidUpdateState_") if centralManager.state() == CBManagerStateUnknown: logger.debug("Cannot detect bluetooth device") elif centralManager.state() == CBManagerStateResetting: @@ -189,17 +190,7 @@ def did_update_state(self, centralManager): elif centralManager.state() == CBManagerStatePoweredOn: logger.debug("Bluetooth powered on") - if centralManager.state() == CBManagerStatePoweredOn: - self.powered_on_event.set() - else: - self.powered_on_event.clear() - - def centralManagerDidUpdateState_(self, centralManager): - logger.debug("centralManagerDidUpdateState_") - self.event_loop.call_soon_threadsafe( - self.did_update_state, - centralManager, - ) + self._did_update_state_event.set() @objc.python_method def did_discover_peripheral( @@ -234,7 +225,7 @@ def did_discover_peripheral( address = uuid_string name = peripheral.name() or None details = peripheral - device = BLEDeviceCoreBluetooth(address, name, details) + device = BLEDeviceCoreBluetooth(address, name, details, delegate=self) self.devices[uuid_string] = device device._rssi = float(RSSI) diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 6e829eb0..fc8f0b92 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -48,9 +48,7 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): if isinstance(address_or_ble_device, BLEDevice): self._device_info = address_or_ble_device.details - self._central_manager_delegate = address_or_ble_device.metadata.get( - "delegate" - ) + self._central_manager_delegate = address_or_ble_device.metadata["delegate"] else: self._device_info = None self._central_manager_delegate = None @@ -79,7 +77,7 @@ async def connect(self, **kwargs) -> bool: if device: self._device_info = device.details - self._central_manager_delegate = device.metadata.get("delegate") + self._central_manager_delegate = device.metadata["delegate"] else: raise BleakError( "Device with address {} was not found".format(self.address) diff --git a/bleak/backends/corebluetooth/device.py b/bleak/backends/corebluetooth/device.py index 2e0e022e..835fb8ac 100644 --- a/bleak/backends/corebluetooth/device.py +++ b/bleak/backends/corebluetooth/device.py @@ -3,7 +3,7 @@ from Foundation import NSDictionary - +from bleak.backends.corebluetooth.utils import cb_uuid_to_str from bleak.backends.device import BLEDevice @@ -14,7 +14,7 @@ class BLEDeviceCoreBluetooth(BLEDevice): - The `details` attribute will be a CBPeripheral object. - - The `metadata` keys are more or less part of the crossplattform interface. + - The `metadata` keys are more or less part of the cross-platform interface. - Note: Take care not to rely on any reference to `advertisementData` and it's data as lower layers of the corebluetooth stack can change it. i.e. @@ -35,7 +35,6 @@ class BLEDeviceCoreBluetooth(BLEDevice): def __init__(self, *args, **kwargs): super(BLEDeviceCoreBluetooth, self).__init__(*args, **kwargs) - self.metadata = {} self._rssi = kwargs.get("rssi") def _update(self, advertisementData: NSDictionary): @@ -46,8 +45,7 @@ def _update_uuids(self, advertisementData: NSDictionary): cbuuids = advertisementData.get("kCBAdvDataServiceUUIDs", []) if not cbuuids: return - # converting to lower case to match other platforms - chuuids = [str(u).lower() for u in cbuuids] + chuuids = [cb_uuid_to_str(u) for u in cbuuids] if "uuids" in self.metadata: for uuid in chuuids: if not uuid in self.metadata["uuids"]: diff --git a/bleak/backends/corebluetooth/discovery.py b/bleak/backends/corebluetooth/discovery.py index f52496ad..f9197d28 100644 --- a/bleak/backends/corebluetooth/discovery.py +++ b/bleak/backends/corebluetooth/discovery.py @@ -7,12 +7,10 @@ """ -import asyncio from typing import List from bleak.backends.corebluetooth.CentralManagerDelegate import CentralManagerDelegate from bleak.backends.device import BLEDevice -from bleak.exc import BleakError async def discover(timeout: float = 5.0, **kwargs) -> List[BLEDevice]: @@ -23,11 +21,6 @@ async def discover(timeout: float = 5.0, **kwargs) -> List[BLEDevice]: """ manager = CentralManagerDelegate.alloc().init() - try: - await manager.wait_for_powered_on(0.1) - except asyncio.TimeoutError: - raise BleakError("Bluetooth device is turned off") - scan_options = {"timeout": timeout} await manager.scanForPeripherals_(scan_options) diff --git a/bleak/backends/corebluetooth/scanner.py b/bleak/backends/corebluetooth/scanner.py index 1a93ded7..f7b75d55 100644 --- a/bleak/backends/corebluetooth/scanner.py +++ b/bleak/backends/corebluetooth/scanner.py @@ -5,8 +5,8 @@ from typing import Callable, Any, Union, List from bleak.backends.corebluetooth.CentralManagerDelegate import CentralManagerDelegate +from bleak.backends.corebluetooth.utils import cb_uuid_to_str from bleak.backends.device import BLEDevice -from bleak.exc import BleakError from bleak.backends.scanner import BaseBleakScanner @@ -39,15 +39,11 @@ def __init__(self, **kwargs): self._timeout = kwargs.get("timeout", 5.0) async def start(self): - try: - await self._manager.wait_for_powered_on(0.1) - except asyncio.TimeoutError: - raise BleakError("Bluetooth device is turned off") - self._identifiers = {} def callback(p, a, r): - self._identifiers[p.identifier()] = a + # update identifiers for scanned device + self._identifiers.setdefault(p.identifier(), {}).update(a) if self._callback: self._callback(p, a, r) @@ -101,8 +97,7 @@ async def get_discovered_devices(self) -> List[BLEDevice]: manufacturer_data = {manufacturer_id: manufacturer_value} uuids = [ - # converting to lower case to match other platforms - str(u).lower() + cb_uuid_to_str(u) for u in advertisementData.get("kCBAdvDataServiceUUIDs", []) ]