Skip to content

Add Spiker:bit EMG + servo support and refactor DataFlow serial onto a transport abstraction [CLUE-567] - #2929

Open
scytacki wants to merge 44 commits into
masterfrom
CLUE-567-microbit-support-2
Open

Add Spiker:bit EMG + servo support and refactor DataFlow serial onto a transport abstraction [CLUE-567]#2929
scytacki wants to merge 44 commits into
masterfrom
CLUE-567-microbit-support-2

Conversation

@scytacki

@scytacki scytacki commented Jul 24, 2026

Copy link
Copy Markdown
Member

Overview

Base: master. Two things developed together:

  1. New hardware support — the Backyard Brains Spiker:bit (a micro:bit expansion board) in the DataFlow tile: connect over WebUSB, auto-flash a fixed micro:bit program, then stream EMG in and drive a servo out.
  2. A refactor of how the DataFlow tile talks to all serial hardware — replacing deviceFamily === "arduino" literals and a single hard-wired Web Serial path with a device-capability model and a transport abstraction.

The two are intertwined by design: the refactor is what lets the Spiker:bit sit cleanly alongside the existing Arduino and radio-hub micro:bit instead of impersonating one of them. Review the two together.

The @microbit/microbit-connection dependency (and the committed compiled firmware .hex) account for nearly all of the +19k line count; the reviewable source change is ~40 files.

New hardware: Spiker:bit EMG + servo

  • WebUSB connectionspikerbit-connection.ts is a thin typed wrapper around @microbit/microbit-connection; MicrobitUsbTransport (in spikerbit-device.ts) is the IDeviceTransport over that connection.
  • SpikerbitDevice drives connect → version checkauto-flash the bundled CLUE-SPIKERBIT firmware when the board is running no known/current version → stream. The USB connection stays Connected across a flash (DAPLink untouched), so no reconnect dance.
  • Firmware — MakeCode source of record (spikerbit-emg-test.ts, spikerbit-envelope-test.ts) plus the compiled spikerbit-clue.hex, imported as a string (hex.d.ts, webpack.config.js, tsconfig.json). spikerbit-firmware-consistency.test.ts guards the version constant against the source. The EMG envelope is computed in firmware (v3).
  • UI — a Spiker:bit connect option in the DataFlow topbar; EMG streams into a Sensor node, and a Live Output node drives the servo.

Serial device refactor

Device-capability model

  • device-capabilities.ts maps device identity (arduino / microbit / spikerbit) to { protocol, displayName, outputs }, cleanly separating device identity from a channel's wire-protocol tag (channel.protocol).
  • Protocols are named by wire format, not hardware: keyValue (labeled channelId:number lines — Arduino and Spiker:bit) and radioHub (multi-hub radio framing — the radio-hub micro:bit).
  • The node layer keys on capabilities instead of device-name literals: channelSatisfiedBy, deviceProtocol, and a three-state output gate (live / unsupported / no-device) via outputGateState. As a result the Spiker:bit correctly shows the Grabber output as unsupported (it has a servo, not the Backyard Brains gripper), and the sensor "missing" prompt is the generic "Connect a device for live EMG".

Transport abstraction

  • IDeviceTransportwrite / close plus an inbound surface (onData / onDisconnect).
  • WebSerialTransport (web-serial-transport.ts) owns the entire Web Serial world: the port, writer, open(), the read loop with reopen/timeout recovery, USB-descriptor device identification, and the navigator.serial connect/disconnect listeners (initWebSerialConnectionEvents, wired once at store creation in stores.ts). MicrobitUsbTransport is its WebUSB peer.
  • SerialDevice is now transport-agnostic — it holds the active transport + channels, writeLine → activeTransport.write, and a central receive(chunk) that routes by protocol to the shared parser (parseKeyValueData / handleRadioHubStreamObj). navigator.serial no longer appears in serial.ts. Stale-transport callbacks are identity-guarded, so a slow old read loop or an unrelated disconnect can't tear down a newer connection.
  • Inbound is unified — every device flows transport.onData → SerialDevice.receive, Spiker:bit included; its only device-specific inbound step is connect-time version detection, which is not part of the steady-state data path.

