Skip to content

Add a USB/BLE hardware check tool for hand controllers (bench Layer 1) - #133

Open
PaulDWhite wants to merge 4 commits into
release/8.1-rcfrom
claude/project-thread-8aghvu
Open

PaulDWhite wants to merge 4 commits into
release/8.1-rcfrom
claude/project-thread-8aghvu

Conversation

@PaulDWhite

@PaulDWhite PaulDWhite commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by paul · project thread

Before: you checked a hand controller against the 8.1 hardware checklist by hand, using the app and the serial monitor. On a real 8.1 unit, most replies to the USB sync command also arrived with bytes missing, which breaks the web config tool too.

After: python tools/hw_check/hw_check.py checks a unit plugged in over USB and prints PASS/FAIL per item. Add --ble to include checks over the computer's Bluetooth. It covers:

  • firmware version over serial and BLE
  • settings write, sanitize, persist across a reboot, and restore
  • reset reason and crash/watchdog/brownout history
  • boot log
  • first-boot QC record
  • barometer, CPU temperature, throttle pot, ESC and BMS telemetry
  • OTA readiness

--dry-run runs every check against a simulated controller, with --fault to inject failures, and needs no hardware. The sync reply is now sent intact.

The tool only reads what the firmware already exposes. It never arms the controller or sends throttle, and it restores every setting it changes.

Firmware fix (src/sp140/device_settings.cpp)

  • send_device_data() streamed serializeJson straight into USBSerial.
    • A short write dropped bytes, and other tasks' log lines landed in the middle of a reply.
    • On a real unit, 0 of 34 sync replies came back intact.
  • It now serializes to a buffer and writes the whole line, retrying short writes. diagnosticsSendJson already did this.
  • The retry gives up after 250 ms with no progress, so a host that has gone away can't stall the serial task.

How

  • protocol.py describes the firmware's wire formats.
  • links.py handles pyserial and bleak, and reconnects across reboots.
    • A damaged reply is reported as garbled, with the raw line. It isn't retried around.
    • A sync reply that parses but is missing keys is also treated as damaged.
    • The tool waits 0.5 s after opening the USB port. Without it, the first reply after opening was sometimes 32 bytes short.
  • fake_controller.py serves the serial protocol on a local socket, so the real pyserial path runs. It also models the BLE layout and Fast-Link packets. tools/hc_emulator only emulates the screen, so it can't stand in here.
  • test_hw_check.py has two kinds of tests:
    • End-to-end dry runs, one per fault.
    • Drift tests. They parse the firmware sources and fail if the tool falls out of step.
  • A new Hardware Check Tool CI job runs the tests and a dry run.

The README lists what's covered, and what still needs the rest of Layer 1 (BLE OTA transfers, esptool flashing paths, soak loop), Layer 2 (fixture and CAN adapter) or Layer 3 (camera, phone).

Tested on hardware (PDPC-MAIN, COM4)

The unit ran 8.1 RC 9d869ca plus this fix. 9d869ca is a local RC build, not yet pushed.

Run Result
Before the fix Stopped at the settings checks: 0 of 30 sync replies intact
After the fix, USB checks 11 PASS, 0 FAIL
After the fix, adding --esptool --firmware 12 PASS, 0 FAIL. The partition table checks out and the image uses 33% of a slot.
Sync replies after the fix 60 of 60 intact
diag_sync replies after the fix 20 of 20 intact

On reboot, the port stayed COM4 and the whole boot log was captured. BLE checks were not run, because the unit isn't bonded to that PC.

Found while testing, not fixed here

After an esptool hardware reset, diag_sync still labels the boot usb_command_reboot. The planned-restart marker kept in RTC memory isn't cleared once a boot has used it.

🤖 Generated with Claude Code

https://claude.ai/code/session_0131SpAAHXzdS5D8UQaHfNrC

tools/hw_check runs the release-checklist items the firmware already
reports: firmware version, settings write/sanitize/persist/restore,
reset reason and crash history, boot log, first-boot QC record, Fast-Link
sensor/ESC/BMS telemetry, and OTA readiness. --dry-run runs every check
against a simulated controller, and the unit tests fail if the tool's
protocol description drifts from the firmware sources. CI runs both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131SpAAHXzdS5D8UQaHfNrC
@PaulDWhite PaulDWhite self-assigned this Oct 15, 2026
pyserial's socket:// open() flushes input that already arrived, so on a
slow CI runner the fake's boot log was discarded on reconnect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131SpAAHXzdS5D8UQaHfNrC
…lies

On a real 8.1 unit most sync replies arrived with bytes missing:
send_device_data() streamed serializeJson straight into USBSerial, so a
short write dropped bytes and other tasks' log lines landed mid-reply.
Serialize to a buffer and write the whole line, retrying short writes
like diagnosticsSendJson does.

hw_check now reports a damaged reply as garbled (with the raw line)
instead of 'no reply', rejects sync replies with missing keys, and reads
the settings back before saying a restore failed. The simulated
controller gains a garbled-sync fault.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131SpAAHXzdS5D8UQaHfNrC
On real hardware the first diag_sync after opening the port came back
32 bytes short; waiting 0.5 s after open avoided it in testing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131SpAAHXzdS5D8UQaHfNrC

This branch has not been deployed

No deployments
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