<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - bluetooth</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/bluetooth.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-05-21T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Bluetooth Device Scanning with PyQt5 QtBluetooth — How to discover Bluetooth and Bluetooth Low Energy devices using PyQt5's QtBluetooth module</title><link href="https://www.pythonguis.com/faq/pyqt5-qtbluetooth-examples-or-documentation/" rel="alternate"/><published>2021-05-21T09:00:00+00:00</published><updated>2021-05-21T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-05-21:/faq/pyqt5-qtbluetooth-examples-or-documentation/</id><summary type="html">PyQt5 includes the &lt;code&gt;QtBluetooth&lt;/code&gt; module, which gives you access to Bluetooth device discovery, services, and even Bluetooth Low Energy (BLE) communication &amp;mdash; all from Python. If you're building a desktop app that needs to talk to Bluetooth devices (sensors, wearables, microcontrollers, etc.), this module can save you from wrestling with lower-level libraries like PyBluez.</summary><content type="html">
            &lt;p&gt;PyQt5 includes the &lt;code&gt;QtBluetooth&lt;/code&gt; module, which gives you access to Bluetooth device discovery, services, and even Bluetooth Low Energy (BLE) communication &amp;mdash; all from Python. If you're building a desktop app that needs to talk to Bluetooth devices (sensors, wearables, microcontrollers, etc.), this module can save you from wrestling with lower-level libraries like PyBluez.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to scan for Bluetooth devices using &lt;code&gt;QBluetoothDeviceDiscoveryAgent&lt;/code&gt;, understand the common "Dummy backend" error, and build a working example that discovers nearby devices and prints their details.&lt;/p&gt;
&lt;h2 id="the-dummy-backend-error"&gt;The "Dummy backend" Error&lt;/h2&gt;
&lt;p&gt;If you've tried running a QtBluetooth script and seen output like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;qt.bluetooth: Dummy backend running. Qt Bluetooth module is non-functional.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This means that the Qt Bluetooth module could not find a working Bluetooth backend on your system. The module loaded, but it can't actually &lt;em&gt;do&lt;/em&gt; anything &amp;mdash; it's running a placeholder ("dummy") backend instead.&lt;/p&gt;
&lt;p&gt;There are a few common reasons for this:&lt;/p&gt;
&lt;h3&gt;No Bluetooth adapter present&lt;/h3&gt;
&lt;p&gt;Your machine might not have a Bluetooth adapter, or it might be disabled. Check your system settings to confirm Bluetooth hardware is available and turned on.&lt;/p&gt;
&lt;h3&gt;Missing system Bluetooth libraries&lt;/h3&gt;
&lt;p&gt;On Linux, Qt's Bluetooth support relies on BlueZ (the Linux Bluetooth stack). Make sure BlueZ is installed and running. You can check with:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;sudo systemctl status bluetooth
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If it's not running, start it:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;sudo systemctl start bluetooth
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You may also need the development libraries. On Debian/Ubuntu:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;sudo apt install libbluetooth-dev bluez
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Qt was built without Bluetooth support&lt;/h3&gt;
&lt;p&gt;Some pre-built Qt packages (including those bundled with PyQt5 via pip) may not include full Bluetooth support for your platform. On Windows, Qt Bluetooth support requires the WinRT Bluetooth backend (available in Qt 5.14+). On macOS, it uses the CoreBluetooth framework.&lt;/p&gt;
&lt;h3&gt;Permissions issues&lt;/h3&gt;
&lt;p&gt;On Linux, accessing Bluetooth often requires elevated permissions. Try running your script with &lt;code&gt;sudo&lt;/code&gt;, or add your user to the &lt;code&gt;bluetooth&lt;/code&gt; group:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;sudo usermod -aG bluetooth $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then log out and back in.&lt;/p&gt;
&lt;p&gt;Once your Bluetooth stack is properly set up and recognized by Qt, the dummy backend message will disappear, and discovery will work.&lt;/p&gt;
&lt;h2 id="scanning-for-bluetooth-devices"&gt;Scanning for Bluetooth Devices&lt;/h2&gt;
&lt;p&gt;The main class for device discovery is &lt;code&gt;QBluetoothDeviceDiscoveryAgent&lt;/code&gt;. You create an instance, connect its signals to your handler functions, and call &lt;code&gt;start()&lt;/code&gt;. The agent will emit a &lt;code&gt;deviceDiscovered&lt;/code&gt; signal each time it finds a device, and a &lt;code&gt;finished&lt;/code&gt; signal when the scan completes.&lt;/p&gt;
&lt;p&gt;Here's a minimal example to see this in action:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PyQt5.QtBluetooth import QBluetoothDeviceDiscoveryAgent, QBluetoothDeviceInfo
from PyQt5.QtCore import QCoreApplication, QTimer


