4#include <QtBluetooth/QLowEnergyConnectionParameters>
12 if (props & QLowEnergyCharacteristic::Read) propList <<
"Read";
13 if (props & QLowEnergyCharacteristic::Write) propList <<
"Write";
14 if (props & QLowEnergyCharacteristic::WriteNoResponse) propList <<
"WriteNoResponse";
15 if (props & QLowEnergyCharacteristic::Notify) propList <<
"Notify";
16 if (props & QLowEnergyCharacteristic::Indicate) propList <<
"Indicate";
17 if (props & QLowEnergyCharacteristic::WriteSigned) propList <<
"WriteSigned";
18 if (props & QLowEnergyCharacteristic::ExtendedProperty) propList <<
"Extended";
19 return propList.isEmpty() ?
"None" : propList.join(
"|");
25 _rssiTimer =
new QTimer(
this);
26 _rssiTimer->setInterval(RSSI_POLL_INTERVAL_MS);
27 _rssiTimer->setSingleShot(
false);
28 (void) connect(_rssiTimer.data(), &QTimer::timeout,
this, [
this]() {
29 if (_controller && _controller->state() != QLowEnergyController::UnconnectedState
30 && _controller->state() != QLowEnergyController::ClosingState) {
31 _controller->readRssi();
46 _service->disconnect();
47 _service->deleteLater();
50 _controller->disconnect();
51 if (_controller->state() != QLowEnergyController::UnconnectedState) {
52 _controller->disconnectFromDevice();
54 _controller->deleteLater();
60 _setupBleController();
65 qCDebug(BluetoothBleWorkerLog) <<
"Attempting to connect to" <<
_device.name() <<
"Mode: BLE";
68 _setupBleController();
79 _controller->connectToDevice();
84 _clearBleWriteQueue();
87 _service->disconnect();
88 _service->deleteLater();
92 _readCharacteristic = QLowEnergyCharacteristic();
93 _writeCharacteristic = QLowEnergyCharacteristic();
96 _controller->disconnectFromDevice();
108 _controller->disconnectFromDevice();
114 qCDebug(BluetoothBleWorkerLog) <<
"Recreating BLE controller after" <<
_consecutiveFailures <<
"consecutive failures";
115 _recreateBleController();
118void BluetoothBleWorker::_setupBleController()
121 qCWarning(BluetoothBleWorkerLog) <<
"BLE controller already exists, skipping setup";
125 _controller = QLowEnergyController::createCentral(
_device,
this);
132 if (_forceRandomAddressType) {
133 qCDebug(BluetoothBleWorkerLog) <<
"Using BLE RandomAddress fallback for" <<
_device.name();
134 _controller->setRemoteAddressType(QLowEnergyController::RandomAddress);
137 (void) connect(_controller.data(), &QLowEnergyController::connected,
this, &BluetoothBleWorker::_onControllerConnected);
138 (void) connect(_controller.data(), &QLowEnergyController::disconnected,
this, &BluetoothBleWorker::_onControllerDisconnected);
139 (void) connect(_controller.data(), &QLowEnergyController::errorOccurred,
this, &BluetoothBleWorker::_onControllerErrorOccurred);
140 (void) connect(_controller.data(), &QLowEnergyController::serviceDiscovered,
this, &BluetoothBleWorker::_onServiceDiscovered);
141 (void) connect(_controller.data(), &QLowEnergyController::discoveryFinished,
this, &BluetoothBleWorker::_onServiceDiscoveryFinished);
143 (void) connect(_controller.data(), &QLowEnergyController::mtuChanged,
this,
146 qCDebug(BluetoothBleWorkerLog) <<
"MTU changed to:" << mtu;
148 (void) connect(_controller.data(), &QLowEnergyController::rssiRead,
this,
149 [
this](qint16 rssi) {
154 if (BluetoothBleWorkerLog().isDebugEnabled()) {
155 (void) connect(_controller.data(), &QLowEnergyController::stateChanged,
this,
156 [](QLowEnergyController::ControllerState state) {
157 qCDebug(BluetoothBleWorkerLog) <<
"BLE Controller State Changed:" << state;
160 (void) connect(_controller.data(), &QLowEnergyController::connectionUpdated,
this,
161 [](
const QLowEnergyConnectionParameters ¶ms) {
162 qCDebug(BluetoothBleWorkerLog) <<
"BLE connection updated: min(ms)" << params.minimumInterval()
163 <<
"max(ms)" << params.maximumInterval()
164 <<
"latency" << params.latency()
165 <<
"supervision(ms)" << params.supervisionTimeout();
170void BluetoothBleWorker::_recreateBleController()
172 qCDebug(BluetoothBleWorkerLog) <<
"Recreating BLE controller";
175 _service->disconnect();
176 _service->deleteLater();
181 _controller->disconnect();
182 if (_controller->state() != QLowEnergyController::UnconnectedState) {
183 _controller->disconnectFromDevice();
185 _controller->deleteLater();
186 _controller =
nullptr;
189 _readCharacteristic = QLowEnergyCharacteristic();
190 _writeCharacteristic = QLowEnergyCharacteristic();
191 _clearBleWriteQueue();
194 _setupBleController();
197void BluetoothBleWorker::_writeBleData(
const QByteArray &data)
199 if (!_writeCharacteristic.isValid()) {
210 const int effectiveMtu = (_mtu > 3) ? (_mtu - 3) : BLE_MIN_PACKET_SIZE;
211 const int packetSize = qBound(BLE_MIN_PACKET_SIZE, effectiveMtu, BLE_MAX_PACKET_SIZE);
213 const bool useWriteWithoutResponse = (_writeCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse);
214 const int numChunks = (data.size() + packetSize - 1) / packetSize;
216 qCDebug(BluetoothBleWorkerLog) <<
_device.name() <<
"BLE Write:" << data.size() <<
"bytes,"
217 << numChunks <<
"chunks (MTU:" << _mtu <<
", packet:" << packetSize <<
")"
218 << (useWriteWithoutResponse ?
"WriteNoResponse" :
"WriteWithResponse");
221 const int chunksNeeded = (data.size() + packetSize - 1) / packetSize;
222 if (_bleWriteQueue.size() + chunksNeeded > MAX_BLE_QUEUE_SIZE) {
223 qCWarning(BluetoothBleWorkerLog) <<
"BLE write queue full:" << _bleWriteQueue.size() <<
"chunks queued";
228 _currentBleWrite = data;
229 for (
int i = 0; i < data.size(); i += packetSize) {
230 _bleWriteQueue.enqueue(data.mid(i, packetSize));
233 if (!_bleWriteInProgress) {
234 _processNextBleWrite();
238void BluetoothBleWorker::_processNextBleWrite()
240 if (_bleWriteQueue.isEmpty()) {
241 _bleWriteInProgress =
false;
242 if (!_currentBleWrite.isEmpty()) {
244 _currentBleWrite.clear();
249 if (!_service || !_writeCharacteristic.isValid()) {
250 _clearBleWriteQueue();
254 _bleWriteInProgress =
true;
255 const QByteArray chunk = _bleWriteQueue.dequeue();
256 const bool useWriteWithoutResponse = (_writeCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse);
258 if (useWriteWithoutResponse) {
259 _service->writeCharacteristic(_writeCharacteristic, chunk, QLowEnergyService::WriteWithoutResponse);
261 QTimer::singleShot(1,
this, &BluetoothBleWorker::_processNextBleWrite);
263 _service->writeCharacteristic(_writeCharacteristic, chunk);
268void BluetoothBleWorker::_clearBleWriteQueue()
270 _bleWriteQueue.clear();
271 _currentBleWrite.clear();
272 _bleWriteInProgress =
false;
275void BluetoothBleWorker::_onControllerConnected()
277 qCDebug(BluetoothBleWorkerLog) <<
"BLE Controller connected to device:" <<
_device.name();
283 _controller->discoverServices();
290void BluetoothBleWorker::_onControllerDisconnected()
292 qCDebug(BluetoothBleWorkerLog) <<
"BLE Controller disconnected from device:" <<
_device.name();
300 _clearBleWriteQueue();
303 _service->deleteLater();
307 _readCharacteristic = QLowEnergyCharacteristic();
308 _writeCharacteristic = QLowEnergyCharacteristic();
314 qCDebug(BluetoothBleWorkerLog) <<
"Starting reconnect timer";
319void BluetoothBleWorker::_onControllerErrorOccurred(QLowEnergyController::Error
error)
330 ((
error == QLowEnergyController::UnknownRemoteDeviceError) ||
331 (
error == QLowEnergyController::ConnectionError) ||
332 (
error == QLowEnergyController::NetworkError))) {
333 qCDebug(BluetoothBleWorkerLog) <<
"BLE connect failed; retrying with RandomAddress fallback:" <<
error;
334 _forceRandomAddressType =
true;
335 _recreateBleController();
337 qCDebug(BluetoothBleWorkerLog) <<
"Issuing BLE reconnect using RandomAddress fallback";
338 _controller->connectToDevice();
350 qCWarning(BluetoothBleWorkerLog) <<
"BLE Controller error:" <<
error <<
errorString;
354 qCDebug(BluetoothBleWorkerLog) <<
"Connection attempt failed, emitting disconnected signal";
359void BluetoothBleWorker::_onServiceDiscovered(
const QBluetoothUuid &uuid)
361 qCDebug(BluetoothBleWorkerLog) <<
"Service discovered:" << uuid.toString();
372void BluetoothBleWorker::_onServiceDiscoveryFinished()
374 qCDebug(BluetoothBleWorkerLog) <<
"Service discovery finished";
380 if (!_service && _controller) {
381 const QList<QBluetoothUuid> services = _controller->services();
382 if (!services.isEmpty()) {
383 qCDebug(BluetoothBleWorkerLog) <<
"Using first available service";
392void BluetoothBleWorker::_setupBleService()
401 if (serviceUuid.isNull()) {
402 const QList<QBluetoothUuid> services = _controller->services();
403 if (services.isEmpty()) {
410 qCDebug(BluetoothBleWorkerLog) <<
"Auto-selected Nordic UART service";
413 qCDebug(BluetoothBleWorkerLog) <<
"Auto-selected TI SensorTag service";
415 serviceUuid = services.first();
416 qCDebug(BluetoothBleWorkerLog) <<
"Auto-selected first available service:" << serviceUuid.toString();
420 _service = _controller->createServiceObject(serviceUuid,
this);
427 (void) connect(_service.data(), &QLowEnergyService::stateChanged,
this, &BluetoothBleWorker::_onServiceStateChanged);
428 (void) connect(_service.data(), &QLowEnergyService::characteristicChanged,
this, &BluetoothBleWorker::_onCharacteristicChanged);
429 (void) connect(_service.data(), &QLowEnergyService::characteristicRead,
this, &BluetoothBleWorker::_onCharacteristicRead);
430 (void) connect(_service.data(), &QLowEnergyService::characteristicWritten,
this, &BluetoothBleWorker::_onCharacteristicWritten);
431 (void) connect(_service.data(), &QLowEnergyService::descriptorRead,
this, &BluetoothBleWorker::_onDescriptorRead);
432 (void) connect(_service.data(), &QLowEnergyService::descriptorWritten,
this, &BluetoothBleWorker::_onDescriptorWritten);
433 (void) connect(_service.data(), &QLowEnergyService::errorOccurred,
this, &BluetoothBleWorker::_onServiceError);
435 _discoverServiceDetails();
438void BluetoothBleWorker::_discoverServiceDetails()
444 qCDebug(BluetoothBleWorkerLog) <<
"Discovering service details";
445 _service->discoverDetails();
448void BluetoothBleWorker::_findCharacteristics()
454 const QList<QLowEnergyCharacteristic> characteristics = _service->characteristics();
455 qCDebug(BluetoothBleWorkerLog) <<
"Service has" << characteristics.size() <<
"characteristics";
457 for (
const QLowEnergyCharacteristic &c : characteristics) {
458 qCDebug(BluetoothBleWorkerLog) <<
" Characteristic:" << c.uuid().toString()
465 for (
const QLowEnergyCharacteristic &c : characteristics) {
466 if (!readUuid.isNull() && (c.uuid() == readUuid)) {
467 _readCharacteristic = c;
468 qCDebug(BluetoothBleWorkerLog) <<
"Read characteristic found by UUID:" << c.uuid().toString();
470 if (!writeUuid.isNull() && (c.uuid() == writeUuid)) {
471 _writeCharacteristic = c;
472 qCDebug(BluetoothBleWorkerLog) <<
"Write characteristic found by UUID:" << c.uuid().toString();
476 if (!_readCharacteristic.isValid() || !_writeCharacteristic.isValid()) {
477 for (
const QLowEnergyCharacteristic &c : characteristics) {
478 if (!_readCharacteristic.isValid() &&
479 (c.properties() & (QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify))) {
480 _readCharacteristic = c;
481 qCDebug(BluetoothBleWorkerLog) <<
"Read characteristic auto-detected:" << c.uuid().toString();
484 if (!_writeCharacteristic.isValid() &&
485 (c.properties() & (QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::WriteNoResponse))) {
486 _writeCharacteristic = c;
487 qCDebug(BluetoothBleWorkerLog) <<
"Write characteristic auto-detected:" << c.uuid().toString();
493 if (_readCharacteristic.isValid()) {
494 qCDebug(BluetoothBleWorkerLog) <<
"Using read characteristic:" << _readCharacteristic.uuid().toString()
497 qCDebug(BluetoothBleWorkerLog) <<
"No read characteristic found";
500 if (_writeCharacteristic.isValid()) {
501 qCDebug(BluetoothBleWorkerLog) <<
"Using write characteristic:" << _writeCharacteristic.uuid().toString()
504 qCDebug(BluetoothBleWorkerLog) <<
"No write characteristic found";
508 if (_readCharacteristic.isValid() && _writeCharacteristic.isValid() &&
509 _readCharacteristic.uuid() == _writeCharacteristic.uuid()) {
510 qCWarning(BluetoothBleWorkerLog) <<
"Read and write characteristics resolved to the same UUID:"
511 << _readCharacteristic.uuid().toString()
512 <<
"- bidirectional communication may not work as intended";
516void BluetoothBleWorker::_onServiceStateChanged(QLowEnergyService::ServiceState state)
518 qCDebug(BluetoothBleWorkerLog) <<
"Service state changed:" << state;
520 if (state == QLowEnergyService::RemoteServiceDiscovered) {
525 _findCharacteristics();
527 if (!_writeCharacteristic.isValid()) {
532 if (_readCharacteristic.isValid()) {
533 _enableNotifications();
538 if (_controller && _controller->state() == QLowEnergyController::ConnectedState) {
539 QLowEnergyConnectionParameters params;
540 params.setIntervalRange(6, 12);
541 params.setLatency(0);
542 params.setSupervisionTimeout(500);
543 _controller->requestConnectionUpdate(params);
551 qCDebug(BluetoothBleWorkerLog) <<
"=== BLE Connection Established ===" <<
_device.name();
552 qCDebug(BluetoothBleWorkerLog) <<
" Service:" << _service->serviceUuid().toString();
553 qCDebug(BluetoothBleWorkerLog) <<
" MTU:" << _mtu;
554 qCDebug(BluetoothBleWorkerLog) <<
" Read:" << (_readCharacteristic.isValid() ? _readCharacteristic.uuid().toString() :
"N/A");
555 qCDebug(BluetoothBleWorkerLog) <<
" Write:" << (_writeCharacteristic.isValid() ? _writeCharacteristic.uuid().toString() :
"N/A");
561void BluetoothBleWorker::_enableNotifications()
563 if (!_service || !_readCharacteristic.isValid()) {
567 const QLowEnergyDescriptor notificationDescriptor = _readCharacteristic.descriptor(
568 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
570 if (!notificationDescriptor.isValid()) {
571 qCWarning(BluetoothBleWorkerLog) <<
"CCCD descriptor missing on read characteristic — notifications will not work";
576 if (_readCharacteristic.properties() & QLowEnergyCharacteristic::Notify) {
577 value = QLowEnergyCharacteristic::CCCDEnableNotification;
578 qCDebug(BluetoothBleWorkerLog) <<
"Enabling notifications for read characteristic";
579 }
else if (_readCharacteristic.properties() & QLowEnergyCharacteristic::Indicate) {
580 value = QLowEnergyCharacteristic::CCCDEnableIndication;
581 qCDebug(BluetoothBleWorkerLog) <<
"Enabling indications for read characteristic";
584 if (!value.isEmpty()) {
585 _service->writeDescriptor(notificationDescriptor, value);
589void BluetoothBleWorker::_onCharacteristicChanged(
const QLowEnergyCharacteristic &characteristic,
590 const QByteArray &value)
592 if ((characteristic.uuid() == _readCharacteristic.uuid()) && !value.isEmpty()) {
593 qCDebug(BluetoothBleWorkerLog) <<
_device.name() <<
"BLE Received" << value.size() <<
"bytes";
598void BluetoothBleWorker::_onCharacteristicRead(
const QLowEnergyCharacteristic &characteristic,
599 const QByteArray &value)
601 qCDebug(BluetoothBleWorkerVerboseLog) <<
"Characteristic read:" << characteristic.uuid().toString()
602 <<
"Value length:" << value.size();
605void BluetoothBleWorker::_onCharacteristicWritten(
const QLowEnergyCharacteristic &characteristic,
606 const QByteArray &value)
610 if (characteristic.uuid() == _writeCharacteristic.uuid()) {
611 _processNextBleWrite();
615void BluetoothBleWorker::_onDescriptorRead(
const QLowEnergyDescriptor &descriptor,
616 const QByteArray &value)
618 qCDebug(BluetoothBleWorkerVerboseLog) <<
"Descriptor read:" << descriptor.uuid().toString()
619 <<
"Value:" << value.toHex();
622void BluetoothBleWorker::_onDescriptorWritten(
const QLowEnergyDescriptor &descriptor,
623 const QByteArray &value)
625 qCDebug(BluetoothBleWorkerLog) <<
"Descriptor written:" << descriptor.uuid().toString()
626 <<
"Value:" << value.toHex();
629void BluetoothBleWorker::_onServiceError(QLowEnergyService::ServiceError
error)
632 qCWarning(BluetoothBleWorkerLog) <<
"BLE Service error:" <<
error;
635 _clearBleWriteQueue();
638 _service->deleteLater();
642 _readCharacteristic = QLowEnergyCharacteristic();
643 _writeCharacteristic = QLowEnergyCharacteristic();
645 const bool wasConnected =
_connected.exchange(
false);
647 qCDebug(BluetoothBleWorkerLog) <<
"Connection attempt failed, emitting disconnected signal";
static QString characteristicPropertiesToString(QLowEnergyCharacteristic::PropertyTypes props)
#define QGC_LOGGING_CATEGORY(name, categoryStr)
~BluetoothBleWorker() override
void onSetupConnection() override
void onResetAfterConsecutiveFailures() override
void onConnectLink() override
BluetoothBleWorker(const BluetoothConfiguration *config, QObject *parent=nullptr)
void onDisconnectLink() override
void onServiceDiscoveryTimeout() override
void onWriteData(const QByteArray &data) override
static const QBluetoothUuid NORDIC_UART_SERVICE
static const QBluetoothUuid TI_SENSORTAG_SERVICE
void dataReceived(const QByteArray &data)
const QBluetoothUuid _readCharacteristicUuid
QPointer< QTimer > _serviceDiscoveryTimer
void dataSent(const QByteArray &data)
const QBluetoothUuid _serviceUuid
void errorOccurred(const QString &errorString)
const QBluetoothUuid _writeCharacteristicUuid
void rssiUpdated(qint16 rssi)
QPointer< QTimer > _reconnectTimer
std::atomic< int > _reconnectAttempts
std::atomic< bool > _connected
const QBluetoothDeviceInfo _device
std::atomic< bool > _intentionalDisconnect