Beispiel #1
0
    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)
Beispiel #2
0
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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
    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