Skip to content

bluetooth improvements - #712

Merged
KenVanHoeylandt merged 4 commits into
TactilityProject:mainfrom
Shadowtrance:bluetooth-improvements
Oct 24, 2026
Merged

KenVanHoeylandt merged 4 commits into
TactilityProject:mainfrom
Shadowtrance:bluetooth-improvements

Conversation

@Shadowtrance

@Shadowtrance Shadowtrance commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Crash fix and HID lifecycle fixes

  • Fix a crash on radio re-enable. nimble_port_deinit() frees the NPL event pool that
    giveup_event's block came from, and npl_freertos_event_init() memsets the stale pointer
    instead of allocating a new block. When the new context reused that block, the memset
    corrupted the new pool's free list and the first HCI event after re-enabling the radio
    (typically connection complete) crashed in os_memblock_get(). The pointer is now cleared on
    every enable cycle.

  • Fix a phantom "connected" device with no app open. The service no longer auto-starts a HID
    device profile on radio-on (it advertised whichever identity was current as a keyboard with no
    app behind it), profile-less radios stay silent instead of advertising the device name, and
    apps stop their HID profile on teardown.

  • HID device sessions:

    • claim the inbound link at BLE_GAP_EVENT_CONNECT, so a stop during a discovery-only
      connection terminates the link instead of leaving a half-dead HID device,
    • switch the GATT profile back to None from the disconnect handler on the host task, where
      the GATT registry can safely change,
    • bluetooth_hid_device_stop() no longer touches the GATT registry, which makes it safe from
      app teardown and non-host tasks.
  • Profile starts abort the background scan's name resolution and wait for the GAP to go idle. The
    resolution chain opens short-lived central connections, and a cancelled connect only clears
    asynchronously, so HID profile switches used to fail with a "GAP procedure still active" while
    the service was scanning.

  • The service skips HID host auto-connect while a peripheral profile is serving. Bonds are stored
    per identity, so a host connection attempt under the app's identity would authenticate against
    the wrong bond set.

  • Advertising carries the complete device name when it fits, with the scan response covering
    names that do not. The shortened 8-character name is gone, it was identical for every
    Tactility ... identity.

  • Central and peripheral roles are separated in the API. bluetooth_host_connect() and
    bluetooth_host_disconnect() cover the central side (HID host), peripheral profiles keep their
    own bluetooth_<profile>_start()/stop() calls. The old profile-based bluetooth_connect() and
    bluetooth_disconnect() were removed and their callers updated.

  • bluetooth_is_peripheral_active() reports whether a HID device, SPP or MIDI profile is
    serving.

  • The identity API is new in this firmware. Apps keep setting the device name, which is the
    default identity, so the app repo does not need an SDK update to keep pairing separately.

Known limitations:

  • Each identity pairs once per host after upgrading (the old shared bond is dropped).
  • A per-app identity cannot change while a link exists, a busy radio keeps its identity for the
    session. Peripheral profiles that start in that state join the current identity, so they show up
    as the same device on the peer.
  • A HID device profile cannot start while a HID host profile has a live connection, because
    NimBLE cannot rebuild the GATT table under a connection. Disconnect the host device first (for
    example a paired BLE keyboard).
  • The paired-devices list is shared across identities while the bond store is per identity, so
    unpairing from Tactility removes the bond from the active identity only.

Bluetooth peripheral identities

  • Apps that expose a peripheral profile can now look like separate bonded devices on the host.
    The identity defaults to the device name (bluetooth_set_identity() overrides it, NULL/""
    reverts to the name) and determines two things:

    • a static random advertising address derived from the controller's public BD_ADDR and an
      FNV-1a hash of the identity (ble_identity_derive_addr()),
    • a separate bond set, swapped between IDF's nimble_bond namespace and one btid_<hash>
      archive namespace per identity, because NimBLE keys store records by peer address only.

    The swap is crash-safe (archive and transition marker committed before the new set is
    installed, so a power loss replays the restore). It runs with the host down in
    dispatch_enable(), or on an idle running radio when the next profile starts, so a second app
    no longer advertises under the first app's identity.

  • Central roles (scan, name resolution, HID host) keep BLE_OWN_ADDR_PUBLIC, so bonds with
    keyboards and other peripherals are unaffected by app identity changes.

  • bluetooth_set_identity() may be called while the radio is on: the change is pending until the
    next profile start with the radio idle, or the next radio cycle. bluetooth_get_identity()
    always reports the identity the running session resolved. bluetooth_is_peripheral_active()
    reports whether a HID device, SPP or MIDI profile is serving. Kernel API, symbol exports and
    mock driver included.

