From 7c7068d1008ac4d813be27cf384f78879d309aa7 Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Sat, 8 Nov 2025 19:33:16 -0500
Subject: [PATCH 1/4] Add channel index handling to NimBLEAdvertisedDevice and
related structures
- Introduced m_channelIndex in NimBLEAdvertisedDevice to store the primary advertising channel.
- Implemented getChannel() method to retrieve the advertising channel.
- Updated setChannelIndex() method for setting the channel index.
- Modified handleGapEvent() in NimBLEScan to set the channel index from the discovery event.
- Added channel_index field to ble_gap_ext_disc_desc and ble_gap_disc_desc structures.
- Enhanced ble_hs_hci_evt_le_adv_rpt() to handle channel index in advertising reports.
---
src/NimBLEAdvertisedDevice.cpp | 14 ++++++++++++++
src/NimBLEAdvertisedDevice.h | 6 ++++++
src/NimBLEScan.cpp | 1 +
src/nimble/nimble/host/include/host/ble_gap.h | 6 ++++++
src/nimble/nimble/host/src/ble_hs_hci_evt.c | 15 +++++++++++++++
5 files changed, 42 insertions(+)
diff --git a/src/NimBLEAdvertisedDevice.cpp b/src/NimBLEAdvertisedDevice.cpp
index 29c9532de..0916528b5 100644
--- a/src/NimBLEAdvertisedDevice.cpp
+++ b/src/NimBLEAdvertisedDevice.cpp
@@ -36,6 +36,7 @@ NimBLEAdvertisedDevice::NimBLEAdvertisedDevice() :
m_callbackSent = false;
m_timestamp = 0;
m_advLength = 0;
+ m_channelIndex = 0xFF;
} // NimBLEAdvertisedDevice
@@ -823,6 +824,19 @@ time_t NimBLEAdvertisedDevice::getTimestamp() {
} // getTimestamp
+/**
+ * @brief Get the primary advertising channel.
+ * @return The advertising channel (37, 38, or 39) when available, or 0xFF when unknown.
+ */
+uint8_t NimBLEAdvertisedDevice::getChannel() {
+ // Map controller channel index (0-2) to BLE advertising channels (37-39)
+ if (m_channelIndex <= 2) {
+ return 37 + m_channelIndex;
+ }
+ return 0xFF;
+} // getChannel
+
+
/**
* @brief Get the length of the payload advertised by the device.
* @return The size of the payload in bytes.
diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h
index 772bab914..468988c89 100644
--- a/src/NimBLEAdvertisedDevice.h
+++ b/src/NimBLEAdvertisedDevice.h
@@ -38,6 +38,9 @@ class NimBLEScan;
*
* When we perform a %BLE scan, the result will be a set of devices that are advertising. This
* class provides a model of a detected device.
+ *
+ * The getChannel() method returns the primary advertising channel (37, 38, or 39) on which the
+ * advertisement was received, or 0xFF if the channel information is not available from the controller.
*/
class NimBLEAdvertisedDevice {
public:
@@ -120,6 +123,7 @@ class NimBLEAdvertisedDevice {
size_t getPayloadLength();
uint8_t getAddressType();
time_t getTimestamp();
+ uint8_t getChannel();
bool isAdvertisingService(const NimBLEUUID &uuid);
bool haveAppearance();
bool haveManufacturerData();
@@ -149,6 +153,7 @@ class NimBLEAdvertisedDevice {
void setAdvType(uint8_t advType, bool isLegacyAdv);
void setPayload(const uint8_t *payload, uint8_t length, bool append);
void setRSSI(int rssi);
+ void setChannelIndex(uint8_t channel) { m_channelIndex = channel; }
#if CONFIG_BT_NIMBLE_EXT_ADV
void setSetId(uint8_t sid) { m_sid = sid; }
void setPrimaryPhy(uint8_t phy) { m_primPhy = phy; }
@@ -164,6 +169,7 @@ class NimBLEAdvertisedDevice {
time_t m_timestamp;
bool m_callbackSent;
uint8_t m_advLength;
+ uint8_t m_channelIndex;
#if CONFIG_BT_NIMBLE_EXT_ADV
bool m_isLegacyAdv;
uint8_t m_sid;
diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp
index e45da4481..eb2e24a5b 100644
--- a/src/NimBLEScan.cpp
+++ b/src/NimBLEScan.cpp
@@ -130,6 +130,7 @@ NimBLEScan::~NimBLEScan() {
advertisedDevice->m_timestamp = time(nullptr);
advertisedDevice->setRSSI(disc.rssi);
+ advertisedDevice->setChannelIndex(disc.channel_index);
advertisedDevice->setPayload(disc.data, disc.length_data, (isLegacyAdv &&
event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP));
diff --git a/src/nimble/nimble/host/include/host/ble_gap.h b/src/nimble/nimble/host/include/host/ble_gap.h
index 9f4b1ee47..1289ad468 100644
--- a/src/nimble/nimble/host/include/host/ble_gap.h
+++ b/src/nimble/nimble/host/include/host/ble_gap.h
@@ -403,6 +403,9 @@ struct ble_gap_ext_disc_desc {
* set (BLE_ADDR_ANY otherwise).
*/
ble_addr_t direct_addr;
+
+ /** Primary advertising channel index (0xFF if unavailable) */
+ uint8_t channel_index;
};
#endif
@@ -433,6 +436,9 @@ struct ble_gap_disc_desc {
* event type (BLE_ADDR_ANY otherwise).
*/
ble_addr_t direct_addr;
+
+ /** Primary advertising channel index (0xFF if unavailable) */
+ uint8_t channel_index;
};
struct ble_gap_repeat_pairing {
diff --git a/src/nimble/nimble/host/src/ble_hs_hci_evt.c b/src/nimble/nimble/host/src/ble_hs_hci_evt.c
index f8d52d583..d9a5efb50 100644
--- a/src/nimble/nimble/host/src/ble_hs_hci_evt.c
+++ b/src/nimble/nimble/host/src/ble_hs_hci_evt.c
@@ -501,6 +501,7 @@ ble_hs_hci_evt_le_adv_rpt(uint8_t subevent, const void *data, unsigned int len)
data += sizeof(*ev);
desc.direct_addr = *BLE_ADDR_ANY;
+ desc.channel_index = 0xFF;
for (i = 0; i < ev->num_reports; i++) {
rpt = data;
@@ -513,6 +514,10 @@ ble_hs_hci_evt_le_adv_rpt(uint8_t subevent, const void *data, unsigned int len)
desc.length_data = rpt->data_len;
desc.data = rpt->data;
desc.rssi = rpt->data[rpt->data_len];
+ /* Channel index follows RSSI if available */
+ if (data - (const uint8_t*)rpt > sizeof(rpt) + rpt->data_len + 1) {
+ desc.channel_index = rpt->data[rpt->data_len + 1];
+ }
ble_gap_rx_adv_report(&desc);
}
@@ -535,6 +540,7 @@ ble_hs_hci_evt_le_dir_adv_rpt(uint8_t subevent, const void *data, unsigned int l
/* Data fields not present in a direct advertising report. */
desc.data = NULL;
desc.length_data = 0;
+ desc.channel_index = 0xFF;
for (i = 0; i < ev->num_reports; i++) {
desc.event_type = ev->reports[i].type;
@@ -612,6 +618,7 @@ ble_hs_hci_evt_le_ext_adv_rpt(uint8_t subevent, const void *data,
report = &ev->reports[0];
for (i = 0; i < ev->num_reports; i++) {
memset(&desc, 0, sizeof(desc));
+ desc.channel_index = 0xFF;
desc.props = (report->evt_type) & 0x1F;
if (desc.props & BLE_HCI_ADV_LEGACY_MASK) {
@@ -649,6 +656,14 @@ ble_hs_hci_evt_le_ext_adv_rpt(uint8_t subevent, const void *data,
desc.prim_phy = report->pri_phy;
desc.sec_phy = report->sec_phy;
desc.periodic_adv_itvl = report->periodic_itvl;
+ /* Channel index may follow the data if available */
+ if (report->data_len < 255) {
+ const uint8_t *channel_ptr = &report->data[report->data_len];
+ /* Verify we're not reading past the event data */
+ if ((const uint8_t*)channel_ptr < (const uint8_t*)data + len) {
+ desc.channel_index = *channel_ptr;
+ }
+ }
ble_gap_rx_ext_adv_report(&desc);
From 0a1e1be2541654bfd9662bfce082824e87791679 Mon Sep 17 00:00:00 2001
From: Darrell
Date: Mon, 10 Nov 2025 05:46:31 -0500
Subject: [PATCH 2/4] Add PlatformIO example build workflow
---
.github/workflows/platformio-examples.yml | 63 +++++++++++++++++++
.../NimBLE_Scan_Continuous.ino | 13 +++-
2 files changed, 75 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/platformio-examples.yml
diff --git a/.github/workflows/platformio-examples.yml b/.github/workflows/platformio-examples.yml
new file mode 100644
index 000000000..a2fbe0a3a
--- /dev/null
+++ b/.github/workflows/platformio-examples.yml
@@ -0,0 +1,63 @@
+name: Build Arduino examples
+
+on:
+ push:
+ paths:
+ - 'src/**'
+ - 'examples/**'
+ - 'library.properties'
+ - '.github/workflows/platformio-examples.yml'
+ pull_request:
+ paths:
+ - 'src/**'
+ - 'examples/**'
+ - 'library.properties'
+ - '.github/workflows/platformio-examples.yml'
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Cache PlatformIO
+ uses: actions/cache@v4
+ with:
+ path: ~/.platformio
+ key: platformio-${{ runner.os }}-${{ hashFiles('library.properties') }}
+ restore-keys: |
+ platformio-${{ runner.os }}-
+
+ - name: Install PlatformIO Core
+ run: pip install --upgrade platformio
+
+ - name: Build examples
+ shell: bash
+ run: |
+ set -euo pipefail
+ shopt -s nullglob
+ sketches=(examples/*/*.ino)
+
+ if [ ${#sketches[@]} -eq 0 ]; then
+ echo "No examples found"
+ exit 1
+ fi
+
+ for sketch in "${sketches[@]}"; do
+ sketch_dir=$(dirname "$sketch")
+ sketch_name=$(basename "$sketch_dir")
+ echo "::group::Building ${sketch_name}"
+ pio ci \
+ --board esp32dev \
+ --lib="." \
+ "$sketch"
+ echo "::endgroup::"
+ done
diff --git a/examples/NimBLE_Scan_Continuous/NimBLE_Scan_Continuous.ino b/examples/NimBLE_Scan_Continuous/NimBLE_Scan_Continuous.ino
index b8d24d264..6660b28c7 100644
--- a/examples/NimBLE_Scan_Continuous/NimBLE_Scan_Continuous.ino
+++ b/examples/NimBLE_Scan_Continuous/NimBLE_Scan_Continuous.ino
@@ -2,6 +2,10 @@
* This example will scan forever while consuming as few resources as possible
* and report all advertisments on the serial monitor.
*
+ * The scan callback prints the primary advertising channel for each
+ * advertisement when available, demonstrating the use of
+ * NimBLEAdvertisedDevice::getChannel().
+ *
* Created: on January 31 2021
* Author: H2zero
*
@@ -13,7 +17,14 @@ NimBLEScan* pBLEScan;
class MyAdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
- Serial.printf("Advertised Device: %s \n", advertisedDevice->toString().c_str());
+ uint8_t channel = advertisedDevice->getChannel();
+ if (channel != 0xFF) {
+ Serial.printf("Advertised Device on channel %u: %s \n",
+ channel, advertisedDevice->toString().c_str());
+ } else {
+ Serial.printf("Advertised Device on unknown channel: %s \n",
+ advertisedDevice->toString().c_str());
+ }
}
};
From 9a828ace21aa3f0d3ad9682d24288a191d95ed0a Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Mon, 10 Nov 2025 05:53:37 -0500
Subject: [PATCH 3/4] Fix channel index calculation in
ble_hs_hci_evt_le_adv_rpt function
---
src/nimble/nimble/host/src/ble_hs_hci_evt.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/nimble/nimble/host/src/ble_hs_hci_evt.c b/src/nimble/nimble/host/src/ble_hs_hci_evt.c
index d9a5efb50..8d61f2b4e 100644
--- a/src/nimble/nimble/host/src/ble_hs_hci_evt.c
+++ b/src/nimble/nimble/host/src/ble_hs_hci_evt.c
@@ -515,7 +515,7 @@ ble_hs_hci_evt_le_adv_rpt(uint8_t subevent, const void *data, unsigned int len)
desc.data = rpt->data;
desc.rssi = rpt->data[rpt->data_len];
/* Channel index follows RSSI if available */
- if (data - (const uint8_t*)rpt > sizeof(rpt) + rpt->data_len + 1) {
+ if ((const uint8_t*)data - (const uint8_t*)rpt > sizeof(*rpt) + rpt->data_len + 1) {
desc.channel_index = rpt->data[rpt->data_len + 1];
}
From 0b6ddeedd9a4dba7aecb07b28c8aa6dd482edd0e Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Mon, 10 Nov 2025 05:59:02 -0500
Subject: [PATCH 4/4] Fix pointer arithmetic in ble_hs_hci_evt_le_adv_rpt
functions
---
src/nimble/nimble/host/src/ble_hs_hci_evt.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/nimble/nimble/host/src/ble_hs_hci_evt.c b/src/nimble/nimble/host/src/ble_hs_hci_evt.c
index 8d61f2b4e..6af9ed977 100644
--- a/src/nimble/nimble/host/src/ble_hs_hci_evt.c
+++ b/src/nimble/nimble/host/src/ble_hs_hci_evt.c
@@ -465,7 +465,7 @@ ble_hs_hci_evt_le_adv_rpt_first_pass(const void *data, unsigned int len)
rpt = data;
len -= sizeof(*rpt) + 1;
- data += sizeof(rpt) + 1;
+ data += sizeof(*rpt) + 1;
if (rpt->data_len > len) {
return BLE_HS_ECONTROLLER;
@@ -506,7 +506,7 @@ ble_hs_hci_evt_le_adv_rpt(uint8_t subevent, const void *data, unsigned int len)
for (i = 0; i < ev->num_reports; i++) {
rpt = data;
- data += sizeof(rpt) + rpt->data_len + 1;
+ data += sizeof(*rpt) + rpt->data_len + 1;
desc.event_type = rpt->type;
desc.addr.type = rpt->addr_type;