Skip to content

Commit 965d9e9

Browse files
authored
Merge branch 'master' into fix/deprecated-flush
2 parents a65c12d + 66fd111 commit 965d9e9

File tree

12 files changed

+579
-17
lines changed

12 files changed

+579
-17
lines changed

.github/scripts/on-release.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,11 @@ pushd "$OUTPUT_DIR" >/dev/null
391391
tar -cJf "$LIBS_XZ" "esp32-arduino-libs"
392392
popd >/dev/null
393393

394+
# Copy esp-hosted binaries
395+
396+
mkdir -p "$GITHUB_WORKSPACE/hosted"
397+
cp "$OUTPUT_DIR/esp32-arduino-libs/hosted"/*.bin "$GITHUB_WORKSPACE/hosted/"
398+
394399
# Upload ZIP and XZ libs to release page
395400

396401
echo "Uploading ZIP libs to release page ..."

.github/workflows/release.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,45 @@ jobs:
3636
env:
3737
GITHUB_TOKEN: ${{ secrets.TOOLS_UPLOAD_PAT }}
3838
run: bash ./.github/scripts/on-release.sh
39+
40+
- name: Upload hosted binaries
41+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
42+
with:
43+
name: hosted
44+
if-no-files-found: ignore
45+
path: ${{ github.workspace }}/hosted
46+
47+
upload-hosted-binaries:
48+
name: Upload hosted binaries
49+
needs: build
50+
runs-on: ubuntu-latest
51+
steps:
52+
- name: Checkout gh-pages branch
53+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
54+
with:
55+
ref: gh-pages
56+
57+
- name: Download hosted binaries
58+
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
59+
with:
60+
name: hosted
61+
path: ${{ github.workspace }}/hosted-latest
62+
63+
- name: Copy hosted binaries to proper directory and commit
64+
env:
65+
GITHUB_TOKEN: ${{ secrets.TOOLS_UPLOAD_PAT }}
66+
run: |
67+
# Create hosted directory if it doesn't exist
68+
mkdir -p ${{ github.workspace }}/hosted
69+
70+
# Copy hosted binaries to proper directory without overwriting existing files
71+
cp -n ${{ github.workspace }}/hosted-latest/*.bin ${{ github.workspace }}/hosted/
72+
73+
# Commit the changes
74+
git config user.name "github-actions[bot]"
75+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
76+
git add hosted/*.bin
77+
if ! git diff --cached --quiet; then
78+
git commit -m "Add new esp-hosted slave binaries"
79+
git push origin HEAD:gh-pages
80+
fi

docs/en/troubleshooting.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Solution
149149
^^^^^^^^
150150

151151
Newer ESP32 variants have two possible USB connectors - USB and UART. The UART connector will go through a USB->UART adapter, and will typically present itself with the name of that mfr (eg, Silicon Labs CP210x UART Bridge). The USB connector can be used as a USB-CDC bridge and will appear as an Espressif device (Espressif USB JTAG/serial debug unit). On Espressif devkits, both connections are available, and will be labeled. ESP32 can only use UART, so will only have one connector. Other variants with one connector will typically be using USB. Please check in the product [datasheet](https://products.espressif.com) or [hardware guide](https://www.espressif.com/en/products/devkits) to find Espressif products with the appropriate USB connections for your needs.
152-
If you use the UART connector, you should disable USB-CDC on boot under the Tools menu (-D ARDUINO_USB_CDC_ON_BOOT=0). If you use the USB connector, you should have that enabled (-D ARDUINO_USB_CDC_ON_BOOT=1) and set USB Mode to "Hardware CDC and JTAG" (-D ARDUINO_USB_MODE=0).
152+
If you use the UART connector, you should disable USB-CDC on boot under the Tools menu (-D ARDUINO_USB_CDC_ON_BOOT=0). If you use the USB connector, you should have that enabled (-D ARDUINO_USB_CDC_ON_BOOT=1) and set USB Mode to "Hardware CDC and JTAG" (-D ARDUINO_USB_MODE=1).
153153
USB-CDC may not be able to initialize in time to catch all the data if your device is in a tight reboot loop. This can make it difficult to troubleshoot initialization issues.
154154

155155
SPIFFS mount failed

libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
/*
2-
Secure client with static passkey
2+
Secure client with static passkey and IRK retrieval
33
44
This example demonstrates how to create a secure BLE client that connects to
55
a secure BLE server using a static passkey without prompting the user.
66
The client will automatically use the same passkey (123456) as the server.
77
8+
After successful bonding, the example demonstrates how to retrieve the
9+
server's Identity Resolving Key (IRK) in multiple formats:
10+
- Comma-separated hex format: 0x1A,0x1B,0x1C,...
11+
- Base64 encoded (for Home Assistant Private BLE Device service)
12+
- Reverse hex order (for Home Assistant ESPresense)
13+
814
This client is designed to work with the Server_secure_static_passkey example.
915
1016
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
1117
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
1218
This means that in NimBLE you can read the insecure characteristic without entering
1319
the passkey. This is not possible in Bluedroid.
1420
15-
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
16-
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
17-
"Just Works" pairing method (with encryption if secure connection is enabled).
21+
IMPORTANT:
22+
- MITM (Man-In-The-Middle protection) must be enabled for password prompts to work.
23+
- Bonding must be enabled to store and retrieve the IRK.
24+
- The server must distribute its Identity Key during pairing.
1825
1926
Based on examples from Neil Kolban and h2zero.
2027
Created by lucasssvaz.
@@ -36,10 +43,59 @@ static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8");
3643
static boolean doConnect = false;
3744
static boolean connected = false;
3845
static boolean doScan = false;
46+
static BLEClient *pClient = nullptr;
3947
static BLERemoteCharacteristic *pRemoteInsecureCharacteristic;
4048
static BLERemoteCharacteristic *pRemoteSecureCharacteristic;
4149
static BLEAdvertisedDevice *myDevice;
4250

51+
// Print an IRK buffer as hex with leading zeros and ':' separator
52+
static void printIrkBinary(uint8_t *irk) {
53+
for (int i = 0; i < 16; i++) {
54+
if (irk[i] < 0x10) {
55+
Serial.print("0");
56+
}
57+
Serial.print(irk[i], HEX);
58+
if (i < 15) {
59+
Serial.print(":");
60+
}
61+
}
62+
}
63+
64+
static void get_peer_irk(BLEAddress peerAddr) {
65+
Serial.println("\n=== Retrieving peer IRK (Server) ===\n");
66+
67+
uint8_t irk[16];
68+
69+
// Get IRK in binary format
70+
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
71+
Serial.println("Successfully retrieved peer IRK in binary format:");
72+
printIrkBinary(irk);
73+
Serial.println("\n");
74+
}
75+
76+
// Get IRK in different string formats
77+
String irkString = BLEDevice::getPeerIRKString(peerAddr);
78+
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
79+
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
80+
81+
if (irkString.length() > 0) {
82+
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
83+
Serial.print("IRK (comma-separated hex): ");
84+
Serial.println(irkString);
85+
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
86+
Serial.println(irkBase64);
87+
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
88+
Serial.println(irkReverse);
89+
Serial.println();
90+
} else {
91+
Serial.println("!!! Failed to retrieve peer IRK !!!");
92+
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
93+
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
94+
}
95+
96+
Serial.println("=======================================\n");
97+
}
98+
4399
// Callback function to handle notifications
44100
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
45101
Serial.print("Notify callback for characteristic ");
@@ -62,11 +118,30 @@ class MyClientCallback : public BLEClientCallbacks {
62118
}
63119
};
64120

121+
// Security callbacks to print IRKs once authentication completes
122+
class MySecurityCallbacks : public BLESecurityCallbacks {
123+
#if defined(CONFIG_BLUEDROID_ENABLED)
124+
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
125+
// Print the IRK received by the peer
126+
BLEAddress peerAddr(desc.bd_addr);
127+
get_peer_irk(peerAddr);
128+
}
129+
#endif
130+
131+
#if defined(CONFIG_NIMBLE_ENABLED)
132+
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
133+
// Print the IRK received by the peer
134+
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
135+
get_peer_irk(peerAddr);
136+
}
137+
#endif
138+
};
139+
65140
bool connectToServer() {
66141
Serial.print("Forming a secure connection to ");
67142
Serial.println(myDevice->getAddress().toString().c_str());
68143

69-
BLEClient *pClient = BLEDevice::createClient();
144+
pClient = BLEDevice::createClient();
70145
Serial.println(" - Created client");
71146

72147
pClient->setClientCallbacks(new MyClientCallback());
@@ -192,15 +267,19 @@ void setup() {
192267
pSecurity->setPassKey(true, CLIENT_PIN);
193268

194269
// Set authentication mode to match server requirements
195-
// Enable secure connection and MITM (for password prompts) for this example
196-
pSecurity->setAuthenticationMode(false, true, true);
270+
// Enable bonding, MITM (for password prompts), and secure connection for this example
271+
// Bonding is required to store and retrieve the IRK
272+
pSecurity->setAuthenticationMode(true, true, true);
197273

198274
// Set IO capability to KeyboardOnly
199275
// We need the proper IO capability for MITM authentication even
200276
// if the passkey is static and won't be entered by the user
201277
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
202278
pSecurity->setCapability(ESP_IO_CAP_IN);
203279

280+
// Set callbacks to handle authentication completion and print IRKs
281+
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
282+
204283
// Retrieve a Scanner and set the callback we want to use to be informed when we
205284
// have detected a new device. Specify that we want active scanning and start the
206285
// scan to run for 5 seconds.

libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,73 @@
4040
// This is an example passkey. You should use a different or random passkey.
4141
#define SERVER_PIN 123456
4242

43+
// Print an IRK buffer as hex with leading zeros and ':' separator
44+
static void printIrkBinary(uint8_t *irk) {
45+
for (int i = 0; i < 16; i++) {
46+
if (irk[i] < 0x10) {
47+
Serial.print("0");
48+
}
49+
Serial.print(irk[i], HEX);
50+
if (i < 15) {
51+
Serial.print(":");
52+
}
53+
}
54+
}
55+
56+
static void get_peer_irk(BLEAddress peerAddr) {
57+
Serial.println("\n=== Retrieving peer IRK (Client) ===\n");
58+
59+
uint8_t irk[16];
60+
61+
// Get IRK in binary format
62+
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
63+
Serial.println("Successfully retrieved peer IRK in binary format:");
64+
printIrkBinary(irk);
65+
Serial.println("\n");
66+
}
67+
68+
// Get IRK in different string formats
69+
String irkString = BLEDevice::getPeerIRKString(peerAddr);
70+
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
71+
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
72+
73+
if (irkString.length() > 0) {
74+
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
75+
Serial.print("IRK (comma-separated hex): ");
76+
Serial.println(irkString);
77+
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
78+
Serial.println(irkBase64);
79+
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
80+
Serial.println(irkReverse);
81+
Serial.println();
82+
} else {
83+
Serial.println("!!! Failed to retrieve peer IRK !!!");
84+
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
85+
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
86+
}
87+
88+
Serial.println("=======================================\n");
89+
}
90+
91+
// Security callbacks to print IRKs once authentication completes
92+
class MySecurityCallbacks : public BLESecurityCallbacks {
93+
#if defined(CONFIG_BLUEDROID_ENABLED)
94+
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
95+
// Print the IRK received by the peer
96+
BLEAddress peerAddr(desc.bd_addr);
97+
get_peer_irk(peerAddr);
98+
}
99+
#endif
100+
101+
#if defined(CONFIG_NIMBLE_ENABLED)
102+
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
103+
// Print the IRK received by the peer
104+
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
105+
get_peer_irk(peerAddr);
106+
}
107+
#endif
108+
};
109+
43110
void setup() {
44111
Serial.begin(115200);
45112
Serial.println("Starting BLE work!");
@@ -76,8 +143,11 @@ void setup() {
76143
pSecurity->setCapability(ESP_IO_CAP_OUT);
77144

78145
// Set authentication mode
79-
// Require secure connection and MITM (for password prompts) for this example
80-
pSecurity->setAuthenticationMode(false, true, true);
146+
// Enable bonding, MITM (for password prompts), and secure connection for this example
147+
pSecurity->setAuthenticationMode(true, true, true);
148+
149+
// Set callbacks to handle authentication completion and print IRKs
150+
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
81151

82152
BLEServer *pServer = BLEDevice::createServer();
83153
pServer->advertiseOnDisconnect(true);

libraries/BLE/src/BLECharacteristic.cpp

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,52 @@ void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic, esp
904904

905905
#if defined(CONFIG_NIMBLE_ENABLED)
906906

907+
/**
908+
* @brief Process a deferred write callback.
909+
*
910+
* This function is called as a FreeRTOS task to execute the onWrite callback
911+
* after the write response has been sent to the client. This maintains backwards
912+
* compatibility with Bluedroid, where the write response is sent before the
913+
* onWrite callback is invoked.
914+
*
915+
* The delay is based on the connection interval to ensure the write response
916+
* packet has been transmitted over the air before the callback executes.
917+
*
918+
* See: https://github.com/espressif/arduino-esp32/issues/11938
919+
*/
920+
void BLECharacteristic::processDeferredWriteCallback(void *pvParameters) {
921+
DeferredWriteCallback *pCallback = (DeferredWriteCallback *)pvParameters;
922+
923+
// Get connection parameters to calculate appropriate delay
924+
ble_gap_conn_desc desc;
925+
int rc = ble_gap_conn_find(pCallback->conn_handle, &desc);
926+
927+
if (rc == 0) {
928+
// Connection interval is in units of 1.25ms
929+
// Wait for at least one connection interval to ensure the write response
930+
// has been transmitted. Add a small buffer for processing.
931+
uint16_t intervalMs = (desc.conn_itvl * 125) / 100; // Convert to milliseconds
932+
uint16_t delayMs = intervalMs + 5; // Add 5ms buffer
933+
934+
log_v("Deferring write callback by %dms (conn_interval=%d units, %dms)", delayMs, desc.conn_itvl, intervalMs);
935+
vTaskDelay(pdMS_TO_TICKS(delayMs));
936+
} else {
937+
// If we can't get connection parameters, use a conservative default
938+
// Most connections use 7.5-30ms intervals, so 50ms should be safe
939+
log_w("Could not get connection parameters, using default 50ms delay");
940+
vTaskDelay(pdMS_TO_TICKS(50));
941+
}
942+
943+
// Call the onWrite callback now that the response has been transmitted
944+
pCallback->pCharacteristic->m_pCallbacks->onWrite(pCallback->pCharacteristic, &pCallback->desc);
945+
946+
// Free the allocated memory
947+
delete pCallback;
948+
949+
// Delete this one-shot task
950+
vTaskDelete(NULL);
951+
}
952+
907953
int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
908954
const ble_uuid_t *uuid;
909955
int rc;
@@ -955,7 +1001,28 @@ int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr
9551001
rc = ble_gap_conn_find(conn_handle, &desc);
9561002
assert(rc == 0);
9571003
pCharacteristic->setValue(buf, len);
958-
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, &desc);
1004+
1005+
// Defer the onWrite callback to maintain backwards compatibility with Bluedroid.
1006+
// In Bluedroid, the write response is sent BEFORE the onWrite callback is invoked.
1007+
// In NimBLE, the response is sent implicitly when this function returns.
1008+
// By deferring the callback to a separate task with a delay based on the connection
1009+
// interval, we ensure the response packet is transmitted before the callback executes.
1010+
// See: https://github.com/espressif/arduino-esp32/issues/11938
1011+
DeferredWriteCallback *pCallback = new DeferredWriteCallback();
1012+
pCallback->pCharacteristic = pCharacteristic;
1013+
pCallback->desc = desc;
1014+
pCallback->conn_handle = conn_handle;
1015+
1016+
// Create a one-shot task to execute the callback after the response is transmitted
1017+
// Using priority 1 (low priority) and sufficient stack for callback operations
1018+
// Note: Stack must be large enough to handle notify() calls from within onWrite()
1019+
xTaskCreate(
1020+
processDeferredWriteCallback, "BLEWriteCB",
1021+
4096, // Stack size - increased to handle notify() operations
1022+
pCallback,
1023+
1, // Priority (low)
1024+
NULL // Task handle (not needed for one-shot task)
1025+
);
9591026

9601027
return 0;
9611028
}

0 commit comments

Comments
 (0)