def _received_handler( self, sender: BluetoothLEAdvertisementWatcher, event_args: BluetoothLEAdvertisementReceivedEventArgs, ): if sender == self.watcher: logger.debug("Received {0}.".format( _format_event_args(event_args))) if (event_args.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse): if event_args.BluetoothAddress not in self._scan_responses: self._scan_responses[ event_args.BluetoothAddress] = event_args else: if event_args.BluetoothAddress not in self._devices: self._devices[event_args.BluetoothAddress] = event_args if self._callback is None: return # Get a "BLEDevice" from parse_event args device = self.parse_eventargs(event_args) # Decode service data service_data = {} # 0x16 is service data with 16-bit UUID for section in event_args.Advertisement.GetSectionsByType(0x16): with BleakDataReader(section.Data) as reader: data = reader.read() service_data[ f"0000{data[1]:02x}{data[0]:02x}-0000-1000-8000-00805f9b34fb"] = data[ 2:] # 0x20 is service data with 32-bit UUID for section in event_args.Advertisement.GetSectionsByType(0x20): with BleakDataReader(section.Data) as reader: data = reader.read() service_data[ f"{data[3]:02x}{data[2]:02x}{data[1]:02x}{data[0]:02x}-0000-1000-8000-00805f9b34fb"] = data[ 4:] # 0x21 is service data with 128-bit UUID for section in event_args.Advertisement.GetSectionsByType(0x21): with BleakDataReader(section.Data) as reader: data = reader.read() service_data[str(UUID(bytes=data[15::-1]))] = data[16:] # Use the BLEDevice to populate all the fields for the advertisement data to return advertisement_data = AdvertisementData( local_name=event_args.Advertisement.LocalName, manufacturer_data=device.metadata["manufacturer_data"], service_data=service_data, service_uuids=device.metadata["uuids"], platform_data=(sender, event_args), ) self._callback(device, advertisement_data)
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 dotnet_notification_parser(sender: Any, args: Any): # Return only the UUID string representation as sender. # Also do a conversion from System.Bytes[] to bytearray. with BleakDataReader(args.CharacteristicValue) as reader: output = reader.read() return loop.call_soon_threadsafe(func, sender.AttributeHandle, bytearray(output))
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
def parse_eventargs(event_args): bdaddr = _format_bdaddr(event_args.BluetoothAddress) uuids = [] for u in event_args.Advertisement.ServiceUuids: uuids.append(u.ToString()) data = {} for m in event_args.Advertisement.ManufacturerData: with BleakDataReader(m.Data) as reader: data[m.CompanyId] = reader.read() local_name = event_args.Advertisement.LocalName return BLEDevice(bdaddr, local_name, event_args, uuids=uuids, manufacturer_data=data)
async def discover(timeout: float = 5.0, **kwargs) -> List[BLEDevice]: """Perform a Bluetooth LE Scan using Windows.Devices.Bluetooth.Advertisement Args: timeout (float): Time to scan for. Keyword Args: SignalStrengthFilter (Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter): A BluetoothSignalStrengthFilter object used for configuration of Bluetooth LE advertisement filtering that uses signal strength-based filtering. AdvertisementFilter (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter): A BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE advertisement filtering that uses payload section-based filtering. string_output (bool): If set to false, ``discover`` returns .NET device objects instead. Returns: List of strings or objects found. """ signal_strength_filter = kwargs.get("SignalStrengthFilter", None) advertisement_filter = kwargs.get("AdvertisementFilter", None) watcher = BluetoothLEAdvertisementWatcher() devices = {} scan_responses = {} def _format_bdaddr(a): return ":".join("{:02X}".format(x) for x in a.to_bytes(6, byteorder="big")) def _format_event_args(e): try: return "{0}: {1}".format( _format_bdaddr(e.BluetoothAddress), e.Advertisement.LocalName or "Unknown", ) except Exception: return e.BluetoothAddress def AdvertisementWatcher_Received(sender, e): if sender == watcher: logger.debug("Received {0}.".format(_format_event_args(e))) if e.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse: if e.BluetoothAddress not in scan_responses: scan_responses[e.BluetoothAddress] = e else: if e.BluetoothAddress not in devices: devices[e.BluetoothAddress] = e def AdvertisementWatcher_Stopped(sender, e): if sender == watcher: logger.debug("{0} devices found. Watcher status: {1}.".format( len(devices), watcher.Status)) watcher.Received += AdvertisementWatcher_Received watcher.Stopped += AdvertisementWatcher_Stopped watcher.ScanningMode = BluetoothLEScanningMode.Active if signal_strength_filter is not None: watcher.SignalStrengthFilter = signal_strength_filter if advertisement_filter is not None: watcher.AdvertisementFilter = advertisement_filter # Watcher works outside of the Python process. watcher.Start() await asyncio.sleep(timeout) watcher.Stop() try: watcher.Received -= AdvertisementWatcher_Received watcher.Stopped -= AdvertisementWatcher_Stopped except Exception as e: logger.debug("Could not remove event handlers: {0}...".format(e)) found = [] for d in list(devices.values()): bdaddr = _format_bdaddr(d.BluetoothAddress) uuids = [] for u in d.Advertisement.ServiceUuids: uuids.append(u.ToString()) data = {} for m in d.Advertisement.ManufacturerData: with BleakDataReader(m.Data) as reader: data[m.CompanyId] = reader.read() local_name = d.Advertisement.LocalName if not local_name and d.BluetoothAddress in scan_responses: local_name = scan_responses[ d.BluetoothAddress].Advertisement.LocalName found.append( BLEDevice( bdaddr, local_name, d, uuids=uuids, manufacturer_data=data, )) return found