led-ble
Advanced tools
+4
-3
| Metadata-Version: 2.1 | ||
| Name: led-ble | ||
| Version: 0.10.1 | ||
| Version: 1.0.0 | ||
| Summary: Control a wide range of LED BLE devices | ||
@@ -18,2 +18,3 @@ Home-page: https://github.com/bluetooth-devices/led-ble | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Topic :: Software Development :: Libraries | ||
@@ -23,4 +24,4 @@ Provides-Extra: docs | ||
| Requires-Dist: async-timeout (>=4.0.1) | ||
| Requires-Dist: bleak (>=0.17.0) | ||
| Requires-Dist: bleak-retry-connector (>=1.17.1) | ||
| Requires-Dist: bleak (>=0.19.0) | ||
| Requires-Dist: bleak-retry-connector (>=2.3.0) | ||
| Requires-Dist: flux-led (>=0.28.32) | ||
@@ -27,0 +28,0 @@ Requires-Dist: myst-parser (>=0.18,<0.19); extra == "docs" |
+3
-3
| [tool.poetry] | ||
| name = "led-ble" | ||
| version = "0.10.1" | ||
| version = "1.0.0" | ||
| description = "Control a wide range of LED BLE devices" | ||
@@ -32,4 +32,4 @@ authors = ["J. Nick Koston <nick@koston.org>"] | ||
| myst-parser = {version = "^0.18", optional = true} | ||
| bleak-retry-connector = ">=1.17.1" | ||
| bleak = ">=0.17.0" | ||
| bleak-retry-connector = ">=2.3.0" | ||
| bleak = ">=0.19.0" | ||
| async-timeout = ">=4.0.1" | ||
@@ -36,0 +36,0 @@ flux-led = ">=0.28.32" |
+3
-3
@@ -15,4 +15,4 @@ # -*- coding: utf-8 -*- | ||
| ['async-timeout>=4.0.1', | ||
| 'bleak-retry-connector>=1.17.1', | ||
| 'bleak>=0.17.0', | ||
| 'bleak-retry-connector>=2.3.0', | ||
| 'bleak>=0.19.0', | ||
| 'flux-led>=0.28.32'] | ||
@@ -27,3 +27,3 @@ | ||
| 'name': 'led-ble', | ||
| 'version': '0.10.1', | ||
| 'version': '1.0.0', | ||
| 'description': 'Control a wide range of LED BLE devices', | ||
@@ -30,0 +30,0 @@ 'long_description': '# LED BLE\n\n<p align="center">\n <a href="https://github.com/bluetooth-devices/led-ble/actions?query=workflow%3ACI">\n <img src="https://img.shields.io/github/workflow/status/bluetooth-devices/led-ble/CI/main?label=CI&logo=github&style=flat-square" alt="CI Status" >\n </a>\n <a href="https://led-ble.readthedocs.io">\n <img src="https://img.shields.io/readthedocs/led-ble.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">\n </a>\n <a href="https://codecov.io/gh/bluetooth-devices/led-ble">\n <img src="https://img.shields.io/codecov/c/github/bluetooth-devices/led-ble.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">\n </a>\n</p>\n<p align="center">\n <a href="https://python-poetry.org/">\n <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">\n </a>\n <a href="https://github.com/ambv/black">\n <img src="https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square" alt="black">\n </a>\n <a href="https://github.com/pre-commit/pre-commit">\n <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">\n </a>\n</p>\n<p align="center">\n <a href="https://pypi.org/project/led-ble/">\n <img src="https://img.shields.io/pypi/v/led-ble.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">\n </a>\n <img src="https://img.shields.io/pypi/pyversions/led-ble.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">\n <img src="https://img.shields.io/pypi/l/led-ble.svg?style=flat-square" alt="License">\n</p>\n\nControl a wide range of LED BLE devices\n\n## Installation\n\nInstall this via pip (or your favourite package manager):\n\n`pip install led-ble`\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- prettier-ignore-start -->\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- markdownlint-disable -->\n<!-- markdownlint-enable -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n<!-- prettier-ignore-end -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n\n## Credits\n\nThis package was created with\n[Cookiecutter](https://github.com/audreyr/cookiecutter) and the\n[browniebroke/cookiecutter-pypackage](https://github.com/browniebroke/cookiecutter-pypackage)\nproject template.\n', |
| from __future__ import annotations | ||
| __version__ = "0.10.1" | ||
| __version__ = "1.0.0" | ||
@@ -5,0 +5,0 @@ |
+40
-140
@@ -8,6 +8,7 @@ from __future__ import annotations | ||
| from dataclasses import replace | ||
| from typing import Any, TypeVar, cast | ||
| from typing import Any, TypeVar | ||
| import async_timeout | ||
| from bleak.backends.device import BLEDevice | ||
| from bleak.backends.scanner import AdvertisementData | ||
| from bleak.backends.service import BleakGATTCharacteristic, BleakGATTServiceCollection | ||
@@ -20,4 +21,4 @@ from bleak.exc import BleakDBusError | ||
| BleakNotFoundError, | ||
| ble_device_has_changed, | ||
| establish_connection, | ||
| retry_bluetooth_connection_error, | ||
| ) | ||
@@ -59,87 +60,15 @@ from flux_led.base_device import PROTOCOL_NAME_TO_CLS, PROTOCOL_TYPES | ||
| def retry_bluetooth_connection_error(func: WrapFuncType) -> WrapFuncType: | ||
| """Define a wrapper to retry on bleak error. | ||
| The accessory is allowed to disconnect us any time so | ||
| we need to retry the operation. | ||
| """ | ||
| async def _async_wrap_retry_bluetooth_connection_error( | ||
| self: "LEDBLE", *args: Any, **kwargs: Any | ||
| ) -> Any: | ||
| _LOGGER.debug("%s: Starting retry loop", self.name) | ||
| attempts = DEFAULT_ATTEMPTS | ||
| max_attempts = attempts - 1 | ||
| for attempt in range(attempts): | ||
| try: | ||
| return await func(self, *args, **kwargs) | ||
| except BleakNotFoundError: | ||
| # The lock cannot be found so there is no | ||
| # point in retrying. | ||
| raise | ||
| except RETRY_BACKOFF_EXCEPTIONS as err: | ||
| if attempt >= max_attempts: | ||
| _LOGGER.debug( | ||
| "%s: %s error calling %s, reach max attempts (%s/%s)", | ||
| self.name, | ||
| type(err), | ||
| func, | ||
| attempt, | ||
| max_attempts, | ||
| exc_info=True, | ||
| ) | ||
| raise | ||
| _LOGGER.debug( | ||
| "%s: %s error calling %s, backing off %ss, retrying (%s/%s)...", | ||
| self.name, | ||
| type(err), | ||
| func, | ||
| BLEAK_BACKOFF_TIME, | ||
| attempt, | ||
| max_attempts, | ||
| exc_info=True, | ||
| ) | ||
| await asyncio.sleep(BLEAK_BACKOFF_TIME) | ||
| except BLEAK_EXCEPTIONS as err: | ||
| if attempt >= max_attempts: | ||
| _LOGGER.debug( | ||
| "%s: %s error calling %s, reach max attempts (%s/%s): %s", | ||
| self.name, | ||
| type(err), | ||
| func, | ||
| attempt, | ||
| max_attempts, | ||
| err, | ||
| exc_info=True, | ||
| ) | ||
| raise | ||
| _LOGGER.debug( | ||
| "%s: %s error calling %s, retrying (%s/%s)...: %s", | ||
| self.name, | ||
| type(err), | ||
| func, | ||
| attempt, | ||
| max_attempts, | ||
| err, | ||
| exc_info=True, | ||
| ) | ||
| return cast(WrapFuncType, _async_wrap_retry_bluetooth_connection_error) | ||
| class LEDBLE: | ||
| def __init__( | ||
| self, ble_device: BLEDevice, retry_count: int = DEFAULT_ATTEMPTS | ||
| self, ble_device: BLEDevice, advertisement_data: AdvertisementData | None = None | ||
| ) -> None: | ||
| """Init the LEDBLE.""" | ||
| self._ble_device = ble_device | ||
| self._advertisement_data = advertisement_data | ||
| self._operation_lock = asyncio.Lock() | ||
| self._state = LEDBLEState() | ||
| self._connect_lock: asyncio.Lock = asyncio.Lock() | ||
| self._cached_services: BleakGATTServiceCollection | None = None | ||
| self._read_char: BleakGATTCharacteristic | None = None | ||
| self._write_char: BleakGATTCharacteristic | None = None | ||
| self._disconnect_timer: asyncio.TimerHandle | None = None | ||
| self._retry_count = retry_count | ||
| self._client: BleakClientWithServiceCache | None = None | ||
@@ -153,10 +82,8 @@ self._expected_disconnect = False | ||
| def set_ble_device(self, ble_device: BLEDevice) -> None: | ||
| def set_ble_device_and_advertisement_data( | ||
| self, ble_device: BLEDevice, advertisement_data: AdvertisementData | ||
| ) -> None: | ||
| """Set the ble device.""" | ||
| if self._ble_device and ble_device_has_changed(self._ble_device, ble_device): | ||
| _LOGGER.debug( | ||
| "%s: New ble device details, clearing cached services", self.name | ||
| ) | ||
| self._cached_services = None | ||
| self._ble_device = ble_device | ||
| self._advertisement_data = advertisement_data | ||
@@ -185,5 +112,7 @@ @property | ||
| @property | ||
| def rssi(self) -> str: | ||
| """Get the name of the device.""" | ||
| return self._ble_device.rssi | ||
| def rssi(self) -> int | None: | ||
| """Get the rssi of the device.""" | ||
| if self._advertisement_data: | ||
| return self._advertisement_data.rssi | ||
| return None | ||
@@ -224,3 +153,2 @@ @property | ||
| @retry_bluetooth_connection_error | ||
| async def update(self) -> None: | ||
@@ -235,3 +163,2 @@ """Update the LEDBLE.""" | ||
| @retry_bluetooth_connection_error | ||
| async def turn_on(self) -> None: | ||
@@ -245,3 +172,2 @@ """Turn on.""" | ||
| @retry_bluetooth_connection_error | ||
| async def turn_off(self) -> None: | ||
@@ -268,3 +194,2 @@ """Turn off.""" | ||
| @retry_bluetooth_connection_error | ||
| async def set_rgb( | ||
@@ -295,3 +220,2 @@ self, rgb: tuple[int, int, int], brightness: int | None = None | ||
| @retry_bluetooth_connection_error | ||
| async def set_rgbw( | ||
@@ -322,3 +246,2 @@ self, rgbw: tuple[int, int, int, int], brightness: int | None = None | ||
| @retry_bluetooth_connection_error | ||
| async def set_white(self, brightness: int) -> None: | ||
@@ -428,3 +351,3 @@ """Set rgb.""" | ||
| self._disconnected, | ||
| cached_services=self._cached_services, | ||
| use_services_cache=True, | ||
| ble_device_callback=lambda: self._ble_device, | ||
@@ -437,3 +360,3 @@ ) | ||
| resolved = self._resolve_characteristics(await client.get_services()) | ||
| self._cached_services = client.services if resolved else None | ||
| self._client = client | ||
@@ -594,2 +517,3 @@ self._reset_disconnect_timer() | ||
| @retry_bluetooth_connection_error(DEFAULT_ATTEMPTS) | ||
| async def _send_command_locked(self, commands: list[bytes]) -> None: | ||
@@ -633,4 +557,2 @@ """Send command to device and read response.""" | ||
| """Send command to device and read response.""" | ||
| if retry is None: | ||
| retry = self._retry_count | ||
| _LOGGER.debug( | ||
@@ -641,3 +563,2 @@ "%s: Sending commands %s", | ||
| ) | ||
| max_attempts = retry + 1 | ||
| if self._operation_lock.locked(): | ||
@@ -650,47 +571,26 @@ _LOGGER.debug( | ||
| async with self._operation_lock: | ||
| for attempt in range(max_attempts): | ||
| try: | ||
| await self._send_command_locked(commands) | ||
| return | ||
| except BleakNotFoundError: | ||
| _LOGGER.error( | ||
| "%s: device not found, no longer in range, or poor RSSI: %s", | ||
| self.name, | ||
| self.rssi, | ||
| exc_info=True, | ||
| ) | ||
| return None | ||
| except CharacteristicMissingError as ex: | ||
| if attempt == retry: | ||
| _LOGGER.error( | ||
| "%s: characteristic missing: %s; Stopping trying; RSSI: %s", | ||
| self.name, | ||
| ex, | ||
| self.rssi, | ||
| exc_info=True, | ||
| ) | ||
| return None | ||
| try: | ||
| await self._send_command_locked(commands) | ||
| return | ||
| except BleakNotFoundError: | ||
| _LOGGER.error( | ||
| "%s: device not found, no longer in range, or poor RSSI: %s", | ||
| self.name, | ||
| self.rssi, | ||
| exc_info=True, | ||
| ) | ||
| raise | ||
| except CharacteristicMissingError as ex: | ||
| _LOGGER.debug( | ||
| "%s: characteristic missing: %s; RSSI: %s", | ||
| self.name, | ||
| ex, | ||
| self.rssi, | ||
| exc_info=True, | ||
| ) | ||
| raise | ||
| except BLEAK_EXCEPTIONS: | ||
| _LOGGER.debug("%s: communication failed", self.name, exc_info=True) | ||
| raise | ||
| _LOGGER.debug( | ||
| "%s: characteristic missing: %s; RSSI: %s", | ||
| self.name, | ||
| ex, | ||
| self.rssi, | ||
| exc_info=True, | ||
| ) | ||
| except BLEAK_EXCEPTIONS as ex: | ||
| if attempt == retry: | ||
| _LOGGER.error( | ||
| "%s: communication failed; Stopping trying; RSSI: %s: %s", | ||
| self.name, | ||
| self.rssi, | ||
| ex, | ||
| exc_info=True, | ||
| ) | ||
| return None | ||
| _LOGGER.debug( | ||
| "%s: communication failed with:", self.name, exc_info=True | ||
| ) | ||
| raise RuntimeError("Unreachable") | ||
@@ -697,0 +597,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
53243
-6.67%720
-11.44%