3#include <QtBluetooth/QLowEnergyConnectionParameters>
8 if (props & QLowEnergyCharacteristic::Read) propList <<
"Read";
9 if (props & QLowEnergyCharacteristic::Write) propList <<
"Write";
10 if (props & QLowEnergyCharacteristic::WriteNoResponse) propList <<
"WriteNoResponse";
11 if (props & QLowEnergyCharacteristic::Notify) propList <<
"Notify";
12 if (props & QLowEnergyCharacteristic::Indicate) propList <<
"Indicate";
13 if (props & QLowEnergyCharacteristic::WriteSigned) propList <<
"WriteSigned";
14 if (props & QLowEnergyCharacteristic::ExtendedProperty) propList <<
"Extended";
15 return propList.isEmpty() ?
"None" : propList.join(
"|");
21 _rssiTimer =
new QTimer(
this);
22 _rssiTimer->setInterval(RSSI_POLL_INTERVAL_MS);
23 _rssiTimer->setSingleShot(
false);
24 (void) connect(_rssiTimer.data(), &QTimer::timeout,
this, [
this]() {
25 if (_controller && _controller->state() != QLowEnergyController::UnconnectedState
26 && _controller->state() != QLowEnergyController::ClosingState) {
27 _controller->readRssi();
42 _service->disconnect();
43 _service->deleteLater();
46 _controller->disconnect();
47 if (_controller->state() != QLowEnergyController::UnconnectedState) {
48 _controller->disconnectFromDevice();
50 _controller->deleteLater();
56 _setupBleController();
61 qCDebug(BluetoothLinkLog) <<
"Attempting to connect to" <<
_device.name() <<
"Mode: BLE";
64 _setupBleController();
75 _controller->connectToDevice();
80 _clearBleWriteQueue();
83 _service->disconnect();
84 _service->deleteLater();
88 _readCharacteristic = QLowEnergyCharacteristic();
89 _writeCharacteristic = QLowEnergyCharacteristic();
92 _controller->disconnectFromDevice();
104 _controller->disconnectFromDevice();
110 qCDebug(BluetoothLinkLog) <<
"Recreating BLE controller after" <<
_consecutiveFailures <<
"consecutive failures";
111 _recreateBleController();
114void BluetoothBleWorker::_setupBleController()
117 qCWarning(BluetoothLinkLog) <<
"BLE controller already exists, skipping setup";
121 _controller = QLowEnergyController::createCentral(
_device,
this);
128 if (_forceRandomAddressType) {
129 qCDebug(BluetoothLinkLog) <<
"Using BLE RandomAddress fallback for" <<
_device.name();
130 _controller->setRemoteAddressType(QLowEnergyController::RandomAddress);
133 (void) connect(_controller.data(), &QLowEnergyController::connected,
this, &BluetoothBleWorker::_onControllerConnected);
134 (void) connect(_controller.data(), &QLowEnergyController::disconnected,
this, &BluetoothBleWorker::_onControllerDisconnected);
135 (void) connect(_controller.data(), &QLowEnergyController::errorOccurred,
this, &BluetoothBleWorker::_onControllerErrorOccurred);
136 (void) connect(_controller.data(), &QLowEnergyController::serviceDiscovered,
this, &BluetoothBleWorker::_onServiceDiscovered);
137 (void) connect(_controller.data(), &QLowEnergyController::discoveryFinished,
this, &BluetoothBleWorker::_onServiceDiscoveryFinished);
139 (void) connect(_controller.data(), &QLowEnergyController::mtuChanged,
this,
142 qCDebug(BluetoothLinkLog) <<
"MTU changed to:" << mtu;
144 (void) connect(_controller.data(), &QLowEnergyController::rssiRead,
this,
145 [
this](qint16 rssi) {
150 if (BluetoothLinkLog().isDebugEnabled()) {
151 (void) connect(_controller.data(), &QLowEnergyController::stateChanged,
this,
152 [](QLowEnergyController::ControllerState state) {
153 qCDebug(BluetoothLinkLog) <<
"BLE Controller State Changed:" << state;
156 (void) connect(_controller.data(), &QLowEnergyController::connectionUpdated,
this,
157 [](
const QLowEnergyConnectionParameters ¶ms) {
158 qCDebug(BluetoothLinkLog) <<
"BLE connection updated: min(ms)" << params.minimumInterval()
159 <<
"max(ms)" << params.maximumInterval()
160 <<
"latency" << params.latency()
161 <<
"supervision(ms)" << params.supervisionTimeout();
166void BluetoothBleWorker::_recreateBleController()
168 qCDebug(BluetoothLinkLog) <<
"Recreating BLE controller";
171 _service->disconnect();
172 _service->deleteLater();
177 _controller->disconnect();
178 if (_controller->state() != QLowEnergyController::UnconnectedState) {
179 _controller->disconnectFromDevice();
181 _controller->deleteLater();
182 _controller =
nullptr;
185 _readCharacteristic = QLowEnergyCharacteristic();
186 _writeCharacteristic = QLowEnergyCharacteristic();
187 _clearBleWriteQueue();
190 _setupBleController();
193void BluetoothBleWorker::_writeBleData(
const QByteArray &data)
195 if (!_writeCharacteristic.isValid()) {
206 const int effectiveMtu = (_mtu > 3) ? (_mtu - 3) : BLE_MIN_PACKET_SIZE;
207 const int packetSize = qBound(BLE_MIN_PACKET_SIZE, effectiveMtu, BLE_MAX_PACKET_SIZE);
209 const bool useWriteWithoutResponse = (_writeCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse);
210 const int numChunks = (data.size() + packetSize - 1) / packetSize;
212 qCDebug(BluetoothLinkLog) <<
_device.name() <<
"BLE Write:" << data.size() <<
"bytes,"
213 << numChunks <<
"chunks (MTU:" << _mtu <<
", packet:" << packetSize <<
")"
214 << (useWriteWithoutResponse ?
"WriteNoResponse" :
"WriteWithResponse");
217 const int chunksNeeded = (data.size() + packetSize - 1) / packetSize;
218 if (_bleWriteQueue.size() + chunksNeeded > MAX_BLE_QUEUE_SIZE) {
219 qCWarning(BluetoothLinkLog) <<
"BLE write queue full:" << _bleWriteQueue.size() <<
"chunks queued";
224 _currentBleWrite = data;
225 for (
int i = 0; i < data.size(); i += packetSize) {
226 _bleWriteQueue.enqueue(data.mid(i, packetSize));
229 if (!_bleWriteInProgress) {
230 _processNextBleWrite();
234void BluetoothBleWorker::_processNextBleWrite()
236 if (_bleWriteQueue.isEmpty()) {
237 _bleWriteInProgress =
false;
238 if (!_currentBleWrite.isEmpty()) {
240 _currentBleWrite.clear();
245 if (!_service || !_writeCharacteristic.isValid()) {
246 _clearBleWriteQueue();
250 _bleWriteInProgress =
true;
251 const QByteArray chunk = _bleWriteQueue.dequeue();
252 const bool useWriteWithoutResponse = (_writeCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse);
254 if (useWriteWithoutResponse) {
255 _service->writeCharacteristic(_writeCharacteristic, chunk, QLowEnergyService::WriteWithoutResponse);
257 QTimer::singleShot(1,
this, &BluetoothBleWorker::_processNextBleWrite);
259 _service->writeCharacteristic(_writeCharacteristic, chunk);
264void BluetoothBleWorker::_clearBleWriteQueue()
266 _bleWriteQueue.clear();
267 _currentBleWrite.clear();
268 _bleWriteInProgress =
false;
271void BluetoothBleWorker::_onControllerConnected()
273 qCDebug(BluetoothLinkLog) <<
"BLE Controller connected to device:" <<
_device.name();
279 _controller->discoverServices();
286void BluetoothBleWorker::_onControllerDisconnected()
288 qCDebug(BluetoothLinkLog) <<
"BLE Controller disconnected from device:" <<
_device.name();
296 _clearBleWriteQueue();
299 _service->deleteLater();
303 _readCharacteristic = QLowEnergyCharacteristic();
304 _writeCharacteristic = QLowEnergyCharacteristic();
310 qCDebug(BluetoothLinkLog) <<
"Starting reconnect timer";
315void BluetoothBleWorker::_onControllerErrorOccurred(QLowEnergyController::Error
error)
326 ((
error == QLowEnergyController::UnknownRemoteDeviceError) ||
327 (
error == QLowEnergyController::ConnectionError) ||
328 (
error == QLowEnergyController::NetworkError))) {
329 qCDebug(BluetoothLinkLog) <<
"BLE connect failed; retrying with RandomAddress fallback:" <<
error;
330 _forceRandomAddressType =
true;
331 _recreateBleController();
333 qCDebug(BluetoothLinkLog) <<
"Issuing BLE reconnect using RandomAddress fallback";
334 _controller->connectToDevice();
346 qCWarning(BluetoothLinkLog) <<
"BLE Controller error:" <<
error <<
errorString;
350 qCDebug(BluetoothLinkLog) <<
"Connection attempt failed, emitting disconnected signal";
355void BluetoothBleWorker::_onServiceDiscovered(
const QBluetoothUuid &uuid)
357 qCDebug(BluetoothLinkLog) <<
"Service discovered:" << uuid.toString();
368void BluetoothBleWorker::_onServiceDiscoveryFinished()
370 qCDebug(BluetoothLinkLog) <<
"Service discovery finished";
376 if (!_service && _controller) {
377 const QList<QBluetoothUuid> services = _controller->services();
378 if (!services.isEmpty()) {
379 qCDebug(BluetoothLinkLog) <<
"Using first available service";
388void BluetoothBleWorker::_setupBleService()
397 if (serviceUuid.isNull()) {
398 const QList<QBluetoothUuid> services = _controller->services();
399 if (services.isEmpty()) {
404 if (services.contains(BluetoothConfiguration::NORDIC_UART_SERVICE)) {
405 serviceUuid = BluetoothConfiguration::NORDIC_UART_SERVICE;
406 qCDebug(BluetoothLinkLog) <<
"Auto-selected Nordic UART service";
407 }
else if (services.contains(BluetoothConfiguration::TI_SENSORTAG_SERVICE)) {
408 serviceUuid = BluetoothConfiguration::TI_SENSORTAG_SERVICE;
409 qCDebug(BluetoothLinkLog) <<
"Auto-selected TI SensorTag service";
411 serviceUuid = services.first();
412 qCDebug(BluetoothLinkLog) <<
"Auto-selected first available service:" << serviceUuid.toString();
416 _service = _controller->createServiceObject(serviceUuid,
this);
423 (void) connect(_service.data(), &QLowEnergyService::stateChanged,
this, &BluetoothBleWorker::_onServiceStateChanged);
424 (void) connect(_service.data(), &QLowEnergyService::characteristicChanged,
this, &BluetoothBleWorker::_onCharacteristicChanged);
425 (void) connect(_service.data(), &QLowEnergyService::characteristicRead,
this, &BluetoothBleWorker::_onCharacteristicRead);
426 (void) connect(_service.data(), &QLowEnergyService::characteristicWritten,
this, &BluetoothBleWorker::_onCharacteristicWritten);
427 (void) connect(_service.data(), &QLowEnergyService::descriptorRead,
this, &BluetoothBleWorker::_onDescriptorRead);
428 (void) connect(_service.data(), &QLowEnergyService::descriptorWritten,
this, &BluetoothBleWorker::_onDescriptorWritten);
429 (void) connect(_service.data(), &QLowEnergyService::errorOccurred,
this, &BluetoothBleWorker::_onServiceError);
431 _discoverServiceDetails();
434void BluetoothBleWorker::_discoverServiceDetails()
440 qCDebug(BluetoothLinkLog) <<
"Discovering service details";
441 _service->discoverDetails();
444void BluetoothBleWorker::_findCharacteristics()
450 const QList<QLowEnergyCharacteristic> characteristics = _service->characteristics();
451 qCDebug(BluetoothLinkLog) <<
"Service has" << characteristics.size() <<
"characteristics";
453 for (
const QLowEnergyCharacteristic &c : characteristics) {
454 qCDebug(BluetoothLinkLog) <<
" Characteristic:" << c.uuid().toString()
461 for (
const QLowEnergyCharacteristic &c : characteristics) {
462 if (!readUuid.isNull() && (c.uuid() == readUuid)) {
463 _readCharacteristic = c;
464 qCDebug(BluetoothLinkLog) <<
"Read characteristic found by UUID:" << c.uuid().toString();
466 if (!writeUuid.isNull() && (c.uuid() == writeUuid)) {
467 _writeCharacteristic = c;
468 qCDebug(BluetoothLinkLog) <<
"Write characteristic found by UUID:" << c.uuid().toString();
472 if (!_readCharacteristic.isValid() || !_writeCharacteristic.isValid()) {
473 for (
const QLowEnergyCharacteristic &c : characteristics) {
474 if (!_readCharacteristic.isValid() &&
475 (c.properties() & (QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify))) {
476 _readCharacteristic = c;
477 qCDebug(BluetoothLinkLog) <<
"Read characteristic auto-detected:" << c.uuid().toString();
480 if (!_writeCharacteristic.isValid() &&
481 (c.properties() & (QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::WriteNoResponse))) {
482 _writeCharacteristic = c;
483 qCDebug(BluetoothLinkLog) <<
"Write characteristic auto-detected:" << c.uuid().toString();
489 if (_readCharacteristic.isValid()) {
490 qCDebug(BluetoothLinkLog) <<
"Using read characteristic:" << _readCharacteristic.uuid().toString()
493 qCDebug(BluetoothLinkLog) <<
"No read characteristic found";
496 if (_writeCharacteristic.isValid()) {
497 qCDebug(BluetoothLinkLog) <<
"Using write characteristic:" << _writeCharacteristic.uuid().toString()
500 qCDebug(BluetoothLinkLog) <<
"No write characteristic found";
504 if (_readCharacteristic.isValid() && _writeCharacteristic.isValid() &&
505 _readCharacteristic.uuid() == _writeCharacteristic.uuid()) {
506 qCWarning(BluetoothLinkLog) <<
"Read and write characteristics resolved to the same UUID:"
507 << _readCharacteristic.uuid().toString()
508 <<
"- bidirectional communication may not work as intended";
512void BluetoothBleWorker::_onServiceStateChanged(QLowEnergyService::ServiceState state)
514 qCDebug(BluetoothLinkLog) <<
"Service state changed:" << state;
516 if (state == QLowEnergyService::RemoteServiceDiscovered) {
521 _findCharacteristics();
523 if (!_writeCharacteristic.isValid()) {
528 if (_readCharacteristic.isValid()) {
529 _enableNotifications();
534 if (_controller && _controller->state() == QLowEnergyController::ConnectedState) {
535 QLowEnergyConnectionParameters params;
536 params.setIntervalRange(6, 12);
537 params.setLatency(0);
538 params.setSupervisionTimeout(500);
539 _controller->requestConnectionUpdate(params);
547 qCDebug(BluetoothLinkLog) <<
"=== BLE Connection Established ===" <<
_device.name();
548 qCDebug(BluetoothLinkLog) <<
" Service:" << _service->serviceUuid().toString();
549 qCDebug(BluetoothLinkLog) <<
" MTU:" << _mtu;
550 qCDebug(BluetoothLinkLog) <<
" Read:" << (_readCharacteristic.isValid() ? _readCharacteristic.uuid().toString() :
"N/A");
551 qCDebug(BluetoothLinkLog) <<
" Write:" << (_writeCharacteristic.isValid() ? _writeCharacteristic.uuid().toString() :
"N/A");
557void BluetoothBleWorker::_enableNotifications()
559 if (!_service || !_readCharacteristic.isValid()) {
563 const QLowEnergyDescriptor notificationDescriptor = _readCharacteristic.descriptor(
564 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
566 if (!notificationDescriptor.isValid()) {
567 qCWarning(BluetoothLinkLog) <<
"CCCD descriptor missing on read characteristic — notifications will not work";
572 if (_readCharacteristic.properties() & QLowEnergyCharacteristic::Notify) {
573 value = QLowEnergyCharacteristic::CCCDEnableNotification;
574 qCDebug(BluetoothLinkLog) <<
"Enabling notifications for read characteristic";
575 }
else if (_readCharacteristic.properties() & QLowEnergyCharacteristic::Indicate) {
576 value = QLowEnergyCharacteristic::CCCDEnableIndication;
577 qCDebug(BluetoothLinkLog) <<
"Enabling indications for read characteristic";
580 if (!value.isEmpty()) {
581 _service->writeDescriptor(notificationDescriptor, value);
585void BluetoothBleWorker::_onCharacteristicChanged(
const QLowEnergyCharacteristic &characteristic,
586 const QByteArray &value)
588 if ((characteristic.uuid() == _readCharacteristic.uuid()) && !value.isEmpty()) {
589 qCDebug(BluetoothLinkLog) <<
_device.name() <<
"BLE Received" << value.size() <<
"bytes";
594void BluetoothBleWorker::_onCharacteristicRead(
const QLowEnergyCharacteristic &characteristic,
595 const QByteArray &value)
597 qCDebug(BluetoothLinkVerboseLog) <<
"Characteristic read:" << characteristic.uuid().toString()
598 <<
"Value length:" << value.size();
601void BluetoothBleWorker::_onCharacteristicWritten(
const QLowEnergyCharacteristic &characteristic,
602 const QByteArray &value)
606 if (characteristic.uuid() == _writeCharacteristic.uuid()) {
607 _processNextBleWrite();
611void BluetoothBleWorker::_onDescriptorRead(
const QLowEnergyDescriptor &descriptor,
612 const QByteArray &value)
614 qCDebug(BluetoothLinkVerboseLog) <<
"Descriptor read:" << descriptor.uuid().toString()
615 <<
"Value:" << value.toHex();
618void BluetoothBleWorker::_onDescriptorWritten(
const QLowEnergyDescriptor &descriptor,
619 const QByteArray &value)
621 qCDebug(BluetoothLinkLog) <<
"Descriptor written:" << descriptor.uuid().toString()
622 <<
"Value:" << value.toHex();
625void BluetoothBleWorker::_onServiceError(QLowEnergyService::ServiceError
error)
628 qCWarning(BluetoothLinkLog) <<
"BLE Service error:" <<
error;
631 _clearBleWriteQueue();
634 _service->deleteLater();
638 _readCharacteristic = QLowEnergyCharacteristic();
639 _writeCharacteristic = QLowEnergyCharacteristic();
641 const bool wasConnected =
_connected.exchange(
false);
643 qCDebug(BluetoothLinkLog) <<
"Connection attempt failed, emitting disconnected signal";
static QString characteristicPropertiesToString(QLowEnergyCharacteristic::PropertyTypes props)
~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
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