Channel Sounding — what is settled, and what still needs measuring¶
Status: CS works. Board V1 (nRF54LM20A) and an nRF54L15-DK range against
each other and produce distance, and ifft is accurate to ±0.20 m after a
single constant calibration. The premise the whole hardware pivot rests on is
no longer an assumption. What follows is the list of things still unmeasured,
in the order they block product decisions.
Rig and raw data: ~/Impulse/ImpulseFirmware/nrf_cs_test/ (RESULTS.md has
the full analysis and the CSVs). Firmware side: nrf_firmware/src/proximity/.
⚠️ TOP PRIORITY — a proper BM20C ↔ BM20C calibration sweep (added 2026-09-10)¶
Two Board V1s now range against each other, which supersedes every number on this page that was measured against a dev kit. Offsets in CS are dominated by antenna delay, so a DK-derived constant does not transfer to a BM20C pair — and the direction of the error matters.
What was measured (2026-09-10): a SINGLE point, boards ~2 ft (61 cm) apart, line-of-sight, 150+ samples:
| estimator | median | sd |
|---|---|---|
ifft |
1.32–1.36 m | 0.02–0.04 m |
phase_slope |
1.66–1.80 m | 0.08 m |
rtt |
0.15 m | 0.49 m, and 38/113 samples exactly 0.00 |
What was changed on that basis: IMPULSE_CS_IFFT_OFFSET_CM 97 → 72.
The old value came from the DK pair and errs in the UNSAFE direction — it
reports the watch ~25 cm CLOSER than it is, so a stayNear commitment reads as
satisfied slightly too far out.
What is still owed, and why one point is not enough:
- A multi-station sweep to separate slope from offset. One point cannot.
The slope in the firmware (0.993) is still the DK's. Stations at roughly
0.5 / 1 / 2 / 4 / 7 m LOS,
./cs_sample.sh 25 "<distance>"at each, then fit. Expect the far stations to need longer captures — sampling drops from ~10 Hz at contact to under 2 Hz past 5 m. - Re-derive the divergence floor.
phase_slope − ifftis +0.30 to +0.48 m on a clean line-of-sight bench with no occlusion at all. The whole design treats divergence as the occlusion/multipath quality signal, so a baseline this large meansCS_QUALITY_MINcannot keep its DK-derived value. Measure the LOS baseline across distance FIRST, then re-measure occluded. - Precision looks materially better than the DK pair —
ifftsd of 0.02–0.04 m versus 0.20 m rmse. If that holds across distance, the burst size (N=10) and possibly the 1.5 m NEAR/AWAY band are more generous than needed, and a burst could be shorter — which is an energy saving, not just tidiness. - Nothing here is orientation- or body-controlled. The board was static on a bench. Repeat with rotation and with a body in the path.
Settled — do not re-litigate without new data¶
| Question | Answer | Evidence |
|---|---|---|
| Does CS work on this silicon? | Yes, ~10 Hz | Board V1 ↔ nRF54L15-DK, HCI 6.3 |
| Which estimator is primary? | ifft |
best noise, least occlusion-sensitive, and the only one whose slope is 1.0 |
Is ifft correctly scaled? |
Yes — 0.993 | LOS sweep, 11 stations, R² 0.9881 |
| Calibration shape | constant offset, +0.97 m | not a scale error |
| Accuracy after calibration | 0.20 m sd, 0.39 m worst | 0.9–7.0 m, board rotating |
| Samples needed per verdict | 10 | resampled: N=10 → 0.19–0.57 m error |
Are NEAR/AWAY = 200/350 cm sane? |
Yes | a ±0.2 m instrument separates them easily |
P0 — blocks correctness, not just tuning¶
1. Anchor targeting¶
The backend ranges against whatever advertises the Ranging Service, not
against event.anchorId. With two anchors in a house it measures the wrong
one. This is a correctness bug and no amount of threshold tuning helps.
Not a measurement — an implementation task.
2. Two BLE roles at once¶
CS holds a central connection while the app needs the watch to be a
peripheral. Concurrent roles is exactly what broke the ESP32 build
(firmware_spec_v2.md §10.1: ble_hs_timer_exp panic, reproduced on both
IDF 4.4.7 and 5.5.5). Zephyr's host has its own version of the hazard. Test
this early — if it fails, the enforcement design changes shape, not just its
constants.
3. Does the engine work inside the product firmware?¶
Proven standalone; never run beside the enforcement loop. Build with
cs.conf, flash Board V1, put a reflector on the DK, and confirm a real burst
drives a real verdict — cond_met flipping and the motor firing as the
reflector crosses the threshold. That single test is the whole product.
P1 — the numbers that set the remaining §11 constants¶
4. BM20C ↔ BM20C¶
Every number so far is Board V1 against a DK, which has a different antenna, ground plane and enclosure. Offsets are antenna-delay dominated, so the +0.97 m constant is specific to this pair and will move. Needs a second Board V1. Until then treat the calibration as provisional.
5. Orientation¶
The sweep rotated the board continuously, so its ±0.20 m already includes rotation — a good sign. What is not known is whether specific orientations are systematically biased (a wrist rolls into repeatable positions). Test: fixed 2 m, eight orientations, 30 s each, look for bias rather than spread.
6. On a body¶
The watch will sit on a wrist with a person between it and half the room. The walk captures included body occlusion but with no ground truth. Test: fixed distances with the board worn, facing toward and away from the anchor.
7. Through walls, and in a second room¶
Decides whether the 150 cm hysteresis band is wide enough. Expect it to need widening, not narrowing (v3 §11).
8. Failure rate at the edge — sets CS_ABSTAIN_MAX_CONSECUTIVE (5)¶
The sample rate collapses with distance: ~10/s at contact, 5.7/s at 2 m, 2.5/s at 3.4 m, 0.2/s at 6.4 m. So a burst takes longer the further away the anchor is. Needed: how often CS legitimately fails at 3–5 m over a long run, because too low a cap trips the fail-safe in normal use and too high makes jamming free.
9. Energy per procedure — feeds the 8 h floor (§6.3)¶
The engine currently free-runs at ~9 Hz once connected, which is certainly wrong for a wrist. Measure per-procedure cost, then decide the duty cycle. This is the one open item that can fail the runtime requirement.
P2 — refinements with a known shape¶
10. Recalibrate rtt and use it for what it is¶
rtt's slope is also ≈1.0 (1.030) with a +3.79 m offset — uncalibrated
antenna/group delay, as predicted. Offset it and it becomes a usable
speed-of-light bound. Its job is not accuracy; it is the only thing that
can catch a relay attack, which PBR cannot see at all (v3 §4.6). Keep the
tolerance generous — its own noise is 0.37 m — and remember a false positive
there costs an abstention, which is not free.
11. Turn estimator divergence into the quality floor¶
In line of sight phase_slope ≈ 0.92·ifft + 1.97; under occlusion the walk
captures gave ≈ 1.45·ifft. That difference is the mechanism: reflections
lengthen phase_slope while ifft can still resolve the direct path, so
divergence is a physically-motivated blocked-path detector. Currently a fixed
250 cm threshold. Wants tuning against labelled occluded/clear data — which
means recording what the test actually was, the one thing the walk captures
lacked.
12. Decide what a dropout means¶
In LOS, dropout correlates strongly with distance. Under occlusion the walks
showed dropouts at close range. So "no signal" means far or blocked, and
must stay an abstention rather than becoming a positive AWAY — a positive AWAY
would let shielding fabricate compliance for getAway. The criterion-dependent
fail-safe (§4.5) already resolves this correctly; the point is not to "improve"
it into a distance claim.
13. Re-derive a third poll tier¶
v2's 600 s STILL tier was deleted with the motion channel. Once per-procedure energy is known, a third tier may be worth reintroducing on a different justification (§4.3).
Method notes, learned the hard way¶
- Record what each capture is. The three walk files mixed body, cushion and house occlusion with no labels, which made every per-file conclusion provisional. One line of context per run is the difference between data and anecdote.
- Whole-file SD is not noise — it is mostly the tester walking. Use a short rolling window, or stationary segments.
- Averaging helps less than it looks. The noise is time-correlated multipath drift: 40 samples cut it by 46% where white noise would give 84%. Prefer a median over ~10 samples to a long mean.
- Ground truth or it is precision, not accuracy. Everything before the LOS sweep could only speak to repeatability.