Live status · Read the latest
monad-knowledge Wi-Fi sensing lab · FIIT STU
open EXP-009

BLE/CSI Self-Interference on the Sensing Node

In plain words

Does the Bluetooth scanner on a sensing node spoil that same node's own Wi-Fi measurement?

one Raspberry Pi 5 AX210 Wi-Fi Bluetooth a few centimetres apart scanner: on off off on repeat on both Wi-Fi bands compare bands, scanner activity and host load before attributing harm
Two radios on one small board, nothing coordinating them. The scanner is switched on and off in an ABBA order while the Wi-Fi capture runs on each band, so any harm can be told apart from the hour it happened in. Schematic of the design, not a measurement.
Why it matters
Each node carries two 2.4 GHz radios a few centimetres apart, the Wi-Fi card and the Pi's Bluetooth, and nothing coordinates them. If one deafens the other, the ground-truth sensor damages the measurement it is meant to label.
How it is done
Compare scanner on/off within nodes and retain the 5 GHz control. Verify scanner activity and transmitted frames for each segment before estimating the paired effect.
Where it stands
Data collection has begun: 43 usable primary sessions are registered from September 9–10. The full scanner-by-band contrast and its causal interpretation remain unevaluated.

Asks, formallyRunning the BLE reference scanner on a sensing node degrades that node's own 2.4 GHz CSI capture, because the Pi's Broadcom BLE controller and the AX210 are two uncoordinated 2.4 GHz radios centimetres apart with no coexistence interface between them. If the degradation is RF desense it appears on 2.4 GHz and NOT on 5 GHz; if it appears on both bands equally the cause is host-side contention, not radio interference. Either way, the act of measuring the BLE reference perturbs the CSI it is meant to calibrate.

FoundThe September 15 register contains 43 usable primary sessions from the September 9–10 coex-03/coex-11 recordings.

StandingStatus not declared

RoomReal hardware — Pi 5 + AX210 fleet, ALL SIX nodes receiving (monad01..monad06); phone illuminator associated to a NON-FLEET access point; FIIT library / lab, empty-room control

BLE/CSI Self-Interference on the Sensing Node

Results and current assessment (2026-09-15)

The September 15 register contains 43 usable primary sessions from the September 9–10 coex-03/coex-11 recordings. The earlier statement that nothing ran is stale. Session presence does not establish the complete preregistered scanner-by-band design, manipulation checks or a self-interference effect.

The 5 GHz control helps separate mechanisms, but it cannot prove a unique cause: band-specific degradation is consistent with RF interference, and shared degradation is consistent with host contention. Verify radio activity and host telemetry before making that attribution. Passive scanning is not itself an intentional BLE transmission; any RF mechanism needs evidence of the relevant controller activity.

Next evidence needed: Reconcile scanner-state markers, the 5 GHz control and injector counters for the recorded sessions. Reduce paired segment effects with uncertainty at the declared replication unit. Keep an incomplete design visibly incomplete.

Basis: Thesis Narrative, the September 10 synthesis, and the September 15 register review. See the review record for evidence sources and outstanding checks.

Earlier protocol and dated results

The methods and observations below retain their original scope. Read their readiness statements and proposed next steps as historical. The current assessment above records what has changed; frozen thresholds have not been retrospectively revised.

The negative finding that motivated this design

The obvious instrument is wrong, and it is worth recording why so nobody reaches for it again.

The AX210 is a combo Wi-Fi/BT part and exposes its internal coexistence state via /sys/kernel/debug/iwlwifi/<bdf>/iwlmvm/bt_notif. On these nodes every field reads 0 and always will:

bt_ci_compliance = 0 ; primary_ch_lut = 0 ; bt_activity_grading = 0 ; bt_rrc = 0 ; bt_ttc = 0

Because the AX210's Bluetooth core is not present — there is no hci1, and hci0 is hci_uart_bcm / brcm,bcm43438-bt on /soc/serial@7d50c000, i.e. the Pi's own controller on the SoC UART (verified on monad04, 2026-08-13). blescand scans on that. Intel coexistence arbitrates between the two cores inside the AX210 package; it has no visibility of a separate chip.

The consequence runs the wrong way from reassurance. Where Intel coex applies it time-shares the medium between the radios. Here there is no arbitration at all — each radio is unaware the other exists, so any interference is uncoordinated receiver desense and is invisible to bt_notif by construction. monad_nic_bt_coex is published only as an invariant that should stay 0 (MonadCsiBtCoexActive fires if it ever does not).

