async def stop_notify( self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID] ) -> None: """Deactivate notification/indication on a specified characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to deactivate notification/indication on, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. """ if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {} not found!".format(char_specifier)) status = await characteristic.obj.write_client_characteristic_configuration_descriptor_async( GattClientCharacteristicConfigurationDescriptorValue.NONE) if status != GattCommunicationStatus.SUCCESS: raise BleakError("Could not stop notify on {0}: {1}".format( characteristic.uuid, _communication_statues.get(status, ""))) else: event_handler_token = self._notification_callbacks.pop( characteristic.handle) characteristic.obj.remove_value_changed(event_handler_token)
async def stop_notify( self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID] ) -> None: """Deactivate notification/indication on a specified characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to deactivate notification/indication on, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. """ manager = self._central_manager_delegate if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {} not found!".format(char_specifier)) success = await manager.connected_peripheral_delegate.stopNotify_( characteristic.obj) if not success: raise BleakError("Could not stop notify on {0}: {1}".format( characteristic.uuid, success))
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. Returns: (bytearray) The read data. """ if not self.is_connected: raise BleakError("Not connected") descriptor = self.services.get_descriptor(handle) if not descriptor: raise BleakError( "Descriptor with handle {0} was not found!".format(handle)) reply = await self._bus.call( Message( destination=defs.BLUEZ_SERVICE, path=descriptor.path, interface=defs.GATT_DESCRIPTOR_INTERFACE, member="ReadValue", signature="a{sv}", body=[{}], )) assert_reply(reply) value = bytearray(reply.body[0]) logger.debug("Read Descriptor {0} | {1}: {2}".format( handle, descriptor.path, value)) return value
async def write_gatt_descriptor(self, handle: int, data: bytearray) -> None: """Perform a write operation on the specified GATT descriptor. Args: handle (int): The handle of the descriptor to read from. data (bytes or bytearray): The data to send. """ descriptor = self.services.get_descriptor(handle) if not descriptor: raise BleakError("Descriptor {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, loop=self.loop, ) if write_result.Status == GattCommunicationStatus.Success: logger.debug("Write Descriptor {0} : {1}".format(handle, data)) else: raise BleakError( "Could not write value {0} to descriptor {1}: {2}".format( data, descriptor.uuid, write_result.Status ) )
async def write_gatt_char(self, _uuid: str, data: bytearray, response: bool = False) -> None: """Perform a write operation of the specified GATT characteristic. Args: _uuid (str or UUID): The uuid of the characteristics to write to. data (bytes or bytearray): The data to send. response (bool): If write-with-response operation should be done. Defaults to `False`. """ characteristic = self.services.get_characteristic(str(_uuid)) if not characteristic: raise BleakError("Characteristic {0} was not found!".format(_uuid)) 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, loop=self.loop, ) if write_result.Status == GattCommunicationStatus.Success: logger.debug("Write Characteristic {0} : {1}".format(_uuid, data)) else: raise BleakError( "Could not write value {0} to characteristic {1}: {2}".format( data, characteristic.uuid, write_result.Status))
async def write_gatt_descriptor( self, handle: int, data: Union[bytes, bytearray, memoryview]) -> None: """Perform a write operation on the specified GATT descriptor. Args: handle (int): The handle of the descriptor to read from. data (bytes or bytearray): The data to send. """ if not self.is_connected: raise BleakError("Not connected") descriptor = self.services.get_descriptor(handle) if not descriptor: raise BleakError( "Descriptor with handle {0} was not found!".format(handle)) reply = await self._bus.call( Message( destination=defs.BLUEZ_SERVICE, path=descriptor.path, interface=defs.GATT_DESCRIPTOR_INTERFACE, member="WriteValue", signature="aya{sv}", body=[bytes(data), { "type": Variant("s", "command") }], )) assert_reply(reply) logger.debug("Write Descriptor {0} | {1}: {2}".format( handle, descriptor.path, data))
async def write_gatt_char( self, _uuid: str, data: bytearray, response: bool = False ) -> Any: """Perform a write operation of the specified characteristic. Args: _uuid (str or UUID): The uuid of the characteristics to start notification on. data (bytes or bytearray): The data to send. response (bool): If write response is desired. """ characteristic = self.characteristics.get(str(_uuid)) if not characteristic: raise BleakError("Characteristic {0} was not found!".format(_uuid)) write_results = await wrap_Task( self._bridge.WriteCharacteristicValueAsync( characteristic, data, response ), loop=self.loop, ) if write_results == GattCommunicationStatus.Success: logger.debug("Write Characteristic {0} : {1}".format(_uuid, data)) else: raise BleakError( "Could not write value {0} to characteristic {1}: {2}", data, characteristic.Uuid.ToString(), write_results, )
async def start_notify(self, _uuid: Union[str, uuid.UUID], callback: Callable[[str, Any], Any], **kwargs) -> None: """Activate notifications/indications on a characteristic. Callbacks must accept two inputs. The first will be a uuid string object and the second will be a bytearray. .. code-block:: python def callback(sender, data): print(f"{sender}: {data}") client.start_notify(char_uuid, callback) Args: _uuid (str or UUID): The uuid of the characteristics to start notification/indication on. callback (function): The function to be called on notification. """ characteristic = self.services.get_characteristic(str(_uuid)) if not characteristic: raise BleakError("Characteristic {0} was not found!".format(_uuid)) if self._notification_callbacks.get(str(_uuid)): await self.stop_notify(_uuid) status = await self._start_notify(characteristic.obj, callback) if status != GattCommunicationStatus.Success: raise BleakError("Could not start notify on {0}: {1}".format( characteristic.uuid, status))
async def write_gatt_descriptor(self, handle: int, data: bytearray) -> None: """Perform a write operation on the specified GATT descriptor. Args: handle (int): The handle of the descriptor to read from. data (bytes or bytearray): The data to send. """ manager = self._device_info.manager().delegate() descriptor = self.services.get_descriptor(handle) if not descriptor: raise BleakError("Descriptor {} was not found!".format(handle)) value = NSData.alloc().initWithBytes_length_(data, len(data)) success = await manager.connected_peripheral_delegate.writeDescriptor_value_( descriptor.obj, value ) if success: logger.debug("Write Descriptor {0} : {1}".format(handle, data)) else: raise BleakError( "Could not write value {0} to descriptor {1}: {2}".format( data, descriptor.uuid, success ) )
async def read_gatt_char(self, _uuid: str) -> bytearray: """Perform read operation on the specified characteristic. Args: _uuid (str or UUID): The uuid of the characteristics to start notification on. Returns: (bytearray) The read data. """ characteristic = self.characteristics.get(str(_uuid)) if not characteristic: raise BleakError("Characteristic {0} was not found!".format(_uuid)) read_results = await wrap_Task( self._bridge.ReadCharacteristicValueAsync(characteristic), loop=self.loop ) status, value = read_results.Item1, bytearray(read_results.Item2) if status == GattCommunicationStatus.Success: logger.debug("Read Characteristic {0} : {1}".format(_uuid, value)) else: raise BleakError( "Could not read characteristic value for {0}: {1}", characteristic.Uuid.ToString(), status, ) return value
async def start_notify(self, _uuid: str, callback: Callable[[str, Any], Any], **kwargs) -> None: """Activate notifications/indications on a characteristic. Callbacks must accept two inputs. The first will be a uuid string object and the second will be a bytearray. .. code-block:: python def callback(sender, data): print(f"{sender}: {data}") client.start_notify(char_uuid, callback) Args: _uuid (str or UUID): The uuid of the characteristics to start notification/indication on. callback (function): The function to be called on notification. """ _uuid = await self.get_appropriate_uuid(_uuid) characteristic = self.services.get_characteristic(_uuid) if not characteristic: raise BleakError("Characteristic {} not found!".format(_uuid)) success = await cbapp.central_manager_delegate.connected_peripheral_delegate.startNotify_cb_( characteristic.obj, callback) if not success: raise BleakError("Could not start notify on {0}: {1}".format( characteristic.uuid, success))
def _result_state_unthreadsafe(self, failure_str, source, data): logger.debug( f"Java state transfer {source} error={failure_str} data={data}") self.states[source] = (failure_str, *data) future = self.futures.get(source, None) if future is not None and not future.done(): if failure_str is None: future.set_result(data) else: future.set_exception(BleakError(source, failure_str, *data)) else: if failure_str is not None: # an error happened with nothing waiting for it exception = BleakError(source, failure_str, *data) namedfutures = [ namedfuture for namedfuture in self.futures.items() if not namedfuture[1].done() ] if len(namedfutures): # send it on existing requests for name, future in namedfutures: warnings.warn( f"Redirecting error without home to {name}") future.set_exception(exception) else: # send it on the event thread raise exception
def _ensure_success(result: Any, attr: Optional[str], fail_msg: str) -> Any: """ Ensures that *status* is ``GattCommunicationStatus.SUCCESS``, otherwise raises ``BleakError``. Args: result: The result returned by a WinRT API method. attr: The name of the attribute containing the result. fail_msg: A message to include in the exception. """ status = result.status if hasattr(result, "status") else result if status == GattCommunicationStatus.SUCCESS: return None if attr is None else getattr(result, attr) if status == GattCommunicationStatus.PROTOCOL_ERROR: err = PROTOCOL_ERROR_CODES.get(result.protocol_error, "Unknown") raise BleakError( f"{fail_msg}: Protocol Error 0x{result.protocol_error:02X}: {err}") if status == GattCommunicationStatus.ACCESS_DENIED: raise BleakError(f"{fail_msg}: Access Denied") if status == GattCommunicationStatus.UNREACHABLE: raise BleakError(f"{fail_msg}: Unreachable") raise BleakError( f"{fail_msg}: Unexpected status code 0x{result.status:02X}")
def get_device_object_path(hci_device, address): """Get object path for a Bluetooth device. Service org.bluez Interface org.bluez.Device1 Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX Args: hci_device (str): Which bluetooth adapter to connect with. address (str): The Bluetooth address of the bluetooth device. Returns: String representation of device object path on format `/org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX`. """ if not validate_mac_address(address): raise BleakError("{0} is not a valid Bluetooth address.".format(address)) if not validate_hci_device(hci_device): raise BleakError("{0} is not a valid HCI device.".format(hci_device)) # TODO: Join using urljoin? Or pathlib? return "/org/bluez/{0}/dev_{1}".format( hci_device, "_".join(address.split(":")).upper() )
async def write_gatt_char(self, _uuid: str, data: bytearray, response: bool = False) -> None: """Perform a write operation of the specified GATT characteristic. Args: _uuid (str or UUID): The uuid of the characteristics to write to. data (bytes or bytearray): The data to send. response (bool): If write-with-response operation should be done. Defaults to `False`. """ _uuid = await self.get_appropriate_uuid(_uuid) characteristic = self.services.get_characteristic(_uuid) if not characteristic: raise BleakError("Characteristic {} was not found!".format(_uuid)) value = NSData.alloc().initWithBytes_length_(data, len(data)) success = await cbapp.central_manager_delegate.connected_peripheral_delegate.writeCharacteristic_value_( characteristic.obj, value) if success: logger.debug("Write Characteristic {0} : {1}".format(_uuid, data)) else: raise BleakError( "Could not write value {0} to characteristic {1}: {2}".format( data, characteristic.uuid, success))
async def stop_notify( self, char_specifier: Union[BleakGATTCharacteristicP4Android, int, str, uuid.UUID], ) -> None: """Deactivate notification/indication on a specified characteristic. Args: char_specifier (BleakGATTCharacteristicP4Android, int, str or UUID): The characteristic to deactivate notification/indication on, specified by either integer handle, UUID or directly by the BleakGATTCharacteristicP4Android object representing it. """ if not isinstance(char_specifier, BleakGATTCharacteristicP4Android): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError(f"Characteristic {char_specifier} not found!") await self.write_gatt_descriptor( characteristic.notification_descriptor, defs.BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, ) if not self.__gatt.setCharacteristicNotification( characteristic.obj, False): raise BleakError( f"Failed to disable notification for characteristic {characteristic.uuid}" ) del self._subscriptions[characteristic.handle]
async def stop_notify( self, char_specifier: Union[BleakGATTCharacteristicBlueZDBus, int, str, UUID], ) -> None: """Deactivate notification/indication on a specified characteristic. Args: char_specifier (BleakGATTCharacteristicBlueZDBus, int, str or UUID): The characteristic to deactivate notification/indication on, specified by either integer handle, UUID or directly by the BleakGATTCharacteristicBlueZDBus object representing it. """ if not self.is_connected: raise BleakError("Not connected") if not isinstance(char_specifier, BleakGATTCharacteristicBlueZDBus): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {} not found!".format(char_specifier)) reply = await self._bus.call( Message( destination=defs.BLUEZ_SERVICE, path=characteristic.path, interface=defs.GATT_CHARACTERISTIC_INTERFACE, member="StopNotify", )) assert_reply(reply) self._notification_callbacks.pop(characteristic.path, None)
async def stop_notify(self, _uuid: Union[str, uuid.UUID]) -> None: """Deactivate notification/indication on a specified characteristic. Args: _uuid: The characteristic to stop notifying/indicating on. """ characteristic = self.services.get_characteristic(str(_uuid)) if not characteristic: raise BleakError("Characteristic {0} was not found!".format(_uuid)) status = await wrap_IAsyncOperation( IAsyncOperation[GattCommunicationStatus]( characteristic.obj. WriteClientCharacteristicConfigurationDescriptorAsync( getattr( GattClientCharacteristicConfigurationDescriptorValue, "None"))), return_type=GattCommunicationStatus, loop=self.loop, ) if status != GattCommunicationStatus.Success: raise BleakError("Could not stop notify on {0}: {1}".format( characteristic.uuid, status)) else: callback = self._callbacks.pop(characteristic.uuid) self._bridge.RemoveValueChangedCallback(characteristic.obj, callback)
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
async def write_gatt_char( self, char_specifier: Union[BleakGATTCharacteristicP4Android, int, str, uuid.UUID], data: bytearray, response: bool = False, ) -> None: """Perform a write operation on the specified GATT characteristic. Args: char_specifier (BleakGATTCharacteristicP4Android, int, str or UUID): The characteristic to write to, specified by either integer handle, UUID or directly by the BleakGATTCharacteristicP4Android object representing it. data (bytes or bytearray): The data to send. response (bool): If write-with-response operation should be done. Defaults to `False`. """ if not isinstance(char_specifier, BleakGATTCharacteristicP4Android): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError(f"Characteristic {char_specifier} was not found!") if ("write" not in characteristic.properties and "write-without-response" not in characteristic.properties): raise BleakError( f"Characteristic {str(characteristic.uuid)} does not support write operations!" ) if not response and "write-without-response" not in characteristic.properties: response = True # Force response here, since the device only supports that. if (response and "write" not in characteristic.properties and "write-without-response" in characteristic.properties): response = False logger.warning( "Characteristic %s does not support Write with response. Trying without..." % str(characteristic.uuid)) if response: characteristic.obj.setWriteType( defs.BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) else: characteristic.obj.setWriteType( defs.BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) characteristic.obj.setValue(data) await self.__callbacks.perform_and_wait( dispatchApi=self.__gatt.writeCharacteristic, dispatchParams=(characteristic.obj, ), resultApi=("onCharacteristicWrite", characteristic.handle), ) logger.debug( f"Write Characteristic {characteristic.uuid} | {characteristic.handle}: {data}" )
async def write_gatt_char( self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID], data: bytearray, response: bool = False, ) -> None: """Perform a write operation of the specified GATT characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to write to, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. data (bytes or bytearray): The data to send. response (bool): If write-with-response operation should be done. Defaults to `False`. """ if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {} was not found!".format(char_specifier)) 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)) else: if write_result.Status == GattCommunicationStatus.ProtocolError: raise BleakError( "Could not write value {0} to characteristic {1}: {2} (Error: 0x{3:02X}: {4})" .format( data, characteristic.uuid, _communication_statues.get(write_result.Status, ""), write_result.ProtocolError, CONTROLLER_ERROR_CODES.get(write_result.ProtocolError, "Unknown"), )) else: raise BleakError( "Could not write value {0} to characteristic {1}: {2}". format( data, characteristic.uuid, _communication_statues.get(write_result.Status, ""), ))
async def connect(self) -> bool: """Connect to the specified GATT server. Returns: Boolean representing connection status. """ # A Discover must have been run before connecting to any devices. Do a quick one here # to ensure that it has been done. await discover(timeout=0.1, loop=self.loop) # Create system bus self._bus = await txdbus_connect( reactor, busAddress="system").asFuture(self.loop) # TODO: Handle path errors from txdbus/dbus self._device_path = get_device_object_path(self.device, self.address) def _services_resolved_callback(message): iface, changed, invalidated = message.body is_resolved = defs.DEVICE_INTERFACE and changed.get( "ServicesResolved", False) if iface == is_resolved: logger.info("Services resolved.") self.services_resolved = True rule_id = await signals.listen_properties_changed( self._bus, self.loop, _services_resolved_callback) logger.debug("Connecting to BLE device @ {0} with {1}".format( self.address, self.device)) try: await self._bus.callRemote( self._device_path, "Connect", interface="org.bluez.Device1", destination="org.bluez", ).asFuture(self.loop) except RemoteError as e: raise BleakError(str(e)) if await self.is_connected(): logger.debug("Connection successful.") else: raise BleakError("Connection to {0} was not successful!".format( self.address)) # Get all services. This means making the actual connection. await self.get_services() properties = await self._get_device_properties() if not properties.get("Connected"): raise BleakError("Connection failed!") await self._bus.delMatch(rule_id).asFuture(self.loop) self._rules["PropChanged"] = await signals.listen_properties_changed( self._bus, self.loop, self._properties_changed_callback) return True
async def pair(self, protection_level: int = None, **kwargs) -> bool: """Attempts to pair with the device. Keyword Args: protection_level: Windows.Devices.Enumeration.DevicePairingProtectionLevel 1: None - Pair the device using no levels of protection. 2: Encryption - Pair the device using encryption. 3: EncryptionAndAuthentication - Pair the device using encryption and authentication. (This will not work in Bleak...) Returns: Boolean regarding success of pairing. """ if (self._requester.device_information.pairing.can_pair and not self._requester.device_information.pairing.is_paired): # Currently only supporting Just Works solutions... ceremony = DevicePairingKinds.CONFIRM_ONLY custom_pairing = self._requester.device_information.pairing.custom def handler(sender, args): args.accept() pairing_requested_token = custom_pairing.add_pairing_requested( handler) try: if protection_level: pairing_result = await custom_pairing.pair_async( ceremony, protection_level) else: pairing_result = await custom_pairing.pair_async(ceremony) except Exception as e: raise BleakError("Failure trying to pair with device!") from e finally: custom_pairing.remove_pairing_requested( pairing_requested_token) if pairing_result.status not in ( DevicePairingResultStatus.PAIRED, DevicePairingResultStatus.ALREADY_PAIRED, ): raise BleakError("Could not pair with device: {0}: {1}".format( pairing_result.status, _pairing_statuses.get(pairing_result.status), )) else: logger.info( "Paired to device with protection level {0}.".format( pairing_result.protection_level_used)) return True else: return self._requester.device_information.pairing.is_paired
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_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_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 write_gatt_char( self, char_specifier: Union[BleakGATTCharacteristic, int, str, uuid.UUID], data: Union[bytes, bytearray, memoryview], response: bool = False, ) -> None: """Perform a write operation of the specified GATT characteristic. Args: char_specifier (BleakGATTCharacteristic, int, str or UUID): The characteristic to write to, specified by either integer handle, UUID or directly by the BleakGATTCharacteristic object representing it. data (bytes or bytearray): The data to send. response (bool): If write-with-response operation should be done. Defaults to `False`. """ if not isinstance(char_specifier, BleakGATTCharacteristic): characteristic = self.services.get_characteristic(char_specifier) else: characteristic = char_specifier if not characteristic: raise BleakError( "Characteristic {} was not found!".format(char_specifier)) response = (GattWriteOption.WRITE_WITH_RESPONSE if response else GattWriteOption.WRITE_WITHOUT_RESPONSE) write_result = await characteristic.obj.write_value_with_result_async( CryptographicBuffer.create_from_byte_array(list(data)), response) if write_result.status == GattCommunicationStatus.SUCCESS: logger.debug("Write Characteristic {0} : {1}".format( characteristic.uuid, data)) else: if write_result.status == GattCommunicationStatus.PROTOCOL_ERROR: raise BleakError( "Could not write value {0} to characteristic {1}: {2} (Error: 0x{3:02X}: {4})" .format( data, characteristic.uuid, _communication_statues.get(write_result.status, ""), write_result.protocol_error, CONTROLLER_ERROR_CODES.get(write_result.protocol_error, "Unknown"), )) else: raise BleakError( "Could not write value {0} to characteristic {1}: {2}". format( data, characteristic.uuid, _communication_statues.get(write_result.status, ""), ))
async def connect(self) -> bool: """Connect to the specified GATT server. Returns: Boolean representing connection status. """ # Try to find the desired device. devices = await discover(2.0, loop=self.loop) sought_device = list( filter(lambda x: x.address.upper() == self.address.upper(), devices) ) if len(sought_device): self._device_info = sought_device[0].details else: raise BleakError( "Device with address {0} was " "not found.".format(self.address) ) logger.debug("Connecting to BLE device @ {0}".format(self.address)) self._requester = await wrap_IAsyncOperation( IAsyncOperation[BluetoothLEDevice]( BluetoothLEDevice.FromBluetoothAddressAsync( UInt64(self._device_info.BluetoothAddress) ) ), return_type=BluetoothLEDevice, loop=self.loop, ) def _ConnectionStatusChanged_Handler(sender, args): logger.debug("_ConnectionStatusChanged_Handler: " + args.ToString()) self._requester.ConnectionStatusChanged += _ConnectionStatusChanged_Handler # Obtain services, which also leads to connection being established. await self.get_services() await asyncio.sleep(0.2, loop=self.loop) connected = await self.is_connected() if connected: logger.debug("Connection successful.") else: raise BleakError( "Connection to {0} was not successful!".format(self.address) ) return connected
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
def did_update_value_for_characteristic( self, peripheral: CBPeripheral, characteristic: CBCharacteristic, value: NSData, error: Optional[NSError], ): c_handle = characteristic.handle() if error is None: notify_callback = self._characteristic_notify_callbacks.get( c_handle) if notify_callback: notify_callback(c_handle, bytearray(value)) future = self._characteristic_read_futures.get(c_handle) if not future: return # only expected on read if error is not None: exception = BleakError( f"Failed to read characteristic {c_handle}: {error}") future.set_exception(exception) else: logger.debug("Read characteristic value") future.set_result(None)