async def wrap_IAsyncOperation(op, return_type, loop): """Enables await on .NET Task using asyncio.Event and a lambda callback. Args: task (System.Threading.Tasks.Task): .NET async task object to await upon. loop (Event Loop): The event loop to await on the Task in. Returns: The results of the the .NET Task. """ done = asyncio.Event() # Register AsyncOperationCompletedHandler callback that triggers the above asyncio.Event. op.Completed = AsyncOperationCompletedHandler[return_type]( lambda x, y: loop.call_soon_threadsafe(done.set) ) # Wait for callback. await done.wait() if op.Status == AsyncStatus.Completed: return op.GetResults() elif op.Status == AsyncStatus.Error: # Exception occurred. Wrap it in BleakDotNetTaskError # to make it catchable. raise BleakDotNetTaskError(op.ErrorCode.ToString()) else: # TODO: Handle IsCancelled. raise BleakDotNetTaskError("IAsyncOperation Status: {0}".format(op.Status))
async def wrap_IAsyncOperation(op: IAsyncOperation, return_type): """Enables await on .NET Task using asyncio.Event and a lambda callback. Args: op (Windows.Foundation.IAsyncOperation[TResult]): .NET async operation object to await. result_type (TResult): The .NET type of the result of the async operation. Returns: The results of the the .NET Task. """ loop = asyncio.get_event_loop() done = asyncio.Event() # Register AsyncOperationCompletedHandler callback that triggers the above asyncio.Event. op.Completed = AsyncOperationCompletedHandler[return_type]( lambda x, y: loop.call_soon_threadsafe(done.set) ) # Wait for callback. await done.wait() if op.Status == AsyncStatus.Completed: return op.GetResults() elif op.Status == AsyncStatus.Error: # Exception occurred. Wrap it in BleakDotNetTaskError # to make it catchable. raise BleakDotNetTaskError(op.ErrorCode.ToString()) else: # TODO: Handle IsCancelled. raise BleakDotNetTaskError("IAsyncOperation Status: {0}".format(op.Status))
async def get_services(self) -> BleakGATTServiceCollection: """Get all services registered for this GATT server. Returns: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ # Return the Service Collection. if self._services_resolved: return self.services else: logger.debug("Get Services...") services_result = await wrap_IAsyncOperation( IAsyncOperation[GattDeviceServicesResult]( self._requester.GetGattServicesAsync()), return_type=GattDeviceServicesResult, loop=self.loop, ) if services_result.Status != GattCommunicationStatus.Success: raise BleakDotNetTaskError("Could not get GATT services.") # TODO: Check if fetching yeilds failures... for service in services_result.Services: characteristics_result = await wrap_IAsyncOperation( IAsyncOperation[GattCharacteristicsResult]( service.GetCharacteristicsAsync()), return_type=GattCharacteristicsResult, loop=self.loop, ) self.services.add_service(BleakGATTServiceDotNet(service)) if characteristics_result.Status != GattCommunicationStatus.Success: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}.".format( service)) for characteristic in characteristics_result.Characteristics: descriptors_result = await wrap_IAsyncOperation( IAsyncOperation[GattDescriptorsResult]( characteristic.GetDescriptorsAsync()), return_type=GattDescriptorsResult, loop=self.loop, ) self.services.add_characteristic( BleakGATTCharacteristicDotNet(characteristic)) if descriptors_result.Status != GattCommunicationStatus.Success: raise BleakDotNetTaskError( "Could not get GATT descriptors for {0}.".format( characteristic)) for descriptor in list(descriptors_result.Descriptors): self.services.add_descriptor( BleakGATTDescriptorDotNet( descriptor, characteristic.Uuid.ToString())) self._services_resolved = True return self.services
def result(self): if self.operation.Status == AsyncStatus.Completed: return self.operation.GetResults() elif self.operation.Status == AsyncStatus.Error: # Exception occurred. Wrap it in BleakDotNetTaskError # to make it catchable. raise BleakDotNetTaskError(self.operation.ErrorCode.ToString()) else: # TODO: Handle IsCancelled. raise BleakDotNetTaskError("IAsyncOperation Status: {0}".format( self.operation.Status))
async def wrap_Task(task, loop): """Enables await on .NET Task using asyncio.Event and a lambda callback. Args: task (System.Threading.Tasks.Task): .NET async task object to await upon. loop (Event Loop): The event loop to await on the Task in. Returns: The results of the the .NET Task. """ done = asyncio.Event() # Register Action<Task> callback that triggers the above asyncio.Event. task.ContinueWith( Action[Task](lambda x: loop.call_soon_threadsafe(done.set))) # Wait for callback. await done.wait() # TODO: Handle IsCancelled. if task.IsFaulted: # Exception occurred. Wrap it in BleakDotNetTaskError # to make it catchable. raise BleakDotNetTaskError(task.Exception.ToString()) return task.Result
async def read_gatt_char( self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID], **kwargs, ) -> bytearray: """Perform read operation on the specified GATT characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to read from, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. Keyword Args: use_cached (bool): ``False`` forces Windows to read the value from the device again and not use its own cached value. Defaults to ``False``. Returns: (bytearray) The read data. """ use_cached = kwargs.get("use_cached", False) if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {0} was not found!".format(char_specifier)) read_result = await wrap_IAsyncOperation( IAsyncOperation[GattReadResult](characteristic.obj.ReadValueAsync( BluetoothCacheMode. Cached if use_cached else BluetoothCacheMode.Uncached)), return_type=GattReadResult, ) if read_result.Status == GattCommunicationStatus.Success: with BleakDataReader(read_result.Value) as reader: value = bytearray(reader.read()) logger.debug("Read Characteristic {0} : {1}".format( characteristic.uuid, value)) else: if read_result.Status == GattCommunicationStatus.ProtocolError: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X}: {3})" .format( characteristic.uuid, _communication_statues.get(read_result.Status, ""), read_result.ProtocolError, CONTROLLER_ERROR_CODES.get(read_result.ProtocolError, "Unknown"), )) else: raise BleakError( "Could not read characteristic value for {0}: {1}".format( characteristic.uuid, _communication_statues.get(read_result.Status, ""), )) return value
def result(self): # TODO: Handle IsCancelled. if self.task.IsFaulted: # Exception occurred. Wrap it in BleakDotNetTaskError # to make it catchable. raise BleakDotNetTaskError(self.task.Exception.ToString()) return self.task.Result
async def read_gatt_char(self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID], use_cached=False, **kwargs) -> bytearray: """Perform read operation on the specified GATT characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to read from, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. use_cached (bool): `False` forces Windows to read the value from the device again and not use its own cached value. Defaults to `False`. Returns: (bytearray) The read data. """ if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {0} was not found!".format(char_specifier)) read_result = await wrap_IAsyncOperation( IAsyncOperation[GattReadResult](characteristic.obj.ReadValueAsync( BluetoothCacheMode. Cached if use_cached else BluetoothCacheMode.Uncached)), return_type=GattReadResult, loop=self.loop, ) 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) logger.debug("Read Characteristic {0} : {1}".format( characteristic.uuid, value)) else: if read_result.Status == GattCommunicationStatus.ProtocolError: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X})" .format( characteristic.uuid, _communication_statues.get(read_result.Status, ""), read_result.ProtocolError, )) else: raise BleakError( "Could not read characteristic value for {0}: {1}".format( characteristic.uuid, _communication_statues.get(read_result.Status, ""), )) return value
async def read_gatt_char(self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID], **kwargs) -> bytearray: """Perform read operation on the specified GATT characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to read from, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. Keyword Args: use_cached (bool): ``False`` forces Windows to read the value from the device again and not use its own cached value. Defaults to ``False``. Returns: (bytearray) The read data. """ use_cached = kwargs.get("use_cached", False) if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {0} was not found!".format(char_specifier)) read_result = await characteristic.obj.read_value_async( BluetoothCacheMode.CACHED if use_cached else BluetoothCacheMode. UNCACHED) if read_result.status == GattCommunicationStatus.SUCCESS: value = bytearray( CryptographicBuffer.copy_to_byte_array(read_result.value)) logger.debug("Read Characteristic {0} : {1}".format( characteristic.uuid, value)) else: if read_result.status == GattCommunicationStatus.PROTOCOL_ERROR: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X}: {3})" .format( characteristic.uuid, _communication_statues.get(read_result.status, ""), read_result.protocol_error, CONTROLLER_ERROR_CODES.get(read_result.protocol_error, "Unknown"), )) else: raise BleakError( "Could not read characteristic value for {0}: {1}".format( characteristic.uuid, _communication_statues.get(read_result.status, ""), )) return value
async def read_gatt_descriptor( self, handle: int, use_cached=False, **kwargs ) -> bytearray: """Perform read operation on the specified GATT descriptor. Args: handle (int): The handle of the descriptor to read from. use_cached (bool): `False` forces Windows to read the value from the device again and not use its own cached value. Defaults to `False`. Returns: (bytearray) The read data. """ descriptor = self.services.get_descriptor(handle) if not descriptor: raise BleakError("Descriptor with handle {0} was not found!".format(handle)) read_result = await wrap_IAsyncOperation( IAsyncOperation[GattReadResult]( descriptor.obj.ReadValueAsync( BluetoothCacheMode.Cached if use_cached else BluetoothCacheMode.Uncached ) ), return_type=GattReadResult, ) if read_result.Status == GattCommunicationStatus.Success: 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: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X}: {3})".format( descriptor.uuid, _communication_statues.get(read_result.Status, ""), read_result.ProtocolError, CONTROLLER_ERROR_CODES.get( read_result.ProtocolError, "Unknown" ), ) ) else: raise BleakError( "Could not read Descriptor value for {0}: {1}".format( descriptor.uuid, _communication_statues.get(read_result.Status, ""), ) ) return value
async def read_gatt_descriptor(self, handle: int, **kwargs) -> bytearray: """Perform read operation on the specified GATT descriptor. Args: handle (int): The handle of the descriptor to read from. Keyword Args: use_cached (bool): `False` forces Windows to read the value from the device again and not use its own cached value. Defaults to `False`. Returns: (bytearray) The read data. """ use_cached = kwargs.get("use_cached", False) descriptor = self.services.get_descriptor(handle) if not descriptor: raise BleakError( "Descriptor with handle {0} was not found!".format(handle)) read_result = await descriptor.obj.read_value_async( BluetoothCacheMode.CACHED if use_cached else BluetoothCacheMode. UNCACHED) if read_result.status == GattCommunicationStatus.SUCCESS: value = bytearray( CryptographicBuffer.copy_to_byte_array(read_result.value)) logger.debug("Read Descriptor {0} : {1}".format(handle, value)) else: if read_result.status == GattCommunicationStatus.PROTOCOL_ERROR: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X}: {3})" .format( descriptor.uuid, _communication_statues.get(read_result.status, ""), read_result.protocol_error, CONTROLLER_ERROR_CODES.get(read_result.protocol_error, "Unknown"), )) else: raise BleakError( "Could not read Descriptor value for {0}: {1}".format( descriptor.uuid, _communication_statues.get(read_result.status, ""), )) return value
async def get_services(self) -> dict: # Return a list of all services for the device. if self.services: return self.services else: logger.debug("Get Services...") services = await wrap_Task( self._bridge.GetGattServicesAsync(self._requester), loop=self.loop ) if services.Status == GattCommunicationStatus.Success: self.services = {s.Uuid.ToString(): s for s in services.Services} else: raise BleakDotNetTaskError("Could not get GATT services.") # TODO: Could this be sped up? await asyncio.gather( *[ asyncio.ensure_future(self._get_chars(service), loop=self.loop) for service_uuid, service in self.services.items() ] ) self._services_resolved = True return self.services
async def get_services(self) -> BleakGATTServiceCollection: """Get all services registered for this GATT server. Returns: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ # Return the Service Collection. if self._services_resolved: return self.services else: logger.debug("Get Services...") services_result = await wrap_IAsyncOperation( IAsyncOperation[GattDeviceServicesResult]( self._requester.GetGattServicesAsync()), return_type=GattDeviceServicesResult, ) if services_result.Status != GattCommunicationStatus.Success: if services_result.Status == GattCommunicationStatus.ProtocolError: raise BleakDotNetTaskError( "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"), )) else: raise BleakDotNetTaskError( "Could not get GATT services: {0}".format( _communication_statues.get(services_result.Status, ""))) for service in services_result.Services: characteristics_result = await wrap_IAsyncOperation( IAsyncOperation[GattCharacteristicsResult]( service.GetCharacteristicsAsync()), return_type=GattCharacteristicsResult, ) self.services.add_service(BleakGATTServiceDotNet(service)) if characteristics_result.Status != GattCommunicationStatus.Success: if (characteristics_result.Status == GattCommunicationStatus.ProtocolError): raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X}: {3})" .format( service, _communication_statues.get( characteristics_result.Status, ""), characteristics_result.ProtocolError, CONTROLLER_ERROR_CODES.get( characteristics_result.ProtocolError, "Unknown"), )) else: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1}". format( service, _communication_statues.get( characteristics_result.Status, ""), )) for characteristic in characteristics_result.Characteristics: descriptors_result = await wrap_IAsyncOperation( IAsyncOperation[GattDescriptorsResult]( characteristic.GetDescriptorsAsync()), return_type=GattDescriptorsResult, ) self.services.add_characteristic( BleakGATTCharacteristicDotNet(characteristic)) if descriptors_result.Status != GattCommunicationStatus.Success: if (characteristics_result.Status == GattCommunicationStatus.ProtocolError): raise BleakDotNetTaskError( "Could not get GATT descriptors for {0}: {1} (Error: 0x{2:02X}: {3})" .format( service, _communication_statues.get( descriptors_result.Status, ""), descriptors_result.ProtocolError, CONTROLLER_ERROR_CODES.get( descriptors_result.ProtocolError, "Unknown"), )) else: raise BleakDotNetTaskError( "Could not get GATT descriptors for {0}: {1}". format( characteristic, _communication_statues.get( descriptors_result.Status, ""), )) for descriptor in list(descriptors_result.Descriptors): self.services.add_descriptor( BleakGATTDescriptorDotNet( descriptor, characteristic.Uuid.ToString(), int(characteristic.AttributeHandle), )) logger.info("Services resolved for %s", str(self)) self._services_resolved = True return self.services
async def get_services(self, **kwargs) -> BleakGATTServiceCollection: """Get all services registered for this GATT server. Keyword Args: use_cached (bool): If set to `True`, then the OS level BLE cache is used for getting services, characteristics and descriptors. Returns: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ use_cached = kwargs.get("use_cached", self._use_cached) # Return the Service Collection. if self._services_resolved: return self.services else: logger.debug("Get Services...") services_result = await self._requester.get_gatt_services_async( BluetoothCacheMode. CACHED if use_cached else BluetoothCacheMode.UNCACHED) if services_result.status != GattCommunicationStatus.SUCCESS: if services_result.status == GattCommunicationStatus.PROTOCOL_ERROR: raise BleakDotNetTaskError( "Could not get GATT services: {0} (Error: 0x{1:02X}: {2})" .format( _communication_statues.get(services_result.status, ""), services_result.protocol_error, CONTROLLER_ERROR_CODES.get( services_result.protocol_error, "Unknown"), )) else: raise BleakDotNetTaskError( "Could not get GATT services: {0}".format( _communication_statues.get(services_result.status, ""))) for service in services_result.services: characteristics_result = await service.get_characteristics_async( BluetoothCacheMode. CACHED if use_cached else BluetoothCacheMode.UNCACHED) self.services.add_service(BleakGATTServiceWinRT(service)) if characteristics_result.status != GattCommunicationStatus.SUCCESS: if (characteristics_result.status == GattCommunicationStatus.PROTOCOL_ERROR): raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1} (Error: 0x{2:02X}: {3})" .format( service, _communication_statues.get( characteristics_result.status, ""), characteristics_result.protocol_error, CONTROLLER_ERROR_CODES.get( characteristics_result.protocol_error, "Unknown"), )) else: raise BleakDotNetTaskError( "Could not get GATT characteristics for {0}: {1}". format( service, _communication_statues.get( characteristics_result.status, ""), )) for characteristic in characteristics_result.characteristics: descriptors_result = await characteristic.get_descriptors_async( BluetoothCacheMode. CACHED if use_cached else BluetoothCacheMode.UNCACHED) self.services.add_characteristic( BleakGATTCharacteristicWinRT(characteristic)) if descriptors_result.status != GattCommunicationStatus.SUCCESS: if (characteristics_result.status == GattCommunicationStatus.PROTOCOL_ERROR): raise BleakDotNetTaskError( "Could not get GATT descriptors for {0}: {1} (Error: 0x{2:02X}: {3})" .format( service, _communication_statues.get( descriptors_result.status, ""), descriptors_result.protocol_error, CONTROLLER_ERROR_CODES.get( descriptors_result.protocol_error, "Unknown"), )) else: raise BleakDotNetTaskError( "Could not get GATT descriptors for {0}: {1}". format( characteristic, _communication_statues.get( descriptors_result.status, ""), )) for descriptor in list(descriptors_result.descriptors): self.services.add_descriptor( BleakGATTDescriptorWinRT( descriptor, str(characteristic.uuid), characteristic.attribute_handle, )) logger.info("Services resolved for %s", str(self)) self._services_resolved = True return self.services