Skip to content

Apps Bluetooth support & 1.0 breaking changes#67

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

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

Conversation

@microbit-matt-hillsdon
Copy link
Copy Markdown
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.0.0 after a few
beta releases. Detailed notes on changes can be found in MIGRATATION.md.

Migrating USB-only code requires minor tweaks only.

Highlights:

  • Switch to capacitor-ble for bluetooth (including on web)
  • Support DFU and partial flashing on iOS/Android platforms (for Capacitor Android/iOS apps)
  • Add BLE APIs for missing services
  • Add Capacitor app and a web based app to help with testing USB
  • Drop a bunch of workarounds that testing suggest no longer apply

Fixes #20
Fixes #57
Fixes #71

@cloudflare-workers-and-pages
Copy link
Copy Markdown

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

Deploying microbit-connection with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3420907
Status:🚫  Build failed.

View logs

Comment thread lib/flashing/nordic-dfu.ts Outdated
Comment thread packages/microbit-connection/src/flashing/nordic-dfu.ts Outdated
Comment thread tsconfig.json Outdated
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.
Running on Mac, testing on Windows via `npm run dev -- --host` and click
through warnings. We need the secure origin for Web Bluetooth/USB.
Review all DeviceErrorCode values for consistency and consolidate error handling patterns ahead of v1.0.

Error code renames (DeviceErrorCode string values) and removals.

Flash errors now propagate the underlying cause (e.g. "timeout", "device-disconnected", "connection-error") rather than wrapping it in a flash-specific code. The caller already knows it was flashing and we didn't consistently use the flash failure codes for all types of error anyway.

DeviceError constructor now accepts an optional `cause` parameter, threaded through all error-wrapping sites to preserve the original error chain for debugging. Bumped tsconfig lib from ES2020 to ES2022 for the ErrorOptions type.

BackgroundErrorData changed from { message: string; error?: unknown } to { error: DeviceError; event?: string }. The error is now always a structured DeviceError rather than an opaque value.

Removed SerialErrorData and the "serialerror" event from USB connections. Serial errors are now reported via the unified "backgrounderror" event with a DeviceError.

TimeoutError (async-util.ts) now extends DeviceError with code "timeout" instead of extending plain Error.

BLE service methods on the connection are now wrapped with withBleErrorMapping so BLE errors are mapped to DeviceError before reaching consumers.

Migration guide for apps checking error codes:

  // Before → After
  error.code === "reconnect-microbit"        → "connection-error"
  error.code === "timeout-error"             → "timeout"
  error.code === "update-req"                → "firmware-update-required"
  error.code === "clear-connect"             → "device-in-use"
  error.code === "flash-cancelled"           → "aborted"
  error.code === "bluetooth-connection-failed" → "connection-error"
  error.code === "background-comms-error"    → "connection-error"
  error.code === "service-missing"           → "connection-error"
  error.code === "flash-partial-failed"      → (removed, see below)
  error.code === "flash-full-failed"         → (removed, see below)

  // BackgroundErrorData
  event.message        → event.error.message
  event.error          → event.error (now always DeviceError)
  event.error?.message → event.error.message

  // Serial errors
  connection.addEventListener("serialerror", ...) →
    connection.addEventListener("backgrounderror", ...)
Demo changes:

One demo for capacitor + web, all features. I've left flashing available on web even though it won't actually work as a starting point for investigating #21.

Library changes:

Removed exports
- `TypedEventTarget`, `DeviceConnectionEventMap`, `ServiceConnectionEventMap`, `SerialConnectionEventMap`, `Listener`

I'm going to try mocking the connections in the apps without this.

`DeviceConnection` interface and connection type discriminants (type property)
- No longer generic or extends `TypedEventTarget`
- Explicit per-event `addEventListener`/`removeEventListener` overloads replace the generic `<K extends keyof M>` pattern (fixes TypeScript union type limitations)
- Added `readonly type: string` discriminant for type narrowing

This made it much easier for the demo app to work with a union type for the connections.

