Add Spiker:bit EMG support to the DataFlow tile [CLUE-567] - #2924
Add Spiker:bit EMG support to the DataFlow tile [CLUE-567]#2924scytacki wants to merge 20 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-567] Split into spikerbit-device.ts (no library import, tested with a fake IMicrobitUsbConnection) and spikerbit-connection.ts (the sole static importer of the ESM-only @microbit/microbit-connection library) so Jest never has to load the library. Added tsconfig paths entries for the library's usb/universal-hex exports subpaths, since this repo's classic TS moduleResolution doesn't read package "exports" maps (same workaround already used for @concord-consortium/codap-formulas).
…-567] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The .ts is MakeCode source (paste target for makecode.microbit.org); the firmware dir is excluded from tsconfig/eslint since it uses MakeCode-only globals. The compiled universal .hex is still authored/committed separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The micro:bit firmware pads each serial line with spaces before the \r\n (e.g. "emg:57 \r\n"), so both parseArduinoSerialData and detectSpikerbitVersion require [ \t]* before the line ending. The Arduino's unpadded lines still match exactly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Universal micro:bit hex (V1+V2) built from spikerbit-clue-v1.ts. Bench-verified: EMG streams into the sensor node, the Servo output drives the servo, and the version query is detected (no reflash on reconnect). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…E-567] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The v2 MakeCode source disables writeLine's 32-byte space padding and moves
the "?" version reply onto the forever-loop fiber so it can't interleave with
the emg stream. Re-export the compiled hex and bump kSpikerbitFirmwareVersion
to 2 to match.
Rename the firmware artifacts to spikerbit-clue.{ts,hex} (the -v1 suffix was
redundant with the versioned banner) and update the README and import.
Add a Jest consistency test that guards against the source VERSION,
kSpikerbitFirmwareVersion, and the committed hex drifting out of sync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pxt-spikerbit extension's musclePowerSignal() subtracts a fixed 580 noise floor and half-wave rectifies, but the board's resting baseline is ~511, so moderate contractions never clear the floor and read as ~0 — only hard, rail-to-rail flexes register. Recorded raw-vs-envelope data on a micro:bit V2 confirmed this. v3 computes the envelope itself: track the DC baseline and full-wave rectify around it (envelope = EMA(|raw - baseline|)). The EMA coefficients are derived from the measured per-sample dt so the smoothing time constant is fixed regardless of sample rate. We no longer call the extension at all — its startMuscleRecording() only drove P8=0/P9=0 to select EMG mode, which we now do ourselves, reading the raw signal on P1. Servo confirmed on P0. Bench-validated on hardware: tracks moderate flexes the old envelope missed. Bump kSpikerbitFirmwareVersion to 3 and re-export the hex. Add the two MakeCode bench-test programs used to diagnose and validate this, and update the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
collaborative-learning
|
||||||||||||||||||||||||||||
| Project |
collaborative-learning
|
| Branch Review |
CLUE-567-microbit-support
|
| Run status |
|
| Run duration | 03m 23s |
| Commit |
|
| Committer | Scott Cytacki |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
4
|
| View all changes introduced in this branch ↗︎ | |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2924 +/- ##
===========================================
- Coverage 86.10% 69.14% -16.96%
===========================================
Files 933 931 -2
Lines 53246 53320 +74
Branches 14076 14087 +11
===========================================
- Hits 45847 36870 -8977
- Misses 7383 16418 +9035
- Partials 16 32 +16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Add a hardware-reference section to the firmware README from the Backyard Brains schematic: the micro:bit pin usage (P0 servo, P1 EMG via JP2, P2 free, P8/P9 mode-select), the servo connector wiring, and the 2xAA → VIN → PAM2401 boost power chain. Records why battery voltage can't be read off any rail (all regulated, including the servo's +5V V+) and how to monitor it instead — a divider from raw VIN into the free P2 analog pin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the spikerbit-clue-v1.{ts,hex} → spikerbit-clue.{ts,hex} rename.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
connectAndStream previously flashed only when the version query got no reply, so a board running an older firmware version was left as-is. Now it also flashes when the reported version is below kSpikerbitFirmwareVersion, so connecting a board running old firmware auto-updates it. Update the device tests: the fake connection now reports a configurable version, the up-to-date cases report the current version, and a new test covers an out-of-date board triggering a flash. Update the design spec (detection step and the former "no version-based re-flash" limitation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a third DataFlow hardware option for Backyard Brains Spiker:bit (micro:bit expansion board) using a new WebUSB + auto-flash connection path, while reusing the existing Arduino-style emg:<value>\r\n serial protocol so the existing EMG Sensor + Servo nodes can work unchanged.
Changes:
- Add a WebUSB-based Spiker:bit connection/flash/streaming module (
SpikerbitDevice) and a thin wrapper that isolates the ESM-only@microbit/microbit-connectiondependency. - Bundle and validate a “firmware of record” (
spikerbit-clue.ts+ compiled.hex) plus Jest tests to keep firmware artifacts consistent with the app’s expected version. - Update DataFlow UI/connect flow and serial store to support a second “transport” (Web Serial vs Spiker:bit WebUSB) while keeping the existing node/protocol layer.
Reviewed changes
Copilot reviewed 21 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| webpack.config.js | Adds webpack handling to import .hex as source text for flashing. |
| tsconfig.json | Adds path aliases for @microbit/microbit-connection/* ESM subpaths and excludes firmware sources from TS checks. |
| .eslintrc.js | Excludes MakeCode firmware sources from ESLint. |
| package.json | Adds @microbit/microbit-connection dependency and extends Jest file mocks to include .hex. |
| package-lock.json | Locks the new micro:bit connection dependency tree. |
| src/plugins/dataflow/nodes/rete-manager.tsx | Switches “connected” checks from hasPort() to transport-agnostic isConnected(). |
| src/plugins/dataflow/components/ui/dataflow-serial-connect-button.tsx | Replaces the single “refresh serial” button with a device-choice menu (Arduino / micro:bit hub / Spiker:bit). |
| src/plugins/dataflow/components/ui/dataflow-program-topbar.tsx | Plumbs the new onConnectDevice() handler into the topbar. |
| src/plugins/dataflow/components/ui/dataflow-program-topbar.scss | Styles the new device-choice dropdown menu. |
| src/plugins/dataflow/components/dataflow-program.tsx | Adds Spiker:bit connect flow via dynamic import and flashes the bundled .hex. |
| src/models/stores/serial.ts | Extracts Arduino parsing to a helper and adds Spiker:bit transport routing (writeLine() via callback). |
| src/models/stores/serial-protocol.ts | Adds shared helpers for parsing Arduino-style serial data and detecting Spiker:bit version banners. |
| src/models/stores/serial-protocol.test.ts | Unit tests for the extracted parsing/version-detection helpers. |
| src/models/stores/spikerbit-device.ts | New WebUSB Spiker:bit device module: connect → detect version → flash → stream → route servo writes. |
| src/models/stores/spikerbit-device.test.ts | Unit tests for Spiker:bit detect/flash/routing/disconnect behavior using a fake connection. |
| src/models/stores/spikerbit-connection.ts | Single static-import entry point for the ESM-only micro:bit library (intended for dynamic import by UI). |
| src/models/stores/spikerbit-firmware-consistency.test.ts | Jest consistency checks to keep MakeCode source, compiled hex, and app version constant in sync. |
| src/models/stores/serial-transport.test.ts | Unit tests for SerialDevice’s new transport routing behavior. |
| src/plugins/dataflow/firmware/README.md | Documents the firmware artifacts, protocol, and manual rebuild steps. |
| src/plugins/dataflow/firmware/spikerbit-clue.ts | MakeCode “source of record” for the Spiker:bit firmware (v3). |
| src/plugins/dataflow/firmware/spikerbit-emg-test.ts | Bench-only diagnostic firmware (not flashed by CLUE). |
| src/plugins/dataflow/firmware/spikerbit-envelope-test.ts | Bench-only diagnostic firmware for sample rate + envelope validation (not flashed by CLUE). |
| src/plugins/dataflow/firmware/spikerbit-clue.hex | Compiled universal hex artifact used for auto-flashing (large). |
| src/hex.d.ts | Adds a TS module declaration for importing .hex files. |
| docs/superpowers/specs/2026-07-17-spikerbit-emg-dataflow-design.md | Adds a design/spec document for the Spiker:bit DataFlow integration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const running = await this.queryVersion(); | ||
| if (running == null) { | ||
| // USB connection stays Connected through a flash (DAPLink interface chip is | ||
| // untouched) and serial auto-reinitialises, so no explicit reconnect here. | ||
| await this.connection.flash(flashDataSource, { partial: true, progress }); | ||
| await this.queryVersion(); | ||
| } |
| public isConnected(){ | ||
| return this.hasPort() || this.spikerbitConnected === true; | ||
| } |
| // routes to it instead of the Web Serial writer. This single-store reuse is a | ||
| // deliberate MVP shortcut; see docs/superpowers/specs/2026-07-17-spikerbit-emg-dataflow-design.md §10. | ||
| spikerbitWrite: ((line: string) => void) | undefined; | ||
| private spikerbitConnected: boolean; |
| const chooseDevice = (deviceType: "serial" | "spikerbit") => { | ||
| setMenuOpen(false); | ||
| onConnectDevice(deviceType); | ||
| }; |
] @microbit/microbit-connection ships a CommonJS build (resolved via its package exports "require" condition), so a plain static import works in both Jest 30 and webpack — the ESM/dynamic-import rationale was incorrect. Import it statically instead of via dynamic import(); this moves ~51 KB solely into the already-lazy Dataflow webpack chunk (+2.7%) with nothing added to initial load, and keeps spikerbit-connection.ts as a thin typed wrapper. Also from the maintainability review: - rename SerialDevice.hasPort() to hasWebSerialPort() so callers don't confuse it with the any-transport isConnected() (the Spiker:bit uses WebUSB, no port). - document that the firmware-consistency test only checks the embedded version string, not that the hex was compiled from the source (test header + README). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…UE-567] - Replace the `as unknown as IMicrobitUsbConnection` cast in createSpikerbitConnection with a plain typed return; the library's MicrobitUSBConnection is structurally assignable to our interface, so the compiler now catches API drift instead of the cast silently erasing it. Correct the interface's comment (the library isn't ESM-only). - Initialize SerialDevice.spikerbitConnected in the constructor and drop the now- redundant `=== true` guard in isConnected(). - Record two known limitations in the design spec's future-work section: the half-wired connect menu (duplicate options, no click-outside dismissal) and the missing listener cleanup / in-flight connect guard in the Spiker:bit connect path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Backyard Brains Spiker:bit (a micro:bit expansion board for biosignal recording) as a third hardware option in the DataFlow tile, covering EMG mode in this first increment:
End-to-end goal: a student plugs in a Spiker:bit (with a micro:bit seated in it), chooses "Spiker:bit" in the connect flow, and the tile connects over WebUSB, detects whether our fixed program is running, flashes it if not, and then streams EMG data in and servo angles out — with no manual MakeCode step.
What's included
SpikerbitDevice(src/models/stores/spikerbit-device.ts) — a WebUSB module that connects, detects the running program/version, flashes our firmware, and streams EMG in / servo out.spikerbit-clue.hexundersrc/plugins/dataflow/firmware/, computing the EMG envelope in firmware (v3). Webpack + Jest are configured to import.hexfiles as source strings, with a consistency test guarding the compiled artifact.isConnected()across the topbar and connect button.SerialDevice— transport-aware output so servo angles route correctly for the Spiker:bit; adetectSpikerbitVersionhelper; a reusableparseArduinoSerialDatahelper; and tolerance for trailing whitespace when parsing serial lines.The Spiker:bit is a distinct third scenario from the existing
"arduino"and radio-hub"microbit"device families — it is neither, and the data path (connect → stream EMG → drive servo) is designed to stand on its own so the auto-flash step can be dropped cleanly if needed.Out of scope for this increment
EEG/ECG modes, the potentiometer/
fsr/tmpchannels, a reproducible in-repo firmware build, and any change to the existing Arduino or radio-hub micro:bit flows.Notes for reviewers
spikerbit-clue.hexfirmware artifact (~17k lines).docs/superpowers/specs/and is not part of this PR's committed diff.🤖 Generated with Claude Code