Behavior changes

  • Spiker:bit: Grabber shows unsupported; servo + EMG work; generic sensor-missing text (applies to all serial sensors).
  • Read-loop give-up now actively disconnects the store (previously it could leave the store "connected").
  • Arduino and radio-hub micro:bit user-facing behavior otherwise unchanged.

Docs

  • src/plugins/dataflow-tool/serial.md consolidates the hardware data-flow documentation: the object map, inbound/outbound Mermaid diagrams (with pre-rendered SVGs), the connect flow + unfiltered-chooser rationale, the Spiker:bit version-check/flash sequence, message reconstitution, and connection-status tracking. Linked from docs/dataflow.md.
  • Design spec: docs/superpowers/specs/2026-07-17-spikerbit-emg-dataflow-design.md; firmware build notes in src/plugins/dataflow/firmware/README.md.

Testing

  • Unit tests: capability predicates, output-gate states, WebSerialTransport lifecycle (identity/knownBoard resolution, write framing + closed no-op, chunk delivery → timeout → give-up → onDisconnect), central receive() routing + stale-transport identity guard, keyValue line parsing, Spiker:bit connect/flash/inbound-through-receive, and firmware-version consistency.
  • check:types and lint:build clean.
  • No Cypress — real hardware can't run there, and it respects the it-block budget.
  • Pending human: manual end-to-end hardware verification — Arduino / radio-hub micro:bit / Spiker:bit connect, stream, drive output, unplug/replug (including a reconnect within ~2s to exercise the stale-transport guard).

Notes for reviewers (deferred minors — safe to defer, called out so they aren't mistaken for oversights)

  • IDeviceTransport.close() is defined but not yet wired to a programmatic disconnect path (disconnect currently comes from the transports' own lifecycle events).
  • SerialDevice.receive() silently no-ops on an unrecognized/undefined protocol — e.g. a late chunk arriving just after disconnect.
  • A now-redundant !! coercion on isConnected() remains in live-output-node.ts (harmless — isConnected() returns a clean boolean after the extraction).

🤖 Generated with Claude Code

scytacki and others added 27 commits July 17, 2026 17:21
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>
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>
]

@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>
…WebSerialTransport [CLUE-567]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cypress

cypress Bot commented Jul 24, 2026

Copy link
Copy Markdown

collaborative-learning    Run #19567

Run Properties:  status check passed Passed #19567  •  git commit e14e0bea6f: Consolidate CLUE-567 design into a single final-state spec [CLUE-567]
Project collaborative-learning
Branch Review CLUE-567-microbit-support-2
Run status status check passed Passed #19567
Run duration 03m 32s
Commit git commit e14e0bea6f: Consolidate CLUE-567 design into a single final-state spec [CLUE-567]
Committer Scott Cytacki
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 4
View all changes introduced in this branch ↗︎

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.91749% with 76 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.29%. Comparing base (96a5a0d) to head (e14e0be).
⚠️ Report is 177 commits behind head on master.

Files with missing lines Patch % Lines
src/plugins/dataflow/nodes/live-output-node.ts 4.34% 22 Missing ⚠️
src/models/stores/web-serial-transport.ts 82.65% 17 Missing ⚠️
...c/plugins/dataflow/components/dataflow-program.tsx 31.81% 15 Missing ⚠️
...w/components/ui/dataflow-serial-connect-button.tsx 47.05% 9 Missing ⚠️
src/models/stores/serial.ts 90.32% 3 Missing ⚠️
src/models/stores/spikerbit-device.ts 94.33% 3 Missing ⚠️
src/models/stores/spikerbit-connection.ts 66.66% 2 Missing ⚠️
src/plugins/dataflow/nodes/rete-manager.tsx 60.00% 2 Missing ⚠️
src/plugins/dataflow/nodes/sensor-node.ts 33.33% 2 Missing ⚠️
src/models/stores/serial-protocol.ts 95.45% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (96a5a0d) and HEAD (e14e0be). Click for more details.

HEAD has 36 uploads less than BASE
Flag BASE (96a5a0d) HEAD (e14e0be)
cypress-regression 15 0
cypress 21 0
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #2929       +/-   ##
===========================================
- Coverage   86.10%   69.29%   -16.82%     
===========================================
  Files         933      933               
  Lines       53246    53377      +131     
  Branches    14076    14107       +31     
===========================================
- Hits        45847    36987     -8860     
- Misses       7383    16358     +8975     
- Partials       16       32       +16     
Flag Coverage Δ
cypress ?
cypress-regression ?
cypress-smoke 41.86% <9.57%> (-0.09%) ⬇️
jest 55.59% <70.95%> (+0.25%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@scytacki
scytacki changed the base branch from CLUE-567-microbit-support to master July 26, 2026 16:31
scytacki and others added 4 commits July 26, 2026 13:01
…LUE-567]

Isolated, not yet wired in - SerialDevice keeps its own Web Serial cluster
until the next task swaps callers over to this transport.
…LUE-567]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scytacki and others added 3 commits July 27, 2026 06:24
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MicrobitUsbTransport now owns the WebUSB connection's inbound events
(serialdata -> onData, status -> onDisconnect), making it a full
IDeviceTransport peer of WebSerialTransport. SpikerbitDevice keeps only
connect-time version detection: it points transport.onData at a
version-only handler during connect, then setActiveDevice repoints it at
SerialDevice.receive (spikerbit maps to the arduino protocol ->
parseArduinoSerialData). Disconnect now flows through transport.onDisconnect,
gaining the stale-transport identity guard.

