Skip to content

BLE workflow: add a guard service so user-created services don't break the web editor - #11333

Merged
dhalbert merged 2 commits into
adafruit:mainfrom
dhalbert:ble-guard-service
Sep 10, 2026
Merged

BLE workflow: add a guard service so user-created services don't break the web editor#11333
dhalbert merged 2 commits into
adafruit:mainfrom
dhalbert:ble-guard-service

Conversation

@dhalbert

@dhalbert dhalbert commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Diagnosis by Claude and @dhalbert. Code by Claude with refactoring and comment rewrites by @dhalbert. PR post by Claude with considerable editing by @dhalbert.

Problem

Creating a _bleio.Service from user code while the web editor is connected hung the editor's REPL terminal on Linux, macOS and Windows. File transfer kept working. The problem is that a peripheral reports its last primary service with End Group Handle 0xffff, and hosts cache that literally, with the 0xffff. When BLE workflow is on, two services are added: File and Serial. The Serial Service then is recorded by the host with 0xffff as its ending handle.

If CircuitPython user code adds another service, the start and end handles will fall inside the cached handle range for the Serial Service, since 0xffff includes everything. The host then treats the addition as a change to the Serial Service, since the new handles fall inside the range it cached for it.

Because the Serial Service handle range changed, Chrome invalidates its service object, and the terminal stops working. (Also there's no Web Bluetooth event provided by Chrome to report that.) In addition, BlueZ (used by Linux) has a bug that drops the service from its cache until a real link-level reconnect.

The serial service was affected only because it was created last. If the file and serial services were reversed, it would have been the file service (verified by testing).

Fix

  • Create an unused primary service (adaf0004, one read-only characteristic adaf0005, three handles) after the file transfer and serial services in supervisor_start_bluetooth(). This is the service a later user-created service will overlap.
  • The unused characteristic is required: on espressif a service is not registered with NimBLE until its first characteristic is added.
  • Minor code cleanups:
    • Move circuitpython_base_uuid from serial.c to bluetooth.c, since the guard uses it too.
    • Simplify conditional compilation guards in bluetooth.c.

Testing

Tested Feather nRF52840 Express and Metro ESP32-S3, using code.circuitpython.org in Chrome, on Linux, macOS and Windows:

  • Do a fresh pair.
  • Connect to the editor.
  • import a file that creates a Battery Service.
  • Confirm the terminal and file browser both still work.

Before the change the terminal died on all three hosts; after it both keep working. btmon logs confirmed the guard is the service the hosts discard and the new service is placed after it.

Ctrl-D after creating a user service still drops the BLE connection. That is a pre-existing problem: you have to reconnect. But now you can do that successfully.

A peripheral reports its last primary service with End Group Handle
0xffff, and hosts cache that literally. A service that user code adds
later lands inside that cached range, so hosts treat it as a change to
the last workflow service: Chrome invalidates the page's object for it
with no event to say so, and BlueZ drops it from its cache. On every
host the web editor's REPL terminal went dead as soon as user code
created a `_bleio.Service`.

Create a placeholder service after the file transfer and serial
services so that it, not a workflow service, is the one affected.
Verified on nordic and espressif against BlueZ, CoreBluetooth and
WinRT. Also move `circuitpython_base_uuid` from `serial.c` to
`bluetooth.c`, since the guard uses it too, and factor the repeated
`CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE` into
`BLE_WORKFLOW`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code looks fine but the zephyr tests broke. Maybe they hardcoded the length of servers?

zephyr-cp restarts the BLE workflow on every reload. The guard service
was created on each start but never deinited on stop, so the second
start re-registered a service still in Zephyr's service list, which
corrupted the list and dropped the file transfer and serial services
from the GATT table. Fixes the zephyr-tests failures in
test_bsim_ble_workflow_nus and test_bsim_ble_workflow_file_transfer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

@dhalbert
dhalbert merged commit 83a148f into adafruit:main Sep 10, 2026
524 checks passed
@dhalbert
dhalbert deleted the ble-guard-service branch September 10, 2026 23:49
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