Skip to content

BLE: add central role and GATT client support - #4436

Open
palmdalian wants to merge 2 commits into
flipperdevices:devfrom
palmdalian:ble-central-role
Open

BLE: add central role and GATT client support#4436
palmdalian wants to merge 2 commits into
flipperdevices:devfrom
palmdalian:ble-central-role

Conversation

@palmdalian

Copy link
Copy Markdown

👋 Hi, I wanted to use my Flipper to do some peripheral exploration and saw the BLE Central role was not supported. I know there are constraints with memory sizes with the official firmware, but I hopefully directed the LLM to a reasonable solution so that BLE central is an optional feature if a power user really needs it. I'm also happy to send over the validation FAP code that uses the new apis.
Note: I'm not a firmware/C developer. I scanned through the code and verified that it worked with real hardware, but I inevitably missed obvious LLM mistakes since this isn't my usual domain. Hopefully this code is still generally helpful!

What's new

This adds an optional BLE central role and GATT client API to the existing STM32WB BLE glue. The firmware previously exposed only the peripheral role even though the Full BLE radio stack supports both roles.

Related: #2906. That scanner request was closed because the Full BLE stack could not be used as the default Core2 firmware within the available memory. This PR does not change the default Light stack; it exposes central functionality only when a user has explicitly installed the supported Full stack.

  • Add furi_ble/central.h with APIs for scanning, connecting, disconnecting, ATT MTU exchange, service and characteristic discovery/enumeration, CCCD discovery, reads, writes with or without response, and notification subscription.
  • Support 16-bit and 128-bit UUID discovery, long/fragmented reads up to the caller's buffer capacity, standard and extended notifications, indications, and multiple-notification events.
  • Route connection, disconnection, GATT, L2CAP, and security events by connection handle so central traffic cannot be mistaken for peripheral GAP traffic.
  • Serialize controller procedures and protect scan, enumeration, notification, and event-handler callback lifetimes during timeout and teardown paths.
  • Make event-handler removal wait for in-flight dispatch before callback context can be freed.
  • Request the combined peripheral/central GAP role only when the installed radio stack reports central support. Light-stack peripheral behavior is unchanged.
  • Add HAL helpers for testing central capability and peripheral advertising state.
  • Export the API for F7/F18 and bump the firmware API from 88.2 to 88.3.
  • Add 15 advertising-data parser tests, including malformed/truncated records, boundary-sized payloads, zero-length records, and exhaustive record-length bounds coverage.

No user application is installed by this PR. The BLE Inspector and the hardware validation applications used during development are external FAPs and are intentionally not included here.

Bug Fixes Included:

While implementing the Central role, the LLM identified and fixed two existing issues in the BLE event_dispatcher:

  • Fixed a Thread-Safety Data Race: Added handlers_mutex to event_dispatcher.c. Previously, the BLE event thread would iterate over the handler list with no synchronization, meaning an app registering or unregistering a handler at the exact same time could corrupt the list or crash the device.
  • Fixed Incorrect Handler Unregistration: Changed unregister_svc_handler to search by exact pointer identity (item == handler) rather than value equality. The old logic could accidentally delete the wrong handler if two different services happened to register using the exact same callback function and context pointer.
  • Prevented gap.c Conflicts: The new central.c dispatcher intentionally intercepts Central-role L2CAP and Passkey requests so they do not fall through to gap.c. (Without this, gap.c misinterprets them because it assumes the device is always operating as a peripheral).

Important constraints

  • Central procedures require the STM32WB Full BLE stack. The default Light stack does not implement them. Applications should call furi_hal_bt_is_central_supported() and fail gracefully when it returns false.
  • The controller rejects scanning while the peripheral role is advertising. The caller must stop advertising before a scan and restore it afterward if appropriate; the central module does not silently stop another profile.
  • The API manages one central connection and one outstanding asynchronous GATT procedure at a time.
  • Scan, enumeration, and notification callbacks run on the BLE event thread. They must remain short and must not call central APIs recursively.
  • Just Works pairing is supported by the controller. Passkey and numeric-comparison UI callbacks are not exposed; those requests are rejected on the central link instead of being forwarded to the peripheral GAP state machine.

Verification

Builds

  • ./fbt firmware_all
  • ./fbt FIRMWARE_APP_SET=unit_tests firmware_all
  • ./fbt COMPACT=1 DEBUG=0 updater_package
  • ./fbt lint
  • API validation reports version 88.3 as up to date.

