Files
claudetools/.claude/skills/unifi-wifi/references/interference-model.md
Mike Swanson ca548687e7 sync: auto-sync from GURU-5070 at 2026-06-15 18:24:52
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-15 18:24:52
2026-06-15 18:25:08 -07:00

98 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AP interference / airtime-reduction model — design + data
Goal (per Mike): a fleet/site-level model that decides **which AP radios to disable, and where to
reduce power**, to cut total airtime contention while preserving client coverage. Per AP **per
radio, all bands** (not 2.4-only, not per-client). Inputs: each AP's neighbor/overlap relationships
+ historical client connections + airtime.
## Data — it's ALL in the controller (corrected 2026-06-15)
First pass only checked the `ace` DB (config/current state) and wrongly concluded the history
"isn't there." It is — in **`ace_stat`** (1.5 GB of accumulated time-series) and the
`wifi_connectivity_event` collection. **No external collector is needed**; the controller already
retains it. Three databases on the UOS Mongo (port 27117):
| DB | Holds | Use |
|---|---|---|
| `ace` | config + current state | radio_table (channel/band/width/power/min_rssi), `rogue` (FOREIGN interference), `channelplan`, device/floorplan |
| **`ace_stat`** | **time-series history** | the model's airtime + client history (below) |
| `ace_audit` | audit log | — |
### `ace_stat.stat_hourly` / `stat_daily` (o:'ap') — per-AP per-band airtime history
Flat per-band fields (`ng`=2.4, `na`=5, `6e`=6): **`<band>-cu_total`** (channel utilization %),
**`<band>-cu_interf`** (airtime lost to interference %), **`<band>-cu_self_rx/tx`**, **`<band>-num_sta`**,
`<band>-satisfaction`, `<band>-tx_retries`. Plus `ap` (AP mac), `site_id`, `time` (ms epoch). This
is the historical airtime/interference/load profile per AP per band — the core "who's contending"
signal. (Also o:'user' per-client retries/anomalies; o:'site' rollups.)
### `ace_stat.wifi_connectivity_event` — the roam graph (empirical AP adjacency)
Each doc: `from_endpoint{mac(AP), channel, channel_width, band, rssi}`, `to_endpoint{mac(AP), ...}`,
`client_mac`, `successful`, `time`, `_class:WIFI_ROAMING`. Every roam is an **edge between two APs a
real client handed off between** → empirical coverage overlap/adjacency, weighted by volume, with
the handoff RSSI (coverage quality). This is the "historical connections → which APs cover the same
space" signal, and it's better than a raw RF scan because it reflects where clients actually move.
### Still useful from `ace` / setup
- `rogue` aggregate = FOREIGN co-channel interference per channel (Plane-1 audit).
- **Floorplan coords**: not set today (0/77 APs placed). Mike is willing to use the floorplan
feature → once APs are placed, `device` x/y gives true physical distance for adjacency edges
(complements the roam graph, esp. for APs that rarely roam-share).
- Live Network API (`stat/device`/`stat/sta`) still adds *current* utilization + the AP's live
RF-neighbor table; nice-to-have for before/after validation, not required to build the model.
## Model
Per band `b`, over a history window (default 7d):
- **Per-radio airtime**: avg `cu_total`, avg `cu_interf`, avg `num_sta` from `stat_hourly`.
- **Overlap/redundancy**: roam volume per AP and per AP-pair from `wifi_connectivity_event` (high
roam-share to neighbors ⇒ a client leaving this radio has somewhere to land ⇒ redundant).
- **Score (v1, `model-rank.sh`)**: `(cu_total + cu_interf) * log(1+roams) / (clients+1)` — high =
this radio burns airtime in a contended band AND its clients are mobile/redundant ⇒ shrink or
disable. Hint: **DISABLE** when high roam + near-zero clients; **POWER-DOWN** when busy but holds
some unique load.
- **v2 (greedy, coverage-safe)**: iteratively disable the top candidate, recompute overlap so we
never open a hole (stop when a radio's clients would lose their only good-RSSI neighbor), and add
floorplan-distance edges once APs are placed. Output = ranked per-AP-per-radio actions with the
metric that justified each, applied per zone with before/after validation.
## v1 result (Cascades 2.4GHz, 7d) — the smoking gun
`model-rank.sh cascades 7 ng`: 2.4 radios run **cu_total 7494%, cu_interf 6181%, ~0.32.6 clients
each** across 75 APs. Translation: 2.4 is saturated and mostly interference, serving almost no one —
a textbook case to disable 2.4 on most APs and power down the rest. Run `na`/`6e` for the 5/6GHz
picture (expected: keep, with 6GHz the clean capacity band).
## Materials matter — and why the roam graph already handles them
Cascades has **reinforced-concrete + steel-sheet walls across the hallways** that kill signal on all
bands (rooms either side of a hall, and floor-to-floor, behave normally; **across a hall is nearly
opaque**). This is why **geometric distance must never be the redundancy signal on its own**: two APs
on opposite sides of a hall are *close* but RF-*isolated*. The **roam graph is materials-aware by
construction** — clients can't roam across the steel wall, so those APs simply never appear as
neighbors, and the model won't call them redundant. Corollaries baked into the design:
- Distance/floorplan (once APs are placed) is only a **prior**; an AP pair counts as redundant **only
with RF/roam evidence** (bidirectional roams at adequate p25 RSSI). Never disable on proximity alone.
- AP **room parity (odd/even)** ≈ hallway side at Cascades — a useful prior to avoid pairing across-hall APs.
- The **live AP-to-AP RF neighbor table (API wireup)** *directly measures* this attenuation — the gold
standard for the coverage graph, and the reason it's worth wiring.
## Hardening from the multi-model review (Grok + Gemini)
Applied: bidirectional roam requirement (not one-way "escape"); band-specific p25 RSSI bars
(2.4 68 / 5 72 / 6 75); **load-shift simulation** (only disable if a strong neighbor stays < 85%
after absorbing the cu_self it inherits — avoids "capacity cascades"); benefit = `cu_interf` (+ a
normalized `tx_retries` thrash term), with `cu_self` treated as transfer cost; 40%/zone disable cap;
**stepwise** output (power-down → observe → disable). `tx_retries` is a raw count → normalized by
`wifi_tx_attempts`.
## Key empirical finding (Cascades): power-down now, disable later
The **airtime data is rich and unambiguous**`optimize-radios.sh cascades ng` recommends
**power-down on 74/75 2.4 radios** (all 7494% utilized, 6181% interference, ~1 client — the safe big
win; power-down keeps the BSSID). But the **roam data is too sparse** to prove coverage redundancy for
**disables** (almost no AP has a strong bidirectional roam-neighbor), so the optimizer correctly
recommends **0 disables until coverage evidence exists**. That evidence is exactly the **live AP-to-AP
RF neighbor table** → **the API wireup is the enabler for confident radio *disables*** (and for
before/after validation). So the rollout is: Phase A power-down (now) → Phase B re-measure (live API)
→ Phase C disable the radios the RF table proves redundant.
## Status
- `scripts/audit-site.sh` — config + foreign-interference audit (Plane 1).
- `scripts/model-rank.sh`**v1 airtime-reduction ranker from real history** (this doc). Works now.
- Next: v2 greedy coverage-safe optimizer + floorplan-distance edges (after APs are placed on the
floorplan) + optional live-API before/after validation.