FIRST_BOOT_QC: factory self-test + per-unit throttle calibration capture (v8.1)#118
Draft
zjwhitehead wants to merge 9 commits into
Draft
FIRST_BOOT_QC: factory self-test + per-unit throttle calibration capture (v8.1)#118zjwhitehead wants to merge 9 commits into
zjwhitehead wants to merge 9 commits into
Conversation
zjwhitehead
marked this pull request as draft
July 8, 2026 02:18
Factory QC + per-unit throttle calibration design: automatic POST (bus-level I2C/SPI/CAN checks), screen-guided calibration capture, interactive operator checks, JSON traceability record, NVS gating. Targeting v8.1 (phases 1-3 + interactive checks + serial record); live throttle-mapping switch is v8.2, data-gated on calibration records collected by this release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- factory_settings: "openppg-factory" NVS namespace (survives user factory reset), mutex-guarded single-commit writes mirroring device_settings. Keys: qc_passed, qc_fw, pot_cal, pot_min, pot_max, qc_rerun, qc_record. - qc_logic: dependency-free gate decision (fresh-NVS or serial rerun only; legacy units back-filled, never auto-calibrated), calibration sanity gates, stability-window detector, v8.2 calibrated-mapping scaffold (unwired), QC record + one-line JSON serializer. - test_qc: 29 native tests incl. exhaustive gate decision table proving the installed fleet can never enter QC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- qcCaptureBootContext() runs BEFORE refreshDeviceData() so a fresh factory board is distinguishable from an existing fleet unit (which gets qc_passed back-filled and never sees QC). - POST as bus-communication tests: I2C (baro probe + sane pressure), SPI (MCP2515 init), CAN-ESC (active DroneCAN hardware-info request/response — never a throttle command), CAN-BMS (passive telemetry listen), CPU temp, factory-NVS round-trip, throttle ADC. - Absent-device skip-confirm: ESC/BMS not detected => operator button press records "skip" (bare-controller bench QC passes); no press => fail. Cosmetic LED cycle only — no operator LED check. - Emits one-line JSON QC record over USBSerial + persists blob for the upcoming BLE fleet-sync characteristic. qc_passed only written when every check passes; cal/interactive checks are NOT_RUN until the next commits, so this intermediate stage can never stamp a pass. - QC tuning constants centralized in shared-config.h. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nner 160x128 three-view screen: POST checklist (8 rows, name + colored status), guided prompt (big instruction, live value in montserrat_28, progress/countdown bar), and full-screen PASS/FAIL banner with failed-check detail. Wired into the QC flow's view seam (serial output kept for the bench log); button-wait now shows a live countdown. Screenshot suite gains 4 QC states (checklist-in-progress, squeeze prompt, passed and failed banners) with generated references, via both the CMake harness (CI path) and the pio native-screenshot filter. Note: SplashScreen_Light/Dark screenshot failures and the pio native-screenshot env linker error on macOS are pre-existing on the base branch (verified via stash) — untouched here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Release -> squeeze -> release-recheck, each auto-advancing on a 50 Hz stability window (25 samples within 30 counts; median captured) with live raw value + fill progress on the display — no button involved. Sanity gates (span >= 2000, idle <= 800, full >= 3200, release-recheck tolerance) reject bad pots; only a passing capture writes pot_min/max to the factory namespace. The live throttle mapping is untouched — capture-only in v8.1; the mapping switch is v8.2, data-gated. Per-step 60 s operator timeout fails cal rather than hanging the line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The freshly calibrated throttle is the confirmation input: each cue pulses intermittently (300/700 ms) while the operator squeezes past 50% of span to confirm observing it; 10 s timeout = fail; the pot must return to idle between checks so one long squeeze can't blanket-pass. The button is used only for its own functional test (single press). No LED check — the LED is inside the case; the TFT is the feedback surface (cosmetic LED cycle stays in POST, unrecorded). Adds vibeDirectSet() to the vibe module: direct LEDC write bypassing the queue, for pre-task contexts (vibeTask doesn't exist during QC). Buzzer uses the already-direct startTone()/stopTone(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QC_RECORD_UUID on the config service, mirroring the proven ESC_PARAM_DATA paged-fetch pattern (app writes u32 LE offset cursor, reads back <=240-byte chunks of the stored QC JSON record; empty read = no record / past end). The smartphone app reads this silently on connect and uploads to Supabase — the only fleet QC/calibration data path once units leave the bench. App-side read + backend land in the app/site repos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zjwhitehead
force-pushed
the
first-boot-qc
branch
from
July 19, 2026 01:35
eae6313 to
ae9d947
Compare
Add {"command":"run_qc"} over webSerial (sets factory rerun flag + reboot)
and tools/qc_bench_logger.py for factory JSONL capture. Ignore local plans/.
Co-authored-by: Cursor <cursoragent@cursor.com>
zjwhitehead
force-pushed
the
first-boot-qc
branch
from
July 19, 2026 01:50
ae9d947 to
b24387a
Compare
Adds a sticky `qc_attempted` factory flag and updates QC gate logic so failed/aborted first-boot runs re-enter QC on reboot instead of being treated as legacy fleet units. QC results are now always written (`qc_passed` true/false), rerun handling is unified, and the gate API/action names were simplified. Refactors interactive pot-confirm behavior to use shared confirm/release thresholds with explicit release-before/after gating, and keeps safe default calibration bounds when calibration fails. Also increases QC JSON record buffer size for added IDs/check data and updates native tests to cover the new gate behavior, pot-confirm levels, and larger JSON payloads.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the FIRST_BOOT_QC design (
FIRST_BOOT_QC.md, committed as the first commit of this branch): a factory QC station that runs as a blocking guided flow at the Phase 4/5 boot boundary — automatic POST framed as bus-communication tests, screen-guided per-unit throttle calibration capture, operator-confirmed output checks, a JSON QC record per unit (USB serial + persisted + readable over BLE for silent fleet sync), and a serialrun_qcre-entry command.Zero live-behavior change for the fleet: the throttle mapping stays on the fixed
0..4095curve. Calibration is capture-only in v8.1; the mapping switch is v8.2, data-gated on the records this release collects. ApotRawToPwmCalibrated()scaffold ships tested but wired to nothing.Fleet-safety contract (the critical bit)
QC entry has exactly two paths: truly fresh NVS (new factory board) or the
{"command":"run_qc"}serial command. Existing units (NVS data from ≤ v8.0) are silently back-filledqc_passed=1, never auto-calibrated, and never see the flow. Boot context is captured beforerefreshDeviceData()(which writes defaults on first boot and would otherwise make a fresh board indistinguishable from a fleet unit). The gate decision is a pure function covered by an exhaustive native decision-table test. No button-hold entry — button-at-boot already means perf-mode toggle.Design decisions (Zach, 2026-07-04)
"skip"(bare-controller QC passes); no press →"fail"(a dead attached device can't be silently skipped)."openppg-factory"NVS namespace (15-char limit; house rule: never abbreviate) — survives user factory reset. Keys:qc_passed,qc_fw,pot_cal,pot_min,pot_max,qc_rerun,qc_record.QC_RECORD_UUIDon the config service using the proven ESC_PARAM_DATA paged-fetch pattern; the app reads it silently on connect → Supabase (app-side lands in the app repo).Verification
test/test_qc: gate decision table, cal gates, stability window, v8.2 mapping scaffold, JSON golden)native-screenshotenv linker error on macOSOpenPPG-CESP32S3-CAN-SP140SUCCESSOutstanding before merge/tag
plans/005-first-boot-qc-v81.mdcarries the 10-item HIL bench checklist (fresh unit, fleet-simulation must show zero QC UI, bare controller skip-confirms, dead-ESC fail, bad-pot rejection, BLE read-back, factory-reset survival, WDT idle) plus STOP conditions. Bench tooling:tools/qc_bench_logger.py.🤖 Generated with Claude Code