A counter cannot answer this. An experiment can.

The scanner does not transmit — which shifts the prior

Worth stating plainly, because the intuitive picture ("Bluetooth jams Wi-Fi") does not apply here. blescand runs a passive scan (scan_type: passive in every session sidecar): it never sends SCAN_REQ, so the BLE radio is receive-only for the whole session. There is no co-channel transmitter on the node.

The remaining mechanisms are therefore both weaker and more interesting than jamming:

  1. Front-end coupling / LO leakage — an active BT receiver has its low-noise amplifier and local oscillator running centimetres from the AX210's antenna. This desenses broadband rather than co-channel, and it is a small effect.
  2. Host contention — the Broadcom controller is on the SoC UART, so scanning generates a steady interrupt load, and blescand adds a Python read loop on a 4-core SoC already running a capture whose RX thread is SCHED_RR 50.

Mechanism 2 is arguably the more likely cause, and it is the one the naive framing would miss entirely. This is precisely why the 5 GHz control arm is load-bearing rather than decorative: it is the only thing that separates them, and the answer changes the remedy from "schedule the bands apart" to "give the scanner its own core or its own node".

Hypotheses (declared before any data is seen)

ID Statement Axis
H1 BLE scanning reduces 2.4 GHz CSI packet-delivery ratio on the same node. primary
H2 BLE scanning does not reduce 5 GHz CSI PDR (no RF mechanism exists). control
H3 If H1 and H2 both hold, the mechanism is RF desense; if H1 and ¬H2, it is host contention. mechanism

H3 is the reason both bands are run. It is a decision rule over the outcomes of H1 and H2, not a separately tested claim.

Confidence / novelty framing. H1 is moderate-confidence, low-novelty — uncoordinated 2.4 GHz co-location degrading a receiver is textbook, so a positive result is a premise to cite and design around, not a contribution. The contribution is the magnitude on this hardware and the H3 discrimination, which determines whether the fix is scheduling or provisioning.

Design

2 x 2 within-node, paired, ABBA-counterbalanced.

Arm Band Scanner Purpose
A 2.4 GHz off H1 baseline
B 2.4 GHz on H1 treatment
C 5 GHz off H2 baseline
D 5 GHz on H2 control
  • Unit of replication = the NODE, not the record. Records within a node share an antenna, a placement, a thermal state and a clock; they are one cluster. Per-record CIs would overstate the information by four orders of magnitude — the modal defect this house contract names first.
  • Paired within node: every node contributes its own A−B and C−D differences, which removes node-to-node variation in placement, antenna orientation and thermal state without needing to model it.
  • ABBA within each band: segment order A B B A (then C D D C), repeated twice. Counterbalances monotone drift — thermal ramp, evening channel-occupancy changes, illuminator battery — so a linear trend cancels in the paired difference instead of loading onto whichever arm ran later.
  • Segment length 5 min, 2 ABBA blocks per band → 8 segments/band, 40 min/band, ~90 min total including changeovers. All receivers run in parallel; the schedule is fleet-wide, not per-node.
  • Discard the first 30 s of each segment (radio retune + scanner spin-up transient). Declared now, not after inspecting edges.

n — settled: the phone illuminates, all six nodes receive

The receiver count is the sample size, and it decides whether this experiment can conclude anything.

Receivers Sign-flip permutations Min two-sided p Verdict
5 (a fleet node illuminates) 2⁵ = 32 0.0625 cannot reach α=0.05 two-sided by construction
6 (phone illuminates) 2⁶ = 64 0.0312 two-sided significance attainable ✓

Decision: the phone illuminates and all six nodes receive. A two-sided paired permutation test at n=5 cannot produce p < 0.05 no matter how large the effect, so a null would have been uninformative by construction. n=6 is the smallest fleet that escapes that, and we have exactly six.

The phone must associate to a NON-FLEET access point. The phone illuminator works by associating to an AP and emitting paced unicast (EXP-P3); if that AP is monad01 then monad01 is not a free receiver and we are back at n=5. Any consumer dual-band router or a laptop hotspot serves. This has a second, unplanned benefit: the AX210 cannot act as a 5 GHz AP at all — that is an Intel LAR policy wall in signed firmware, not a bug (the 5 GHz AP wall) — so an external AP is the only way the 5 GHz control arm gets a clean association-based illuminator anyway.

