-
Notifications
You must be signed in to change notification settings - Fork 95
feat(NimBLEServer): add registerServicesFirst() to place app services at low handles #443
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,6 +54,7 @@ NimBLEServer::NimBLEServer() | |
| : m_gattsStarted{false}, | ||
| m_svcChanged{false}, | ||
| m_deleteCallbacks{false}, | ||
| m_registerServicesFirst{false}, | ||
| # if !MYNEWT_VAL(BLE_EXT_ADV) | ||
| m_advertiseOnDisconnect{false}, | ||
| # endif | ||
|
|
@@ -366,6 +367,20 @@ void NimBLEServer::advertiseOnDisconnect(bool enable) { | |
| } // advertiseOnDisconnect | ||
| # endif | ||
|
|
||
| /** | ||
| * @brief Register the application's services before the standard GAP/GATT services. | ||
| * @param [in] enable true == register the application services first (they take the | ||
| * low attribute handles, starting at 0x0001, and GAP/GATT are placed after them); | ||
| * false (default) == the standard behavior, GAP/GATT register first and take the low | ||
| * handles. | ||
| * @details Must be called before the services are started (i.e. before | ||
| * NimBLEServer::start()). Useful when a peripheral must expose a fixed attribute-table | ||
| * layout that another device relies on by hardcoded handle rather than discovery. | ||
| */ | ||
| void NimBLEServer::registerServicesFirst(bool enable) { | ||
| m_registerServicesFirst = enable; | ||
| } // registerServicesFirst | ||
|
|
||
| /** | ||
| * @brief Return the number of connected clients. | ||
| * @return The number of connected clients. | ||
|
|
@@ -897,14 +912,24 @@ bool NimBLEServer::resetGATT() { | |
| #endif | ||
|
|
||
| ble_gatts_reset(); | ||
| ble_svc_gap_init(); | ||
|
|
||
| // Register the standard GAP (0x1800) and GATT (0x1801) services, restoring the | ||
| // device name/appearance that ble_gatts_reset() clears. By default this happens | ||
| // first, so GAP/GATT take the low attribute handles (0x0001+). When | ||
| // registerServicesFirst() is enabled, it is deferred until after the application | ||
| // services below, so those take the low handles instead. | ||
| auto initGapGattServices = [&]() { | ||
| ble_svc_gap_init(); | ||
| #ifndef CONFIG_USING_NIMBLE_COMPONENT | ||
| ble_svc_gap_device_name_set(name.c_str()); | ||
| ble_svc_gap_device_appearance_set(appearance); | ||
| ble_svc_gap_device_name_set(name.c_str()); | ||
| ble_svc_gap_device_appearance_set(appearance); | ||
| #endif | ||
| ble_svc_gatt_init(); | ||
| }; | ||
|
|
||
| ble_svc_gatt_init(); | ||
| if (!m_registerServicesFirst) { | ||
| initGapGattServices(); | ||
| } | ||
|
|
||
| for (auto svcIt = m_svcVec.begin(); svcIt != m_svcVec.end();) { | ||
| auto* pSvc = *svcIt; | ||
|
|
@@ -941,6 +966,12 @@ bool NimBLEServer::resetGATT() { | |
| if (pSvc->getRemoved() == 0) { | ||
| if (!pSvc->start_internal()) { | ||
| NIMBLE_LOGE(LOG_TAG, "Failed to start service: %s", pSvc->getUUID().toString().c_str()); | ||
| // When deferring GAP/GATT (registerServicesFirst), still register | ||
| // them on the failure path so the mandatory GAP/GATT services (and | ||
| // the restored name/appearance) are never left out of the database. | ||
| if (m_registerServicesFirst) { | ||
| initGapGattServices(); | ||
|
Comment on lines
+972
to
+973
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
# Resolve the effective ESP-IDF/NimBLE version from repository configuration.
fd -HI -t f 'idf_component\.yml|platformio\.ini|CMakeLists\.txt|sdkconfig.*|.*(lock|requirements).*' . \
-x rg -n -i -C 2 'esp[-_ ]?idf|nimble|version|revision|ref|tag' {}
# Inspect reset, service-queue, and start behavior in the selected implementation.
rg -n -C 12 '\bble_gatts_(reset|add_svcs|start)\s*\(' . \
-g '*.c' -g '*.cc' -g '*.cpp' -g '*.h'
rg -n -C 8 'ble_gatts_(svc_defs|num_svc_defs)|free_svc_defs' . \
-g '*.c' -g '*.cc' -g '*.cpp' -g '*.h'Repository: h2zero/esp-nimble-cpp Length of output: 8268 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- changed hunk ---'
git diff --unified=25 -- src/NimBLEServer.cpp
printf '%s\n' '--- deferred initialization and start path ---'
cat -n src/NimBLEServer.cpp | sed -n '900,1010p'
printf '%s\n' '--- bound NimBLE API declarations and local callers ---'
rg -n -C 8 '\b(ble_gatts_reset|ble_gatts_add_svcs|ble_gatts_start)\s*\(' src include examplesRepository: h2zero/esp-nimble-cpp Length of output: 8719 🤖 get_repo_knowledge executed:
Length of output: 1019 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- server start flow ---'
cat -n src/NimBLEServer.cpp | sed -n '250,315p'
printf '%s\n' '--- service start and definition cleanup ---'
cat -n src/NimBLEService.cpp | sed -n '110,180p'
rg -n -C 8 '\b(clearServiceDefinitions|m_pSvcDef|start_internal)\b' src/NimBLEService.cpp src/NimBLEService.hRepository: h2zero/esp-nimble-cpp Length of output: 13756 🌐 Web query:
💡 Result: In the Apache Mynewt NimBLE host, GATT services are registered using a table-based design [1][2]. The implementation involves queuing service definitions before they are formally activated [3][4]. Service Registration Queue The function ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs) is used to queue an array of service definitions for registration [3][1]. These services are not immediately made available to peers; instead, they are held in a registration queue [3][1]. The array of definitions must be terminated by an entry where the type field is set to 0 [4][1]. Activation The actual registration and activation of all queued services occur when ble_gatts_start is called [3][1]. In many NimBLE applications, the host handles this automatically during startup, making the services available to peers [3][1]. GATT Server Reset The function ble_gatts_reset is used to reset the GATT server to its initial state, which removes all supported services, characteristics, and descriptors [5][6]. This operation is subject to strict constraints: - No peers must be connected [5][6]. - No GAP operations (such as advertising, discovery, or connecting) can be active [5][6]. If these conditions are not met, the function returns BLE_HS_EBUSY [5][6]. Key Implementation Note NimBLE's architecture is generally optimized for static service definitions configured at initialization time [7]. While developers have explored techniques for dynamic service addition, the standard workflow relies on pre-calculating resources using functions like ble_gatts_count_cfg before invoking ble_gatts_add_svcs [7][8]. Citations:
🌐 Web query:
💡 Result: In ESP-IDF 5.3 using the NimBLE stack, managing GATT services is performed through a declarative approach where you define your services in a static table and register them with the NimBLE host during initialization [1][2]. Standard GATT Service Initialization To add services to your GATT server, the established workflow involves defining an array of ble_gatt_svc_def structures (the GATT service table) and then calling the following two core functions [1][2]: 1. ble_gatts_count_cfg(gatt_svr_svcs): This function counts the number of services and attributes defined in your table to allow the NimBLE stack to pre-allocate the necessary memory/handles [1][3]. 2. ble_gatts_add_svcs(gatt_svr_svcs): This function registers the services defined in your table into the GATT server database [1][2]. Typically, these are called within a custom initialization function (often named gatt_svr_init) before the BLE stack begins operation [3][2]. Regarding ble_gatts_reset There is no standard public API named ble_gatts_reset within the ESP-IDF NimBLE host stack for resetting GATT services during runtime. The NimBLE architecture handles stack resets via the ble_hs_cfg.reset_cb callback, which is registered during the host initialization [4][5]. If you need to change your GATT database configuration, the standard procedure is to perform these actions during the stack sync event (triggered via the ble_hs_cfg.sync_cb). This ensures that the services are registered in the correct state after the host has initialized or recovered from a reset [4][5]. Summary of Key Steps 1. Define your services in a NULL-terminated array of ble_gatt_svc_def [6][2]. 2. Call ble_gatts_count_cfg [1]. 3. Call ble_gatts_add_svcs [1]. 4. Use the registration callback (ble_hs_cfg.gatts_register_cb) to capture assigned attribute handles if needed for subsequent operations like notifications [2][5]. Citations:
🌐 Web query:
💡 Result: The functions ble_gatts_reset and ble_gatts_add_svcs are part of the NimBLE host stack (commonly used in Apache Mynewt and other Bluetooth Low Energy implementations) [1]. ble_gatts_add_svcs This function is used to queue a set of Bluetooth Low Energy service definitions for registration [1]. It takes an array of service definitions as its argument and returns 0 on success or an error code (such as BLE_HS_ENOMEM for heap exhaustion) on failure [1]. These services are formally registered when the GATT server is started (typically via ble_gatts_start) [1]. ble_gatts_reset This function resets the GATT server to its initial state, effectively removing all previously supported services, characteristics, and descriptors [1]. To execute successfully, it requires that no peers are currently connected and that no Generic Access Profile (GAP) operations, such as advertising, discovery, or active connections, are currently underway [1]. If the server is busy with these tasks, it will return the error code BLE_HS_EBUSY [1]. Citations: 🌐 Web query:
💡 Result: The functions ble_gatts_count_cfg and ble_gatts_add_svcs are core components of the NimBLE host stack used within the ESP-IDF framework [1][2]. They are specifically used to configure and register GATT (Generic Attribute Profile) services on a BLE server [1][3]. Function Overview: 1. ble_gatts_count_cfg: This function parses the provided GATT service definition array (often named gatt_svr_svcs) to calculate the resource requirements (such as the number of attributes) needed to host those services [1][2]. It must be called before registering the services to ensure the stack allocates sufficient memory and handles [1]. 2. ble_gatts_add_svcs: This function takes the same GATT service definition array and registers the services, characteristics, and descriptors with the NimBLE host stack [1][2]. Once this function returns successfully, the GATT services are ready to be used by the BLE server [1]. Typical Initialization Flow: The standard pattern for initializing a NimBLE GATT server involves calling these functions in sequence within a dedicated GATT server initialization function (commonly named gatt_svr_init) [4][2]: int gatt_svr_init(void) { int rc; // Initialize base services (GAP, GATT) ble_svc_gap_init; ble_svc_gatt_init; // Calculate requirements rc = ble_gatts_count_cfg(gatt_svr_svcs); if (rc!= 0) return rc; // Register services rc = ble_gatts_add_svcs(gatt_svr_svcs); if (rc!= 0) return rc; return 0; } Note: There is no standard function named ble_gatts_reset in the public NimBLE ESP-IDF API for resetting services [5]. If you need to stop or re-initialize the BLE stack, the recommended approach is to use the NimBLE porting layer functions such as nimble_port_stop and nimble_port_deinit, followed by a fresh initialization sequence [5]. The functions mentioned are specific to the NimBLE implementation [2]. If you are using the Bluedroid BLE stack, different APIs such as esp_ble_gatts_create_attr_tab or esp_ble_gatts_add_char are used instead [6][7]. Citations:
🌐 Web query:
💡 Result: No exact match was found in
These use 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
fetch() {
local name="$1"
local url="$2"
curl --fail --silent --show-error --location "$url" -o "$tmpdir/$name"
printf '%s\n' "--- $name ---"
rg -n -C 18 \
'ble_gatts_(reset|add_svcs|start)|svc_defs|num_svc_defs|free.*svc|svc.*free' \
"$tmpdir/$name"
}
fetch apache-master \
https://raw.githubusercontent.com/apache/mynewt-nimble/master/nimble/host/src/ble_gatts.c
fetch esp-idf-v5.3 \
https://raw.githubusercontent.com/espressif/esp-idf/v5.3/components/bt/host/nimble/nimble/host/src/ble_gatts.c
fetch esp-idf-v5.3.5 \
https://raw.githubusercontent.com/espressif/esp-idf/v5.3.5/components/bt/host/nimble/nimble/host/src/ble_gatts.cRepository: h2zero/esp-nimble-cpp Length of output: 8868 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
for ref in release/v5.3 v5.3.0 v5.3.5; do
url="https://raw.githubusercontent.com/espressif/esp-idf/${ref}/components/bt/host/nimble/nimble/host/src/ble_gatts.c"
if curl --fail --silent --show-error --location "$url" -o "$tmpdir/${ref//\//_}.c"; then
printf '%s\n' "--- $ref ---"
rg -n -C 12 \
'ble_gatts_(reset|add_svcs|start)|svc_defs|num_svc_defs|free.*svc' \
"$tmpdir/${ref//\//_}.c"
else
printf '%s\n' "--- $ref: source path unavailable ---"
fi
done
printf '%s\n' '--- ESP-IDF repository paths containing ble_gatts.c ---'
curl --fail --silent --show-error \
'https://api.github.com/repos/espressif/esp-idf/git/trees/release/v5.3?recursive=1' |
rg -o '"path": "[^"]*ble_gatts[^"]*"' | head -20Repository: h2zero/esp-nimble-cpp Length of output: 656 Arrr, clear the queued GATT definitions before allowing a retry.
🤖 Prompt for AI Agents |
||
| } | ||
| return false; | ||
| } | ||
| } | ||
|
|
@@ -949,6 +980,10 @@ bool NimBLEServer::resetGATT() { | |
| ++svcIt; | ||
| } | ||
|
|
||
| if (m_registerServicesFirst) { | ||
| initGapGattServices(); | ||
| } | ||
|
Comment on lines
+983
to
+985
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch — fixed in 6a396c7. On the |
||
|
|
||
| return true; | ||
| } // resetGATT | ||
|
|
||
|
|
||
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 is a pre-existing conditional-compilation mismatch in
advertiseOnDisconnect/m_advertiseOnDisconnect(the member is guarded by!MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER), while the constructor init and the method are guarded only by!MYNEWT_VAL(BLE_EXT_ADV)). It predates this PR, which doesn't touchadvertiseOnDisconnect— it's flagged only because the new code is adjacent.registerServicesFirstitself is unguarded and always valid. Happy to align those guards in a separate focused PR if you'd like; leaving it out here to keep this change scoped to the new feature.