Skip to content

CLUE-728: Dataflow speaks the CC Device Protocol to the Spiker:bit - #3055

Merged
scytacki merged 58 commits into
CLUE-728-spikerbit-statusfrom
CLUE-728-spikerbit-firmware-status
Oct 27, 2026
Merged

scytacki merged 58 commits into
CLUE-728-spikerbit-statusfrom
CLUE-728-spikerbit-firmware-status

Conversation

@scytacki

@scytacki scytacki commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

CLUE-728, part 2 of 3: Dataflow speaks the CC Device Protocol to the Spiker:bit. It builds on #3056, which added the protocol and firmware v4 and is merged into the feature branch CLUE-728-spikerbit-status. The story's UI half (the "connection stopped" display and the "Is it alive?" button) comes in a later PR into the same feature branch.

The protocol and the reasoning behind it are in the spec ("Dataflow protocol code") and cc-device-protocol.md. serial.md is updated to describe the new flow, including a new section on Dataflow's side of the protocol session, and its diagrams are re-exported.

What changes

  • A ccDevice protocol type for the Spiker:bit. The Arduino and the radio hub stay on keyValue and radioHub. Device capabilities can now list the channels a device provides, so the Spiker:bit satisfies only emg: fsr, tmp and a1 show as missing right away instead of after the 7 s freshness timeout.
  • CcDeviceSession (cc-device-session.ts), the host side of a session:
    • It opens with a random session id, resending an unanswered open, then pings every second.
    • It re-opens on err nosession and stops on err othersession.
    • It routes readings to channels, unrounded and allowing negative values.
    • It sends outputs such as servo:<angle>.
    • It records lastHeartbeatAt for the UI half.
    • SerialDevice creates one when a ccDevice device becomes active and stops it on clear, unplug or reconnect.
  • Connecting with !! (spikerbit-device.ts). Right after connecting, Dataflow writes !! #n and a bare ? in one write, so the board is reset to idle and then identified. CLUE-728: CC Device Protocol and Spiker:bit firmware v4 #3056 explains the backlog in the interface chip that makes this necessary: with one ?, a board left streaming answered with a hello line glued to a cut-off reading, and Dataflow reflashed a current board.
    • It treats the backlog as drained at the first serial read shorter than 62 bytes (the library's DAPLink read size), or after 100 ms with no data.
    • If nothing is identified 200 ms after that, it writes !! and ? once more. The overall limit stays 1.5 s, and the re-query after a flash uses the same sequence.
    • Its #n numbers start at 1000, outside the session's 1–999, so a late reply to a connect-time !! can't be taken for a reply to the session's own commands.
  • Firmware recognition and the flash decision (serial-protocol.ts, spikerbit-device.ts). It flashes when:
    • there's no reply;
    • the reply is a legacy CLUE-SPIKERBIT v<N> banner;
    • the hello names another device or a protocol version it doesn't speak;
    • the firmware is older than expected.
  • Disconnect sends close first, bounded by a 500 ms timeout so a stuck write can't wedge Connect, Reconnect or Disconnect.
  • The servo goes out as servo:<angle> with the same scaling. The gripper output stays Arduino-only.

Where the device code lives

This PR adds cc-device-session.ts to src/models/stores and extends serial.ts and spikerbit-device.ts there. These files sit in src/models/stores because the existing serial and Spiker:bit device code already lives there (serial.ts, spikerbit-device.ts, serial-protocol.ts, the transports), and the app-wide store creates the one SerialDevice at startup. That store already imports Dataflow code statically (serial.ts imports Dataflow's node.ts and device-capabilities.ts), against the rule that tile plugins load only through dynamic imports. Of the code this PR adds or changes, cc-device-session.ts and cc-device-protocol.ts newly reach the main bundle, through stores.ts, serial.ts and serial-protocol.ts. spikerbit-device.ts, spikerbit-connection.ts, the microbit-connection library and the hex are imported only from Dataflow's device-connection.ts, so they stay in the Dataflow chunk. (This is from the imports; no bundle analysis was run.) Moving just the new files into src/plugins/dataflow/ wouldn't help, because serial.ts would still import them.

CLUE-754 covers fixing it: a core serial service that any tile can use, with Dataflow's own device code (the CC Device Protocol, the Spiker:bit, the firmware simulator) moved into src/plugins/dataflow/ and registered when the plugin loads. CLUE-755 covers the other static imports of tile plugins from core, and a lint rule against them.

What the tests cover

  • The session:
    • open, retry and pings, each interval tested on both sides (1999 and 2000 ms, 999 and 1000 ms);
    • re-open on nosession and stop on othersession;
    • close;
    • unanswered pings, which don't cause a re-open loop;
    • writes that reject or throw;
    • #n wrapping.
  • The flash decision: every row of the spec's table, including another device and a newer protocol version with newer firmware.
  • Wiring:
    • the session starting and stopping on connect, clear, unplug and a second connect;
    • servo scaling for both protocols, tested at n=0 (n=90 is a fixed point of the scaling);
    • channel matching with and without a channel list;
    • Disconnect still closing the transport when the close write never finishes.
  • Connecting:
    • a hello line after a cut-off fragment and the !! reply's empty line;
    • a backlog delivered in 62-byte reads ahead of the replies;
    • a lost first reply, recovered by the second attempt;
    • the v1–v3 banner, flashed as soon as it arrives, and no reply at all, flashed after the timeout;
    • a write that throws before returning a promise, as the library's does once the board is gone;
    • the re-query after a flash;
    • a late connect-time reply that never reaches the session.
  • End to end: Dataflow's real SpikerbitDevice, SerialDevice and CcDeviceSession connected to the firmware running in the simulator from CLUE-728: CC Device Protocol and Spiker:bit firmware v4 #3056: no flash, EMG reaching a channel, the servo moving, the session staying alive, and Disconnect leaving the board idle. Its fake connection models the interface chip's buffer (512 bytes, oldest kept, 62-byte reads): a board left in free-run, and one whose host vanished mid-session, both connect without a flash, and both fail against the old one-? connect.

Run on a real Spiker:bit (V2, with a servo) in a Mac browser, on an earlier commit of this PR from before the !! connect, and passing:

  • connect;
  • EMG;
  • the servo;
  • Disconnect and Reconnect;
  • auto-flash, starting from a MakeCode program, which doesn't answer, so the flash came after the timeout;
  • auto-disconnect on unplug.

Connecting after 15 s of unread free-run was run on a V2 from Node over WebUSB, with Dataflow's own SpikerbitDevice and microbit-connection. The code from before the !! connect reflashed the board in 2 of 2 runs, and the new sequence identified it in 3 of 3, in about 30 ms. Those runs didn't flash through the library, so they don't cover connecting again after a browser flash, when the library has filled the interface chip's buffers.

Retesting at this PR's head is done in the UI-half PR, which builds on this one:

  • V1 and V2 Spiker:bits: a freshly plugged-in board already on v4, auto-flash from the v3 firmware and from a MakeCode program, connecting again after a browser flash, EMG, the servo, and Disconnect and Reconnect;
  • an Arduino, since the device capabilities, the Live Output node and Disconnect changed for every device.

The radio hub isn't retested.

On a Chromebook, connecting works sometimes but not always. The cause isn't known yet: CLUE-727's logging of each connection step, including when a flash starts and finishes, should show where it fails, and making it reliable will be done there.

For later (UI half)

  • Hidden tabs: Chrome probably throttles hidden tabs with an open WebUSB connection. After about 5 minutes hidden, the pings slow to about one a minute, and the session lapses and reopens.
  • The first heartbeat: lastHeartbeatAt is first set by the first ping's reply, about a second after connecting.
  • The session's state: nothing observable says whether a session is opening, open, or gave up after err othersession; all three look like a stale lastHeartbeatAt. lastHeartbeatAt is never reset, and MobX won't re-run anything as time passes, so the UI needs its own timer to notice it going stale.
  • A hidden tab or a stopped board: document.visibilityState tells a hidden, throttled tab apart from a board that stopped answering.
  • No Paused status: the library reports Paused only from listeners that its initialize() sets up, and CLUE doesn't call it.
  • Unplug detection: an unplugged Spiker:bit is noticed only when a write to it fails, so after err othersession stops the pings, an unplug goes unnoticed (serial.md, "Connection status tracking").

No Firebase functions, rules or indexes change.

🤖 Generated with Claude Code

scytacki and others added 20 commits October 22, 2026 23:20
The wire formats were spread across serial.md, arduino.md and hub.md, and
nothing described them as a whole. The new CC Device Protocol is designed
against this record of what the current protocols do and don't do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pure line handling that the session and firmware detection build on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ash it

Firmware answering ? with a hello line must be told apart from the legacy banner, and a protocol
Dataflow doesn't speak must be reflashed rather than trusted on its firmware number alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Spiker:bit now uses the ccDevice protocol: SerialDevice opens a session
when it becomes active, routes inbound data to it, writes servo as
servo:<angle>, and Disconnect ends the session before closing the transport.
A device that lists its channels satisfies only those, so the Spiker:bit no
longer claims the Arduino's fsr/tmp/a1 channels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ted MakeCode runtime

The MakeCode simulator can't receive serial input and no emulator runs a V2 hex, so the
firmware's protocol logic had no automated tests. MakeCodeSim transpiles spikerbit-clue.ts and
runs it against fake MakeCode globals on a simulated clock, so tests can drive serial input,
buttons and the analog pin and check what the firmware writes, shows and plays. It is proven
against the current v3 firmware before the protocol rewrite.

The firmware header now states the loop rule the simulator depends on (pause only as a loop's
last statement), and the hex is rebuilt because it embeds the source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the firmware sim

MakeCode's forever loop sleeps 20 ms after every pass, not only after a body that doesn't
pause, so the sim's forever passes were 20 ms too short. And the board's serial receive buffer
holds 20 characters unless the program raises it with serial.setRxBufferSize; the sim buffered
without limit, so firmware that never raised it would pass in Jest and truncate protocol
commands on the board. The sim now drops characters that arrive while the buffer is full, as
codal-core does, and fakes setRxBufferSize.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…clearing

The old firmware compares the whole line to "?", so a checksummed one got no banner and
the checks then ran against firmware that treats any line as a servo angle. Clearing the
receive buffer mid-line left a fragment that a check could take for a reply.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Spiker:bit firmware now answers ? with a hello line instead of the
legacy banner, stays silent until a host opens a session or asks it to
stream, and checks and adds a checksum on every line. Sessions keep the
board streaming only while Dataflow keeps pinging, so a closed tab no
longer leaves it filling the USB buffer with stale readings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r the board to finish

Copying the hex with copyFile left a "._" file on the drive, which made DAPLink's transfer
fail, and the wait needed to catch the drive and port disappearing between polls.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The LED display now shows how far a connection got (waiting, found, connected, streaming), so
a stuck connection can be diagnosed on the board. identify shows an icon and plays a sound on
the built-in speaker, with the P0 audio output off so the servo doesn't twitch; a V1 has no
speaker and only shows the icon, keeping the hex universal. A long press of A starts and stops
free-run outside a session, for plotters, and does nothing during one.

The firmware sim now raises the button events CODAL does and panics like a V1 on speaker
calls. serial-protocols.md documents the CC Device Protocol, and serial.md and the firmware
README no longer describe the Spiker:bit as speaking keyValue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File calls on the DAPLink drive can block in the kernel while the board reprograms, hanging
the whole process. The copy is now a child process that is abandoned if stuck, and readiness
is judged by the board answering ? rather than the drive or port appearing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Firmware numbers are per device, so a board on the same protocol with a higher number must not be accepted as a Spiker:bit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Every committed firmware answers identify, so the --expect-identify flag only let a regression pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A close write that never settles left Disconnect hanging and kept Connect and Reconnect blocked by busyChoice.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An angle of 90 maps to itself, so those tests could not catch a missing scale. Use 0, which maps to 30.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The protocol doc said it fills a gap that includes capability discovery, which is only reserved. One test mixed the protocol-tag rule with the channel-list rule.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n MakeCode

The firmware is silent until it's spoken to, so trying it on a board needs
a terminal or a long press of button A. serialterminal.com sends whole lines
and runs in any Chrome or Edge, and MakeCode's plotter button only appears
once readings arrive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CC Device Protocol

Records the protocol design (framing, sessions, acknowledgments, the
compatibility rules and what is reserved), the firmware behavior, and the
Dataflow side, as agreed while designing this story. serial-protocols.md
points here for the reasoning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bee0bff5-387c-4d0e-8408-5f4a0c42c980

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.53052% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 87.68%. Comparing base (21024ec) to head (413ded5).
⚠️ Report is 1 commits behind head on CLUE-728-spikerbit-status.

Files with missing lines Patch % Lines
src/models/stores/cc-device-session.ts 99.06% 1 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                      @@
##           CLUE-728-spikerbit-status    #3055      +/-   ##
=============================================================
+ Coverage                      87.65%   87.68%   +0.02%     
=============================================================
  Files                           1084     1085       +1     
  Lines                          62318    62501     +183     
  Branches                       16669    16714      +45     
=============================================================
+ Hits                           54625    54804     +179     
- Misses                          7672     7676       +4     
  Partials                          21       21              
Flag Coverage Δ
cypress-regression 70.46% <6.43%> (-0.31%) ⬇️
cypress-smoke 39.77% <5.94%> (-0.15%) ⬇️
jest 63.96% <99.53%> (+0.10%) ⬆️

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.

@cypress

cypress Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

collaborative-learning    Run #20962

Run Properties:  status check passed Passed #20962  •  git commit 8132d058d5: Merge pull request #3055 from concord-consortium/CLUE-728-spikerbit-firmware-sta...
Project collaborative-learning
Branch Review CLUE-728-spikerbit-status
Run status status check passed Passed #20962
Run duration 03m 54s
Commit git commit 8132d058d5: Merge pull request #3055 from concord-consortium/CLUE-728-spikerbit-firmware-sta...
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 ↗︎

scytacki and others added 2 commits October 24, 2026 09:41
src/plugins/dataflow-tool only exists to keep the ccemgfsr.ino URL that
printed materials and the brain and vibe teacher guides link to. The serial
docs this branch changes, and the micro:bit and diagram files they refer to,
now live with the plugin in src/plugins/dataflow/docs. The Arduino folder
stays where it is.

The firmware header names the new path, so the hex is rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scytacki scytacki changed the title CLUE-728: Spiker:bit firmware v4 speaks the CC Device Protocol CLUE-728: Dataflow speaks the CC Device Protocol to the Spiker:bit Oct 24, 2026
scytacki and others added 2 commits October 26, 2026 11:42
…tale or repeated

endSession's close condition, queryFirmware's backlog story (now in the docs), the flash list
(shouldFlash decides), shouldFlash's Dataflow wording, the channel-list rule (channelSatisfiedBy
holds it), the backlog range and a few test comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
queryFirmware's reset-and-query, the another-device flash row, CcDeviceSession and where
lastHeartbeatAt lives, the settled builder's calls, the hidden-tab outcome, and #3055's tests
and docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scytacki and others added 4 commits October 26, 2026 13:58
Device lines now need a checksum, so the tests send them as the board does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ware 4 does

The fake answered with the v1–v3 banner, so every Spiker:bit connect flashed and never opened a
session. It now answers !! with an empty line and its ok, ? with the hello line, and open with
its ok; the disconnect test checks that nothing was flashed and that the session was opened and
closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…device lines need a checksum

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from CLUE-728-protocol-firmware to CLUE-728-spikerbit-status October 26, 2026 18:34
@scytacki
scytacki requested a review from dougmartin October 26, 2026 22:01

@dougmartin dougmartin 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.

Looks good 👍

Reviewed at a25dc69. All the related suites pass locally. A v4 board is identified without a flash, a v3 banner or a missing reply still flashes, firmware 5 connects without one, and the Arduino and radio-hub paths behave as before. Copilot's three fixes in 394c316 each fail a test when reverted. I checked user-guide.md: its edits are outside the generated sections, and all ten example checksums in it and in cc-device-protocol.md are correct. device-connection.test.ts's fake now answers as firmware 4 does, and its test checks no flash and a session opened and closed.

A few non-blocking notes (take or leave). The last two are PR-body updates I'd ask for before merge:

  • src/models/stores/serial-cc-device.test.ts (lines 107-111), with src/models/stores/serial-transport.test.ts (line 32): the body says servo scaling is tested "for both protocols, tested at n=0", but the Arduino tests use n=90, the scaling's fixed point. Changing serial.ts line 190 to this.writeLine(Math.round(n).toString()) leaves all 1019 tests in src/models/stores and src/plugins/dataflow passing. Calling writeToOutForServo(0, "Servo") for the Arduino and expecting ["30"] would guard it.
  • src/models/stores/cc-device-session.ts (lines 5-9, 39-40), with src/models/stores/cc-device-session.test.ts (lines 38-54, 97-105): no caller or test passes pingIntervalMs or openRetryMs. The tests check only one side of each threshold, so openRetryMs ?? 1600 and pingIntervalMs ?? 950 both pass every suite. Meanwhile serial.md (lines 214, 216) and user-guide.md (lines 258-259) state 2 s and 1 s. Suggest replacing the two options with exported kOpenRetryMs = 2000 and kPingIntervalMs = 1000, and asserting both sides: one open at 1999 ms and two at 2000 ms, no ping at 999 ms after the ok and one at 1000 ms. randomSessionId is used by the tests and stays.
  • src/plugins/dataflow/docs/serial.md (lines 119-120, 401-421): "auto-disconnect on unplug" works only because one of the session's writes fails. CLUE never calls initialize(), so the library's navigator.usb listener is off, and a failed serial read only fires backgrounderror. The path is: a ping (every 1 s), an open retry or a servo write fails, the library's withEnrichedErrors calls disconnect(true) and sets Disconnected, and MicrobitUsbTransport's status listener (spikerbit-device.ts lines 67-69) clears the device. So after err othersession stops the pings, an unplug goes unnoticed unless a program is writing to the servo. serial.md lines 119-120 still say "it doesn't watch for the board being unplugged", and "Connection status tracking" doesn't mention the Spiker:bit. Worth a sentence in both. A test can only imitate the library's half with a fake whose serialWrite rejects and emits Disconnected, so docs matter more here than a test.
  • src/models/stores/spikerbit-device.ts (lines 72-77, 203): the write comment says "A failure is logged, not thrown", but the library's serialWrite isn't async. Its assertConnected (connection.js lines 516-517) throws synchronously when the device is gone, before any promise exists. Nothing reaches that today. If something did, though, the queryFirmware retry at line 203 would throw out of the setTimeout callback, the poll would never reschedule, the promise would never resolve, and busyChoice would stay set. The synchronous try/catch in CcDeviceSession.send (cc-device-session.ts lines 170-177) is the only guard on the session's writes, and removing it passes every suite. Making write and writeResetAndQuery async turns every failure into a rejection, makes the comment true, and changes nothing for callers, since IDeviceTransport.write already allows a promise.
  • src/models/stores/spikerbit-device.test.ts (lines 140-151): the legacy-banner test passes without legacy detection, because a missing reply flashes too. With the legacy branch at serial-protocol.ts line 69 disabled, only serial-protocol.test.ts line 119 fails. Asserting with fake timers that flash is called well before kFirmwareQueryTimeoutMs would make it test the banner. Low stakes: the only difference is flashing at once rather than after 1.5 s.
  • src/models/stores/serial-protocol.ts (lines 9-12, 23-24), src/models/stores/serial-protocol.test.ts (lines 72-73) and src/plugins/dataflow/model/utilities/channel.ts (lines 40-42): comments still describe the Spiker:bit as a keyValue device. parseKeyValueData's header justifies [ \t]* by Spiker:bit v1/v2 padding, and its lines 23-24 say "a Spiker:bit never emits fsr/a1". The test says "A Spiker:bit satisfies fsr by protocol". channel.ts says fsr/a1 on a Spiker:bit time out to missing and that parseKeyValueData refreshes it. None of that holds now that the Spiker:bit has a channel list and CcDeviceSession. Make them Arduino-only or drop the Spiker:bit examples.
  • docs/superpowers/specs/2026-10-20-clue-728-spikerbit-firmware-protocol-design.md (lines 98-100, 502, 506): "a line without one is accepted" and "Check the *hh checksum when present" contradict the code (cc-device-protocol.ts lines 46-47) and cc-device-protocol.md (lines 63-64), which drop a non-empty device line that has no checksum. Line 506's "answering the latest open" doesn't match the code either, which accepts the ok to any pending open (fine, since every retry carries the same id). This text dates from #3056, but this PR rewrites that section.
  • src/models/stores/spikerbit-device.ts (lines 35-39, 112-113, 125-128): comment trims. Lines 35-39 could be one line (e.g. "Above CcDeviceSession's 1..kMaxSeq, so a late reply to a connect-time !! can't match a session command."). Lines 112-113 repeat what lines 167-169 say. Lines 125-128 narrate what setActiveDevice does.
  • src/models/stores/spikerbit-sim/spikerbit-end-to-end.test.ts (lines 150-155): "moves the servo from a Live Output" calls serialDevice.writeToOutForServo directly. Rename it (e.g. "moves the servo for a servo output"), or drive it through LiveOutputNode.sendDataToSerialDevice.
  • PR body, "For later": it covers hidden-tab throttling, the first heartbeat, the session state not being observable, and the UI needing its own timer. Three things from the #3056 discussion are missing: using document.visibilityState to tell a hidden tab from a stopped board; that the library never emits Paused, because CLUE doesn't call initialize(); and that unplug detection depends on the session's writes (the serial.md note above).
  • PR body, hardware and bundle sections: the hardware list has only a V2 Spiker:bit. It names no V1, no commit, and which firmware "auto-flash from other firmware" started from, and records no Arduino or radio-hub retest, although device-capabilities.ts, live-output-node.ts and DeviceConnection.disconnect changed for every device. Separately, "Where the device code lives" says all of this device code ships in the main bundle. By the import graph, spikerbit-device.ts, spikerbit-connection.ts, the library and the hex are imported only from src/plugins/dataflow/model/device-connection.ts, so they stay in the Dataflow chunk. What newly reaches the main bundle is cc-device-session.ts and cc-device-protocol.ts, via stores.ts, serial.ts and serial-protocol.ts. I traced this from the imports and didn't run a webpack build.

Questions

  • Chromebook: is the body's "connecting works sometimes but not always" from a retest on this branch's head with the !! connect, as you planned after #3056? If so, is it the same as on #3040 (works once, then stops) or different, and does it flash when it fails?
  • Your 3-of-3 Node runs: had the board been unplugged since the old code's reflashes? The library fills the interface chip's buffers only inside flash() (cdcSaturated), and they stay full until an unplug, so if it wasn't unplugged, those runs don't cover Kirk's case from #2929. Does the first Connect in the browser, on a freshly plugged-in board already running v4, skip the flash?
  • Besides the V2, was this branch tried on a V1 Spiker:bit (connect, auto-flash, EMG), an Arduino, and the radio hub? And which firmware was "auto-flash from other firmware" run from: the v3 banner, a MakeCode program (no reply, so the 1.5 s timeout), or something else?

scytacki added a commit that referenced this pull request Oct 27, 2026
…h checks

The review of #3055 asked for them at its head; they run here because this branch contains that code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scytacki and others added 9 commits October 27, 2026 11:15
…s the value

At 90 the scaling leaves the value unchanged, so writing the unscaled value passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and test both sides of each

Nothing passed the two options, and the tests checked only one side of each interval, so other
values passed while the docs state 1 s and 2 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is logged, not thrown

The library's serialWrite throws before returning a promise once the board is gone. A throw from
the connect-time retry would have escaped its timer and left the connect attempt waiting forever.
Also tests the session's own guard on a throwing write, which nothing exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not after the timeout

A board that doesn't answer is flashed too, so the test passed without legacy detection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents

It speaks the CC Device Protocol now, so the keyValue parser, its test and the EMG channel's
comment are about the Arduino.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One shortened, one that repeated the firmware handler's comment, and one that narrated what
setActiveDevice does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It calls writeToOutForServo directly; the Live Output node's own test covers the node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only by a failed write, since CLUE doesn't call the library's initialize(); after err othersession
stops the pings, an unplug goes unnoticed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host drops a non-empty device line without a checksum, and the session accepts an ok to any
pending open, since every re-sent open carries the same id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scytacki

Copy link
Copy Markdown
Member Author

Thanks, Doug. I took all of your notes. Here's where each went.

Note What changed
Arduino servo scaling only tested at n=90 558db70: the Arduino test uses n=0 and expects 30, so the description's line is now true.
pingIntervalMs/openRetryMs unused, thresholds one-sided 5ab320f: exported kPingIntervalMs = 1000 and kOpenRetryMs = 2000 replace the options, and the tests check 999/1000 ms and 1999/2000 ms.
Unplug detection depends on the session's writes 4052e32: serial.md says so in the Reconnect paragraph and in "Connection status tracking", including that nothing notices an unplug after err othersession. The description's "For later" lists it too.
write can throw synchronously c9a7eff: write and writeResetAndQuery are async. New tests: a serialWrite that throws before returning a promise is logged by write; a connect whose retry write throws still flashes after the timeout; and a session whose write throws keeps going, which tests the guard in send. Each fails on the old code.
Legacy-banner test passes without legacy detection f020174: with fake timers, it checks the flash comes within 100 ms, and it fails with the legacy branch disabled.
Comments describing the Spiker:bit as keyValue aa2738b: parseKeyValueData's comments, its test and channel.ts are about the Arduino now.
Spec's checksum rule and "the latest open" 413ded5.
Comment trims in spikerbit-device.ts 3b93609, using your one-line wording for the reset numbers.
End-to-end "from a Live Output" 01354c7: renamed "moves the servo for a servo output". The node's own test already fails if its ccDevice branch is removed.
"For later" items The description now lists document.visibilityState, the missing Paused status, and unplug detection.
Hardware list and bundle section The description now says what ran on which commit, and the bundle section follows the import graph as you traced it. The retests at this PR's head are moved to the UI-half PR, which builds on this one: V1 and V2 Spiker:bits and an Arduino, not the radio hub.

Your questions:

  • Chromebook: yes, on this branch's head. The failure is different from CLUE-716: Connect and Reset buttons in the DataFlow toolbar (2 of 2) #3040's, but we can't describe it accurately without the connection-step logging from CLUE-727, so it stays vague until then.
  • The 3-of-3 Node runs: they don't cover Kirk's case from Add Spiker:bit EMG + servo support and refactor DataFlow serial onto a transport abstraction [CLUE-567] #2929. Those runs intercepted flash(), and the bench flashes by copying to the drive, so the library's CDC saturation never ran. The backlog came from 15 s of unread free-run. Connecting a freshly plugged-in v4 board, and connecting again after a browser flash, are both in the UI-half PR's retests.
  • V1, Arduino, radio hub, and where the auto-flash started: the browser test was a V2 only, on an earlier commit, and its auto-flash started from a MakeCode program, so the timeout path. The V1 and the Arduino are in the UI-half PR's retests; the radio hub isn't retested.

@scytacki
scytacki merged commit 8132d05 into CLUE-728-spikerbit-status Oct 27, 2026
31 of 32 checks passed
@scytacki
scytacki deleted the CLUE-728-spikerbit-firmware-status branch October 27, 2026 15:40
scytacki added a commit that referenced this pull request Oct 28, 2026
Notes from running the hand checks and the #3055 retests on a micro:bit V2 and V1 on a
Spiker:bit, and an Arduino, at the branch preview. Adds the Web Serial terminal check, which
shows not responding and recovery; holding the reset button turned out to need Reconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
development — 413ded59 Deployed Oct 27, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants