Skip to content

Restore GAP device name after GATT reset#426

Open
chmorgan wants to merge 1 commit into
h2zero:masterfrom
chmorgan:feature/restore-after-ble-svc-gap-init
Open

Restore GAP device name after GATT reset#426
chmorgan wants to merge 1 commit into
h2zero:masterfrom
chmorgan:feature/restore-after-ble-svc-gap-init

Conversation

@chmorgan

@chmorgan chmorgan commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

NOTE: Codex 5.5 created this patch and writeup etc but I reviewed it and it looks like what I would have done by hand, and tested it here and it does resolve the issue and explains why I've been seeing my device misnamed for a bit. Not sure how or when I broke it, or if the issue was library side or a nimble change but figured others might be hitting this issue.

ESP-IDF NimBLE can reset the GAP Device Name when the standard GAP service is reinitialized during GATT server startup. NimBLEDevice::init(deviceName) calls NimBLEDevice::setDeviceName(deviceName), but that happens before the server is started. Later, starting advertising starts the server and resets/re-registers the GAP service, causing the C GAP name backing storage to be rebuilt from CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME. In projects where that Kconfig default remains "nimble", the advertised Local Name can be correct while the Generic Access Device Name characteristic reports "nimble".

The failing sequence is:

-> pAdvertising->start()
-> NimBLEAdvertising::start()
-> NimBLEDevice::getServer()->start()
-> NimBLEServer::resetGATT()
-> ble_gatts_reset()
-> ble_svc_gap_init()
-> ESP-IDF NimBLE resets GAP name to CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME
-> "nimble"
-> ble_svc_gatt_init()

Fix this by saving the device name supplied through NimBLEDevice::setDeviceName() and reapplying it immediately after ble_svc_gap_init() in NimBLEServer::resetGATT(). This preserves the C++ wrapper contract that the configured device name remains effective even when the wrapper internally resets the GATT/GAP service tables.

Summary by CodeRabbit

  • Bug Fixes
    • Preserved the Bluetooth device name and appearance when the GATT server is reset, so these settings no longer revert unexpectedly after a reset.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8e9e6fe-aa41-4a44-a5ef-e49aa901364c

📥 Commits

Reviewing files that changed from the base of the PR and between 4063b55 and 975c084.

📒 Files selected for processing (1)
  • src/NimBLEServer.cpp

📝 Walkthrough

Walkthrough

Arr, this here change makes NimBLEServer::resetGATT() hoist the ship's flag afore scuttlin' the deck: it grabs the current GAP device name and appearance before ble_gatts_reset(), then plants 'em back after ble_gatts_reset() and ble_svc_gap_init() be done, keepin' the treasure from bein' lost overboard.

Changes

GATT Reset GAP Value Preservation

Layer / File(s) Summary
Preserve and restore GAP name/appearance
src/NimBLEServer.cpp
resetGATT() now saves ble_svc_gap_device_name() and ble_svc_gap_device_appearance() (guarded by \!CONFIG_USING_NIMBLE_COMPONENT) before ble_gatts_reset(), and restores them via the corresponding setters after ble_svc_gap_init().

Estimated code review effort: 1 (Trivial) | ~5 minutes

Poem

Yarrr, the name be lost when the deck be swept clean,
So a wee rabbit stashed it where none could be seen! 🐇🏴‍☠️
After the storm o' ble_gatts_reset,
The name and look sail back home, no regrets.
Anchors aweigh, GAP treasure be kept!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Arrr, the title clearly summarizes the main fix: preserving the GAP device name across GATT reset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/NimBLEServer.cpp`:
- Around line 894-896: The static members NimBLEDevice::m_deviceName and
NimBLEDevice::m_deviceNameSet are accessed without synchronization in the
resetGATT() method, creating a potential race condition if another thread calls
setDeviceName() concurrently. To fix this, add a static mutex member (like
m_deviceNameMutex) to the NimBLEDevice class, then protect all accesses to
m_deviceName and m_deviceNameSet with std::lock_guard in both the
setDeviceName() method and the resetGATT() method. In resetGATT(), acquire the
lock, cache the values of m_deviceNameSet and m_deviceName into local variables,
release the lock, then perform the conditional call to setDeviceName() with the
cached name outside the critical section.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7ecf420-5f09-4fa4-8875-aea742681393

📥 Commits

Reviewing files that changed from the base of the PR and between 1eddc28 and 4063b55.

📒 Files selected for processing (3)
  • src/NimBLEDevice.cpp
  • src/NimBLEDevice.h
  • src/NimBLEServer.cpp

Comment thread src/NimBLEServer.cpp Outdated
Comment on lines +894 to +896
if (NimBLEDevice::m_deviceNameSet && !NimBLEDevice::setDeviceName(NimBLEDevice::m_deviceName)) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Shiver me timbers! A potential race condition be lurkin' in these waters!

Arr matey, while yer logic fer reapplyin' the cached device name be sound as a Spanish doubloon, there be a concurrency concern that could make yer ship run aground:

The static members NimBLEDevice::m_deviceName and NimBLEDevice::m_deviceNameSet be accessed here without any synchronization. If some scallywag calls NimBLEDevice::setDeviceName() from another thread whilst this resetGATT() be executin', ye could have yerself a classic race condition! One thread be writin' to m_deviceName while another be readin' from it - that be a recipe fer trouble on the high seas.

Now, in most BLE applications, these operations be serialized by the nature o' the code flow. But from a library design standpoint, there be no enforcement o' this serialization, savvy?

🏴‍☠️ Possible solutions fer this concurrency concern

Option 1: Document that BLE operations must be serialized by the application (low effort, shifts responsibility to user)

Option 2: Add a mutex to protect access to the static members (higher effort, makes library thread-safe by design)

// In NimBLEDevice.h, add a static mutex
static std::mutex m_deviceNameMutex;

// In setDeviceName:
{
    std::lock_guard<std::mutex> lock(m_deviceNameMutex);
    m_deviceName = deviceName;
    m_deviceNameSet = true;
}

// In resetGATT:
std::string cachedName;
bool nameWasSet;
{
    std::lock_guard<std::mutex> lock(NimBLEDevice::m_deviceNameMutex);
    cachedName = NimBLEDevice::m_deviceName;
    nameWasSet = NimBLEDevice::m_deviceNameSet;
}
if (nameWasSet && !NimBLEDevice::setDeviceName(cachedName)) {
    return false;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/NimBLEServer.cpp` around lines 894 - 896, The static members
