async def start(self): self._reactor = get_reactor(self.loop) self._bus = await client.connect(self._reactor, "system").asFuture(self.loop) # Add signal listeners self._rules.append( await self._bus.addMatch( self.parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesAdded", ).asFuture(self.loop) ) self._rules.append( await self._bus.addMatch( self.parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesRemoved", ).asFuture(self.loop) ) self._rules.append( await self._bus.addMatch( self.parse_msg, interface="org.freedesktop.DBus.Properties", member="PropertiesChanged", ).asFuture(self.loop) ) # Find the HCI device to use for scanning and get cached device properties objects = await self._bus.callRemote( "/", "GetManagedObjects", interface=defs.OBJECT_MANAGER_INTERFACE, destination=defs.BLUEZ_SERVICE, ).asFuture(self.loop) self._adapter_path, self._interface = _filter_on_adapter(objects, self._device) self._cached_devices = dict(_filter_on_device(objects)) # Apply the filters await self._bus.callRemote( self._adapter_path, "SetDiscoveryFilter", interface="org.bluez.Adapter1", destination="org.bluez", signature="a{sv}", body=[self._filters], ).asFuture(self.loop) # Start scanning await self._bus.callRemote( self._adapter_path, "StartDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(self.loop)
async def discover(timeout=5.0, loop=None, **kwargs): """Discover nearby Bluetooth Low Energy devices. For possible values for `filters`, see the parameters to the ``SetDiscoveryFilter`` method in the `BlueZ docs <https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt?h=5.48&id=0d1e3b9c5754022c779da129025d493a198d49cf>`_ The ``Transport`` parameter is always set to ``le`` by default in Bleak. Args: timeout (float): Duration to scan for. loop (asyncio.AbstractEventLoop): Optional event loop to use. Keyword Args: device (str): Bluetooth device to use for discovery. filters (dict): A dict of filters to be applied on discovery. Returns: List of tuples containing name, address and signal strength of nearby devices. """ device = kwargs.get("device", "hci0") loop = loop if loop else asyncio.get_event_loop() cached_devices = {} devices = {} rules = list() reactor = get_reactor(loop) # Discovery filters filters = kwargs.get("filters", {}) filters["Transport"] = "le" def parse_msg(message): if message.member == "InterfacesAdded": msg_path = message.body[0] try: device_interface = message.body[1].get("org.bluez.Device1", {}) except Exception as e: raise e devices[msg_path] = ({ **devices[msg_path], **device_interface } if msg_path in devices else device_interface) elif message.member == "PropertiesChanged": iface, changed, invalidated = message.body if iface != defs.DEVICE_INTERFACE: return msg_path = message.path # the PropertiesChanged signal only sends changed properties, so we # need to get remaining properties from cached_devices. However, we # don't want to add all cached_devices to the devices dict since # they may not actually be nearby or powered on. if msg_path not in devices and msg_path in cached_devices: devices[msg_path] = cached_devices[msg_path] devices[msg_path] = ({ **devices[msg_path], **changed } if msg_path in devices else changed) elif (message.member == "InterfacesRemoved" and message.body[1][0] == defs.BATTERY_INTERFACE): logger.info("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) return else: msg_path = message.path logger.info("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) logger.info("{0}, {1} ({2} dBm), Object Path: {3}".format( *_device_info(msg_path, devices.get(msg_path)))) bus = await client.connect(reactor, "system").asFuture(loop) # Add signal listeners rules.append(await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesAdded", path_namespace="/org/bluez", ).asFuture(loop)) rules.append(await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesRemoved", path_namespace="/org/bluez", ).asFuture(loop)) rules.append(await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.Properties", member="PropertiesChanged", path_namespace="/org/bluez", ).asFuture(loop)) # Find the HCI device to use for scanning and get cached device properties objects = await bus.callRemote( "/", "GetManagedObjects", interface=defs.OBJECT_MANAGER_INTERFACE, destination=defs.BLUEZ_SERVICE, ).asFuture(loop) adapter_path, interface = _filter_on_adapter(objects, device) cached_devices = dict(_filter_on_device(objects)) # Running Discovery loop. await bus.callRemote( adapter_path, "SetDiscoveryFilter", interface="org.bluez.Adapter1", destination="org.bluez", signature="a{sv}", body=[filters], ).asFuture(loop) await bus.callRemote( adapter_path, "StartDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) await asyncio.sleep(timeout) await bus.callRemote( adapter_path, "StopDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) # Reduce output. discovered_devices = [] for path, props in devices.items(): if not props: logger.debug( "Disregarding %s since no properties could be obtained." % path) continue name, address, _, path = _device_info(path, props) if address is None: continue uuids = props.get("UUIDs", []) manufacturer_data = props.get("ManufacturerData", {}) discovered_devices.append( BLEDevice( address, name, { "path": path, "props": props }, uuids=uuids, manufacturer_data=manufacturer_data, )) for rule in rules: await bus.delMatch(rule).asFuture(loop) # Try to disconnect the System Bus. try: bus.disconnect() except Exception as e: logger.error("Attempt to disconnect system bus failed: {0}".format(e)) return discovered_devices
async def connect(self, **kwargs) -> bool: """Connect to the specified GATT server. Keyword Args: timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. Returns: Boolean representing connection status. """ # A Discover must have been run before connecting to any devices. # Find the desired device before trying to connect. timeout = kwargs.get("timeout", self._timeout) if self._device_path is None: device = await BleakScannerBlueZDBus.find_device_by_address( self.address, timeout=timeout, adapter=self._adapter) if device: self._device_info = device.details.get("props") self._device_path = device.details["path"] else: raise BleakError( "Device with address {0} was not found.".format( self.address)) loop = asyncio.get_event_loop() self._reactor = get_reactor(loop) # Create system bus self._bus = await txdbus_connect(self._reactor, busAddress="system").asFuture(loop) 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 for %s", str(self)) self.services_resolved = True rule_id = await signals.listen_properties_changed( self._bus, _services_resolved_callback) logger.debug("Connecting to BLE device @ {0} with {1}".format( self.address, self._adapter)) try: await self._bus.callRemote( self._device_path, "Connect", interface=defs.DEVICE_INTERFACE, destination=defs.BLUEZ_SERVICE, timeout=timeout, ).asFuture(loop) except RemoteError as e: await self._cleanup_all() if 'Method "Connect" with signature "" on interface' in str(e): raise BleakError( "Device with address {0} could not be found. " "Try increasing `timeout` value or moving the device closer." .format(self.address)) else: raise BleakError(str(e)) if await self.is_connected(): logger.debug("Connection successful.") else: await self._cleanup_all() 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"): await self._cleanup_all() raise BleakError("Connection failed!") await self._bus.delMatch(rule_id).asFuture(loop) self._rules["PropChanged"] = await signals.listen_properties_changed( self._bus, self._properties_changed_callback) return True
async def connect(self, **kwargs) -> bool: """Connect to the specified GATT server. Keyword Args: timeout (float): Timeout for required ``discover`` call. Defaults to 2.0. 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. timeout = kwargs.get("timeout", self._timeout) discovered = await discover(timeout=timeout, device=self.device, loop=self.loop) # Issue 150 hints at the device path not being possible to create as # is done in the `get_device_object_path` method. Try to get it from # BlueZ instead. # Otherwise, use the old fallback and hope for the best. bluez_devices = list( filter(lambda d: d.address.lower() == self.address.lower(), discovered)) if bluez_devices: self._device_path = bluez_devices[0].details["path"] else: # TODO: Better to always get path from BlueZ backend... self._device_path = get_device_object_path(self.device, self.address) self._reactor = get_reactor(self.loop) # Create system bus self._bus = await txdbus_connect( self._reactor, busAddress="system").asFuture(self.loop) 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: await self._cleanup_all() if 'Method "Connect" with signature "" on interface' in str(e): raise BleakError( "Device with address {0} could not be found. " "Try increasing `timeout` value or moving the device closer." .format(self.address)) else: raise BleakError(str(e)) if await self.is_connected(): logger.debug("Connection successful.") else: await self._cleanup_all() 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"): await self._cleanup_all() 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 connect(self, **kwargs) -> bool: """Connect to the specified GATT server. Keyword Args: timeout (float): Timeout for required ``discover`` call. Defaults to 2.0. 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. timeout = kwargs.get("timeout", self._timeout) await discover(timeout=timeout, device=self.device, loop=self.loop) self._reactor = get_reactor(self.loop) # Create system bus self._bus = await txdbus_connect( self._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: await self._cleanup_all() raise BleakError(str(e)) if await self.is_connected(): logger.debug("Connection successful.") else: await self._cleanup_all() 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"): await self._cleanup_all() 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