-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Comms: Add BluetoothLE Link #13340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comms: Add BluetoothLE Link #13340
Conversation
201d480 to
26b515b
Compare
|
@Davidsastresas Should there be a toggle between Bluetooth and BLE or just scan whatever is available? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds Bluetooth Low Energy (BLE) support to the existing Bluetooth communication link functionality. The changes extend the current classic Bluetooth implementation to support both classic Bluetooth and BLE modes through a unified interface.
Key changes:
- Added BLE mode selection to BluetoothConfiguration with enum for ModeClassic and ModeLowEnergy
- Implemented BLE controller and service management in BluetoothWorker alongside existing classic socket handling
- Enhanced device discovery to filter devices based on selected mode (classic vs BLE)
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/UI/AppSettings/BluetoothSettings.qml | Minor QML formatting fixes and brace corrections |
| src/Comms/LinkConfiguration.h | Added logging category declaration for LinkConfiguration |
| src/Comms/LinkConfiguration.cc | Implemented logging category and updated debug statements |
| src/Comms/CMakeLists.txt | Added formatting whitespace after Bluetooth source files |
| src/Comms/BluetoothLink.h | Major refactor to support both classic and BLE modes with new properties and methods |
| src/Comms/BluetoothLink.cc | Complete implementation of BLE functionality alongside existing classic Bluetooth support |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
Thanks! To be honest I have very little experience with these devices, so I don't know if I am the best to answer that. |
26b515b to
8378bb9
Compare
|
@DonLakeFlyer if I just get something usable down would you make any changes/fixes to the QML? |
|
Sure, no problem |
8378bb9 to
eb6baa8
Compare
d3aff7d to
808399d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
808399d to
b43f873
Compare
b43f873 to
6c5d390
Compare
6c5d390 to
997fab0
Compare
997fab0 to
059df88
Compare
Build ResultsPlatform Status
All builds passed. Test Resultslinux_gcc_64: 550 passed, 0 skipped Total: 550 passed, 0 skipped Code CoverageCoverage: 100.0% No baseline available for comparison Artifact Sizes
No baseline available for comparisonUpdated: 2026-01-31 09:21:15 UTC • Triggered by: Android |
059df88 to
b4a7c8e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 14 comments.
|
|
||
| QGCLabel { | ||
| // paired property in binding forces re-evaluation when pairing status changes | ||
| text: (subEditConfig && paired !== undefined) ? subEditConfig.getPairingStatus(currentAddress) : "" |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The text binding references the paired property to force re-evaluation, but it's checking paired !== undefined which is unnecessary. In JavaScript/QML, the property will always be defined (initialized to false on line 22). The check can be simplified to just check subEditConfig.
| text: (subEditConfig && paired !== undefined) ? subEditConfig.getPairingStatus(currentAddress) : "" | |
| text: (paired, subEditConfig ? subEditConfig.getPairingStatus(currentAddress) : "") |
| readonly property bool isPaired: subEditConfig && paired !== undefined && | ||
| isClassicMode && | ||
| deviceAddress !== "" && | ||
| subEditConfig.getPairingStatus(deviceAddress) !== qsTr("Unpaired") |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same unnecessary check for paired !== undefined here. The paired property is always defined (initialized to false on line 22).
| readonly property bool isPaired: subEditConfig && paired !== undefined && | |
| isClassicMode && | |
| deviceAddress !== "" && | |
| subEditConfig.getPairingStatus(deviceAddress) !== qsTr("Unpaired") | |
| readonly property bool isPaired: (paired, | |
| subEditConfig && | |
| isClassicMode && | |
| deviceAddress !== "" && | |
| subEditConfig.getPairingStatus(deviceAddress) !== qsTr("Unpaired")) |
| void BluetoothConfiguration::setReadUuid(const QString &uuid) | ||
| { | ||
| const QBluetoothUuid newUuid(uuid); | ||
| if (!uuid.isEmpty() && newUuid.isNull()) { | ||
| emit errorOccurred(tr("Invalid read characteristic UUID format: %1").arg(uuid)); | ||
| return; | ||
| } | ||
| if (_readCharacteristicUuid != newUuid) { | ||
| _readCharacteristicUuid = newUuid; | ||
| emit readUuidChanged(); | ||
| } |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue with read UUID validation - empty strings should be allowed for auto-detect but the code will emit an error for empty input.
| void BluetoothConfiguration::setWriteUuid(const QString &uuid) | ||
| { | ||
| const QBluetoothUuid newUuid(uuid); | ||
| if (!uuid.isEmpty() && newUuid.isNull()) { | ||
| emit errorOccurred(tr("Invalid write characteristic UUID format: %1").arg(uuid)); | ||
| return; | ||
| } | ||
| if (_writeCharacteristicUuid != newUuid) { | ||
| _writeCharacteristicUuid = newUuid; | ||
| emit writeUuidChanged(); | ||
| } |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue with write UUID validation - empty strings should be allowed for auto-detect but the code will emit an error for empty input.
| #include <QtCore/QVariantMap> | ||
|
|
||
| QGC_LOGGING_CATEGORY(BluetoothLinkLog, "Comms.BluetoothLink") | ||
| QGC_LOGGING_CATEGORY(BluetoothLinkVerboseLog, "Comms.BluetoothLink:verbose") |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logging category BluetoothLinkVerboseLog is defined and used in the .cc file but is not declared in the header file. This means it's not accessible from other translation units that might want to use it. Add Q_DECLARE_LOGGING_CATEGORY(BluetoothLinkVerboseLog) to the header file after the BluetoothLinkLog declaration.
| // Filter out entries with unknown/invalid RSSI. These often come from cached results | ||
| // and cause turned-off devices to appear with no signal. | ||
| if (info.rssi() == 0) { | ||
| return; | ||
| } |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When checking RSSI validity, the code filters out devices with rssi() == 0 (lines 156, 490, 522). However, a legitimate RSSI value of 0 dBm is theoretically possible (though rare in practice). The comment on line 488-489 mentions "unknown/invalid RSSI" but 0 dBm is a valid signal strength. Consider using a different sentinel value or checking info.rssi() == std::numeric_limits<qint16>::min() or similar to distinguish between "no RSSI data" and "RSSI is actually 0 dBm".
| void BluetoothConfiguration::setServiceUuid(const QString &uuid) | ||
| { | ||
| const QBluetoothUuid newUuid(uuid); | ||
| if (!uuid.isEmpty() && newUuid.isNull()) { | ||
| emit errorOccurred(tr("Invalid service UUID format: %1").arg(uuid)); | ||
| return; | ||
| } | ||
| if (_serviceUuid != newUuid) { | ||
| _serviceUuid = newUuid; | ||
| emit serviceUuidChanged(); | ||
| } |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service UUID validation allows empty strings to indicate "auto-detect" (line 321), but the code on line 320 creates a QBluetoothUuid from the empty string which results in a null UUID. The subsequent check newUuid.isNull() on line 321 will be true for empty strings, causing an error to be emitted. This contradicts the intention stated in the comment on line 16 of BluetoothSettings.qml. The logic should be: if empty, accept it; if not empty and invalid, reject it.
| const int nextInterval = qMin(RECONNECT_BASE_INTERVAL_MS * (1 << (_reconnectAttempts - 1)), MAX_RECONNECT_INTERVAL_MS); | ||
| if (_reconnectTimer) { | ||
| _reconnectTimer->setInterval(nextInterval); | ||
| } |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code attempts to reconnect with exponential backoff using bit shifting (line 1832), but there's a subtle bug. If _reconnectAttempts grows large enough, the shift (1 << (_reconnectAttempts - 1)) can overflow. With _reconnectAttempts = 10 (the max), this becomes 1 << 9 = 512, giving a 2560 second (42.7 minute) interval, which is then clamped to 60 seconds. However, if MAX_RECONNECT_ATTEMPTS were increased in the future, this could overflow an int. Consider using std::min with explicit calculations or safer exponential calculations.
| if (_workerThread) { | ||
| if (isConnected()) { | ||
| (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::QueuedConnection); | ||
| } | ||
|
|
||
| _workerThread->quit(); | ||
| if (!_workerThread->wait()) { | ||
| qCWarning(BluetoothLinkLog) << "Failed to wait for Bluetooth Thread to close"; | ||
| _workerThread->quit(); | ||
| if (!_workerThread->wait(5000)) { | ||
| qCWarning(BluetoothLinkLog) << "Failed to wait for Bluetooth Thread to close"; | ||
| _workerThread->terminate(); | ||
| if (!_workerThread->wait(1000)) { | ||
| qCCritical(BluetoothLinkLog) << "Failed to terminate Bluetooth Thread"; | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the destructor, after calling disconnectLink() with Qt::QueuedConnection (line 1929), the code immediately calls _workerThread->quit() (line 1932). Since the disconnect is queued, it may not execute before the thread quits, potentially leaving resources in an inconsistent state. Consider using Qt::BlockingQueuedConnection for the disconnectLink call in the destructor to ensure cleanup completes before thread termination.
| } | ||
| } | ||
| } | ||
|
|
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The auto-detection loop (lines 1649-1661) could select the same characteristic for both read and write if it has both Read/Notify and Write/WriteNoResponse properties. This might not be the intended behavior for bidirectional communication. After auto-detection, consider adding a check to warn or handle the case where both characteristics point to the same UUID.
| // Warn if read and write ended up using the same characteristic UUID | |
| if (_readCharacteristic.isValid() && | |
| _writeCharacteristic.isValid() && | |
| _readCharacteristic.uuid() == _writeCharacteristic.uuid()) { | |
| qCWarning(BluetoothLinkLog) << "Read and write characteristics resolved to the same UUID:" | |
| << _readCharacteristic.uuid().toString() | |
| << "- bidirectional Bluetooth communication may not work as intended"; | |
| } |
b4a7c8e to
9ca4670
Compare
Add Bluetooth Low Energy Comm Link.
Closes #12884
TODO: Add RSSI?