Summary by CodeRabbit

  • New Features
    • Bluetooth supports configurable BLE identities, each with its own advertising address and bond set. Clearing an identity returns to using the device name.
    • Host connections can be managed separately from peripheral profiles, and Bluetooth can report whether a peripheral profile is active.
    • BLE advertising includes the complete device name, using the scan response when needed.
  • Behavior Changes
    • HID device profiles no longer start automatically when the Bluetooth service starts; an app must start them.
    • Identity changes take effect when the radio is idle or when a profile next starts. Renaming updates the broadcast name but does not change the current connection identity.
  • Bug Fixes
    • Stopping an active scan now cancels scan and name-resolution activity and reports completion.

crash fix and HID lifecycle fixes
Bluetooth peripheral identities
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 197ffbc1-7a62-4c25-9af0-ccbd62f6d9e4
📥 Commits

Reviewing files that changed from the base of the PR and between b7d47e3 and 4a605e3.

📒 Files selected for processing: 4
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_hid.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_midi.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_spp.cpp
🚧 Files skipped from review as they are similar to previous changes: 1
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

📝 Walkthrough

The Bluetooth API now provides identity accessors, peripheral-state reporting, and host-specific connect and disconnect methods. ESP32 BLE adds identity-derived advertising addresses and per-identity bond storage. Identity changes can remain pending until the radio is idle or a later profile start. The ESP32 runtime also updates advertising, scan cancellation, and connection tracking. Bluetooth service and profile-start behavior now use the host-specific API and identity preparation.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 4a605

A profile start can still race with background Bluetooth name resolution, and a stopped scan can report a peer after completion. Resolve the profile-start race before merging; the scan event ordering also needs correction.

Pre-merge checks: Passed 3 · Failed 1 · Inconclusive 1

Failed Failed checks: 1 warning · 1 inconclusive

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 70 functions across 13 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check Inconclusive The title identifies Bluetooth as the affected area but uses the vague phrase "improvements" and does not identify the primary changes, such as identity support, HID lifecycle fixes, or API changes. Replace the title with a specific summary of the main change, such as "Add per-app Bluetooth identities and fix HID lifecycle handling".
Passed Passed checks: 3 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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: 3


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a900d9f2-ca38-4b5c-9d19-ef7663d0bdec
📥 Commits

Reviewing files that changed from the base of the PR and between 096f167 and 3100857.

📒 Files selected for processing: 13
  • Drivers/mock-drivers-module/source/mock_bluetooth.cpp
  • Modules/bluetooth-module/source/bluetooth_service.cpp
  • Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_hid.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_hid_host.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_identity.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_midi.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_spp.cpp
  • TactilityKernel/include/tactility/drivers/bluetooth.h
  • TactilityKernel/source/drivers/bluetooth.cpp
  • TactilityKernel/source/symbols.c

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_identity.cpp Outdated
Comment thread Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
Comment thread Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (3)

🟠 Major · Fail activation when initial bond-slot setup fails. · esp32_ble_identity.cpp:219-231

Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_identity.cpp:219-231
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail activation when initial bond-slot setup fails.

When no owner marker exists, ble_identity_bond_slot_activate() logs failures from clear_namespace(BOND_NAMESPACE) and write_owner(identity_hash) but still returns true. Startup then accepts the requested identity and loads the active nimble_bond records. A failed clear can associate stale bonds with the new identity. A failed owner write can allow pairing without a durable owner marker, so a later activation can erase those bonds.