Blocked assignment — what an uncalibrated illuminator forces

The phone is a built but unmeasured instrument: EXP-P3's uniform-illumination gate (H2) has never been run, so we must assume its emitted rate wanders between segments.

That assumption breaks the obvious schedule. If every node runs the same arm at the same time, then segment-level illumination variance is perfectly confounded with the treatment — a segment where the phone happened to emit less looks exactly like a segment where the scanner hurt. With a deterministic paced injector that risk is small; with a phone it is the dominant one.

So the arm is assigned WITHIN each segment, not across segments:

  • Nodes are split into two fixed groups of three: X = {monad01, monad03, monad05}, Y = {monad02, monad04, monad06}.
  • In every segment one group scans and the other does not; which group scans alternates ABBA (X Y Y X X Y Y X per band).
  • Every segment therefore contains both arms simultaneously, under identical illumination. Whatever the phone did in that segment, it did to both arms equally, and it cancels in the contrast.
  • Every node still sees both arms across the session (4 ON + 4 OFF segments per band), so the analysis stays paired within node and n stays 6.

This is a randomised block design with segment as the block. It removes two nuisances at once: node-to-node variation (placement, antenna, thermal) by pairing within node, and segment-to-segment illumination variation by contrasting within segment. Neither has to be modelled.

Does one node's scanner disturb its neighbours? This design assumes not, and the assumption follows from the mechanism rather than from hope: host contention cannot cross hosts at all, and front-end coupling is a centimetres-scale effect on a shared board. Requirement: nodes at least 1 m apart, and their spacing recorded. If nodes must sit closer than that, this design is invalid and the fleet-synchronous one must be used instead, accepting the illuminator confound.

Endpoints

Primary: within-segment relative delivery. For each segment s and node i,

R(i,s) = delivered_records(i,s) / median_over_nodes( delivered_records(·,s) )

then per node the paired contrast mean R over its ON segments − mean R over its OFF segments.

The median is taken across all six nodes in that segment, which is exactly why the blocked assignment matters: the denominator is a per-segment estimate of "how much the phone actually emitted", built from three ON and three OFF nodes hearing the same transmission. It divides out illuminator wander without touching the ON/OFF contrast, because both arms are inside every median.

A raw PDR against the commanded rate is recorded as a secondary, and is the primary only if EXP-P3's H2 gate is measured first and the phone's emission proves uniform. Until then the commanded rate is a number we asked for, not one we have observed.

Secondary (mechanism, not confirmation):

Signal Source Reads on
CSI amplitude variance / noise floor capture desense raises the floor even when PDR holds
Monitor frame drops node_network_receive_drop_total{device=~".*mon.*"} separates "never arrived" (RF) from "arrived, host discarded" (contention)
NIC die temperature monad_nic_temp_celsius rules out a thermal explanation for a PDR change
Node CPU / load node_load1, node_cpu_seconds_total the host-contention pathway, measured directly

The frame-drops signal is the second mechanism discriminator and is nearly free: RF desense means frames never arrive, so drops stay flat while PDR falls; host contention means frames arrive and are discarded, so drops rise with the PDR fall.

Manipulation check (gate, not endpoint): monad_ble:report_rate_hz:current > 0 throughout every ON segment. An ON segment where the scanner was silently rfkill-blocked is a void segment, not a null result — without this gate the experiment's most likely failure is a confident false negative.

Statistical analysis (fixed before execution)

  • Test: paired permutation (sign-flip) over per-node differences, 2ⁿ exhaustive enumeration — at n=6 that is all 64 sign assignments, so the p-value is exact, not sampled.
  • α = 0.05, two-sided. n=6 makes this attainable (floor 0.031); no one-sided special pleading is needed and none is claimed.
  • Estimate + interval: mean per-node paired difference with a Student-t interval (n < 40, so t and never z), reported alongside the exhaustive bootstrap over C(2n−1, n) distinct resamples (462 at n=6) — enumerated and the count reported, since at this n a percentile interval is a range, not a coverage-calibrated CI, and will be labelled as such.
  • Effect size: Cohen's d_z for paired differences, reported always — with a large sweep a trivial effect can be "significant", and the CI on the difference carries more information than the p-value.
  • Multiplicity: two families (H1, H2), pre-declared, no correction — this is not a screen. Any further per-node or per-subcarrier comparison is exploratory and labelled as such.
  • Stopping rule: fixed at 2 ABBA blocks per band per node, decided now. No inspect-and-extend.
  • Power, stated honestly: a paired design at n=6 has ~80% power only for a large standardised effect (d_z ≈ 1.3–1.4). It can detect "the scanner obviously costs us delivery"; it cannot resolve a 1–2 % PDR shift. Precision per node is the lever — longer segments shrink each node's within-segment variance and raise d_z for the same true effect — not more nodes, which we do not have.
  • A null result will therefore be reported as bounded, not absent: "no effect larger than X detected at n=6", with X read off the CI. "Fail to reject" is never "no effect".

