Skip to content

Apps Bluetooth support & 1.0 breaking changes#67

Draft
microbit-matt-hillsdon wants to merge 75 commits intomainfrom
apps
Draft

Apps Bluetooth support & 1.0 breaking changes#67
microbit-matt-hillsdon wants to merge 75 commits intomainfrom
apps

Conversation

@microbit-matt-hillsdon
Copy link
Contributor

@microbit-matt-hillsdon microbit-matt-hillsdon commented Jan 5, 2026

This contains breaking changes (e.g. flash progress interface, connect
signature, device error codes) and will form part of a v1 at some point.
Migrating USB-only code should be trivial though.

Changes:

  • Switch to capacitor-ble for bluetooth

    • Quite a simplication as the service/characteristic lookups are deferred to
      point of use and the interactions are internally queued.
  • Support DFU and partial flashing on iOS/Android platforms

  • Drop a bunch of workarounds that need reevaluating after the switch

  • Don't try to start notifications on absent services. Prevents issues when not
    in application mode for a flash.

  • Improve connect interface (which had a misleading return value) and
    connect/flash progress.

Design issues:

  • Should connecting and connecting for flashing both be the same flow? Or are
    the ideas different enough that we split them? It's nice in that it matches
    USB but it's also quite different because of pairing.

TODO:

Fixes #20
Fixes #57
Fixes #71

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 5, 2026

Deploying microbit-connection with  Cloudflare Pages  Cloudflare Pages

Latest commit: e1d71c3
Status:🚫  Build failed.

View logs

This contains breaking changes (e.g. flash progress interface, connect
signature, device error codes) and will form part of a v1 at some point.
Migrating USB-only code should be trivial though.

Changes:
- Switch to capacitor-ble for bluetooth
  - Quite a simplication as the service/characteristic lookups are deferred to
    point of use and the interactions are internally queued.

- Support DFU and partial flashing on iOS/Android platforms
- Drop a bunch of workarounds that need reevaluating after the switch
- Temporarily drop uBit name support due to capacitor-ble limitation
- Don't try to start notifications on absent services. Prevents issues when not
  in application mode for a flash.
- Improve connect interface (which had a misleading return value) and
  connect/flash progress.

This branch is going to be long lived for a month or two during apps work, then
we'll loop back around and see what it means for Web Bluetooth - does it
replace it or do we have both implementations.

Design issues:

- Should connecting and connecting for flashing both be the same flow? Or are
  the ideas different enough that we split them? It's nice in that it matches
  USB but it's also quite different because of pairing.
microbit-matt-hillsdon and others added 19 commits January 8, 2026 15:35
- Use .js extension for imports consistently
- Fix typo in exports for types (also on main, but at least VS Code seems to cope)
This might be a behaviour change for bluetooth but is consistent with USB.
For USB there's not really anything to do but let's keep it consistent
This has caused app-level issues because disconnected doesn't let the app understand that
a reconnect will automatically happen.
You can want to pause due to a hidden tab, but defer it due to flashing then
become visible before you ever did pause. But we went ahead with the pause
incorrectly.
Remove them from the connection status.

Check them before connect but also provide pre-flight API for UX flows.

