Bluetooth Scanner Example#

An example showing how to locate Bluetooth devices.

Download this example

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

"""PySide6 port of the bluetooth/btscanner example from Qt v6.x"""

import sys

from PySide6.QtWidgets import QApplication

from device import DeviceDiscoveryDialog


if __name__ == '__main__':
    app = QApplication(sys.argv)
    d = DeviceDiscoveryDialog()
    d.exec()
    sys.exit(0)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import QPoint, Qt, Slot
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QDialog, QListWidgetItem, QMenu
from PySide6.QtBluetooth import (QBluetoothAddress, QBluetoothDeviceDiscoveryAgent,
                                 QBluetoothDeviceInfo, QBluetoothLocalDevice)

from ui_device import Ui_DeviceDiscovery
from service import ServiceDiscoveryDialog


class DeviceDiscoveryDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._local_device = QBluetoothLocalDevice()
        self._ui = Ui_DeviceDiscovery()
        self._ui.setupUi(self)
        # In case of multiple Bluetooth adapters it is possible to set adapter
        # which will be used. Example code:
        #
        # address = QBluetoothAddress("XX:XX:XX:XX:XX:XX")
        # discoveryAgent = QBluetoothDeviceDiscoveryAgent(address)

        self._discovery_agent = QBluetoothDeviceDiscoveryAgent()

        self._ui.scan.clicked.connect(self.start_scan)
        self._discovery_agent.deviceDiscovered.connect(self.add_device)
        self._discovery_agent.finished.connect(self.scan_finished)
        self._ui.list.itemActivated.connect(self.item_activated)
        self._local_device.hostModeStateChanged.connect(self.host_mode_state_changed)

        self.host_mode_state_changed(self._local_device.hostMode())
        # add context menu for devices to be able to pair device
        self._ui.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self._ui.list.customContextMenuRequested.connect(self.display_pairing_menu)
        self._local_device.pairingFinished.connect(self.pairing_done)

    @Slot(QBluetoothDeviceInfo)
    def add_device(self, info):
        a = info.address().toString()
        label = f"{a} {info.name()}"
        items = self._ui.list.findItems(label, Qt.MatchExactly)
        if not items:
            item = QListWidgetItem(label)
            pairing_status = self._local_device.pairingStatus(info.address())
            if (pairing_status == QBluetoothLocalDevice.Paired
                    or pairing_status == QBluetoothLocalDevice.AuthorizedPaired):
                item.setForeground(QColor(Qt.green))
            else:
                item.setForeground(QColor(Qt.black))
            self._ui.list.addItem(item)

    @Slot()
    def start_scan(self):
        self._discovery_agent.start()
        self._ui.scan.setEnabled(False)

    @Slot()
    def scan_finished(self):
        self._ui.scan.setEnabled(True)

    @Slot(QListWidgetItem)
    def item_activated(self, item):
        text = item.text()
        index = text.find(' ')
        if index == -1:
            return

        address = QBluetoothAddress(text[0:index])
        name = text[index + 1:]

        d = ServiceDiscoveryDialog(name, address)
        d.exec()

    @Slot(bool)
    def on_discoverable_clicked(self, clicked):
        if clicked:
            self._local_device.setHostMode(QBluetoothLocalDevice.HostDiscoverable)
        else:
            self._local_device.setHostMode(QBluetoothLocalDevice.HostConnectable)

    @Slot(bool)
    def on_power_clicked(self, clicked):
        if clicked:
            self._local_device.powerOn()
        else:
            self._local_device.setHostMode(QBluetoothLocalDevice.HostPoweredOff)

    @Slot("QBluetoothLocalDevice::HostMode")
    def host_mode_state_changed(self, mode):
        self._ui.power.setChecked(mode != QBluetoothLocalDevice.HostPoweredOff)
        self._ui.discoverable.setChecked(mode == QBluetoothLocalDevice.HostDiscoverable)

        on = mode != QBluetoothLocalDevice.HostPoweredOff
        self._ui.scan.setEnabled(on)
        self._ui.discoverable.setEnabled(on)

    @Slot(QPoint)
    def display_pairing_menu(self, pos):
        if self._ui.list.count() == 0:
            return
        menu = QMenu(self)
        pair_action = menu.addAction("Pair")
        remove_pair_action = menu.addAction("Remove Pairing")
        chosen_action = menu.exec(self._ui.list.viewport().mapToGlobal(pos))
        current_item = self._ui.list.currentItem()

        text = current_item.text()
        index = text.find(' ')
        if index == -1:
            return

        address = QBluetoothAddress(text[0:index])
        if chosen_action == pair_action:
            self._local_device.requestPairing(address, QBluetoothLocalDevice.Paired)
        elif chosen_action == remove_pair_action:
            self._local_device.requestPairing(address, QBluetoothLocalDevice.Unpaired)

    @Slot(QBluetoothAddress, "QBluetoothLocalDevice::Pairing")
    def pairing_done(self, address, pairing):
        items = self._ui.list.findItems(address.toString(), Qt.MatchContains)

        color = QColor(Qt.red)
        if (pairing == QBluetoothLocalDevice.Paired
                or pairing == QBluetoothLocalDevice.AuthorizedPaired):
            color = QColor(Qt.green)
        for item in items:
            item.setForeground(color)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import Slot
