Source code
Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2019 Ford Motor Company
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "connectpage.h"
#include "ui_connectpage.h"
#include "bleiodevice.h"
#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/QLowEnergyController>
#include <QtBluetooth/QLowEnergyService>
using namespace Qt::Literals::StringLiterals;
ConnectPage::ConnectPage(QWidget *parent)
: QWidget(parent)
, ui(new Ui::ConnectPage)
, m_discoveryAgent(new QBluetoothDeviceDiscoveryAgent(this))
{
ui->setupUi(this);
m_discoveryAgent->setLowEnergyDiscoveryTimeout(20000);
connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred, this, [this]() {
emit showMessage(m_discoveryAgent->errorString());
ui->scanButton->setEnabled(true);
ui->connectButton->setEnabled(false);
});
connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
this, &ConnectPage::refreshDevices);
connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, [this]() {
ui->scanButton->setEnabled(true);
emit showMessage("Scan finished"_L1);
refreshDevices();
});
connect(ui->devicesListWidget, &QListWidget::clicked, this, [this](const QModelIndex &index) {
ui->connectButton->setEnabled(index.isValid() && index.row() >= 0
&& index.row() < m_leDevices.size());
showMessage("Scan stopped");
ui->scanButton->setEnabled(true);
m_discoveryAgent->stop();
});
connect(ui->connectButton, &QPushButton::clicked, this, [this]() {
if (ui->devicesListWidget->currentRow() < 0
|| ui->devicesListWidget->currentRow() >= m_leDevices.size()) {
return; // Should never happen, but better safe than sorry
}
auto device = m_leDevices[ui->devicesListWidget->currentRow()];
qDebug() << "Connecting to:" << device.name() << device.address() << device.deviceUuid();
connectToDevice(device);
});
connect(ui->scanButton, &QPushButton::clicked, this, &ConnectPage::startScanning);
}
ConnectPage::~ConnectPage()
{
if (m_controller)
m_controller->disconnect(); // Avoid possible stray signals
if (m_discoveryAgent && m_discoveryAgent->isActive())
m_discoveryAgent->stop();
delete ui;
}
void ConnectPage::startScanning()
{
ui->scanButton->setEnabled(false);
ui->connectButton->setEnabled(false);
emit showMessage("Scanning for BLE devices, please wait"_L1);
m_discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
ui->devicesListWidget->setEnabled(true);
ui->devicesListWidget->clear();
m_leDevices.clear();
}
void ConnectPage::disconnectFromDevice()
{
if (m_controller)
m_controller->disconnectFromDevice();
}
void ConnectPage::refreshDevices()
{
ui->devicesListWidget->clear();
m_leDevices.clear();
// The LE device list is used to filter out classic bluetooth devices which the device
// discovery method may find (even if using low energy device discovery method). This list
// helps in matching the right device listwidget index the user has selected
for (const auto &device : m_discoveryAgent->discoveredDevices()) {
if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
m_leDevices.append(device);
#ifdef Q_OS_DARWIN
// On macOS/iOS we don't have address available
ui->devicesListWidget->addItem(device.name() + " " + device.deviceUuid().toString());
#else
ui->devicesListWidget->addItem(device.name() + " " + device.address().toString());
#endif
}
}
}
void ConnectPage::connectToDevice(const QBluetoothDeviceInfo &device)
{
ui->scanButton->setEnabled(false);
ui->connectButton->setEnabled(false);
ui->devicesListWidget->setEnabled(false);
QString deviceIdentifier;
if (!device.name().isEmpty())
deviceIdentifier = device.name();
else if (!device.address().isNull())
deviceIdentifier = device.address().toString();
else
deviceIdentifier = device.deviceUuid().toString(QUuid::WithoutBraces);
emit showMessage("Connecting to %1"_L1.arg(deviceIdentifier));
// If this is not the first time, release the previous low energy controller
if (m_controller) {
m_controller->disconnectFromDevice();
delete m_controller;
m_controller = nullptr;
}
m_controller = QLowEnergyController::createCentral(device, this);
connect(m_controller, &QLowEnergyController::connected, this, [this]() {
m_controller->discoverServices();
});
connect(m_controller, &QLowEnergyController::errorOccurred, this, [this]() {
ui->devicesListWidget->setEnabled(true);
ui->scanButton->setEnabled(!m_discoveryAgent->isActive());
m_service = nullptr;
emit showMessage("Error occurred: "_L1 + m_controller->errorString());
});
connect(m_controller, &QLowEnergyController::disconnected, this, [this]() {
m_service = nullptr;
ui->devicesListWidget->setEnabled(true);
emit showMessage("Diconnected from remote"_L1);
emit disconnected();
});
connect(m_controller, &QLowEnergyController::serviceDiscovered,
this, [this](const QBluetoothUuid &newService) {
if (newService != BLEIoDevice::SERVICE_UUID)
return;
emit showMessage("Service found"_L1);
m_service = m_controller->createServiceObject(newService, m_controller);
if (!m_service) {
qWarning("BT LE Service couldn't be created");
return;
}
if (m_service->state() == QLowEnergyService::RemoteService) {
emit showMessage("Service found, scanning for details"_L1);
connect(m_service, &QLowEnergyService::stateChanged,
this, [this](QLowEnergyService::ServiceState state) {
if (state == QLowEnergyService::ServiceState::RemoteServiceDiscovered)
connectNode();
});
m_service->discoverDetails();
} else {
connectNode();
}
});
connect(m_controller, &QLowEnergyController::discoveryFinished, this, [this]() {
if (m_service)
return;
emit showMessage("Service was not found");
ui->scanButton->setEnabled(true);
ui->devicesListWidget->setEnabled(true);
});
m_controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
m_controller->connectToDevice();
}
void ConnectPage::connectNode()
{
if (!m_service)
return;
// Below we subscribe to characteristic value change notifications.
// If subscription fails, disconnect as there's no point in continuing
QObject::connect(m_service.data(), &QLowEnergyService::descriptorWritten, this,
[this](const QLowEnergyDescriptor &info,
const QByteArray &value) {
if (info.uuid() != QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)
return;
if (value != QLowEnergyCharacteristic::CCCDEnableNotification) {
// We failed to subscribe to characteristic change notifications
m_controller->disconnectFromDevice();
} else {
emit showMessage("Connected to QtRO node"_L1);
auto ioDevice = new BLEIoDevice(m_service,
BLEIoDevice::CLIENT_RX_SERVER_TX_CHAR_UUID,
BLEIoDevice::CLIENT_TX_SERVER_RX_CHAR_UUID,
this);
connect(this, &ConnectPage::disconnected, ioDevice, &BLEIoDevice::disconnected);
connect(ioDevice, &BLEIoDevice::disconnected, ioDevice, &BLEIoDevice::deleteLater);
emit connected(ioDevice);
}
});
QObject::connect(m_service, &QLowEnergyService::errorOccurred, this, [this]
(QLowEnergyService::ServiceError error) {
if (error == QLowEnergyService::ServiceError::DescriptorWriteError)
m_controller->disconnectFromDevice();
});
m_service->writeDescriptor(
m_service->characteristic(BLEIoDevice::CLIENT_RX_SERVER_TX_CHAR_UUID).descriptor(
QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration),
QLowEnergyCharacteristic::CCCDEnableNotification);
}