This better matches the iOS/Android permission model and is easier for client
code to manage.
This is trivial for the connections and saves apps from additional state
management as they often need to understand which transition happened.
…arting notifications (#76)

- Attempt to connect four times before throwing error.
- Throw immediately if disconnect occurs whilst connecting instead of waiting for timeout.
- Ensure latest services are found.
Fixes connecting to micro:bit on Android after full flashing. Otherwise there is a "Characteristic not found" error from trying to get board version.
This seems to help significantly with reconnect on Android, where as the retry solution has encountered issues (unexpected disconnects, trouble discovering services that just increasing the delay didn't help with).

Remove the retries for now - we might well reinstate but we need to do so with more care as it's led to very long retries in the case where there is no device due to app-level retries doubling the retry count.

Tested on Android and iOS with a branch of ml-trainer that removes the delay after flashing.
In ml-trainer you can see this if you:
1. connect, pair, DFU
2. trigger connection errors until start-over state
3. edit the pattern away from the correct one
4. note you go on to partial flash

In Web Bluetooth this wasn't an issue as we always did a clearDevice but we should fix it here for other library scenarios.
- Add a new error code for pairing information lost on peripheral.
- Add new native-only progress stages (ProgressStage.CheckingBond and ProgressStage.ResettingDevice).
- Add abort signal to allow for stopping of scanning (and potentially other steps in future)
…g of device on disconnect (#85)

- Remove auto reconnect logic and `ConnectionStatus.Reconnecting` status in favour of the consuming app handling reconnections where necessary.
- Remove discarding of connection/device on disconnect in favour of the consuming app calling `clearDevice`. So unless the app calls `clearDevice`, we assume the same device is used for the connection.
- Extend radio bridge connection timeout to 10_000ms to match Bluetooth.
- Remove no longer needed `ignoreDelegateStatus`
- Keep serial session open when connecting to allow for reconnection after switch tabs
1. Skip 64-byte blocks that are entirely 0xFF during BLE partial
   flashing, matching the iOS native app's approach. The device erases
   each flash page when it receives a write at a page-aligned address,
   so interior 0xFF blocks are redundant. Flash page size is determined
   from the board version (V1: 0x400, V2: 0x1000).

2. Handle MakeCode hex files that nrf-intel-hex rejects. Older thin
   (non-universal) hex files have trailing blank lines or embedded
   source in custom Intel HEX record type 0x0E after the EOF record.
   Added truncateHexAfterEof() in hex-flash-data-source.ts, used by
   both BLE (bluetooth.ts) and USB (usb-partial-flashing.ts) code
   paths.

Region bounds were verified against the iOS app's hexDataToAppRegion
calculation which derives [SoftDevice end, bootloader start).

Added tests covering MakeCode v0–v8 hex files (both universal and thin)
with exact expected region assertions (start, end, hash).
…lative delays (#87)

- Add `deviceBondState` option to Bluetooth connect.
- Set device as bonded when connection is established successfully.
- Set device as not bonded when connection fails.
- Remove speculative delays.
- Ensure partial flashing errors are raised so that the app can handle them.
Simple monorepo with npm workspaces. We'll always build everything.
microbit-grace and others added 3 commits February 13, 2026 12:12
This reduces partial flashing time. The `waitForDisconnect` after partial flashing has been moved to `connect`. Most of the time, the disconnect can happen in the background, except when connecting straight after partial flashing. The disconnect interferes with connecting on Android. 

CreateAI project hex is added as a Capacitor app file option for testing purposes.

The Capacitor app has been updated to include a 'Connect' and 'Disconnect' button. Live accelerometer data is displayed if available and connected.

---------

Co-authored-by: Matt Hillsdon <matt.hillsdon@microbit.org>
Compare the MakeCode application hash from the hex file (SHA-256
truncated to 8 bytes, at magic+24) against the device's MakeCode
region hash. When they match the program is already on the device,
so we reset to application mode and return AlreadyUpToDate instead
of reflashing identical data. This mirrors the optimisation in PXT's
own Web BLE partial flashing code.

Fixes #651
Capacitor BLE, Filesystem, Core, and Nordic DFU are now peer
dependencies so consuming apps control versioning and avoid
duplicates.

This follows the pattern used by Nordic DFU and other similar plugins.
Breaking: Replace EventTarget with custom typed event system
…tion-state

Document post-flash connection state differences
Breaking: Move serialWrite from DeviceConnection to USB only
Document radio bridge as experimental / limited
The partial flashing code already duplicated significant portions of the
dapjs protocol stack. Consolidate everything into a single cmsis-dap.ts
module covering the subset we use: SWD transport, Cortex-M debug, and
DAPLink serial/flash vendor commands.

Also renames DAPWrapper to USBDeviceWrapper and disambiguates device
field names (bleDevice/usbDevice).
Fix stale serial data appearing after flash by addressing a race in the
serial stop/start cycle. When stopSerial() is called, an in-flight
serialRead() can still deliver data after the stop, refilling any
consumer line buffer that was cleared on reset. Fix by suppressing data
delivery once polling is set to false, and by firing the serialreset
event after the polling loop actually exits rather than when stop is
requested.

Drain DAPLink's serial buffer before flash to discard output that
accumulated between the last serial read and the halt. After flash,
start serial listening before resetting (partial flash only) so early
program output is captured in DAPLink's 512-byte ring buffer.

Simplify flashAsync: remove the disconnect/reconnect cycle (invalidate
DAP state instead, keeping USB open) and remove the timeout-based
fallback from partial to full flash which masked connection problems.

Minor:
- Strip \r in demo app serial listener (for CRLF)
- Fix vitest exclude pattern for build directory
Rename reconnectDaplink for clarity.

Add ability to inject faults into flashing via the demo app to test this flow.
Move board info caching and logging up to connection.ts, reducing
device-wrapper.ts to a thin composition root. Rename fields for
clarity: device→usbDevice/bleDevice for raw devices, connection→device
for wrapper state.
We now control all sources of errors other than the browser cases matched by
regexp. Review the browser ones and link to the Chromium source, which
fortunately proves they're not localized.
DAPLink's CDC serial path races with WebUSB vendor serial reads for the
same UART ring buffer, causing truncated serial output after flash.
Work around this by pushing NUL bytes through the target's UART to fill
DAPLink's internal buffers before flashing. Once full, CDC stops
consuming and post-flash output is preserved for vendor reads.

V1 (nRF51) runs a Thumb blob for the legacy byte-at-a-time UART.
V2 (nRF52833) uses SWD register writes to trigger a UARTE DMA transfer.
Both paths fully configure the UART from scratch (pins, baud rate) since
the previous program may not have set it up.

Only applied on the first flash after physical USB connection and only
when serial listeners are attached.

See: ARMmbed/DAPLink#903
Tests basic flashing flows including checking serial output.

Move the hex files as they're useful more widely than the capacitor project.

Remove fault injection from the demo now hardware-test has it.
- Inline what we need from the CMSIS-DAP protocol handling, removing the dapjs dependency
    - Removes a bunch of duplicate code for partial flashing with very confused layering
    - Removes vendored copy for ESM packaging reasons
    - Enables more structured error handling / no string matching and errors from timeouts
    - Allows us to push logging into this code
- Fix partial flash retry by reinitialising SWD (potentially fine before, maybe refactor broke it)
- Saturate DAPLink CDC buffers before flash to prevent serial data loss
    - We'll see how this goes on the apps branch before shipping it for real.
- Improve serial data handling around flash
- Simplify device wrapper and clarify naming across USB and Bluetooth
- Simplify error handler; review error landing and logging
- Deduplicate ABORT_ALL constant between cmsis-dap and arm-debug
- Add test coverage for extracting interfaces
- Add a human-in-the-loop hardware test runner (USB flash, fallback paths, serial integrity, replug)
- Minor demo improvement: the ability to easily reflash the same hex without faff

This targets the apps branch because of the scale of the change and the fact it's targeting 1.0 release rather than because of the apps as such.
…#111)

Delete the shared constants.ts file, inlining DAPLink vendor commands
as bare constants in daplink.ts and FICR register addresses in
device-wrapper.ts. Both were only used by USB code.
- Move bluetooth code into src/bluetooth/ with services/ subdirectory
- Move radio-bridge code into src/radio-bridge/
- Move universal-hex code into src/universal-hex/
- Move USB-only files (serial-events, board-serial-info, promise-queue) into src/usb/
- Move bluetooth-only file (device-bond-state) into src/bluetooth/
- Consolidate sensor type files into service-events.ts
- Move DeviceBondState export from main entrypoint to bluetooth entrypoint
- Move serial event exports from main entrypoint to usb entrypoint
- Update package.json exports and app imports accordingly

DeviceBondState and the serial events have had their public export changed (to
make them bluetooth and USB specific respectively)
Chrome 105 shipped August 2022. The affected Chromebooks (those whose
auto-update expiration landed exactly on 105) are a vanishingly small
population. Drop the user-agent check so those devices can at least
attempt a USB connection.
Convert ConnectionStatus, ProgressStage, DeviceSelectionMode, and
ButtonState from TypeScript enums to `as const` objects with companion
union types. This avoids isolatedModules footguns for consumers and
allows plain string/number literals to be used without importing the
object.
- Remove BoardId from public exports (internal-only, consumers use BoardVersion)
- Make BoardId.id readonly
- Make radio bridge logging option optional (consistent with USB/Bluetooth)
- Remove stale comment about extracting a library
- Remove BoardId usage (no longer exported)
- Fix status value casing to match as-const values (e.g. "Connected" not "CONNECTED")
- Fix connect() examples that incorrectly showed a return value (it returns void)
The same program continues running on the micro:bit during a pause,
so clearing the terminal is unnecessary and frustrating for users.
Also documents the serial event map.
Partial flashing skips addresses >= 0x10000000, so UICR is never
written. After an interrupted full flash (which chip-erases UICR),
the fallback partial flash leaves UICR blank, bricking the device.

Now ensureUicr() runs after every partial flash to compare device UICR
against the hex file. Write-without-erase when only 1→0 bit changes
are needed (both V1/V2). On V2, use NVMC.ERASEUICR when 0→1 changes
are required. On V1 (no independent UICR erase), fall back to full
flash.

Also refactors hex parsing: toMemoryMap() + extractFlashAndUicr()
replace the old convertDataToPaddedBytes chain, parsing the input
once to produce both flash bytes and UICR entries.

Fixes #112
@microbit-matt-hillsdon microbit-matt-hillsdon changed the title Apps Bluetooth support Apps Bluetooth support & 1.0 breaking changes Mar 7, 2026
Running on Mac, testing on Windows via `npm run dev -- --host` and click
through warnings. We need the secure origin for Web Bluetooth/USB.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants