From 08e118c48b65090ec5f3a81b84738a64cc2489aa Mon Sep 17 00:00:00 2001 From: ruvnet Date: Mon, 31 Aug 2026 19:08:12 +0000 Subject: [PATCH] deploy: e04f269f1a76eb6ed4e8cd33e2ccfeda6fd391a3 --- ...346-fail-closed-edge-occupancy-evidence.md | 52 ++++++++ ...R-347-rate-aware-esp32-temporal-sensing.md | 85 ++++++++++++ api-docs/adr/README.md | 2 + api-docs/releases/v0.8.8-esp32.md | 111 +++++++++++++++ ...08-31-esp32-c6-node7-rate-aware-sensing.md | 120 +++++++++++++++++ ...2026-08-31-esp32-c6-occupancy-integrity.md | 60 +++++++++ .../2026-08-31-esp32-c6-rate-aware-sensing.md | 124 +++++++++++++++++ ...026-08-31-esp32-s3-rate-aware-transport.md | 126 ++++++++++++++++++ 8 files changed, 680 insertions(+) create mode 100644 api-docs/adr/ADR-346-fail-closed-edge-occupancy-evidence.md create mode 100644 api-docs/adr/ADR-347-rate-aware-esp32-temporal-sensing.md create mode 100644 api-docs/releases/v0.8.8-esp32.md create mode 100644 api-docs/validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md create mode 100644 api-docs/validation/2026-08-31-esp32-c6-occupancy-integrity.md create mode 100644 api-docs/validation/2026-08-31-esp32-c6-rate-aware-sensing.md create mode 100644 api-docs/validation/2026-08-31-esp32-s3-rate-aware-transport.md diff --git a/api-docs/adr/ADR-346-fail-closed-edge-occupancy-evidence.md b/api-docs/adr/ADR-346-fail-closed-edge-occupancy-evidence.md new file mode 100644 index 00000000..0148a2b8 --- /dev/null +++ b/api-docs/adr/ADR-346-fail-closed-edge-occupancy-evidence.md @@ -0,0 +1,52 @@ +# ADR 346: Fail closed ESP32 occupancy evidence + +## Status + +Accepted and implemented. Physical qualification is required after each firmware build. + +## Date + +2026 08 31 + +## Context + +The ESP32 Tier 2 pipeline produces two different signals. Presence is a debounced room level decision. Person count is a bounded subcarrier diversity heuristic. A live four node installation emitted packets with `presence=false` and `n_persons=3` or `4`. The server eventually gated the aggregate room count, but raw WebSocket consumers and diagnostics could still treat the contradictory count as occupancy evidence. + +That contradiction is more dangerous than a missed optional count. It can contaminate empty room calibration, train a room model on false labels, and encourage a product claim that the firmware cannot support. The count is not identity, pose, or a validated multi person estimator. + +## Decision + +1. Firmware person slots are subordinate to the debounced presence gate. +2. When presence is false, the firmware clears slot activity, slot history, candidate count, persistence streak, and stable count. +3. The serialized person count is always zero when presence is false and is clamped to `EDGE_MAX_PERSONS` when presence is true. +4. The sensing server repeats the invariant for older firmware. A contradictory or out of range count becomes zero and carries `person_count_valid=false`. +5. Fused CSI plus mmWave packets use either CSI presence or mmWave presence as the supporting presence condition. +6. The node inventory and WebSocket diagnostics expose person count validity. Consumers must not infer a person from an invalid count. +7. No count accuracy claim is created by this change. The firmware output remains a heuristic until a leakage free, held out physical dataset demonstrates otherwise. +8. OTA admission uses the selected update partition size rather than a stale fixed 900 KB ceiling. The status endpoint reports that same hardware bound, while image validation and authenticated OTA remain mandatory. + +## Security and privacy + +The change retains no raw CSI or personal data. It reduces authority by preventing a secondary heuristic from asserting occupancy after the primary gate has closed. The host validates packet length, magic, range, and logical consistency before using count evidence. + +## Consequences + +Older firmware remains wire compatible. Invalid count evidence becomes visibly unavailable instead of silently affecting calibration. A true multi person event can still be undercounted when the presence gate is false, which is the intended fail closed behavior. Current C6 images larger than 900 KB can use the installed 1,900,544 byte OTA slots after one serial upgrade, without weakening the OTA authentication gate. + +The largest uncertainty is whether the current presence gate itself generalizes across the installed rooms. The fix path is a room bound empty baseline plus the fixed room selective held out protocol, not a global threshold reduction. + +## Evidence and acceptance + +MEASURED before implementation on 2026 08 31: four live nodes streamed for 86 seconds with zero transport errors, while edge packets repeatedly contradicted `presence=false` with counts of three or four. + +Software acceptance requires: + +1. Host firmware tests prove absent plus four active slots serializes zero. +2. Rust parser tests prove contradictory and out of range counts fail closed. +3. The node API exposes count validity without breaking older firmware. + +Physical acceptance requires the updated firmware on a confirmed board, a captured boot log, five minutes of live packets, zero logical count contradictions, and no increase in transport errors. Accuracy remains unmeasured until labelled held out sequences are recorded. + +Physical occupancy qualification completed for ESP32 C6 node 4 on 2026 08 31. The five minute run observed 242 edge packets, including 61 absent packets, with zero logical count contradictions and zero parse errors. See `docs/validation/2026-08-31-esp32-c6-occupancy-integrity.md`. + +ESP32 C6 node 7 was subsequently identified, upgraded to firmware 0.8.8, and transport qualified for five minutes with zero fused presence count contradictions and zero steady state transport errors. Its controlled empty room sequence remains required before occupancy qualification. See `docs/validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md`. Other nodes remain unqualified until separately identified and upgraded. diff --git a/api-docs/adr/ADR-347-rate-aware-esp32-temporal-sensing.md b/api-docs/adr/ADR-347-rate-aware-esp32-temporal-sensing.md new file mode 100644 index 00000000..28901117 --- /dev/null +++ b/api-docs/adr/ADR-347-rate-aware-esp32-temporal-sensing.md @@ -0,0 +1,85 @@ +# ADR 347: Rate aware ESP32 temporal sensing + +## Status + +Accepted. Implemented in firmware 0.8.8. The timing and transport path is +physically qualified on ESP32 C6. The independent raw transport path is also +physically qualified on an ESP32 S3 running Tier 0. Held out inference accuracy +remains required. + +## Context + +The ESP32 firmware creates CSI opportunities by sending one byte ICMP probes to +the connected access point. The traffic source is configured for 50 Hz, but the +delivered CSI cadence varies with channel contention and callback safety gates. +A physical ESP32 C6 produced 28 to 37 callbacks per second during the baseline +capture. Firmware 0.8.5 then exposed that the old per-interval estimator saw +only 12 to 16 Hz because WiFi replies arrived in short bursts separated by +longer gaps. The filters still consumed those burst frames, so excluding them +from the clock estimate was incorrect. + +The edge DSP estimates its sample rate from timestamps so that breathing, +heartbeat, motion, and future Doppler features stay in physical Hertz. That +estimator was capped at 30 Hz. Once the actual cadence exceeded the cap, every +temporal feature was scaled against the wrong clock. + +Physical firmware 0.8.5 validation corrected that initial diagnosis. Although +the callback path received 26 to 40 frames per second, Tier 2 on the unicore C6 +processed an irregular subset that converged toward the 8 Hz estimator floor. +The right design is not to force the edge DSP to match raw capture. The paths +need independent, explicit cadence contracts. + +The device free gesture preprint at +`https://www.preprints.org/manuscript/202602.0018` reinforces the importance of +timestamp correct Doppler features, but its 100 Hz controlled link is not a +safe firmware default for RuView. Existing S3 and C6 evidence records WiFi ISR +and packet buffer failures under sustained callback pressure above 50 Hz. + +## Decision + +1. Make the connected STA probe rate a build time setting from 10 through 50 + Hz, with a default and hard ceiling of 50 Hz. + +2. Track the delivered DSP cadence by counting every processed frame interval + over one second timestamp windows, then smooth successive windows in an 8 + through 60 Hz estimator range. The 60 Hz estimator ceiling accommodates + timestamp jitter; it does not authorize more than 50 Hz callback processing. + +3. Reject incomplete windows below one second and stalled windows above three + seconds. Do not discard valid burst frames from the estimated clock. + +4. Surface the DSP rate in the one second controller diagnostic so hardware + validation can compare callback yield with the clock used by temporal + filters. + +5. Keep raw CSI on the wire at the independent network cadence. Rate-limit the + C6 on-device Tier 1 and Tier 2 DSP input to a uniform 8 Hz. Physical 0.8.7 + evidence showed that a requested 10 Hz input still converged to 8.0 through + 8.4 Hz under Tier 2 load, while raw delivery remained 30 through 40 pps. + Eight hertz retains a 4 Hz Nyquist limit for the 0.1 through 2.0 Hz vital + bands without creating a backlog. The S3 default remains 20 Hz. + +6. STFT, spectrogram gating, and learned temporal + classification remain host or iPhone responsibilities where memory, + rollback, and held out evaluation are stronger. + +## Consequences + +Heartbeat, respiration, and motion features receive a stable timestamped clock +instead of an accidental subset determined by C6 backlog. Operators can lower +the probe or DSP load for constrained networks without editing source. The host +still receives the higher-rate raw stream for richer Doppler processing. + +This does not prove vital sign accuracy or gesture recognition. Higher temporal +fidelity only improves the representation available to a separately validated +model. The 50 Hz ceiling also means the paper's 100 Hz results are not directly +transferable. + +## Acceptance test + +On a physical C6, run at least five minutes after flashing. Pass when the boot +log reports the configured probe and DSP rates, the controller converges within +one hertz of the configured DSP cadence, raw callback yield remains at least 20 +pps, no steady-state ENOMEM, watchdog, panic, or reboot occurs, and the fail +closed occupancy invariant remains zero contradictions for at least 30 absent +packets. diff --git a/api-docs/adr/README.md b/api-docs/adr/README.md index 6ea87665..95a200e4 100644 --- a/api-docs/adr/README.md +++ b/api-docs/adr/README.md @@ -106,6 +106,8 @@ Statuses: **Proposed** (under discussion), **Accepted** (approved and/or impleme | [ADR-036](ADR-036-rvf-training-pipeline-ui.md) | Training Pipeline UI Integration | Proposed | | [ADR-043](ADR-043-sensing-server-ui-api-completion.md) | Sensing Server UI API Completion (14 endpoints) | Accepted | | [ADR-344](ADR-344-adaptive-local-installation-discovery.md) | Adaptive Local Installation Discovery | Accepted (local software path) | +| [ADR-346](ADR-346-fail-closed-edge-occupancy-evidence.md) | Fail closed ESP32 occupancy evidence | Accepted (C6 occupancy integrity qualified) | +| [ADR-347](ADR-347-rate-aware-esp32-temporal-sensing.md) | Rate aware ESP32 temporal sensing | Accepted (C6 timing and transport qualified) | | [ADR-115](ADR-115-home-assistant-integration.md) | Home Assistant integration via MQTT auto-discovery + Matter bridge (HA-DISCO + HA-FABRIC + HA-MIND) | Accepted (MQTT track) / Proposed (Matter SDK P8b) | | [ADR-169](ADR-169-adam-mode-light-theme.md) | adam-mode — light theme toggle for the three.js realtime demo | Proposed | | [ADR-170](ADR-170-yoga-mode-pose-system.md) | yoga-mode — yoga pose detection, classification, and scoring for the three.js realtime demo | Proposed | diff --git a/api-docs/releases/v0.8.8-esp32.md b/api-docs/releases/v0.8.8-esp32.md new file mode 100644 index 00000000..77287902 --- /dev/null +++ b/api-docs/releases/v0.8.8-esp32.md @@ -0,0 +1,111 @@ +# RuView ESP32 firmware 0.8.8 + +Firmware 0.8.8 is a reliability and correctness release for ESP32-S3 and +ESP32-C6 RuView nodes. It makes the timing used by signal processing explicit, +prevents contradictory occupancy output, and improves update diagnostics. + +## What changed + +### Empty means zero people + +Older firmware could report `presence=false` and a nonzero person count in the +same edge packet. That was internally contradictory and could contaminate an +empty-room calibration. Firmware 0.8.8 clears the count whenever the presence +gate is closed. The sensing server repeats the same check when it receives data +from older nodes. + +This is a consistency fix, not proof that the heuristic can count multiple +people accurately. See +[ADR 346](../adr/ADR-346-fail-closed-edge-occupancy-evidence.md). + +### Stable time scale on ESP32-C6 + +Raw CSI and on-device signal processing now have separate clocks. The C6 keeps +raw CSI moving over the network while its Tier 2 filters process a stable 8 Hz +sample stream. The S3 retains its 20 Hz DSP default. A phase-preserving sampler +keeps callback jitter from shifting those clocks. + +The result is a correct time base for motion and vital-band features. It does +not by itself prove that heartbeat, respiration, gesture, or pose estimates are +more accurate. See +[ADR 347](../adr/ADR-347-rate-aware-esp32-temporal-sensing.md). + +### Better diagnostics and safer updates + +The one-second controller log now shows both raw callback yield and DSP rate. +The OTA status endpoint reports the actual selected application partition size +instead of a fixed 900 KB assumption. Firmware upload remains fail closed when +the node has no provisioned OTA signing secret. + +## Measured hardware validation + +All results below are physical measurements from 2026-08-31. They are not +simulator claims. + +| Board | Duration | Raw CSI mean | DSP clock | Live coverage | Steady-state transport errors | +|-------|---------:|-------------:|----------:|--------------:|------------------------------:| +| ESP32-C6 node 4 | 300.64 s | 34.92 pps | 8.00 Hz | 97.62% | 0 | +| ESP32-C6 node 7 | 300.70 s | 36.32 pps | 8.00 Hz | 97.40% | 0 | +| ESP32-S3 node 1 | 300 s | 28.03 pps | Tier 0 | 100.00% | 0 | + +The first C6 empty-room qualification observed 61 absent packets with zero +nonzero counts. The second C6 was transport-qualified in an occupied room and +still needs its own controlled empty-room sequence. Full evidence is recorded +in: + +1. [C6 timing and transport](../validation/2026-08-31-esp32-c6-rate-aware-sensing.md) +2. [C6 occupancy integrity](../validation/2026-08-31-esp32-c6-occupancy-integrity.md) +3. [Second C6 timing and transport](../validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md) +4. [S3 transport](../validation/2026-08-31-esp32-s3-rate-aware-transport.md) + +## Choose the correct download + +| Release file | Target | +|--------------|--------| +| `esp32-csi-node-v0.8.8-s3-8mb-flash-bundle.zip` | ESP32-S3 with 8 MB flash | +| `esp32-csi-node-v0.8.8-s3-4mb-flash-bundle.zip` | ESP32-S3 with 4 MB flash | +| `esp32-csi-node-v0.8.8-c6-4mb-flash-bundle.zip` | ESP32-C6 using the supported 4 MB partition layout | +| `esp32-csi-node-v0.8.8-s3-8mb.bin` | S3 8 MB application only | +| `esp32-csi-node-v0.8.8-s3-4mb.bin` | S3 4 MB application only | +| `esp32-csi-node-v0.8.8-c6-4mb.bin` | C6 application only | + +Never mix S3 and C6 images. Confirm the chip and physical flash before writing. + +## Install or update + +For a fresh installation, extract the matching bundle and follow its included +`FLASHING.md`. The standard offsets are: + +| Image | Offset | +|-------|-------:| +| Bootloader | `0x0000` | +| Partition table | `0x8000` | +| OTA metadata | `0xf000` | +| Application | `0x20000` | + +For an existing provisioned node: + +1. Back up the current application partition. +2. Confirm the exact chip, flash layout, logical node, and serial port. +3. Read `http://DEVICE_IP:8032/ota/status`. +4. Use an application-only serial update at `0x20000` only when the running + partition is `ota_0` and the image matches the board. +5. Reboot and confirm version 0.8.8, the preserved node identity, channel, and + sensing-server target. +6. Run a five-minute burn-in before returning the node to calibration duty. + +The full bundle does not contain an NVS image. A four-offset install therefore +preserves the existing WiFi and node settings, but operators should still keep +a backup before changing firmware. + +## What this release does not prove + +Firmware 0.8.8 does not prove medical-grade vital signs, accurate person +counting, identity, dense pose, through-wall video, or room separation. Those +claims require synchronized references and leakage-free held-out sequences. + +The practical next acceptance test is a controlled empty-room capture with at +least 30 absent edge packets per updated node, zero absent packets carrying a +nonzero count, and zero transport or parser errors. Accuracy evaluation then +needs held-out occupied, movement, heartbeat-reference, and adjacent-room +sequences. diff --git a/api-docs/validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md b/api-docs/validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md new file mode 100644 index 00000000..0b39148d --- /dev/null +++ b/api-docs/validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md @@ -0,0 +1,120 @@ +# ESP32 C6 node 7 rate aware sensing qualification + +## Scope + +This record qualifies the firmware 0.8.8 timing and transport path on a second +physically attached ESP32 C6. It measures raw callback cadence, edge DSP +cadence, process stability, and end to end sensing delivery. It does not +qualify heartbeat, respiration, pose, identity, room separation, or person +count accuracy against labelled ground truth. + +## Hardware and firmware + +| Field | Measured value | +|---|---| +| Board | ESP32 C6 QFN40 revision 0.2 | +| Logical node | 7 | +| Firmware before | 0.8.4 | +| Firmware after | 0.8.8 development build | +| App image | 1,051,552 bytes | +| App SHA 256 | `eab7561d65e302dc33e9331ac591763a46f92fb3fa9f824fef0e9b541daddb3f` | +| OTA slot size | 1,900,544 bytes | +| OTA headroom | 848,992 bytes, 45 percent | + +Only the application partition at offset `0x20000` was flashed. WiFi +credentials, logical node identity, sensing server target, channel, edge tier, +bootloader, partition table, OTA metadata, and NVS were preserved. The pre +update application was copied to a private recovery file outside the +repository. Its SHA 256 is +`f5ebc5e0142425adae16e9180bf298ef444ea8862ba0d8809c310a39fe45721d`. +The device partition table was also read before the update and had SHA 256 +`0a8d2f192a8fff209d6c75ab639fcf8aa2f43c64abb732c6e74596fbd6971dca`. + +The post update boot log reported firmware 0.8.8, node 7, channel 10, Tier 2, +an 8 Hz edge DSP cadence, and the preserved sensing server target. The OTA +status endpoint reported firmware 0.8.8 running from `ota_0`, with `ota_1` as +the next partition and the correct 1,900,544 byte limit. The sensing server +health endpoint remained ready with ESP32 input. + +## Before and after + +The pre update baseline was a 20 second observation on the same attached board. +The post update observation was a five minute steady state run after boot. + +| Observation | Before 0.8.4 | After 0.8.8 | +|---|---:|---:| +| Raw callback mean | 39.05 pps | 36.32 pps | +| Raw callback range | 35 through 42 pps | 24 through 42 pps | +| Server CSI FPS mean | 46.23 Hz | 48.88 Hz | +| WebSocket parser errors | 0 | 0 | +| WebSocket reconnects | 0 | 0 | + +Raw callback mean changed by negative 7.0 percent while the server CSI FPS +estimate changed by positive 5.7 percent. Both remain above the 20 pps +transport floor. The result is transport neutral rather than an accuracy lift; +the room and WiFi traffic were not controlled between the two windows. + +Firmware 0.8.4 did not expose the edge DSP cadence used by the temporal +filters. Firmware 0.8.8 held that separately governed clock at exactly 8.0 Hz +for every controller sample while preserving the higher rate raw network path. + +## Five minute physical result + +MEASURED on 2026 08 31 after flashing firmware 0.8.8: + +| Device observation | Result | +|---|---:| +| Duration | 300.70 seconds | +| Controller samples | 300 | +| Raw callback mean | 36.32 pps | +| Raw callback range | 24 through 42 pps | +| Edge DSP mean | 8.00 Hz | +| Edge DSP range | 8.00 through 8.00 Hz | +| ENOMEM events | 0 | +| UDP send failures | 0 | +| ESP NOW nonzero failure lines | 0 | +| Other steady state errors | 0 | +| Watchdogs, panics, or reboots | 0 | + +| End to end WebSocket observation | Result | +|---|---:| +| Duration | 300.06 seconds | +| Sensing frames | 36,254 | +| JSON parse errors | 0 | +| WebSocket errors or reconnects | 0 | +| Frames containing node 7 | 35,313 | +| Node 7 frame coverage | 97.40 percent | +| Node 7 stale frames | 0 | +| Maximum node 7 staleness | 972 ms | +| Maximum node 7 inference age | 483 ms | +| Maximum WebSocket frame gap | 108 ms | +| Nodes per frame | 0 through 5 | +| Fused presence count contradictions | 0 | + +One ENOMEM backoff occurred during startup and recovered in 210 ms. No memory +backoff or send failure recurred in the separate five minute steady state +window. The boot log also reported the documented fail closed OTA behavior: +the status service was available, but image upload remained rejected because +this node has no provisioned OTA signing secret. + +## Result and limitation + +The node 7 timing and transport update passes. Its configuration survived, the +edge DSP clock remained phase stable at the measured sustainable C6 rate, and +the live service received fresh node 7 data throughout the run. This does not +complete the ADR 346 occupancy qualification for node 7 because the room was +not held empty and the live aggregate did not expose 30 absent edge packets. + +The largest uncertainty remains inference accuracy. Timing stability cannot +prove better vital, motion, room separation, or multi person estimates without +synchronized held out labels and a controlled empty room sequence. + +## Acceptance test + +Repeat this five minute procedure after timing, WiFi, filter, or scheduling +changes. Pass transport only when raw callback yield remains at least 20 pps, +DSP cadence stays within one hertz of the configured target, the device has +zero steady state memory backoff, send failure, watchdog, panic, and reboot +events, the server has zero parse failures and reconnects, and the updated node +stays fresh. Complete occupancy qualification separately with at least 30 +absent edge packets and zero absent packets carrying a nonzero person count. diff --git a/api-docs/validation/2026-08-31-esp32-c6-occupancy-integrity.md b/api-docs/validation/2026-08-31-esp32-c6-occupancy-integrity.md new file mode 100644 index 00000000..9446bd94 --- /dev/null +++ b/api-docs/validation/2026-08-31-esp32-c6-occupancy-integrity.md @@ -0,0 +1,60 @@ +# ESP32 C6 occupancy evidence qualification + +## Scope + +This record qualifies the fail closed person count invariant in ADR 346 on one physically attached ESP32 C6. It does not qualify person counting accuracy, identity, pose, room separation, or vital sign accuracy. + +## Hardware and firmware + +| Field | Measured value | +|---|---| +| Board | ESP32 C6 QFN40 revision 0.2 | +| Logical node | 4 | +| Firmware before | 0.7.0 | +| Firmware after | 0.8.4 development build | +| App image | 1,051,168 bytes | +| App SHA 256 | `f9470a31b82612f1740f0cf0943ddb78917cd58784ba16d2e9d57cc8fb39364c` | +| OTA slot size | 1,900,544 bytes | +| CSI stream target | Preserved from NVS | + +The device partition table was read before the update. NVS, OTA metadata, bootloader, and partition table were not overwritten. A private recovery copy was created outside the repository and excluded from version control. + +## Software gates + +| Gate | Result | +|---|---| +| Firmware host tests | PASS, 54 assertions across encoding, vital evidence, and mmWave detection | +| Rust sensing server package | PASS, 532 library tests plus all package integration and documentation tests | +| Mobile Jest suite | PASS, 164 suites and 1,223 tests | +| Mobile TypeScript | PASS | +| Mobile ESLint | PASS | +| Mobile security verifier | PASS | +| Repository wide Rust formatting | PREEXISTING DRIFT outside this change; changed code builds and package tests pass | + +## Physical result + +MEASURED on 2026 08 31 from the live local sensing WebSocket for 300 seconds: + +| Node | Firmware state | Edge packets | Absent packets | Absent with nonzero count | Result | +|---|---|---:|---:|---:|---| +| 4 | Updated | 242 | 61 | 0 | PASS | +| 3 | Unupdated control | 216 | 216 | 216 | Expected control failure | +| 7 | Unupdated control | 280 | 278 | 278 | Expected control failure | + +Node 4 reduced the targeted logical contradiction from observed to zero, a 100 percent reduction for this invariant during this run. This is not a person count accuracy result. + +The WebSocket run had zero JSON parse errors and one expected client close at completion. The sensing server remained ready with `engine_error_count=0`. A separate 45 second serial observation recorded 120 log lines, 17 CSI callback markers, zero ENOMEM backoffs, and zero other error lines. + +The updated OTA status endpoint reports the selected 1,900,544 byte partition rather than the stale 921,600 byte constant. The 1,051,168 byte image therefore fits with 849,376 bytes of partition headroom. + +## Remaining qualification + +Nodes 3 and 7 still demonstrate the old contradictory behavior and must be upgraded only after their network identity, OTA credential, and rollback path are verified. The current run had no labelled ground truth, so multi person fidelity and adjacent room rejection remain unmeasured. + +## Subsequent node 7 status + +Later on 2026 08 31, node 7 was separately identified, backed up, upgraded to firmware 0.8.8, and transport qualified for five minutes. That later occupied room run had zero fused presence count contradictions but did not produce the 30 absent edge packets required to supersede the historical control result above. See `docs/validation/2026-08-31-esp32-c6-node7-rate-aware-sensing.md`. + +## Acceptance test + +Repeat a five minute capture after every firmware change. Pass only when every updated node has at least 30 absent packets, zero packets where `presence=false` and `n_persons>0`, zero parser errors, and a ready sensing server with zero engine errors. diff --git a/api-docs/validation/2026-08-31-esp32-c6-rate-aware-sensing.md b/api-docs/validation/2026-08-31-esp32-c6-rate-aware-sensing.md new file mode 100644 index 00000000..ac394d69 --- /dev/null +++ b/api-docs/validation/2026-08-31-esp32-c6-rate-aware-sensing.md @@ -0,0 +1,124 @@ +# ESP32 C6 rate aware sensing qualification + +## Scope + +This record qualifies ADR 347 on one physically attached ESP32 C6 and verifies +that the same source compiles for ESP32 S3. It measures transport cadence, edge +DSP cadence, process stability, and end to end sensing delivery. It does not +qualify heartbeat, respiration, gesture, pose, identity, or person count +accuracy against labelled ground truth. + +## Hardware and firmware + +| Field | Measured value | +|---|---| +| Board | ESP32 C6 QFN40 revision 0.2 | +| Logical node | 4 | +| Firmware before | 0.8.4 | +| Firmware after | 0.8.8 development build | +| C6 app image | 1,051,552 bytes | +| C6 app SHA 256 | `f2ea422c9b99ec13c7a168afc2b019229642769ffabfd8f29a85978770236e87` | +| OTA slot size | 1,900,544 bytes | +| OTA headroom | 848,992 bytes, 45 percent | +| S3 compile image | 1,127,104 bytes | +| S3 compile SHA 256 | `63e4f0c484d79e7dd37eb28275951c8beb924e6908942f7fec0b90d92109129c` | + +Only the application partition at offset `0x20000` was flashed. WiFi +credentials, node identity, sensing server target, bootloader, partition table, +OTA metadata, and NVS were preserved. The pre update OTA application was read +to a private recovery file outside the repository. Its SHA 256 is +`a2e503f1622b2f3f9c1cfce0a07ba34b9fc5d6a413b6346311622af1fe18a6d8`. + +The OTA status endpoint reported firmware 0.8.8 running from `ota_0` after the +update. The sensing server health endpoint remained ready with ESP32 input. + +## Software gates + +| Gate | Result | +|---|---| +| Rate estimator and occupancy host tests | PASS, 30 assertions | +| ADR 110 encoding host tests | PASS, 21 assertions | +| mmWave frame predicate host tests | PASS, 8 assertions | +| ESP32 C6 IDF 5.4 ARM64 build | PASS | +| ESP32 S3 IDF 5.4 ARM64 build | PASS, compile only | +| Image checksum and validation hash | PASS | +| Repository diff whitespace check | PASS | +| Local libFuzzer aggregate | NOT RUN, local Xcode toolchain lacks `libclang_rt.fuzzer_osx.a` | + +For this C6 record, the S3 result was source and toolchain validation only and +no S3 runtime claim is made here. The later physical S3 Tier 0 transport run is +recorded separately in +`docs/validation/2026-08-31-esp32-s3-rate-aware-transport.md`. + +## Measured rate correction + +The pre update 20 second C6 baseline delivered a mean 34.05 raw callbacks per +second, median 34.5, and range 28 through 37. An intermediate 0.8.7 physical +run requested 10 Hz edge DSP but converged to 8.0 through 8.4 Hz while raw CSI +remained 30 through 40 packets per second. This proved that C6 Tier 2 compute, +not the raw transport, was the limiting path. + +Firmware 0.8.8 therefore keeps the 50 Hz probe and independent raw network +path, but sets the C6 Tier 2 DSP clock to its measured sustainable 8 Hz. The +phase preserving sampler prevents callback jitter from shifting the configured +clock, and the filter estimator follows processed timestamps rather than raw +probe intent. + +## Five minute physical result + +MEASURED on 2026 08 31 after flashing firmware 0.8.8: + +| Device observation | Result | +|---|---:| +| Duration | 300.64 seconds | +| Controller ticks | 300 | +| Raw callback mean | 34.92 pps | +| Raw callback range | 22 through 41 pps | +| Edge DSP mean | 8.00 Hz | +| Edge DSP range | 8.00 through 8.00 Hz | +| ENOMEM events | 0 | +| UDP send failures | 0 | +| Other steady state errors | 0 | +| Watchdogs, panics, or reboots | 0 | + +| End to end WebSocket observation | Result | +|---|---:| +| Duration | 300.01 seconds | +| Sensing frames | 26,786 | +| JSON parse errors | 0 | +| Reconnects | 0 | +| Frames containing node 4 | 26,148 | +| Node 4 frame coverage | 97.62 percent | +| Node 4 stale frames | 0 | +| Maximum node 4 inference age | 176 ms | +| Maximum WebSocket frame gap | 110 ms | +| Nodes per frame | 0 through 4 | +| Fused `presence=false` with nonzero count contradictions | 0 | + +The boot log emitted one expected iTWT negotiation error because the access +point rejected the requested target wake time parameters. Firmware immediately +selected its documented opportunistic CSI fallback. No iTWT or other error +recurred during the five minute steady state window. + +## Result and limitation + +ADR 347 timing and transport acceptance passes on the attached C6. Raw +throughput did not regress relative to the short baseline, the edge clock now +matches the rate the temporal filters actually receive, and node 4 was never +stale when present in the live sensing service. The separate occupancy +qualification recorded 61 absent node 4 packets with zero contradictions for +the unchanged fail closed invariant. This run did not repeat an empty room +sequence because the room was occupied during qualification. + +The largest remaining uncertainty is inference accuracy. Stable timing removes +one source of feature distortion but cannot prove better heartbeat, respiration, +gesture, or multi person classification without synchronized held out labels. + +## Acceptance test + +Repeat this five minute procedure after any timing, WiFi, filter, or task +scheduling change. Pass only when raw callback yield remains at least 20 pps, +DSP cadence remains within one hertz of the configured target, the device has +zero steady state ENOMEM, send failure, watchdog, panic, and reboot events, the +server has zero parse failures and reconnects, node 4 stays fresh, and fused +presence count contradictions remain zero. diff --git a/api-docs/validation/2026-08-31-esp32-s3-rate-aware-transport.md b/api-docs/validation/2026-08-31-esp32-s3-rate-aware-transport.md new file mode 100644 index 00000000..4899f1db --- /dev/null +++ b/api-docs/validation/2026-08-31-esp32-s3-rate-aware-transport.md @@ -0,0 +1,126 @@ +# ESP32 S3 rate aware transport qualification + +## Scope + +This record qualifies the firmware 0.8.8 raw transport path on one physically +attached ESP32 S3. The node retained its existing Tier 0 configuration, so this +run does not qualify the S3 edge DSP rate, temporal filters, heartbeat, +respiration, gesture, pose, identity, person count, or localization accuracy. + +## Hardware and firmware + +| Field | Measured value | +|---|---| +| Board | ESP32 S3 QFN56 revision 0.2 with 2 MB embedded PSRAM | +| Logical node | 1 | +| Firmware before | 0.8.4 | +| Firmware after | 0.8.8 development build | +| Edge tier | 0, raw passthrough | +| App image | 1,127,104 bytes | +| App SHA 256 | `b531c76900c07d0d6f6e864a5f28afff3e71f124777af97358bb405b34e339a2` | +| OTA slot size | 2,097,152 bytes | +| OTA headroom | 970,048 bytes, 46 percent | + +The production partition table was read from the device before the update. +Only the application partition at offset `0x20000` was flashed. WiFi +credentials, logical node identity, channel, sensing server target, bootloader, +partition table, OTA metadata, and NVS were preserved. A private recovery copy +of the prior 2 MB application partition was saved outside the repository. Its +SHA 256 is +`14e72c060c4f1a465f739949b873f6aade5b8899a8909f6c5bb591ee49837c1b`. + +The post update boot log reported firmware 0.8.8, logical node 1, channel 4, +the preserved UDP target, Tier 0 raw passthrough, and successful CSI streaming. +The OTA status endpoint reported firmware 0.8.8 running from `ota_0` with the +correct 2,097,152 byte update limit. The sensing server health endpoint remained +ready with ESP32 input. + +## Software and image gates + +| Gate | Result | +|---|---| +| Firmware encoding, vitals, occupancy, and mmWave host tests | PASS, 59 assertions | +| Firmware provisioning Python tests | PASS, 14 tests | +| ESP32 S3 IDF 5.4 ARM64 clean build | PASS | +| Image target detection | PASS, ESP32 S3 | +| Image checksum | PASS | +| Image validation hash | PASS | +| Application partition fit | PASS, 46 percent free | +| Physical flash write verification | PASS | +| Preserved runtime configuration | PASS | + +The build excluded the optional WASM3 source because it was not present in the +firmware checkout. The boot log therefore reported WASM Tier 3 disabled. That +is not a regression introduced by this update and is outside this transport +qualification. + +## Before and after comparison + +The pre update baseline was a 20 second serial and WebSocket capture on firmware +0.8.4. The post update stability observation was 300 seconds on firmware 0.8.8. + +| Observation | Before 0.8.4 | After 0.8.8 | Change | +|---|---:|---:|---:| +| Raw CSI yield mean | 27.80 pps | 28.03 pps | plus 0.83 percent | +| Server CSI FPS mean | 39.58 | 39.13 | minus 1.15 percent | +| Node frame coverage | 100 percent | 100 percent | unchanged | +| Maximum node staleness | 1,571 ms | 1,565 ms | minus 0.38 percent | +| Maximum WebSocket frame gap | 111 ms | 112 ms | plus 0.90 percent | +| Device or parser errors | 0 | 0 | unchanged | + +These small movements are operationally neutral and within uncontrolled room +and WiFi variation. Firmware 0.8.8 did not regress the raw transport. Because +Tier 0 bypasses the DSP task, this run provides no evidence that temporal +features or inference accuracy improved. + +## Five minute physical result + +MEASURED on 2026 08 31 after flashing firmware 0.8.8: + +| Device observation | Result | +|---|---:| +| Duration | 300 seconds | +| Controller yield samples | 300 | +| Raw CSI yield mean | 28.03 pps | +| Raw CSI yield range | 22 through 34 pps | +| ENOMEM or stack errors | 0 | +| UDP send failures | 0 | +| ESP NOW send failures | 0 | +| Unexpected resets | 0 | +| Watchdogs or panics | 0 | + +| End to end WebSocket observation | Result | +|---|---:| +| Duration | 300.03 seconds | +| Sensing frames | 10,559 | +| Frames containing node 1 | 10,559 | +| Node 1 frame coverage | 100 percent | +| Source offline frames | 0 | +| JSON parse errors | 0 | +| WebSocket errors | 0 | +| Early closes | 0 | +| Maximum node staleness | 1,565 ms | +| Maximum WebSocket frame gap | 112 ms | + +## Result and limitation + +The ESP32 S3 raw transport acceptance passes. Firmware 0.8.8 booted from the +existing slot, retained the installation configuration, sustained the prior raw +CSI delivery rate, and completed the burn with zero transport or runtime +errors. The result extends ADR 347 physical coverage to the S3 transport path. + +The largest remaining uncertainty is S3 Tier 2 behavior and inference accuracy. +The log line reporting the configured 20 Hz DSP cadence is not proof that DSP +ran because the preserved Tier 0 setting explicitly disables the DSP task. A +separate, rollback protected Tier 2 qualification with synchronized held out +labels is required before making heartbeat, respiration, motion, or accuracy +claims. + +## Acceptance test + +Repeat this five minute procedure after any S3 timing, WiFi, transport, or task +scheduling change. Pass only when raw callback yield remains at least 20 pps, +node frame coverage remains at least 99 percent, and the device and server have +zero send failures, offline frames, parser errors, WebSocket errors, watchdogs, +panics, and unexpected resets. Qualify Tier 2 separately and require its +measured DSP cadence to stay within one hertz of the configured target.