NimbleBluetooth: switch to high-throughput BLE mode during config, then lower-power BLE mode for steady-state

This commit is contained in:
Mike Robbins 2025-10-17 22:19:52 -04:00
parent a4785eed80
commit cdb4d689b0

View File

@ -27,14 +27,6 @@
#include "nimble/nimble/host/include/host/ble_gap.h" #include "nimble/nimble/host/include/host/ble_gap.h"
#endif #endif
// Debugging options: careful, they slow things down quite a bit!
// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration
// #define DEBUG_NIMBLE_ON_WRITE_TIMING // uncomment to time onWrite duration
// #define DEBUG_NIMBLE_NOTIFY // uncomment to enable notify logging
#define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3
#define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3
namespace namespace
{ {
constexpr uint16_t kPreferredBleMtu = 517; constexpr uint16_t kPreferredBleMtu = 517;
@ -43,12 +35,21 @@ constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
} // namespace } // namespace
#endif #endif
// Debugging options: careful, they slow things down quite a bit!
// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration
// #define DEBUG_NIMBLE_ON_WRITE_TIMING // uncomment to time onWrite duration
// #define DEBUG_NIMBLE_NOTIFY // uncomment to enable notify logging
#define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3
#define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3
NimBLECharacteristic *fromNumCharacteristic; NimBLECharacteristic *fromNumCharacteristic;
NimBLECharacteristic *BatteryCharacteristic; NimBLECharacteristic *BatteryCharacteristic;
NimBLECharacteristic *logRadioCharacteristic; NimBLECharacteristic *logRadioCharacteristic;
NimBLEServer *bleServer; NimBLEServer *bleServer;
static bool passkeyShowing; static bool passkeyShowing;
static std::atomic<int32_t> nimbleBluetoothConnHandle{-1}; // actual handles are uint16_t, so -1 means "no connection"
class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{ {
@ -90,6 +91,32 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
return INT32_MAX; return INT32_MAX;
} }
virtual void onConfigStart() override
{
LOG_INFO("BLE onConfigStart");
// Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
if (bleServer && isConnected()) {
int32_t conn_handle = nimbleBluetoothConnHandle.load();
if (conn_handle != -1) {
requestHighThroughputConnection(static_cast<uint16_t>(conn_handle));
}
}
}
virtual void onConfigComplete() override
{
LOG_INFO("BLE onConfigComplete");
// Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
if (bleServer && isConnected()) {
int32_t conn_handle = nimbleBluetoothConnHandle.load();
if (conn_handle != -1) {
requestLowerPowerConnection(static_cast<uint16_t>(conn_handle));
}
}
}
bool runOnceHasWorkToDo() { return runOnceHasWorkToPhone() || runOnceHasWorkFromPhone(); } bool runOnceHasWorkToDo() { return runOnceHasWorkToPhone() || runOnceHasWorkFromPhone(); }
bool runOnceHasWorkToPhone() { return onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket(); } bool runOnceHasWorkToPhone() { return onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket(); }
@ -236,6 +263,54 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
/// Check the current underlying physical link to see if the client is currently connected /// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; } virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; }
void requestHighThroughputConnection(uint16_t conn_handle)
{
/* Request a lower-latency, higher-throughput BLE connection.
This comes at the cost of higher power consumption, so we may want to only use this for initial setup, and then switch to
a slower mode.
See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
recommendations.)
Selected settings:
minInterval (units of 1.25ms): 7.5ms = 6 (lower than the Apple recommended minimum, but allows faster when the client
supports it.)
maxInterval (units of 1.25ms): 15ms = 12
latency: 0 (don't allow peripheral to skip any connection events)
timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
These are intentionally aggressive to prioritize speed over power consumption, but are only used for a few seconds at
setup. Not worth adjusting much.
*/
LOG_INFO("BLE requestHighThroughputConnection");
bleServer->updateConnParams(conn_handle, 6, 12, 0, 600);
}
void requestLowerPowerConnection(uint16_t conn_handle)
{
/* Request a lower power consumption (but higher latency, lower throughput) BLE connection.
This is suitable for steady-state operation after initial setup is complete.
See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
recommendations.)
Selected settings:
minInterval (units of 1.25ms): 30ms = 24
maxInterval (units of 1.25ms): 50ms = 40
latency: 2 (allow peripheral to skip up to 2 consecutive connection events to save power)
timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
There's an opportunity for tuning here if anyone wants to do some power measurements, but these should allow 10-20 packets
per second.
*/
LOG_INFO("BLE requestLowerPowerConnection");
bleServer->updateConnParams(conn_handle, 24, 40, 2, 600);
}
}; };
static BluetoothPhoneAPI *bluetoothPhoneAPI; static BluetoothPhoneAPI *bluetoothPhoneAPI;
@ -489,11 +564,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
screen->endAlert(); screen->endAlert();
} }
// Request high-throughput connection parameters for faster setup // Store the connection handle for future use
#ifdef NIMBLE_TWO #ifdef NIMBLE_TWO
requestHighThroughputConnection(connInfo); nimbleBluetoothConnHandle = connInfo.getConnHandle();
#else #else
requestHighThroughputConnection(desc); nimbleBluetoothConnHandle = desc->conn_handle;
#endif #endif
} }
@ -561,6 +636,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
memset(lastToRadio, 0, sizeof(lastToRadio)); memset(lastToRadio, 0, sizeof(lastToRadio));
nimbleBluetoothConnHandle = -1; // -1 means "no connection"
#ifdef NIMBLE_TWO #ifdef NIMBLE_TWO
// Restart Advertising // Restart Advertising
ble->startAdvertising(); ble->startAdvertising();
@ -575,70 +653,6 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
} }
#endif #endif
} }
#ifdef NIMBLE_TWO
void requestHighThroughputConnection(NimBLEConnInfo &connInfo)
#else
void requestHighThroughputConnection(ble_gap_conn_desc *desc)
#endif
{
/* Request a lower-latency, higher-throughput BLE connection.
This comes at the cost of higher power consumption, so we may want to only use this for initial setup, and then switch to
a slower mode.
See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
recommendations.)
Selected settings:
minInterval (units of 1.25ms): 7.5ms = 6 (lower than the Apple recommended minimum, but allows faster when the client
supports it.)
maxInterval (units of 1.25ms): 15ms = 12
latency: 0 (don't allow peripheral to skip any connection events)
timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
These are intentionally aggressive to prioritize speed over power consumption, but are only used for a few seconds at
setup. Not worth adjusting much.
*/
LOG_INFO("BLE requestHighThroughputConnection");
#ifdef NIMBLE_TWO
bleServer->updateConnParams(connInfo.getConnHandle(), 6, 12, 0, 600);
#else
bleServer->updateConnParams(desc->conn_handle, 6, 12, 0, 600);
#endif
}
#ifdef NIMBLE_TWO
void requestLowerPowerConnection(NimBLEConnInfo &connInfo)
#else
void requestLowerPowerConnection(ble_gap_conn_desc *desc)
#endif
{
/* Request a lower power consumption (but higher latency, lower throughput) BLE connection.
This is suitable for steady-state operation after initial setup is complete.
See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
recommendations.)
Selected settings:
minInterval (units of 1.25ms): 30ms = 24
maxInterval (units of 1.25ms): 50ms = 40
latency: 2 (allow peripheral to skip up to 2 consecutive connection events to save power)
timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
There's an opportunity for tuning here if anyone wants to do some power measurements, but these should allow 10-20 packets
per second.
*/
LOG_INFO("BLE requestLowerPowerConnection");
#ifdef NIMBLE_TWO
bleServer->updateConnParams(connInfo.getConnHandle(), 24, 40, 2, 600);
#else
bleServer->updateConnParams(desc->conn_handle, 24, 40, 2, 600);
#endif
}
}; };
static NimbleBluetoothToRadioCallback *toRadioCallbacks; static NimbleBluetoothToRadioCallback *toRadioCallbacks;
@ -879,4 +893,4 @@ void clearNVS()
ESP.restart(); ESP.restart();
#endif #endif
} }
#endif #endif