Inbound is now unified across all devices; update the hardware data-flow
doc and regenerate the inbound diagram.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…LUE-567]

Rename the two wire protocols after their format rather than a device, now
that they no longer map one-to-one to hardware (the Spiker:bit speaks the
"arduino" protocol):

- protocol values "arduino" -> "keyValue", "microbit" -> "radioHub"
- channel field deviceFamily -> protocol (it was always a protocol tag)
- parsers parseArduinoSerialData -> parseKeyValueData,
  handleArduinoStreamObj -> handleKeyValueStreamObj,
  handleMicroBitStreamObj -> handleRadioHubStreamObj

Device identity (SerialDevice.deviceFamily: arduino/microbit/spikerbit) is
unchanged; only the protocol concept is renamed. No persisted data uses these
strings, so no migration is needed.

Consolidate the hardware data-flow documentation into
src/plugins/dataflow-tool/serial.md: fold in the still-relevant intent from
the old serial.md (connect flow, unfiltered device chooser rationale, message
reconstitution, connection-status tracking) and give the Spiker:bit version
check/flash its own section. Move the diagrams' SVGs alongside the doc, drop
the now-empty docs/dataflow-hardware.md, and link to serial.md from
docs/dataflow.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@scytacki scytacki changed the title Refactor Spiker:bit onto a device-capability model + transport interface [CLUE-567] Add Spiker:bit EMG + servo support and refactor DataFlow serial onto a transport abstraction [CLUE-567] Jul 27, 2026
scytacki and others added 2 commits July 27, 2026 07:49
Fold the ambient "*.hex" string-import declaration into src/typings.d.ts
alongside the existing *.png/*.svg asset declarations, rather than keeping
a standalone hex.d.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the comment as JSDoc and correct two overstatements: the connect
event is what the browser fires for already-authorized devices (not
Arduino-specific), and the disconnect handler does real teardown (guarded
against killing a WebUSB session), not symmetry decoration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…E-567]

The property's only purpose is deciding whether the connect-button UI can
trust the browser connect/disconnect events to reflect physical-connection
state; name it for that and note it only applies to the Web Serial path
(stays false for a WebUSB/Spiker:bit connection). Also guard the connect
path on a resolved deviceFamily instead of a non-null assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scytacki and others added 6 commits July 27, 2026 17:02
…cted [CLUE-567]

The class was renamed from a port-specific check to isConnected(), which is
true for any active transport (including the portless WebUSB Spiker:bit), so
name it for connection state rather than a port.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ambient *.hex declaration now lives in src/typings.d.ts, and the hex is
committed, so drop the "until this file is committed the build will fail"
caveat in favor of the evergreen build/type-resolution facts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The servo pin is confirmed to be P0 (already used in AnalogPin.P0), so the
"confirm P0 vs P8 before shipping" note is resolved. Comment-only change; no
behavior change and no hex re-export needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nothing reads channel.hubId (the hubIds in live-output/serial are the
micro:bit relay hub letter, a different value), so drop the field and its
assignments across the arduino, micro:bit, virtual, and simulated channels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sensor-select label was `hubName:type` (e.g. "Arduino:emg-reading"),
which mislabels a Spiker:bit's reused EMG channel as "Arduino". Switch to
the channel's displayName ("EMG", "Temperature A", ...), which stays unique
per option and is device-neutral. That was hubName's only reader, so drop
the field like hubId.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…E-567]

The sensor-missing prompt is now "Connect a device for live <sensor>" for
every serial device (not "Connect Arduino"/"Connect micro:bit"), so update
the 12 hardcoded labels in the "verify sensor select" assertion to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the EMG design doc to describe the whole PR's architecture as the
final result — Spiker:bit support plus the device-capability model and
transport abstraction — with a rationale section documenting why the final
design was chosen over the alternatives it replaced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds Spiker:bit (micro:bit expansion board) EMG input + servo output support to the DataFlow tile, while refactoring all DataFlow “serial hardware” communication to use a device-capability model and a transport abstraction so Web Serial (Arduino/radio-hub micro:bit) and WebUSB (Spiker:bit) can share the same inbound/outbound data path.

Changes:

  • Introduces a transport abstraction (IDeviceTransport) with concrete Web Serial (WebSerialTransport) and micro:bit WebUSB (MicrobitUsbTransport) implementations, and makes SerialDevice transport-agnostic.
  • Adds device capabilities (kDeviceCapabilities) to decouple device identity from wire protocol and drive output gating (“live/unsupported/no-device”).
  • Bundles and documents Spiker:bit firmware (.hex as asset/source), adds consistency tests, and updates UI/docs/tests accordingly.

Reviewed changes

Copilot reviewed 37 out of 43 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 firmware as source text.
tsconfig.json Adds path aliases for microbit-connection subpaths; excludes MakeCode firmware sources from TS compilation.
src/typings.d.ts Declares *.hex module typing for firmware imports.
src/plugins/dataflow/nodes/sensor-node.ts Makes missing-sensor messaging device-neutral; uses channel display names for labels.
src/plugins/dataflow/nodes/rete-manager.tsx Switches “connected” logic to isConnected() and uses protocol satisfaction vs device literal comparisons.
src/plugins/dataflow/nodes/live-output-node.ts Routes output behavior via protocol/capabilities and adds output gating states/options.
src/plugins/dataflow/model/utilities/virtual-channel.ts Removes now-unused hub identity fields from virtual channels.
src/plugins/dataflow/model/utilities/simulated-channel.ts Removes now-unused hub identity fields from simulated channels.
src/plugins/dataflow/model/utilities/node.ts Adds output gating helpers (outputGateState, unsupportedOutputOption).
src/plugins/dataflow/model/utilities/node-output-gating.test.ts Adds unit coverage for output gating helpers.
src/plugins/dataflow/model/utilities/device-capabilities.ts Introduces device capability map and helper predicates (protocol/display/output support).
src/plugins/dataflow/model/utilities/device-capabilities.test.ts Adds unit tests for capability predicates.
src/plugins/dataflow/model/utilities/channel.ts Replaces channel device-family tagging with protocol tagging; updates serial channel definitions.
src/plugins/dataflow/firmware/spikerbit-envelope-test.ts Adds bench-only MakeCode program for envelope/rate validation (excluded from TS build).
src/plugins/dataflow/firmware/spikerbit-emg-test.ts Adds bench-only MakeCode program for raw-vs-envelope diagnostics (excluded from TS build).
src/plugins/dataflow/firmware/spikerbit-clue.ts Adds source-of-record MakeCode firmware for CLUE Spiker:bit (versioned banner + EMG/servo protocol).
src/plugins/dataflow/firmware/README.md Documents firmware rebuild workflow and hardware notes.
src/plugins/dataflow/components/ui/dataflow-serial-connect-button.tsx Adds connect menu to choose between Web Serial devices and Spiker:bit; updates connection-state styling hooks.
src/plugins/dataflow/components/ui/dataflow-program-topbar.tsx Wires new onConnectDevice callback into topbar.
src/plugins/dataflow/components/ui/dataflow-program-topbar.scss Styles the new connect dropdown menu and updates state class names.
src/plugins/dataflow/components/dataflow-program.tsx Adds Spiker:bit connection/flash flow and moves Web Serial connect to WebSerialTransport.
src/plugins/dataflow-tool/serial.md Consolidates/updates hardware connection documentation (inbound/outbound, transports, flashing, status).
src/plugins/dataflow-tool/images/dataflow-hardware-outbound.svg Adds rendered diagram for outbound hardware flow.
src/plugins/dataflow-tool/images/dataflow-hardware-inbound.svg Adds rendered diagram for inbound hardware flow.
src/models/stores/web-serial-transport.ts Adds Web Serial transport implementation (open/write/read loop + connection event wiring).
src/models/stores/web-serial-transport.test.ts Adds unit tests for WebSerialTransport lifecycle/read/write/disconnect behavior.
src/models/stores/stores.ts Initializes global Web Serial connect/disconnect listeners via initWebSerialConnectionEvents.
src/models/stores/spikerbit-firmware-consistency.test.ts Adds tests to keep firmware version source/hex/app constant in sync.
src/models/stores/spikerbit-device.ts Adds Spiker:bit connect/version/flash state machine and WebUSB transport wrapper.
src/models/stores/spikerbit-device.test.ts Adds unit tests for Spiker:bit connect/flash/streaming and disconnect behavior.
src/models/stores/spikerbit-connection.ts Adds typed wrapper around microbit-connection’s USB + universal-hex helpers.
src/models/stores/serial.ts Refactors SerialDevice to be transport-agnostic and to route inbound by protocol.
src/models/stores/serial-transport.test.ts Adds tests for transport routing, stale-transport guards, and protocol routing.
src/models/stores/serial-protocol.ts Extracts key-value parser and Spiker:bit version banner detection.
src/models/stores/serial-protocol.test.ts Adds unit tests for key-value parsing and version detection behavior.
src/models/stores/device-transport.ts Introduces IDeviceTransport interface contract.
package.json Adds @microbit/microbit-connection dependency; adds .hex to Jest file mocks.
package-lock.json Locks dependency graph changes for microbit-connection and related packages.
docs/superpowers/specs/2026-07-17-spikerbit-emg-dataflow-design.md Adds design spec documenting architecture decisions and rationale.
docs/dataflow.md Links to the new serial hardware documentation.
cypress/e2e/functional/tile_tests/dataflow_tool_spec.js Updates expected sensor dropdown labels to the new generic “Connect a device…” wording.
.eslintrc.js Excludes MakeCode firmware directory from ESLint checks.
Comments suppressed due to low confidence (1)

src/plugins/dataflow/model/utilities/channel.ts:149

  • lastMessageRecievedAt is misspelled, so relay-info channels never initialize lastMessageReceivedAt. This can keep channels from timing out/missing as intended.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

serialConnected: null,
deviceFamily: "microbit",
protocol: "radioHub",
lastMessageRecievedAt: Date.now()
Comment on lines +119 to +131
private async readWithTimeout(
streamReader: ReadableStreamDefaultReader<Uint8Array>
): Promise<{ value: string | undefined; done: boolean; timedOut: boolean }> {
const timeoutPromise = new Promise<{ value: undefined; done: false; timedOut: true }>((resolve) => {
setTimeout(() => resolve({ value: undefined, done: false, timedOut: true }), this.READ_TIMEOUT_MS);
});
const readPromise = streamReader.read().then(({ value, done }) => ({
value: value ? textDecoder.decode(value) : undefined,
done,
timedOut: false
}));
return Promise.race([readPromise, timeoutPromise]);
}
Comment on lines +133 to +147
private async closePort(streamReader?: ReadableStreamDefaultReader<Uint8Array>) {
if (!this.port) return;
try {
if (streamReader) {
try { await streamReader.cancel(); } catch (e) { /* reader may already be released */ }
try { streamReader.releaseLock(); } catch (e) { /* lock may already be released */ }
}
if (this.writer) {
try { await this.writer.close(); } catch (e) { /* writer may already be closed */ }
}
await this.port.close();
} catch (e) {
console.error("Error closing port:", e);
}
}
Comment on lines +55 to +61
public setActiveDevice(deviceFamily: string, transport: IDeviceTransport, channels: NodeChannelInfo[]){
this.activeTransport = transport;
this.deviceFamily = deviceFamily;
this.channels = channels;
transport.onData = (chunk: string) => { if (this.activeTransport === transport) this.receive(chunk); };
transport.onDisconnect = () => { if (this.activeTransport === transport) this.clearActiveDevice(); };
this.updateConnectionInfo(Date.now(), "connect");
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