def main():
    app = QCoreApplication(sys.argv)

    agent = QBluetoothDeviceDiscoveryAgent()

    def on_device_discovered(info: QBluetoothDeviceInfo):
        print(f"Discovered: {info.name() or '(unnamed)'} [{info.address().toString()}]")

    def on_finished():
        print("Scan complete.")
        app.quit()

    def on_error(error):
        print(f"Scan error: {error}")
        app.quit()

    agent.deviceDiscovered.connect(on_device_discovered)
    agent.finished.connect(on_finished)
    agent.error.connect(on_error)

    agent.start()
    print("Scanning for Bluetooth devices...")

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this script, and if your Bluetooth stack is working, you should see device names and addresses printed to the console as they're discovered. The scan runs for a default duration and then the &lt;code&gt;finished&lt;/code&gt; signal fires, which quits the application.&lt;/p&gt;
&lt;p&gt;A few things to note about this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use &lt;code&gt;QCoreApplication&lt;/code&gt; because we don't need a GUI &amp;mdash; this is a console-only Bluetooth scanner.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;info&lt;/code&gt; parameter passed to &lt;code&gt;on_device_discovered&lt;/code&gt; is a &lt;code&gt;QBluetoothDeviceInfo&lt;/code&gt; object, which contains the name, address, RSSI, and other metadata about the discovered device.&lt;/li&gt;
&lt;li&gt;Some devices may not advertise a name, so &lt;code&gt;info.name()&lt;/code&gt; can return an empty string. The &lt;code&gt;or '(unnamed)'&lt;/code&gt; fallback handles that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scanning-for-ble-devices-specifically"&gt;Scanning for BLE Devices Specifically&lt;/h2&gt;
&lt;p&gt;By default, &lt;code&gt;QBluetoothDeviceDiscoveryAgent&lt;/code&gt; scans for classic Bluetooth devices. If you're looking for Bluetooth Low Energy (BLE) devices &amp;mdash; such as fitness trackers, IoT sensors, or BLE-enabled microcontrollers &amp;mdash; you need to tell the agent to look for them specifically.&lt;/p&gt;
&lt;p&gt;You do this by passing &lt;code&gt;QBluetoothDeviceDiscoveryAgent.LowEnergyMethod&lt;/code&gt; to the &lt;code&gt;start()&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;agent.start(QBluetoothDeviceDiscoveryAgent.LowEnergyMethod)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also set a timeout for BLE discovery. Unlike classic Bluetooth scanning, BLE scanning can run indefinitely, so setting a timeout is a good idea:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;agent.setLowEnergyDiscoveryTimeout(5000)  # 5 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you want to find &lt;em&gt;both&lt;/em&gt; classic and BLE devices in one scan, you can combine the methods:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;agent.start(
    QBluetoothDeviceDiscoveryAgent.ClassicMethod
    | QBluetoothDeviceDiscoveryAgent.LowEnergyMethod
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="getting-more-information-from-discovered-devices"&gt;Getting More Information from Discovered Devices&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;QBluetoothDeviceInfo&lt;/code&gt; object provides a lot of useful information. Here are the most commonly used properties:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def on_device_discovered(info: QBluetoothDeviceInfo):
    print(f"Name:    {info.name()}")
    print(f"Address: {info.address().toString()}")
    print(f"RSSI:    {info.rssi()} dBm")

    # Check if this is a BLE device
    core_config = info.coreConfigurations()
    if core_config &amp;amp; QBluetoothDeviceInfo.LowEnergyCoreConfiguration:
        print("  &amp;rarr; This is a BLE device")
    else:
        print("  &amp;rarr; This is a classic Bluetooth device")

    # List any service UUIDs the device advertises
    uuids = info.serviceUuids()
    if uuids:
        for uuid in uuids[0]:  # serviceUuids() returns (list, completeness)
            print(f"  Service UUID: {uuid.toString()}")

    print("---")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;rssi()&lt;/code&gt; method returns the signal strength in dBm, which can help you estimate how close a device is. The &lt;code&gt;coreConfigurations()&lt;/code&gt; method tells you whether a device is classic Bluetooth, BLE, or both. And &lt;code&gt;serviceUuids()&lt;/code&gt; returns a tuple of &lt;code&gt;(uuid_list, completeness)&lt;/code&gt; &amp;mdash; the completeness flag indicates whether the list of UUIDs is the full set or just a partial advertisement.&lt;/p&gt;
&lt;h2 id="a-note-on-the-event-loop"&gt;A Note on the Event Loop&lt;/h2&gt;
&lt;p&gt;It's important to remember that the scanning happens asynchronously &amp;mdash; when you call &lt;code&gt;agent.start()&lt;/code&gt;, it returns immediately. The actual discovery results arrive later via signals.&lt;/p&gt;
&lt;p&gt;This means you &lt;em&gt;must&lt;/em&gt; call &lt;code&gt;app.exec_()&lt;/code&gt; (or &lt;code&gt;app.exec()&lt;/code&gt; in PyQt6) to start the event loop. Without it, your program will exit before any devices are found.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete Working Example&lt;/h2&gt;
&lt;p&gt;Here's a complete, well-structured example that scans for both classic and BLE devices, prints detailed information about each discovery, and exits cleanly when the scan finishes:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PyQt5.QtBluetooth import (
    QBluetoothDeviceDiscoveryAgent,
    QBluetoothDeviceInfo,
)
from PyQt5.QtCore import QCoreApplication, QTimer