The normal firmware, unit-test firmware/plugin configuration, and release updater package build successfully from the current branch. The repository-wide source-format and filename lint also passes.

Automated parser coverage

The test_ble_central minunit plugin covers:

  • valid complete-local-name and multi-record advertisements;
  • absent types, empty payloads, and null output parameters;
  • exact-fit and full 31-byte legacy advertising payloads;
  • zero-length padding and zero-length values;
  • truncated, dangling, and overrunning records;
  • type bytes embedded inside another record's value;
  • all received payload sizes from 1 through 31 bytes against every possible one-byte record length.

Hardware regression peripheral

Tested on a Flipper Zero running this firmware and the STM32WB Full BLE stack against a Heltec Mesh Node T114 / nRF52840 acting as a deterministic GATT peripheral.

The unattended regression test passed the following operations:

  • active scanning and advertising-data parsing;
  • connection setup and ATT MTU exchange;
  • 128-bit service/characteristic discovery and CCCD discovery;
  • a 180-byte fragmented read with byte-for-byte validation;
  • notification subscribe, receive, and unsubscribe;
  • writes with response and without response, followed by readback validation;
  • confirmed local disconnect and central teardown.

Recorded result: PASSED.

Repeated lifecycle stress test

A separate unattended test repeatedly exercised initialization, scanning, connecting, service and characteristic enumeration, 16-bit and 128-bit discovery, CCCD presence/absence, 255-byte and truncated reads, empty reads, notifications, both write modes, local and peer-initiated disconnects, and deinitialization.

Recorded result:

PASSED cycle=10/10 heap_delta=0 notifications=50 retries=4

The four retries were transient link-establishment failures from the deliberately fast-restarting test peripheral. Feature, value, state, and memory-lifetime failures were not retried.

Real peripheral validation

The same API was exercised against a commercial BLE peripheral using two external applications: a focused reader and a generic BLE Inspector. Scanning, connection, GATT enumeration, characteristic reads, CCCD subscription, and button/sensor notification delivery were verified without a device-specific handshake.

The unattended focused-reader result was:

PASS buttons=20 sensors=105 connected=1

The generic Inspector was also used manually to enumerate standard Device Information and Battery services and to receive notifications from both the real peripheral and the nRF52840 emulator.

Suggested reviewer verification

  1. Build and install a complete update package containing the Full BLE stack:

    ./fbt flash_usb_full COMPACT=1 DEBUG=0 \
        COPRO_OB_DATA=scripts/ob_custradio.data \
        COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin \
        COPRO_STACK_TYPE=ble_full
  2. Use a central-role FAP to scan for a known GATT peripheral.

  3. Connect, enumerate its services and characteristics, and read a standard value such as Device Name (0x2A00) or Battery Level (0x2A19).

  4. Subscribe to a notifying characteristic and confirm that notifications arrive with the expected handle and payload.

  5. Disconnect and repeat the sequence to exercise cleanup and reinitialization.

  6. With the Light BLE stack installed, confirm that furi_hal_bt_is_central_supported() returns false and that the application reports the unsupported stack rather than starting a central procedure.

Author checklist (Fill this out)

  • I've read the contribution guidelines and my PR follows them.
  • I own the code I'm submitting or have code owner's permission (or code license allows redistribution) to submit it.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.

AI usage disclosure (Fill this out)

  • Partially AI assisted (clarify below which code was AI assisted and briefly explain what it does).
  • Fully AI generated (explain what all the generated code does in moderate detail).

The tracked implementation and tests in this PR were generated with AI under human direction. AI was used to inspect the existing BLE architecture and STM32WB/BlueNRG interfaces; implement central-role initialization, event routing, GATT procedures, synchronization, callback-lifetime handling, error recovery, HAL capability checks, public API exports, and parser tests; and revise the implementation in response to multiple focused code-review passes.

The generated code was not accepted based on generation alone. It was built in normal and unit-test configurations, reviewed specifically for bounds safety, controller/host state consistency, connection-handle routing, timeout behavior, callback ownership, and teardown races, and exercised on physical hardware using both a deterministic nRF52840 peripheral and a real BLE device. The external validation FAPs and peripheral emulator were also AI-generated for testing but are not part of this PR.

Reviewer checklist (Don't fill this out!)

  • PR has description of feature/bug or link to GitHub Project task.
  • Description contains actions to verify feature/bugfix.
  • I've built this code, uploaded it to the device and verified feature/bugfix.

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.

1 participant