Scope of inference. Arms are randomly assigned within node (order counterbalanced), so a difference licenses a causal claim about the scanner on this hardware. Nodes are not a random sample of anything — they are six Pi 5 + AX210 units in one building — so the magnitude does not generalise beyond this hardware and placement without further evidence. Two independent knobs; this design turns only one.

Threats to validity

Threat Handling
Ambient 2.4 GHz traffic differs between segments both arms are inside every segment, so ambient is common to them; ABBA additionally cancels monotone drift
Phone emission wanders between segments (EXP-P3 H2 unmeasured) within-segment median normalisation; both arms share every segment's illumination
Phone drops association / backgrounds itself per-segment delivered-record median across nodes; a segment whose median collapses is void
One node's scanner disturbs a neighbour (SUTVA) mechanism is on-board (same host, or centimetres); ≥ 1 m node spacing required and recorded
Thermal ramp over the session ABBA + monad_nic_temp_celsius as covariate
Scanner silently blocked during an ON arm manipulation-check gate voids the segment
Illuminator drift / battery fixed commanded rate, PDR normalises it, illuminator health logged
Channel retune failure between bands monad_nic_channel read-back asserts the tuned channel per segment (this is why that metric exists)
Node reboots / csid restart mid-segment node_systemd_unit_start_time_seconds — a segment spanning a restart is void

Execution

Room setup (must be true before the playbook runs — the playbook cannot check these):

  1. Non-fleet AP up and reachable, serving both ch 6 (2.4 GHz) and ch 36 (5 GHz). Not monad01.
  2. Phone associated to that AP, MonadCount app emitting paced unicast, screen off / app backgrounded exactly as it would be in the field.
  3. Node spacing ≥ 1 m, measured and written into the session note. The blocked design assumes one node's scanner does not reach its neighbour; that assumption is mechanistic, not decorative.
  4. Empty room. People are the signal in every other experiment and pure noise in this one.

Fleet preconditions are asserted by the playbook and visible on 42 · csid Stack: precondition units active, no rfkill block, chrony disciplined, monad_nic_channel matching the intended band per segment, no throttling, fleet clock spread < 10 ms. It fails rather than warns if fewer than six receivers are reachable, because at n=5 the preregistered two-sided test is unreachable and the session would be wasted.

# Dry run first — prints the full schedule and asserts preconditions, changes nothing
ansible-playbook playbooks/exp-ble-csi-coex.yml --check

# Execute (≈90 min, all receivers in parallel)
ansible-playbook playbooks/exp-ble-csi-coex.yml -e exp_id=EXP-009-$(date -u +%Y%m%dT%H%MZ)

Every segment is its own csid@ session, so the session directory is the segment and no post-hoc time-slicing is needed. Sessions ship to S3 under the normal datasets/ax210-csi-captures/<host>/ prefix, tagged with the arm in the session note.

Reduction

Per-segment PDR from the sealed session sidecars; per-node paired differences; permutation + t-CI as declared above. The secondary signals come from Mimir over the segment windows:

# delivered rate per segment
max by (host) (monad_csi:capture_rate_hz:current{unit=~"csid@exp009.+"})
# host-discard pathway
sum by (host) (rate(node_network_receive_drop_total{device=~".*mon.*"}[5m]))
# manipulation check — must be > 0 for every ON segment
max by (host) (monad_ble:report_rate_hz:current)

Changelog

Date Author Change
2026-08-13 Claude AI Initial design. Motivated by the bt_notif negative finding; 2x2 band x scanner with ABBA; n and the attainable-p floor worked out and made the primary design decision.

Provenance

—
not recorded

Data types

  • csi-amplitude
  • packet-delivery-ratio
  • ble-advertisement-rate
  • nic-die-temperature
  • monitor-frame-drops