15 KiB
name, description
| name | description |
|---|---|
| unifi-wifi | Analyze and tune UniFi WiFi for performance + stability, especially in dense/congested environments. Audits AP/radio config and the neighbor-interference map from the UOS controller, flags issues (2.4GHz over-provisioning, channel width, min-RSSI/sticky clients, channel plan), and recommends prioritized changes. Works for any UniFi site on the UOS (172.16.3.29); Cascades is the hard case. Triggers: unifi wifi tuning, RF/airtime/channel analysis, 2.4GHz congestion, AP channel plan, sticky clients, wireless performance. |
UniFi WiFi tuning (UOS sites)
Data-driven WiFi tuning for UniFi sites on the UOS Server (172.16.3.29). Goal: solid
performance + stability for connected devices in congested environments by analyzing what the
controller knows and making prioritized, validated changes. Built for any site; Cascades
(77 APs, ~550 clients, brutal 2.4GHz) is the reference hard case.
Multi-client (any UOS site)
scripts/sites.sh lists all ~49 sites (APs/switches/gateways) + per-client AP-cred readiness — the
entry point for pointing the skill at another client. Controller-side scripts (audit-site,
live-stats, model-rank, optimize-radios, apply-radio) work on ANY site with zero per-client setup
(shared controller creds) — pass the site name/desc/id. AP-side collectors (neighbor/survey/dfs/
watch-ap) additionally need that client's clients/<slug>/unifi-ap-ssh vaulted + L3 reach (site VPN);
if missing they print the exact vault command and exit (controller-side still works). Default AP-cred
path is Cascades — override with the script's vault-path arg per client.
Status (2026-06-15)
- [WORKING] WiFi monitoring + RF tuning — complete data-gathering for any UOS site/client:
config + interference (
audit-site), live per-AP + per-client (live-stats), airtime history- roam graph (
model-rank/optimize-radios), measured per-channel occupancy (survey-collect), empirical DFS radar history (dfs-check), the AP-to-AP SNR neighbor matrix from/proc/ui_neighbor(neighbor-collect), per-AP live stream (watch-ap), and gated config apply (apply-radio). All site-parameterized → works on every UniFi site we monitor.
- roam graph (
- [WORKING] Switch / PoE audit —
scripts/switch-audit.sh <site> [--all-ports]: per-switch PoE budget, port up/total, and flags (UNDERSPEED gig-port-at-10/100, rate-based DROPS, ERRORS, PoE faults, PoE-budget pressure). Controller-side, any site. (Found ~25 100M-linked gig ports at Cascades.) - [WORKING] Gateway / WAN / internet + site health —
scripts/gw-audit.sh <site>: WAN status/IP/ uplink, internet latency/drops/speedtest, gateway CPU/mem/uptime, and the adoption/health rollup (APs/switches adopted vs disconnected vs pending, client counts, firmware-upgradable) with flags. Handles third-party-firewall sites (num_gw=0). Controller-side, any site. - [WIP] Deeper firewall/VPN policy, client DHCP/DNS, adoption remediation — read/health is covered by gw-audit; deeper config + remediation actions are future. Access layer reaches them already.
- Per-client requirement:
watch-ap/neighbor-collect/survey-collect/dfs-checkdefault the AP device-auth SSH cred toclients/cascades-tucson/unifi-ap-ssh; for another client, vault its ownclients/<x>/unifi-ap-sshand pass it as the script's vault-path arg.
First, load context
- references/data-access.md — what data the UOS exposes and how to read it (the two planes: Mongo config/interference now, live Network API later).
- references/methodology.md — the prioritized tuning playbook (synthesized from a multi-model pass + live recon). Read before recommending any change.
- references/interference-model.md — design for the AP-interference / airtime-reduction model (which radios to disable / power down), incl. the data feasibility (needs Plane 2 + a collector — the signals aren't in Mongo).
What it does (current = Plane 1, read-only)
- Audit a site — config + interference, no live plane needed:
Outputs 2.4/5/6 config summary, the per-channel neighbor-density (interference) map, and flagged issues (2.4 over-provisioning, 40/80/160MHz width, off-1/6/11 channels, min-RSSI off, high power).
bash .claude/skills/unifi-wifi/scripts/audit-site.sh <site-name|site_id> bash .claude/skills/unifi-wifi/scripts/audit-site.sh cascades - Rank airtime-reduction candidates — which radios to disable / power down, from real history
(
ace_stat: per-AP airtimecu_total/cu_interf/num_sta+ thewifi_connectivity_eventroam graph). Works for any band:(Cascades 2.4: 75 radios at 74–94% utilization, 61–81% interference, ~1 client each → disable/power-down.)bash .claude/skills/unifi-wifi/scripts/model-rank.sh <site> [days=7] [band=ng|na|6e|all] - Optimize (coverage-safe plan) — which radios to power-down (do first) vs disable
(after), per band, without opening coverage holes or capacity cascades:
Coverage model = the roam graph (materials-aware: clients can't roam through Cascades' steel hallway walls, so the model never calls cross-wall APs redundant). Multi-model hardened (bidirectional roams, load-shift simulation, 40%/zone cap, stepwise). On Cascades 2.4 it recommends power-down on 74/75 radios (safe big win) and 0 disables until the live RF table proves redundancy — see interference-model.md.
bash .claude/skills/unifi-wifi/scripts/optimize-radios.sh <site> [days=14] [band=ng|na|6e] - Validate live (Plane 2) — current cu_total/satisfaction/per-AP RF, before+after a change, and
the AP-to-AP RF-neighbor table that unlocks confident disables:
Needs a read-only controller admin vaulted at
bash .claude/skills/unifi-wifi/scripts/live-stats.sh <site> [--clients]infrastructure/uos-server-network-api(the script prints the one-time provisioning steps if it's missing).- Per-AP live watch (no controller lag) — SSH straight into an AP and stream channel-busy% +
noise + clients/retries every ~2s, to watch a targeted change land in real time:
Uses
bash .claude/skills/unifi-wifi/scripts/watch-ap.sh <ap-ip> [interval]mca-dump+iw surveyon the AP. Device-auth cred vaulted (clients/cascades-tucson/unifi-ap-ssh). Needs L3 reach to the AP (at Cascades: bring up the site VPN; APs are on 192.168.2.x/3.x) + localsshpass.
- Per-AP live watch (no controller lag) — SSH straight into an AP and stream channel-busy% +
noise + clients/retries every ~2s, to watch a targeted change land in real time:
- Interpret the flags against
methodology.md(fix order: prune 2.4 -> shrink cells/power -> min data rates -> manual 1/6/11 plan -> min-RSSI + roaming -> steer to 6GHz). - Recommend a prioritized, per-zone change plan. Roll out per zone, not site-wide at once.
AP-side collectors (direct AP SSH over the site VPN — non-disruptive, foreground)
These read each AP directly (controller hides this data). All take <site> [ap-ssh-vault-path],
use sshpass-or-SSH_ASKPASS auth, and must run in the foreground:
# AP-to-AP SNR neighbor matrix (/proc/ui_neighbor) -> redundancy = data-backed disable candidates.
# Set NBR_JSON to also emit a machine-readable adjacency, then feed it to optimize-radios.sh:
NBR_JSON=.claude/tmp/<site>-nbr.json bash .claude/skills/unifi-wifi/scripts/neighbor-collect.sh cascades
NEIGHBOR_JSON=.claude/tmp/<site>-nbr.json bash .claude/skills/unifi-wifi/scripts/optimize-radios.sh cascades 14 ng
# ^ optimize-radios uses the measured SNR overlap (not the sparse roam graph) -> real DISABLE list.
bash .claude/skills/unifi-wifi/scripts/neighbor-collect.sh cascades [vault-path] [snr_min=20]
# measured per-channel busy%/noise per AP -> cleanest-channel plan (iw survey dump)
bash .claude/skills/unifi-wifi/scripts/survey-collect.sh cascades [vault-path]
# empirical DFS radar-event history per AP (dmesg) -> is DFS safe at this site?
bash .claude/skills/unifi-wifi/scripts/dfs-check.sh cascades [vault-path]
The neighbor matrix is the breakthrough: UniFi exposes managed-AP-to-managed-AP visibility
nowhere in the controller API/DB (all filter our own APs), but each AP keeps it in
/proc/ui_neighbor/{ess_ap_list,ssid/*}, populated non-disruptively by background RRM scanning.
Ad-hoc Mongo queries: .claude/scripts/uos-mongo.sh (recipes in data-access.md). Access is the
vaulted dedicated key infrastructure/uos-server-ssh-key (works from any fleet machine).
The two data planes (know which you're using)
- Plane 1 — Mongo
ace(available now, read-only via SSH): radio config (radio_table), therogueneighbor-interference map,channelplan, AP/client inventory. Enough for the full config audit + channel/interference plan — covers the 2.4GHz "first problem". - Plane 2 — live Network API (
stat/device,stat/sta; NOT yet wired): live channel utilization (cu_total), per-client RSSI/SNR/retries, AP satisfaction. Needed to validate changes (before/after) and find the worst APs by live airtime. Wiring it needs a dedicated read-only UniFi admin or integration API key on.29, vaulted asinfrastructure/uos-server-network-api. See data-access.md "Plane 2".
Applying changes — IMPORTANT boundary
Config changes are automated across many APs (no per-AP UI clicking) via the controller REST API
(PUT .../rest/device/<id> radio_table) — scripts/apply-radio.sh. Actions (all radio_table):
bash .../apply-radio.sh <site> <ng|na|6e> power low|medium|high|auto|<dBm> [--zone Z] [--apply]
bash .../apply-radio.sh <site> <ng|na|6e> width 20|40|80|160 [--zone Z] [--apply]
bash .../apply-radio.sh <site> <ng|na|6e> channel <number>|auto [--zone Z] [--apply]
bash .../apply-radio.sh <site> <ng|na|6e> minrssi off|on|-<NN> [--zone Z] [--apply]
bash .../apply-radio.sh <site> <ng|na|6e> disable # radio OFF (tx_power_mode=disabled) [--ap NAME] [--apply]
bash .../apply-radio.sh <site> <ng|na|6e> enable # radio ON (tx_power_mode=auto) [--ap NAME] [--apply]
--ap "<name>" targets a single AP (the right scope for disables — execute optimize-radios' disable
list one AP at a time: optimize-radios ... -> for each, apply-radio <site> ng disable --ap <name> --apply).
Dry-run (default) prints per-AP before->after + rollback values + the REST payload. Writes are GATED
OFF until (1) infrastructure/uos-server-network-api-rw is vaulted (the root SSH key is the data
plane, NOT an API write session) and (2) --apply is passed. Even then: rollback is auto-saved to
.claude/tmp/apply-rollback-*.json, go one --zone at a time, validate live with watch-ap.sh
before and after, and never auto channel-optimize in ultra-dense sites. WRITE PATH VALIDATED
2026-06-16 (apply->verify->revert + full disable/enable cycle on 0-client 6 GHz radios). Radio
disable IS implemented = tx_power_mode:"disabled" (confirmed via UI-toggle + device-JSON diff);
enable sets it back to auto. min-data-rate / band-steering live in wlanconf (not radio_table)
— separate future apply path (see references/ROADMAP.md).
Get explicit go before any write. Full roadmap: references/ROADMAP.md.
WLAN-level knobs — scripts/apply-wlan.sh (wlanconf, not radio_table; affects every AP on the WLAN
— target with --wlan). Same gated REST path (rest/wlanconf), dry-run default, rollback saved:
# tuning
apply-wlan.sh <site> minrate ng|na auto|off|<Mbps> [--wlan N] [--apply] # kill 1-11Mbps: minrate ng 12
apply-wlan.sh <site> dtim ng|na|6e <1-255> [--wlan N] [--apply] # DTIM (power-save/mcast)
apply-wlan.sh <site> mcast|bcfilter on|off [--wlan N] [--apply] # multicast-enhance / broadcast-filter
# steering / roaming
apply-wlan.sh <site> bandsteer on|off [--wlan N] [--apply] # no2ghz_oui: 5GHz-capable off 2.4
apply-wlan.sh <site> bands both|5g|5g6e|6e|all [--wlan N] [--apply] # wlan_bands: force SSID onto bands
apply-wlan.sh <site> steer|bsstm|rrm|ftroam on|off [--wlan N] [--apply] # roam-assist / 802.11v / 802.11k / 802.11r
# access / security
apply-wlan.sh <site> wlan on|off [--wlan N] [--apply] # enable/disable the SSID
apply-wlan.sh <site> isolation|hidessid on|off [--wlan N] [--apply]
apply-wlan.sh <site> macfilter off|allow|deny <macs> [--wlan N] [--apply] # per-WLAN MAC allow/deny
apply-wlan.sh <site> aps <ap1,ap2|all> [--wlan N] [--apply] # broadcasting_aps: restrict SSID to APs
"Lock a device to an AP" (UniFi has no native per-client AP pin): use aps to put an SSID on only
the chosen AP(s) ± macfilter to admit only that device — that constrains it to those APs. Band steering:
no classic bandsteering_mode; replacements are bandsteer/bands/bsstm. (802.11r ftroam warns — risky for IoT.)
Client controls — scripts/client-control.sh (operational; controller-side, gated):
client-control.sh <site> block|unblock|kick <mac> [--apply] # ban a MAC / un-ban / force-reconnect
Device / adoption remediation — scripts/device-control.sh (pairs with gw-audit flags; gated):
device-control.sh <site> adopt|restart|provision|locate|unlocate|upgrade <mac> [--apply]
Channel plan — scripts/channel-plan.sh (computes + applies a co-channel-minimizing plan):
NEIGHBOR_JSON=...nbr.json SURVEY_JSON=...survey.json \
channel-plan.sh <site> ng|na [--apply] # ng: 1/6/11 graph-color; na: cleanest NON-DFS + separation
ng uses the neighbor matrix to graph-color 1/6/11; na picks each AP's lowest-cost non-DFS channel
(measured busy% + neighbor-collision penalty). Reports co-channel pairs before/after. (Cascades dry-run:
ng 92→35 pairs; na 20→0 + all off DFS.) survey-collect.sh emits its JSON via SURVEY_JSON=<path>.
GOTCHA (handled): a manual min rate is only honored when minrate_setting_preference=manual — the
script sets it; minrate ... auto hands rate management back to the controller. Write path validated
2026-06-16 on a 0-client WLAN (Green Valley Computer Club) — apply->verify->restore.
Roadmap
- Phase 1 (done): config + interference audit, flags, methodology. Read-only.
- Phase 2: wire the live Network API (Plane 2) for
cu_total/satisfaction/per-client RF → before/after validation + "worst APs by airtime" ranking. - Phase 3: assisted change application (per-zone, API-driven, with live before/after gating).
Notes
- The methodology is independent-model guidance + config-plane recon — validate against live stats before trusting any single recommendation, and roll out per zone.
- Multi-site: pass any site name/id;
uos-mongo.sh --siteslists them. Cascades =685f39068e65331c46ef6dd2.