6.5 KiB
UniFi WiFi data access — what the UOS controller exposes and how to read it
This is the data-capability map for the unifi-wifi tuning skill, built from live recon of the
UOS Server (172.16.3.29, self-hosted UniFi OS / classic Network app, Mongo DB ace) using
the Cascades site (site_id 685f39068e65331c46ef6dd2) as the hard case (77 APs / 12 switches,
~550 concurrent wireless clients, severe 2.4GHz neighbor congestion). Access: infrastructure/uos-server-ssh-key
(vaulted) + .claude/scripts/uos-mongo.sh. See also uos-server.
Two data planes (the key finding)
| Plane | Source | Reach | Holds |
|---|---|---|---|
| Config | Mongo ace via uos-mongo.sh |
root SSH, available now | radio config, foreign-interference map (rogue), channel-plan, device/floorplan |
| History | Mongo ace_stat (db.getSiblingDB('ace_stat')) |
root SSH, available now | per-AP/per-band airtime time-series (<band>-cu_total/cu_interf/num_sta, satisfaction, retries) in stat_hourly/stat_daily; the roam graph in wifi_connectivity_event; per-client history |
| Live (optional) | Controller Network API (stat/device, stat/sta) |
needs a session / integration key — not wired | current utilization + the live RF-neighbor table; nice for before/after validation |
The accumulated history plane (
ace_stat) is the key source for the interference / airtime model — it already holds what the UniFi UI shows. See interference-model.md.
The live per-AP utilization and per-client RF stats are NOT persisted in Mongo (the device
collection carries config but no radio_table_stats; the user/client collection only keeps
last_radio). So a first-pass audit + channel/interference plan comes entirely from Mongo; the
live "current airtime / who's unhappy right now" feedback loop needs the local Network API (see
"Live-stats gap" below).
Plane 1 — Mongo ace (available now)
device collection (per AP/switch; filter type:'uap' for APs)
Per-AP radio_table[] (the config we tune), one entry per radio:
radio:ng= 2.4GHz,na= 5GHz,6e= 6GHzchannel,ht(width: 20/40/80/160),tx_power_mode(auto/low/medium/high/custom)min_rssi_enabled,min_rssi(sticky-client / roaming floor, e.g. -77)- plus
atf_enabled(airtime fairness),country_code,antenna_table,scan_radio_table,support_wifi6e,wifi_caps, model/firmware,num_stais present at the device level (last reported).
Cascades is a U7-Pro / WiFi-6E fleet (tri-band ng/na/6e). Example AP: 2.4 ch11/20MHz, 5GHz ch153/80MHz, 6GHz ch145/160MHz, min_rssi -77 enabled.
rogue collection — the interference map (586,688 docs fleet-wide; the gold)
Every neighbor/over-the-air BSSID an AP has seen, with band, channel, ap_mac (which of our
APs saw it), bssid, essid, rssi, age, site_id. Aggregate by channel to quantify
co-channel congestion. Cascades 2.4GHz is brutal:
| Band | Channel | Neighbor BSSIDs seen |
|---|---|---|
| 2.4 (ng) | 6 | 33,359 |
| 2.4 (ng) | 1 | 19,275 |
| 2.4 (ng) | 11 | 16,578 |
| 5 (na) | 149 | 8,889 |
| 5 (na) | 157 | 6,964 |
| 5 (na) | 44 | 5,477 |
| 6 (6e) | 69 | 86 |
Takeaways the skill uses: 2.4GHz is saturated on all three usable channels (so the fix is fewer 2.4 radios + tight power, not "find a clean channel"); 6GHz is nearly empty (steer capable clients up); 5GHz upper band (149/157) is busier than the UNII-1/DFS lower band.
channelplan collection
The controller's auto-channel-plan inputs/outputs per site: channels_ng/channels_na/channels_6e
(allowed channel lists), ht_modes_ng/na/6e, method, optimize, exclude_devices,
high_priority_devices, date. Lets the skill read/propose the channel plan the controller will honor.
user collection — client inventory/history
~1,807 client records for Cascades. Holds identity + last_radio (band) but not live RF; use
for inventory/segmentation, not live signal.
Config-audit signals already computable from Mongo
- 2.4GHz width != 20MHz (40MHz on 2.4 in density = self-inflicted overlap).
min_rssi_enabled=falseon 2.4 radios → sticky-client risk. (Cascades: 6 of 77 APs have 2.4 min_rssi disabled.)- 2.4 channels not on the 1/6/11 plan; adjacent APs on the same channel.
- TX-power mode (auto/high) on 2.4 in dense clusters (should be low/medium).
- Per-AP radio enable: which dense-cluster APs should have their 2.4 radio disabled entirely.
Plane 2 — Live RF/airtime (the gap to wire next)
Live data the tuner ideally also wants (current utilization, satisfaction, per-client RSSI/SNR/ retries, roam events) lives in the classic Network API, session-authenticated:
GET /proxy/network/api/s/<site_short>/stat/device→ per-APradio_table_stats[]:cu_total(channel utilization %),cu_self_rx/cu_self_tx(our own airtime),num_sta,tx_retries,satisfaction, per radio.GET /proxy/network/api/s/<site_short>/stat/sta→ per-client:rssi,signal,noise,tx_rate/rx_rate,tx_retries,satisfaction,radio/channel,nss, anomalies.
Auth options (none wired yet): (a) a dedicated read-only local UniFi admin → login for a
session cookie; (b) a Network integration API key (X-API-KEY vs /proxy/network/integration/v1/...).
The cloud Site Manager key does NOT authenticate the local API (401); the existing local "Claude"
integration key's value is hashed/unrecoverable. Action for phase 2: create a dedicated
read-only admin or integration key on .29, vault it (infrastructure/uos-server-network-api),
and read live stats from it. Until then the skill runs config+interference analysis (Plane 1),
which already covers the 2.4GHz "first problem".
Quick recipes
# Cascades site id
bash .claude/scripts/uos-mongo.sh --sites | grep -i casc # 685f39068e65331c46ef6dd2
# 2.4GHz neighbor congestion per channel for a site (the interference map)
cat <<'JS' | bash .claude/scripts/uos-mongo.sh
db.rogue.aggregate([{$match:{site_id:'685f39068e65331c46ef6dd2',band:'ng'}},
{$group:{_id:'$channel',neighbors:{$sum:1}}},{$sort:{neighbors:-1}}]).forEach(printjson)
JS
# Per-AP 2.4GHz config audit (channel, width, power, min_rssi)
cat <<'JS' | bash .claude/scripts/uos-mongo.sh
db.device.find({site_id:'685f39068e65331c46ef6dd2',type:'uap'},{name:1,radio_table:1}).forEach(function(a){
(a.radio_table||[]).forEach(function(r){ if(r.radio=='ng')
print(a.name+" ch="+r.channel+" ht="+r.ht+" pwr="+r.tx_power_mode+" min_rssi="+(r.min_rssi_enabled?r.min_rssi:'OFF')); });
});
JS