Radio bridge
- Only declares events it emits (accelerometer, buttons) — removed magnetometer and UART overloads
- `BridgeError`/`RemoteError` replaced with `DeviceError` (`"connection-error"`, `"timeout"`)
- Bug fix: `connect()` catch block now re-throws after cleanup (previously swallowed errors)

`LedMatrix` type
- Changed from fixed-length tuple to `boolean[][]` with runtime 5×5 validation in `setLedMatrix`

This type was cute but hard to use in practice as you always ended up with casts for any realistic source of data.
Enables parameterised A/B without having to have mapped events
crypto.subtle requires a secure context (HTTPS) and is not available
on Capacitor dev servers over plain HTTP. When unavailable, set
hash_size to 0 so the bootloader bypasses its integrity check.

Fixes #109
…bus events (#124)

Expose the micro:bit's BLE Event Service as high-level typed events
(gesturechanged, buttonaaction, buttonbaction, buttonabaction, logoaction)
and a low-level microbitevent channel for arbitrary message bus traffic
via subscribeToEvent/sendEvent.

Named events demux incoming notifications by source ID (V1 DAL / V2
CODAL) so listeners receive structured data without needing to know
the underlying message bus layout. The raw microbitevent channel is
opt-in: only events matching explicit subscribeToEvent registrations
are dispatched, keeping it isolated from the named event traffic.

Client requirements and BLE notification subscriptions are replayed
on reconnect so the micro:bit re-registers its event forwarding.

Adds an Events tab to the demo app for interactive testing.
The native connect() flow previously assumed the caller intended to flash: after a new bond it always reset the device into pairing mode. This is wrong for two other common scenarios:

1. BLE-enabled firmware, interact with services (sensors, UART, LEDs): The device should end up in application mode, not pairing mode. bondMode: "application" handles this — it still bonds if needed (required to get past the firmware whitelist on first connection) but resets to application mode instead of pairing mode.

2. Open-link firmware, no bonding needed: bondMode: "none" skips createBond() on Android and the iOS pairing trigger entirely, avoiding an unnecessary pairing dialog and the 15s post-bond firmware reset.

The default for connect() is "pairing". flash() explicitly passes bondMode: "pairing" internally, so the flash flow is unchanged.

On web, bondMode is ignored — the browser manages pairing transparently via the OS when accessing encrypted characteristics (well, so Claude claims, we need more practical experience here). But either way we for sure can't trigger it.

The demo app gains a Bond mode selector (native Bluetooth only) to exercise all three modes.

Fixes #118
Comment thread .github/workflows/build-docs.yml
Comment thread AGENTS.md Outdated
Comment thread .github/workflows/build.yml Outdated
microbit-matt-hillsdon and others added 7 commits April 28, 2026 16:54
This will merge back to main cleanly.
v3 of the action does this automatically based on the tag and won't cause
trouble when we merge back to main
* Format the whole repo with prettier

The format/format:check scripts only checked packages/*/src, so apps/
and root markdown drifted out of style. Widen the glob to "." with a
.prettierignore that skips node_modules, build output, Capacitor
native projects, hex files and the lockfile.

* Run format:check before versioner action mutates package.json

The versioner action rewrites packages/microbit-connection/package.json
to set the version, and its output isn't prettier-formatted, so
format:check fails in CI even when the committed file is clean locally.
#130)

* Include README and LICENSE in published package; fix stale branch refs

- Add prepack/postpack to copy README.md and LICENSE from the repo root
  into packages/microbit-connection so they ship in the npm tarball.
- Fix reference to LICENCE.md from the package.json file.
- Update README migration-guide URL from the apps branch to main. This doesn't
  work until we merge back but getting it in place now.
- Tweek time-bound CreateAI sentence in the V1-no-DFU-service note.
- Update MIGRATION.md.

* More link fixes

- Hardcode links for typedoc (main)
- Fix cross-module typedoc links and AbortSignal isn't ours

* Make license URL absolute too

Co-authored-by: Grace <145345672+microbit-grace@users.noreply.github.com>
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