from PySide6.QtWidgets import QDialog
from PySide6.QtBluetooth import (QBluetoothAddress, QBluetoothServiceInfo,
                                 QBluetoothServiceDiscoveryAgent, QBluetoothLocalDevice)

from ui_service import Ui_ServiceDiscovery


class ServiceDiscoveryDialog(QDialog):
    def __init__(self, name, address, parent=None):
        super().__init__(parent)
        self._ui = Ui_ServiceDiscovery()
        self._ui.setupUi(self)

        # Using default Bluetooth adapter
        local_device = QBluetoothLocalDevice()
        adapter_address = QBluetoothAddress(local_device.address())

        # In case of multiple Bluetooth adapters it is possible to
        # set which adapter will be used by providing MAC Address.
        # Example code:
        #
        # adapterAddress = QBluetoothAddress("XX:XX:XX:XX:XX:XX")
        # discoveryAgent = QBluetoothServiceDiscoveryAgent(adapterAddress)

        self._discovery_agent = QBluetoothServiceDiscoveryAgent(adapter_address)
        self._discovery_agent.setRemoteAddress(address)

        self.setWindowTitle(name)

        self._discovery_agent.serviceDiscovered.connect(self.add_service)
        self._discovery_agent.finished.connect(self._ui.status.hide)
        self._discovery_agent.start()

    @Slot(QBluetoothServiceInfo)
    def add_service(self, info):
        line = info.serviceName()
        if not line:
            return

        if info.serviceDescription():
            line += "\n\t" + info.serviceDescription()
        if info.serviceProvider():
            line += "\n\t" + info.serviceProvider()
        self._ui.list.addItem(line)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>DeviceDiscovery</class>
 <widget class="QDialog" name="DeviceDiscovery">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>411</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Bluetooth Scanner</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QListWidget" name="list"/>
   </item>
   <item>
    <widget class="QGroupBox" name="groupBox">
     <property name="title">
      <string>Local Device</string>
     </property>
     <layout class="QHBoxLayout" name="horizontalLayout_2">
      <item>
       <widget class="QCheckBox" name="power">
        <property name="text">
         <string>Bluetooth Powered On</string>
        </property>
        <property name="checked">
         <bool>true</bool>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QCheckBox" name="discoverable">
        <property name="text">
         <string>Discoverable</string>
        </property>
        <property name="checked">
         <bool>true</bool>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QPushButton" name="scan">
       <property name="text">
        <string>Scan</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="clear">
       <property name="text">
        <string>Clear</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="quit">
       <property name="text">
        <string>Quit</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>quit</sender>
   <signal>clicked()</signal>
   <receiver>DeviceDiscovery</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>323</x>
     <y>275</y>
    </hint>
    <hint type="destinationlabel">
     <x>396</x>
     <y>268</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>clear</sender>
   <signal>clicked()</signal>
   <receiver>list</receiver>
   <slot>clear()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>188</x>
     <y>276</y>
    </hint>
    <hint type="destinationlabel">
     <x>209</x>
     <y>172</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ServiceDiscovery</class>
 <widget class="QDialog" name="ServiceDiscovery">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>539</width>
    <height>486</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Available Services</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QListWidget" name="list"/>
   </item>
   <item>
    <widget class="QLabel" name="status">
     <property name="text">
      <string>Querying...</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QDialogButtonBox" name="buttonBox">
     <property name="standardButtons">
      <set>QDialogButtonBox::Close</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>ServiceDiscovery</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>396</x>
     <y>457</y>
    </hint>
    <hint type="destinationlabel">
     <x>535</x>
     <y>443</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>ServiceDiscovery</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>339</x>
     <y>464</y>
    </hint>
    <hint type="destinationlabel">
     <x>535</x>
     <y>368</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>