class BluetoothScanner:
    def __init__(self):
        self.agent = QBluetoothDeviceDiscoveryAgent()
        self.agent.setLowEnergyDiscoveryTimeout(10000)  # 10 second timeout for BLE

        self.agent.deviceDiscovered.connect(self.on_device_discovered)
        self.agent.finished.connect(self.on_scan_finished)
        self.agent.error.connect(self.on_scan_error)

        self.devices_found = []

    def start(self):
        print("Starting Bluetooth scan (classic + BLE)...")
        print("This will take a few seconds.\n")
        # Scan for both classic and Low Energy devices
        self.agent.start(
            QBluetoothDeviceDiscoveryAgent.ClassicMethod
            | QBluetoothDeviceDiscoveryAgent.LowEnergyMethod
        )

    def on_device_discovered(self, info: QBluetoothDeviceInfo):
        name = info.name() or "(unnamed)"
        address = info.address().toString()

        # Determine device type
        core_config = info.coreConfigurations()
        if core_config &amp;amp; QBluetoothDeviceInfo.LowEnergyCoreConfiguration:
            device_type = "BLE"
        else:
            device_type = "Classic"

        rssi = info.rssi()
        print(f"[{device_type}] {name}  ({address})  RSSI: {rssi} dBm")

        self.devices_found.append(info)

    def on_scan_finished(self):
        print(f"\nScan complete. Found {len(self.devices_found)} device(s).")
        QCoreApplication.instance().quit()

    def on_scan_error(self, error):
        error_messages = {
            QBluetoothDeviceDiscoveryAgent.NoError: "No error",
            QBluetoothDeviceDiscoveryAgent.InputOutputError: "I/O error",
            QBluetoothDeviceDiscoveryAgent.PoweredOffError: "Bluetooth adapter is powered off",
            QBluetoothDeviceDiscoveryAgent.InvalidBluetoothAdapterError: "Invalid Bluetooth adapter",
            QBluetoothDeviceDiscoveryAgent.UnsupportedPlatformError: "Unsupported platform",
            QBluetoothDeviceDiscoveryAgent.UnsupportedDiscoveryMethod: "Unsupported discovery method",
        }
        message = error_messages.get(error, f"Unknown error ({error})")
        print(f"Scan error: {message}")
        QCoreApplication.instance().quit()


def main():
    app = QCoreApplication(sys.argv)

    scanner = BluetoothScanner()

    # Use a single-shot timer to start the scan after the event loop is running.
    QTimer.singleShot(0, scanner.start)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this on a machine with a working Bluetooth adapter, you'll see output like:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;Starting Bluetooth scan (classic + BLE)...
This will take a few seconds.

[BLE] Mi Band 5  (AA:BB:CC:DD:EE:FF)  RSSI: -62 dBm
[Classic] Living Room Speaker  (11:22:33:44:55:66)  RSSI: -45 dBm
[BLE] (unnamed)  (77:88:99:AA:BB:CC)  RSSI: -78 dBm

Scan complete. Found 3 device(s).
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;QTimer.singleShot(0, scanner.start)&lt;/code&gt; call ensures the scan starts after the event loop has begun. This is a common Qt pattern &amp;mdash; it schedules the &lt;code&gt;start()&lt;/code&gt; call to happen on the very next iteration of the event loop, which guarantees everything is properly initialized.&lt;/p&gt;
&lt;h2 id="where-to-go-from-here"&gt;Where to Go from Here&lt;/h2&gt;
&lt;p&gt;Once you've discovered the device you want to connect to, the next steps in a BLE workflow are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service discovery&lt;/strong&gt; using &lt;code&gt;QLowEnergyController&lt;/code&gt; &amp;mdash; this connects to the device and enumerates its GATT services.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reading and writing characteristics&lt;/strong&gt; using &lt;code&gt;QLowEnergyService&lt;/code&gt; &amp;mdash; this is where you actually send and receive data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subscribing to notifications&lt;/strong&gt; &amp;mdash; many BLE devices push data to you via characteristic notifications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Qt documentation for &lt;a href="https://doc.qt.io/qt-5/qlowenergycontroller.html"&gt;QLowEnergyController&lt;/a&gt; and &lt;a href="https://doc.qt.io/qt-5/qlowenergyservice.html"&gt;QLowEnergyService&lt;/a&gt; covers these classes in detail. The API translates directly to PyQt5 &amp;mdash; method names, signal names, and enum values are all the same.&lt;/p&gt;
&lt;p&gt;If you're still seeing the "Dummy backend" error after checking your hardware and system libraries, it may be worth trying a different Python environment or building PyQt5 from source against a Qt installation that was compiled with Bluetooth support enabled. On Linux, the &lt;a href="https://wiki.archlinux.org/title/Bluetooth"&gt;Arch Wiki Bluetooth page&lt;/a&gt; is an excellent resource for troubleshooting BlueZ setup, regardless of which distribution you're using.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt5"/><category term="bluetooth"/><category term="qtbluetooth"/><category term="python"/><category term="qt"/><category term="qt5"/></entry></feed>