- apply-wlan.sh: wlan_bands token was "6e" but this controller stores "6g" (verified live on Cascades Guest SSID) -> setting 6 GHz membership would have failed. Fixed band values + option names (5g6g/6g/all). - Cascades 2.4 runbook: folded in Phase 5 (5 GHz: width 80->40 on 76 radios; channel plan with the DFS decision flagged -- DFS empirically clean here, so including clean-DFS gives ~20 channels vs ~5 non-DFS-only for 77 APs) and Phase 6 (6 GHz: root cause = production SSID CSCNet not on 6 GHz [bands 2g,5g only]; add 6g + enable bss-transition; band-steering already on). Per Howard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
167 lines
9.6 KiB
Markdown
167 lines
9.6 KiB
Markdown
# Cascades of Tucson — 2.4 GHz Remediation Runbook (execute 2026-06-16 night)
|
|
|
|
**Goal:** cut 2.4 GHz airtime/co-channel contention (the "bad for some users" cause) by (1) powering down
|
|
over-strong 2.4 cells and (2) disabling redundant 2.4 radios where a nearby **active-2.4** neighbor still
|
|
covers the area — **without** breaking mesh backhaul, orphaning SSIDs, or opening coverage holes.
|
|
|
|
**Reference data:** SNR matrix `.claude/tmp/cascades-nbr.json` (fresh tonight), audit
|
|
`reports/2026-06-16-unifi-full-audit.md`. Regenerate any list with the commands in each phase.
|
|
|
|
---
|
|
|
|
## Safety facts established before touching anything
|
|
- **Mesh topology (live):** `2nd Floor Atrium` is the wireless-mesh **parent** for **CC Bridge** + **salon**
|
|
(backhaul ch36 / 5 GHz); `206 U7 Pro` carries `108`. **MESH-PROTECTED — do NOT disable:** `2nd Floor
|
|
Atrium, CC Bridge, salon, 206 U7 Pro, 108`. (coverage-thin now auto-excludes these.)
|
|
- **SSID pinning:** none — CSC ENT / CSCNet / Guest broadcast on **all** APs (`broadcasting_aps` off). No
|
|
client is locked to one AP; disabling a radio just makes clients roam to a neighbor. **No orphaning risk.**
|
|
- **Reversibility:** every change is `apply-radio` with a rollback JSON saved to `.claude/tmp/`. Re-enable =
|
|
`apply-radio <site> ng enable --ap "<name>" --apply`; restore power = `... ng power auto --ap "<name>"`.
|
|
- **AP-hang recovery:** if an AP goes/stays offline after a change, do NOT force-provision (that took 445
|
|
offline on 06-16). Recover with `device-control.sh cascades poe-cycle "<AP name>" --apply`.
|
|
|
|
---
|
|
|
|
## Phase 0 — Pre-flight (do first, ~5 min)
|
|
1. Site VPN up + stable; controller + APs reachable:
|
|
- `bash .claude/skills/unifi-wifi/scripts/live-stats.sh cascades | head -3` (expect 77 APs reporting)
|
|
2. **Baseline snapshot** (to compare after):
|
|
- `bash .claude/skills/unifi-wifi/scripts/live-stats.sh cascades > .claude/tmp/baseline-pre.txt`
|
|
- `bash .claude/skills/unifi-wifi/scripts/radio-usage.sh cascades ng 77 > .claude/tmp/usage-pre.txt`
|
|
3. Pick a **watch AP** per floor (a KEEP neighbor) to eyeball during changes, e.g.
|
|
`bash .claude/skills/unifi-wifi/scripts/watch-ap.sh <ap-ip>` in a second terminal.
|
|
|
|
---
|
|
|
|
## Phase 1 — Power-down 2.4 to Low (biggest safe win, fully reversible)
|
|
Smaller 2.4 cells = less mutual interference, **no coverage loss, no radios turned off.** Do per floor;
|
|
DRY-RUN first (omit `--apply`), eyeball, then `--apply`.
|
|
|
|
```
|
|
# SCOPE (Howard 2026-06-16): Floors 5 & 6 EXCLUDED. Floor 4 already powered-down (pilot).
|
|
# Floor 3 has no disabled radios + no mesh parent -> clean by-zone apply (17 radios):
|
|
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades ng power low --zone "Floor 3" --apply
|
|
# Floor 1 PER-AP (skip 128 = keep it disabled; skip 108 = offline mesh child) -> no pointless re-enable of 128:
|
|
for ap in "132 (Rec Room)" 133 127 140 145 121 122 109 116 115 139 146 103 102; do \
|
|
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades ng power low --ap "$ap" --apply; done
|
|
# (Floor 4 = DONE; Floors 5 & 6 = SKIP per scope; 128 stays disabled; 108 left alone.)
|
|
```
|
|
For **Floor 2** and **misc** (contain mesh APs), power-down **per-AP** to skip the mesh parents — do the
|
|
non-mesh APs only, and WATCH CC Bridge/salon stay online:
|
|
```
|
|
# Floor 2 (skip 2nd Floor Atrium, 206 U7 Pro):
|
|
for ap in 203 204 209 210 217 221 222 229 237 240 241 248 236; do \
|
|
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades ng power low --ap "$ap" --apply; done
|
|
# misc (skip CC Bridge, salon): Kitchen, Memcare*, memcare piano, Dining Room
|
|
for ap in "Kitchen" "Dining Room" "Memcare Nurse Station" "memcare piano" "Memcare TV room"; do \
|
|
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades ng power low --ap "$ap" --apply; done
|
|
```
|
|
**GATE:** after each floor, confirm client counts hold and no dead spot (`live-stats`, watch-ap). If CC
|
|
Bridge/salon blip during Floor 2/misc, STOP and reassess.
|
|
|
|
---
|
|
|
|
## Phase 2 — Settle + re-measure (~15 min)
|
|
Let it converge, then:
|
|
```
|
|
bash .claude/skills/unifi-wifi/scripts/live-stats.sh cascades > .claude/tmp/baseline-postA.txt
|
|
```
|
|
Compare retry% / cu_total vs `baseline-pre.txt`. Expect 2.4 retry + cu_interf to drop. **If Phase 1 alone
|
|
looks good, it is a legitimate stopping point for tonight** — disables can be a separate night.
|
|
|
|
---
|
|
|
|
## Phase 3 — Disable redundant 2.4 radios (per-AP, floor by floor, with gates)
|
|
Each AP below is low-client (<2 avg), non-mesh, non-memcare, and stays covered by **≥2 active-2.4**
|
|
neighbors. **Do ONE FLOOR, validate 15 min, then the next.** Recommend Floor 1 or Floor 4 as the pilot.
|
|
|
|
**Floor 1 (7):** `139 115 102 145 127 116 109`
|
|
**Floor 2 (8):** `240 221 209 229 241 248 203 236`
|
|
**Floor 3 (5):** `342 317 304 336 347`
|
|
**Floor 4 (5):** `445 441 403 407 450`
|
|
|
|
```
|
|
# one floor at a time, e.g. Floor 4 pilot:
|
|
for ap in 445 441 403 407 450; do \
|
|
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades ng disable --ap "$ap" --apply; \
|
|
sleep 5; done
|
|
# then VALIDATE before the next floor:
|
|
bash .claude/skills/unifi-wifi/scripts/live-stats.sh cascades --clients | head -40
|
|
bash .claude/skills/unifi-wifi/scripts/coverage-thin.sh cascades 14 # re-check remaining coverage (NEIGHBOR_JSON=...)
|
|
```
|
|
**GATE (per floor):** clients on disabled APs moved to neighbors; no AP shows a sudden client-drop to ~0
|
|
in a covered area; no user complaints. If a dead spot appears → re-enable that AP:
|
|
`apply-radio.sh cascades ng enable --ap "<name>" --apply`.
|
|
|
|
---
|
|
|
|
## Phase 5 — 5 GHz tuning (NEXT phase, after the 2.4 power-down settles)
|
|
Both changes are reversible and coverage-safe (width/channel don't change coverage). Per zone, dry-run
|
|
(omit `--apply`) then apply; Floors 5 & 6 excluded per scope.
|
|
|
|
**5a. Width 80 -> 40 MHz** (currently 80 MHz on 76/77). 40 MHz doubles the non-overlapping channels ->
|
|
better spatial reuse in this AP density. (Halves peak per-client rate, but the density win dominates.)
|
|
```
|
|
for z in "Floor 3" "Floor 1" "Floor 2" "Floor 4"; do \
|
|
bash .claude/skills/unifi-wifi/scripts/apply-radio.sh cascades na width 40 --zone "$z" --apply; done
|
|
# rollback: na width 80
|
|
```
|
|
|
|
**5b. Channel plan — DECISION NEEDED (non-DFS vs include clean-DFS):**
|
|
- DFS reality here: `dfs-check.sh` found **ZERO radar events fleet-wide** -> DFS channels are empirically CLEAN.
|
|
- Tradeoff at 40 MHz: **non-DFS-only** = ~5 usable channels (36-48 + 149-165) for 77 APs -> heavy co-channel.
|
|
**Including the clean DFS channels** (52-144) gives ~20 channels -> far better reuse, tiny resilience risk
|
|
(a radar hit would force a vacate, but none seen near Davis-Monthan in the logs).
|
|
- RECOMMENDATION: use a plan that INCLUDES clean DFS for channel diversity. Choose non-DFS-only only if you
|
|
want maximum resilience and will accept more co-channel overlap. (55/77 are on DFS today.)
|
|
```
|
|
SURVEY=.claude/tmp/cascades-survey.json; SURVEY_JSON=$SURVEY bash .../survey-collect.sh cascades
|
|
SURVEY_JSON=$SURVEY bash .../channel-plan.sh cascades na # dry-run; review, then apply per zone w/ validation
|
|
```
|
|
|
|
## Phase 6 — 6 GHz adoption + steering (the untapped clean band)
|
|
**Root cause 6 GHz is empty (1 client):** the production SSID **CSCNet is NOT broadcasting on 6 GHz**
|
|
(`wlan_bands=["2g","5g"]`); only Guest is. So step 1 is enabling the band, then assisting steering.
|
|
```
|
|
# 6a. add 6 GHz to CSCNet (keeps 2.4/5 for legacy):
|
|
bash .claude/skills/unifi-wifi/scripts/apply-wlan.sh cascades bands all --wlan CSCNet --apply # -> [2g,5g,6g]
|
|
# 6b. enable 802.11v BSS-transition on CSCNet (Guest already has it; assists up-band steering + roaming):
|
|
bash .claude/skills/unifi-wifi/scripts/apply-wlan.sh cascades bsstm on --wlan CSCNet --apply
|
|
```
|
|
Band-steering (`no2ghz_oui`) is already ON for CSCNet. After 6a/6b, 6E/7-capable clients gravitate to the
|
|
clean 6 GHz, offloading 5 GHz. Validate: `live-stats` client mix shifts toward 6e; confirm no SSID-visibility
|
|
loss for legacy devices (none expected — 2.4/5 stay on). 6 GHz has shorter range, fine with 77 APs.
|
|
(CSC ENT is legacy/being deprecated — leave as-is or add 6g too.)
|
|
> NOTE: this controller's 6 GHz token is `6g` (not `6e`); apply-wlan.sh fixed accordingly 2026-06-16.
|
|
|
|
---
|
|
|
|
## NOT doing tonight (explicit — so nothing is ambiguous)
|
|
- **Power-DOWN instead of disable** (redundant but high-client, keep serving): `3rd Floor Atrium, 318, 303,
|
|
4th Floor Atrium, 406-408, Dining Room` — already set Low in Phase 1; leave ON. (505 was here but
|
|
Floor 5 is now EXCLUDED — Howard 2026-06-16 — so 505/517 and Floor 6's 615/608/622 are left fully alone.)
|
|
- **HOLD (leave fully on):** `Memcare TV room` (memory-care area); all **MESH-PROTECTED** APs.
|
|
- **Remaining 2.4 levers deferred** (isolate cause/effect; do after disables validate): min-data-rate raise
|
|
(1→12 Mbps) and 2.4 min-RSSI on the OFF APs that aren't on excluded floors. (Band-steering `no2ghz_oui`
|
|
is already ON for CSCNet/CSC ENT/Guest.)
|
|
- **5 GHz tuning + 6 GHz adoption are now Phase 5 & Phase 6** above (folded in per Howard 2026-06-16) —
|
|
the next phase after the 2.4 power-down settles.
|
|
|
|
---
|
|
|
|
## Phase 4 — Wrap
|
|
```
|
|
bash .claude/skills/unifi-wifi/scripts/live-stats.sh cascades > .claude/tmp/baseline-post.txt
|
|
# re-snapshot 2.4 usage + audit; then /save the session log.
|
|
```
|
|
**Success = 2.4 retry% and cu_interf down on kept radios, client counts steady, zero coverage complaints.**
|
|
**Abort/rollback at any point:** re-enable/raise-power the affected APs (rollback JSONs in `.claude/tmp/`),
|
|
or `device-control poe-cycle` a hung AP. Power-down is always safe to leave; only disables can hole coverage.
|
|
|
|
---
|
|
### Tunable to regenerate the disable plan live
|
|
```
|
|
NBR=.claude/tmp/cascades-nbr.json; NBR_JSON=$NBR bash .claude/skills/unifi-wifi/scripts/neighbor-collect.sh cascades
|
|
MINCOV=2 CLIENT_CAP=8 NEIGHBOR_JSON=$NBR bash .claude/skills/unifi-wifi/scripts/coverage-thin.sh cascades 14
|
|
```
|