Return false from either failure path so the caller keeps the current identity and can retry activation later.

Suggested fix
        if (!clear_namespace(BOND_NAMESPACE)) {
-            LOG_W(TAG, "could not clear stale bonds; continuing");
+            LOG_W(TAG, "could not clear stale bonds");
+            return false;
        }
        if (!write_owner(identity_hash)) {
            LOG_W(TAG, "could not record bond slot owner; bonds may be shared between identities");
+            return false;
        }
🟡 Minor · Preserve a discoverable name for supported long device names. · esp32_ble.cpp:715-718

Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp:715-718
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve a discoverable name for supported long device names.

api_set_device_name accepts names up to 64 bytes, but both advertising paths remove names that do not fit in the advertisement. They then pass the complete name to the scan response and continue advertising when that operation fails. Use a shortened name marked incomplete, or reject names that cannot be advertised.

🟡 Minor · Synchronize aborted name-resolution results with scan completion. · esp32_ble_scan.cpp:53

Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp:53
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Synchronize aborted name-resolution results with scan completion.

ble_scan_abort() runs outside the NimBLE host task and publishes BT_EVENT_SCAN_FINISHED without waiting for an active name_read_callback(). The callback can therefore enqueue BT_EVENT_PEER_FOUND afterward. Guard the callback with name_res_abort and serialize its publication with finish_scan_events().

Suggested fix
 static void finish_scan_events(struct Device* device) {
-    if (!ble_get_scan_active(device)) return;
+    BleCtx* ctx = ble_get_ctx(device);
+    xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY);
+    if (!ble_get_scan_active(device)) {
+        xSemaphoreGive(ctx->scan_mutex);
+        return;
+    }
     ble_set_scan_active(device, false);
     struct BtEvent e = {};
     e.type = BT_EVENT_SCAN_FINISHED;
     ble_publish_event(device, e);
+    xSemaphoreGive(ctx->scan_mutex);
 }
                 }
                 BtPeerRecord record = (idx < ctx->scan_count) ? ctx->scan_results[idx] : BtPeerRecord{};
-                xSemaphoreGive(ctx->scan_mutex);
-
-                struct BtEvent e = {};
-                e.type = BT_EVENT_PEER_FOUND;
-                e.peer  = record;
-                ble_publish_event(ctx->device, e);
+                if (!ctx->name_res_abort.load()) {
+                    struct BtEvent e = {};
+                    e.type = BT_EVENT_PEER_FOUND;
+                    e.peer  = record;
+                    ble_publish_event(ctx->device, e);
+                }
+                xSemaphoreGive(ctx->scan_mutex);
             }

ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 86d02ba5-9d3d-40d5-b4fa-29a4d2c6b501
📥 Commits

Reviewing files that changed from the base of the PR and between 3100857 and dedbf26.

📒 Files selected for processing: 4
  • Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_identity.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp
🚧 Files skipped from review as they are similar to previous changes: 1
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_identity.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Abort HID start when GAP preparation times out. · esp32_ble_hid.cpp:399-404

Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_hid.cpp:399-404
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Abort HID start when GAP preparation times out.

ble_prepare_profile_start(device) can return false while the terminated name-resolution link is still closing. hid_device_start() ignores this result and continues to ble_hid_switch_profile(), which can fail with ERROR_INVALID_STATE. Return the preparation error before changing the identity or HID context.

Suggested fix
-    ble_prepare_profile_start(device);
+    if (!ble_prepare_profile_start(device)) {
+        return ERROR_INVALID_STATE;
+    }

ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5ec53364-41de-41d4-9ab7-db5cd2a6e286
📥 Commits

Reviewing files that changed from the base of the PR and between dedbf26 and b7d47e3.

📒 Files selected for processing: 3
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_identity.cpp
  • Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Comment thread Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp Outdated
@KenVanHoeylandt
KenVanHoeylandt merged commit e3254d7 into TactilityProject:main Oct 24, 2026
78 checks passed
@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

Thanks a lot!

@Shadowtrance
Shadowtrance deleted the bluetooth-improvements branch October 26, 2026 21:18
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.

2 participants