← RFC

Coordinator DFS (Dynamic Frequency Selection) — Result

Status: Stage 2 design experiment, quantifying issue #65 (coordinator DFS). Date: 2026-06-22. Reproduce: cd sim && PYTHONPATH=. python3 -m experiments.dfs_channel_selection (writes docs/figures/dfs_channel_selection.png). Occupancy data: sim/data/ng_channel_occupancy.json, derived from the live North-Georgia Meshtastic firehose /exchange/mtnme-mqtt/packets_log.csv (see Occupancy source, below).

Single cell DFS dodges the occupied channel; two coordinators DFS separates the 911.5 collision


What this answers

Loomwave coordinators currently use a fixed operating channel. Issue #65 reframes this as coordinator DFS: scan the candidate band, pick the least-occupied channel, announce it in the beacon (frequency-agility-multisector-note.md §3). We hit the cost of not doing this live — two Loomwave coordinators both on 911.5 collided (co-channel contention). This experiment quantifies, on the existing PHY/collision engine, the DFS benefit in two cases:

  1. Single cell — a fixed coordinator that lands on an occupied channel (the survey-flagged 915.0 / loud-Meshtastic case) vs a DFS coordinator that measures and picks the quietest candidate.
  2. Two co-located coordinators — fixed (both may pick the same channel → the 911.5 collision) vs DFS (they spread to different clear channels → frequency separation).

Occupancy source — real mtnme.sh data (not synthetic)

The per-channel busy fractions are anchored to real data, the North-Georgia Meshtastic firehose at /exchange/mtnme-mqtt/packets_log.csv (753,979 rows, 9.65-day window 2026-06-12 → 06-22, 712k GA-region packets). Processing (sim/data/ng_channel_occupancy.json):

Measured per-channel occupancy (GA, busy-hour p90 of the deduped floor):

Meshtastic channel SF/BW default freq busy-frac median mean p90 max
MediumFast (loud slot) SF9/250 909.375 1.14% 1.24% 1.67% 2.12%
LongFast (quiet slot) SF11/250 904.625 0.34% 0.36% 0.49% 0.74%
ShortFast SF7/250 904.625 ~0% ~0% ~0% ~0%

Key real ratio used: the MediumFast slot runs ≈3.4× the airtime of the LongFast slot — i.e. the spectrum is genuinely not uniform, the premise the band survey asserted (frequency-agility-multisector-note.md §2: 915.0 OCCUPIED, 911.5/902.5/921 CLEAR).

Caveat (and how we handle it): the firehose is gateway-deduped, so it is a floor of the true on-channel occupancy a node senses — a single node hears more local transmissions than the gateway-merged trace, and the NG validation gate measured real per-node channel_utilization at median 5.7%, p90 14.8%, max 37.3% vs this deduped ~1.2%. We therefore do not treat the deduped floor as the absolute busy level. We take the relative structure (loud vs quiet, the 3.4× ratio) as real, and sweep an interference scale that lifts the loud channel from the floor (1.7%) up to ×20 (33%), which brackets the gate's measured busy-hour reality (≈37% max). All results are reported across that range.

So: real data sets the channel structure and ratio; the absolute interference level is swept across the real-data-bracketed range rather than asserted from the deduped floor.


Model

On the existing PHY/collision engine (phy.py / network.py), unchanged:


Result 1 — single cell: fixed vs DFS

interf level loud busy% K fixed ch fixed deliv DFS ch DFS busy% DFS deliv deliv gain
floor 1.7 10 915.0 0.966 921.0 0.24 0.997 +0.030
floor 1.7 20 915.0 0.966 921.0 0.24 0.995 +0.029
×5 8.3 10 915.0 0.882 921.0 0.24 0.997 +0.115
×5 8.3 20 915.0 0.874 921.0 0.24 0.995 +0.121
×10 16.7 10 915.0 0.750 921.0 0.24 0.997 +0.247
×10 16.7 20 915.0 0.767 921.0 0.24 0.995 +0.228
×20 33.4 10 915.0 0.542 921.0 0.24 0.997 +0.455
×20 33.4 20 915.0 0.594 921.0 0.24 0.995 +0.401

Reading it:


Result 2 — two co-located coordinators (the live 911.5 collision)

Two coordinators within RF range. Fixed: both configured to the same channel → their TDMA grids are referenced to different beacons, uncoordinated, so cell B's uplinks land in cell A's slots and the beacons collide (one shared medium). DFS: the two cells spread to different clear channels → frequency separation means they share no medium (modelled as each cell alone on its own quiet channel — the physically-correct consequence of different frequencies).

interf K fixed deliv fixed p50 (ms) fixed p90 (ms) DFS deliv DFS p50 (ms) DFS p90 (ms) deliv gain
floor 10 0.437 1580 2492 0.990 1597 2417 +0.553
floor 20 0.464 3163 4970 0.988 3143 4853 +0.524
×10 10 0.357 1646 2653 0.990 1597 2417 +0.634
×10 20 0.367 3318 5680 0.988 3143 4853 +0.621

Reading it:


Headline numbers


Assumptions and limits (explicit)

Bottom line

DFS's value is conditional and the real data tells us the condition is met: the North-Georgia spectrum is non-uniform (3.4× loud/quiet ratio), so a fixed coordinator that lands on the loud slot loses 23–45 points of delivery that DFS recovers — and two coordinators sharing a channel (the live 911.5 failure) lose >half their traffic, which DFS frequency-separation restores to ≈0.99 (+0.52–0.63 delivery). This supports the note's recommendation: the single-cell win is marginal on an already-clear default, so the strong case for DFS is multi-coordinator co-channel avoidance and contested-band sites — exactly the cases the live failure and band survey identified.

Files: experiment sim/experiments/dfs_channel_selection.py; occupancy data sim/data/ng_channel_occupancy.json; figure docs/figures/dfs_channel_selection.png.