diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 529c2267..74d402ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,15 @@ and this project adheres to `Semantic Versioning bool: def handle_services_changed(): if not self._services_changed_events: - logger.warn("%s: unhandled services changed event", self.address) + logger.warning("%s: unhandled services changed event", self.address) else: for event in self._services_changed_events: event.set() @@ -568,7 +574,10 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: services_changed_event.wait() ) self._services_changed_events.append(services_changed_event) - get_services_task = self._requester.get_gatt_services_async(*args) + + get_services_task = FutureLike( + self._requester.get_gatt_services_async(*args) + ) try: await asyncio.wait( @@ -587,10 +596,10 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: "%s: restarting get services due to services changed event", self.address, ) - args = [BluetoothCacheMode.UNCACHED] + args = [BluetoothCacheMode.CACHED] services: Sequence[GattDeviceService] = _ensure_success( - get_services_task.get_results(), + get_services_task.result(), "services", "Could not get GATT services", ) @@ -885,3 +894,64 @@ async def stop_notify( event_handler_token = self._notification_callbacks.pop(characteristic.handle) characteristic.obj.remove_value_changed(event_handler_token) + + +class FutureLike: + """ + Wraps a WinRT IAsyncOperation in a "future-like" object so that it can + be passed to Python APIs. + + Needed until https://github.com/pywinrt/pywinrt/issues/14 + """ + + _asyncio_future_blocking = True + + def __init__(self, async_result: IAsyncOperation) -> None: + self._async_result = async_result + self._callbacks = [] + self._loop = asyncio.get_running_loop() + + def call_callbacks(op: IAsyncOperation, status: AsyncStatus): + for c in self._callbacks: + c(self) + + async_result.completed = functools.partial( + self._loop.call_soon_threadsafe, call_callbacks + ) + + def result(self) -> Any: + return self._async_result.get_results() + + def done(self) -> bool: + return self._async_result.status != AsyncStatus.STARTED + + def cancelled(self) -> bool: + return self._async_result.status == AsyncStatus.CANCELED + + def add_done_callback(self, callback, *, context=None) -> None: + self._callbacks.append(callback) + + def remove_done_callback(self, callback) -> None: + self._callbacks.remove(callback) + + def cancel(self, msg=None) -> bool: + if self._async_result.status != AsyncStatus.STARTED: + return False + self._async_result.cancel() + return True + + def exception(self) -> Optional[Exception]: + if self._async_result.status == AsyncStatus.STARTED: + raise asyncio.InvalidStateError + if self._async_result.status == AsyncStatus.COMPLETED: + return None + if self._async_result.status == AsyncStatus.CANCELED: + raise asyncio.CancelledError + if self._async_result.status == AsyncStatus.ERROR: + try: + pythonapi.PyErr_SetFromWindowsErr(self._async_result.error_code) + except OSError as e: + return e + + def get_loop(self) -> asyncio.AbstractEventLoop: + return self._loop diff --git a/pyproject.toml b/pyproject.toml index 16e6fddb..1a6a1c11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bleak" -version = "0.19.1" +version = "0.19.2" description = "Bluetooth Low Energy platform Agnostic Klient" authors = ["Henrik Blidh "] license = "MIT"