NimBLEDevice::m_deviceName and NimBLEDevice::m_deviceNameSet are accessed
without synchronization in the resetGATT() method, creating a potential race
condition if another thread calls setDeviceName() concurrently. To fix this, add
a static mutex member (like m_deviceNameMutex) to the NimBLEDevice class, then
protect all accesses to m_deviceName and m_deviceNameSet with std::lock_guard in
both the setDeviceName() method and the resetGATT() method. In resetGATT(),
acquire the lock, cache the values of m_deviceNameSet and m_deviceName into
local variables, release the lock, then perform the conditional call to
setDeviceName() with the cached name outside the critical section.

@h2zero

h2zero commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Looks like espressif changed the behavior of this :(
https://github.com/espressif/esp-nimble/blob/685675c0128deafdd201c9eb82e61d227364646c/nimble/host/services/gap/src/ble_svc_gap.c#L597

I think a better solution is to save the values in resetGATT and reapply the name and appearance after reinitializing the gap services.

Personally I don't use espressifs fork anymore for this reason.

Comment thread src/NimBLEServer.cpp
Comment thread src/NimBLEServer.cpp
@h2zero

h2zero commented Jun 24, 2026

Copy link
Copy Markdown
Owner

@finger563

If your confusion was my remark about the espressif fork, I use this instead: https://github.com/h2zero/esp-nimble-component

Updated to mynewt/nimble 1.9, doesn't have much of the espressif changes and is more stable.

@chmorgan

chmorgan commented Jun 24, 2026 via email

Copy link
Copy Markdown
Contributor Author

@h2zero

h2zero commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Sure, we can use this flag: #ifndef CONFIG_USING_NIMBLE_COMPONENT

@finger563

Copy link
Copy Markdown
Contributor

Has that been updated or does it work for esp idf v6?

@h2zero

h2zero commented Jun 25, 2026

Copy link
Copy Markdown
Owner

It's a bit of work in progress still, haven't checked it against IDF v6. The same code is being used in nimble-arduino as well so it will be well tested.

@h2zero

h2zero commented Jun 28, 2026

Copy link
Copy Markdown
Owner

@finger563, updated now, idf 5 and 6 working.

@chmorgan

chmorgan commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Looks like espressif changed the behavior of this :( https://github.com/espressif/esp-nimble/blob/685675c0128deafdd201c9eb82e61d227364646c/nimble/host/services/gap/src/ble_svc_gap.c#L597

I think a better solution is to save the values in resetGATT and reapply the name and appearance after reinitializing the gap services.

Personally I don't use espressifs fork anymore for this reason.

are you using that mynewt version with esp-idf or another platform? I figured there were a ton of porting changes present to support the particular ble devices so it seemed overwhelming to consider going that route.

ESP-IDF NimBLE can reset the GAP Device Name when the standard GAP service is reinitialized during GATT server startup. NimBLEDevice::init(deviceName) calls NimBLEDevice::setDeviceName(deviceName), but that happens before the server is started. Later, starting advertising starts the server and resets/re-registers the GAP service, causing the C GAP name backing storage to be rebuilt from CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME. In projects where that Kconfig default remains "nimble", the advertised Local Name can be correct while the Generic Access Device Name characteristic reports "nimble".

The failing sequence is:

  -> pAdvertising->start()
     -> NimBLEAdvertising::start()
        -> NimBLEDevice::getServer()->start()
           -> NimBLEServer::resetGATT()
              -> ble_gatts_reset()
              -> ble_svc_gap_init()
                 -> ESP-IDF NimBLE resets GAP name to CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME
                 -> "nimble"
              -> ble_svc_gatt_init()

Fix this by saving the device name supplied through NimBLEDevice::setDeviceName() and reapplying it immediately after ble_svc_gap_init() in NimBLEServer::resetGATT(). This preserves the C++ wrapper contract that the configured device name remains effective even when the wrapper internally resets the GATT/GAP service tables.
@chmorgan chmorgan force-pushed the feature/restore-after-ble-svc-gap-init branch from 4063b55 to 975c084 Compare July 3, 2026 02:06
@chmorgan chmorgan requested a review from h2zero July 3, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants