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 "rep_heater_source.h"
#include "bleiodevice.h"
#include <QtRemoteObjects/QRemoteObjectNode>
#include <QtBluetooth/QLowEnergyCharacteristicData>
#include <QtBluetooth/QLowEnergyDescriptorData>
#include <QtBluetooth/QLowEnergyServiceData>
#include <QtBluetooth/QLowEnergyAdvertisingData>
#include <QtBluetooth/QLowEnergyController>
#include <QtBluetooth/QLowEnergyAdvertisingParameters>
#include <QtCore/QCoreApplication>
#include <QtCore/QTimer>
#include <QtCore/QLoggingCategory>
#include <QtCore/QCommandLineOption>
#include <QtCore/QCommandLineParser>
using namespace Qt::Literals::StringLiterals;
class Heater : public HeaterSimpleSource
{
Q_OBJECT
public:
explicit Heater(QObject* parent = nullptr) : HeaterSimpleSource(parent)
{
m_changeTimer.setInterval(std::chrono::seconds{2});
m_changeTimer.setSingleShot(false);
QObject::connect(&m_changeTimer, &QTimer::timeout, this, [this]() {
if (heaterPoweredOn())
setCurrentTemperature(currentTemperature() + 1);
else
setCurrentTemperature(currentTemperature() - 1);
});
m_changeTimer.start();
}
private:
QTimer m_changeTimer;
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QCommandLineParser parser;
parser.addHelpOption();
QCommandLineOption verboseOption("verbose", "Verbose mode");
parser.addOption(verboseOption);
parser.process(app);
if (parser.isSet(verboseOption)) {
QLoggingCategory::setFilterRules("qt.remoteobjects* = true"_L1);
QLoggingCategory::setFilterRules("qt.bluetooth* = true"_L1);
}
// The model that will be used as the source object
std::unique_ptr<Heater> heater = std::make_unique<Heater>();
std::unique_ptr<QLowEnergyController> bleController;
std::unique_ptr<QRemoteObjectHost> hostNode;
std::unique_ptr<BLEIoDevice> ioDevice;
// Setup BLE server. The two-way communication consists of a GATT service
// which has two characteristics. The characteristics form the two-way communication
// between client/server. The characteristic notifications are enabled so that each
// side (client/server) gets notified if the other has written data.
QLowEnergyCharacteristicData rxCharData;
rxCharData.setUuid(BLEIoDevice::CLIENT_TX_SERVER_RX_CHAR_UUID);
// Allow the remote end (client) to write to this characteristic
rxCharData.setProperties(QLowEnergyCharacteristic::Write);
QLowEnergyCharacteristicData txCharData;
txCharData.setUuid(BLEIoDevice::CLIENT_RX_SERVER_TX_CHAR_UUID);
// Allow the remote end (client) to read characteristic's data and subscribe to value changes
const QLowEnergyDescriptorData clientConfig(
QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration,
QLowEnergyCharacteristic::CCCDDisable);
txCharData.addDescriptor(clientConfig);
txCharData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
// Set up the service, its characteristics, and advertisement data
QLowEnergyServiceData sppServiceData;
sppServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
sppServiceData.setUuid(BLEIoDevice::SERVICE_UUID);
sppServiceData.addCharacteristic(txCharData);
sppServiceData.addCharacteristic(rxCharData);
QLowEnergyAdvertisingData sppAdvertisingData;
sppAdvertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
sppAdvertisingData.setIncludePowerLevel(true);
// The device name that is broadcasted in the advertisement. Note that this is not supported
// on Android where the device's name is used instead
sppAdvertisingData.setLocalName("QtRO peripheral"_L1);
bleController.reset(QLowEnergyController::createPeripheral());
// Add the service to the BT LE controller and start advertising so that clients
// can discover this server. When the client disconnects the advertisement is
// resumed so that next clients can find the server
std::unique_ptr<QLowEnergyService> sppService;
auto startAdvertising = [&bleController, sppAdvertisingData, sppServiceData, &sppService]
{
sppService.reset(bleController->addService(sppServiceData));
if (sppService) {
bleController->startAdvertising(QLowEnergyAdvertisingParameters(),
sppAdvertisingData, sppAdvertisingData);
}
};
QObject::connect(bleController.get(), &QLowEnergyController::disconnected, bleController.get(),
[&]() {
// Upon disconnection the underlying BLE service used by the ioDevice becomes
// obsolete. Free up the resources, resume advertising and wait for a new client
hostNode.reset(nullptr);
ioDevice.reset(nullptr);
startAdvertising();
});
auto errorHandler = [&](QLowEnergyController::Error errorCode)
{
// Exit server in cases we won't try to recover
qWarning().noquote().nospace() << errorCode << " error: " << bleController->errorString();
if (errorCode != QLowEnergyController::RemoteHostClosedError) {
qWarning("BLE server quitting due to the error.");
QCoreApplication::quit();
}
};
// Use queued connection in case the advertising fails before we even enter
// the event loop / exec(), in which case the quit() does nothing
QObject::connect(bleController.get(), &QLowEnergyController::errorOccurred,
&app, errorHandler, Qt::QueuedConnection);
// Start initial advertising
startAdvertising();
// Now we are advertising, next:
// 1) Wait for a client to discover the service and get connected
// 2) Wait for client to indicate it is fully ready
// 3) Create RO sourcenode and BLE IO device
QMetaObject::Connection readConnection;
QObject::connect(bleController.get(), &QLowEnergyController::connected, bleController.get(),
[&]() {
Q_ASSERT(!hostNode);
Q_ASSERT(!ioDevice);
readConnection = QObject::connect(sppService.get(),
&QLowEnergyService::characteristicChanged,
bleController.get(),
[&](const QLowEnergyCharacteristic &info,
const QByteArray &value) {
// Wait for the client to signal it is ready
if (info.uuid() != BLEIoDevice::CLIENT_TX_SERVER_RX_CHAR_UUID
|| value != BLEIoDevice::CLIENT_READY_SIGNAL) {
return;
}
QObject::disconnect(readConnection);
readConnection = {};
// We have a client that is connected and has indicated it is ready;
// Create the hostnode and the associated BLE IO device and enable
// remote access
hostNode = std::make_unique<QRemoteObjectHost>();
QRemoteObjectHost::AllowExternalRegistration);
hostNode->enableRemoting(heater.get());
ioDevice = std::make_unique<BLEIoDevice>(sppService.get(),
BLEIoDevice::CLIENT_TX_SERVER_RX_CHAR_UUID,
BLEIoDevice::CLIENT_RX_SERVER_TX_CHAR_UUID);
QObject::connect(ioDevice.get(), &BLEIoDevice::closing, bleController.get(),
&QLowEnergyController::disconnectFromDevice);
hostNode->addHostSideConnection(ioDevice.get());
});
});
return QCoreApplication::exec();
}
#include "main.moc"