Skip to main content

unifly_api/session/
models.rs

1// Session API response types
2//
3// Models for the UniFi controller's session JSON API. All responses are wrapped
4// in the `SessionResponse<T>` envelope. Fields use `#[serde(default)]` liberally
5// because the API is inconsistent about field presence across firmware versions.
6
7use serde::{Deserialize, Serialize};
8
9// ── Response Envelope ────────────────────────────────────────────────
10
11/// Standard UniFi session API response envelope.
12///
13/// Every session endpoint wraps its payload:
14/// ```json
15/// { "meta": { "rc": "ok", "msg": "optional" }, "data": [...] }
16/// ```
17#[derive(Debug, Deserialize)]
18pub struct SessionResponse<T> {
19    pub meta: Meta,
20    pub data: Vec<T>,
21}
22
23/// Metadata from the session envelope. `rc` == `"ok"` means success.
24#[derive(Debug, Deserialize)]
25pub struct Meta {
26    pub rc: String,
27    #[serde(default)]
28    pub msg: Option<String>,
29}
30
31// ── Device ───────────────────────────────────────────────────────────
32
33/// Full device object from `stat/device`.
34///
35/// The session API can return 100+ fields per device. We model the most
36/// commonly needed ones explicitly; everything else lands in `extra`.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SessionDevice {
39    #[serde(default, rename = "_id")]
40    pub id: String,
41    pub mac: String,
42    #[serde(rename = "type")]
43    pub device_type: String,
44    #[serde(default)]
45    pub ip: Option<String>,
46    #[serde(default)]
47    pub name: Option<String>,
48    #[serde(default)]
49    pub model: Option<String>,
50    #[serde(default)]
51    pub version: Option<String>,
52    #[serde(default)]
53    pub adopted: bool,
54    /// 0=offline, 1=online, 2=pending, 4=upgrading, 5=provisioning
55    #[serde(default)]
56    pub state: i32,
57    #[serde(default)]
58    pub sys_stats: Option<SysStats>,
59    #[serde(default)]
60    pub uptime: Option<i64>,
61    #[serde(default)]
62    pub num_sta: Option<i32>,
63    #[serde(default)]
64    pub serial: Option<String>,
65    #[serde(default)]
66    pub site_id: Option<String>,
67    #[serde(default)]
68    pub last_seen: Option<i64>,
69    #[serde(default)]
70    pub upgradable: Option<bool>,
71    #[serde(default, rename = "user-num_sta")]
72    pub user_num_sta: Option<i32>,
73    #[serde(default, rename = "guest-num_sta")]
74    pub guest_num_sta: Option<i32>,
75    /// Catch-all for undocumented fields.
76    #[serde(flatten)]
77    pub extra: serde_json::Map<String, serde_json::Value>,
78}
79
80/// System statistics nested inside `SessionDevice`.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct SysStats {
83    #[serde(default, rename = "loadavg_1")]
84    pub load_1: Option<String>,
85    #[serde(default, rename = "loadavg_5")]
86    pub load_5: Option<String>,
87    #[serde(default, rename = "loadavg_15")]
88    pub load_15: Option<String>,
89    #[serde(default)]
90    pub mem_total: Option<i64>,
91    #[serde(default)]
92    pub mem_used: Option<i64>,
93    #[serde(default)]
94    pub cpu: Option<String>,
95}
96
97// ── Client (Station) ─────────────────────────────────────────────────
98
99/// Connected client from `stat/sta`.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct SessionClientEntry {
102    #[serde(rename = "_id")]
103    pub id: String,
104    pub mac: String,
105    #[serde(default)]
106    pub hostname: Option<String>,
107    #[serde(default)]
108    pub ip: Option<String>,
109    #[serde(default)]
110    pub oui: Option<String>,
111    #[serde(default)]
112    pub name: Option<String>,
113    #[serde(default)]
114    pub is_guest: Option<bool>,
115    #[serde(default)]
116    pub is_wired: Option<bool>,
117    #[serde(default)]
118    pub authorized: Option<bool>,
119    #[serde(default)]
120    pub blocked: Option<bool>,
121    #[serde(default)]
122    pub signal: Option<i32>,
123    #[serde(default)]
124    pub tx_bytes: Option<i64>,
125    #[serde(default)]
126    pub rx_bytes: Option<i64>,
127    #[serde(default)]
128    pub tx_rate: Option<i64>,
129    #[serde(default)]
130    pub rx_rate: Option<i64>,
131    #[serde(default)]
132    pub uptime: Option<i64>,
133    #[serde(default)]
134    pub first_seen: Option<i64>,
135    #[serde(default)]
136    pub last_seen: Option<i64>,
137    #[serde(default)]
138    pub site_id: Option<String>,
139    #[serde(default)]
140    pub essid: Option<String>,
141    #[serde(default)]
142    pub bssid: Option<String>,
143    #[serde(default)]
144    pub channel: Option<i32>,
145    #[serde(default)]
146    pub radio: Option<String>,
147    #[serde(default)]
148    pub rssi: Option<i32>,
149    #[serde(default)]
150    pub noise: Option<i32>,
151    #[serde(default)]
152    pub satisfaction: Option<i32>,
153    #[serde(default)]
154    pub ap_mac: Option<String>,
155    #[serde(default)]
156    pub network: Option<String>,
157    #[serde(default)]
158    pub network_id: Option<String>,
159    #[serde(default)]
160    pub sw_mac: Option<String>,
161    #[serde(default)]
162    pub sw_port: Option<i32>,
163    /// Catch-all for undocumented fields.
164    #[serde(flatten)]
165    pub extra: serde_json::Map<String, serde_json::Value>,
166}
167
168// ── User (known client / DHCP reservation) ──────────────────────────
169
170/// User object from `rest/user`.
171///
172/// The "user" collection stores persistent client configuration such as
173/// names, notes, and DHCP reservations. Unlike `stat/sta` (currently
174/// connected stations), `rest/user` includes offline/historical clients.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct SessionUserEntry {
177    #[serde(rename = "_id")]
178    pub id: String,
179    pub mac: String,
180    #[serde(default)]
181    pub name: Option<String>,
182    #[serde(default)]
183    pub hostname: Option<String>,
184    #[serde(default)]
185    pub use_fixedip: Option<bool>,
186    #[serde(default)]
187    pub fixed_ip: Option<String>,
188    #[serde(default)]
189    pub network_id: Option<String>,
190    #[serde(default)]
191    pub site_id: Option<String>,
192    #[serde(default)]
193    pub noted: Option<bool>,
194    #[serde(default)]
195    pub note: Option<String>,
196    /// Catch-all for undocumented fields.
197    #[serde(flatten)]
198    pub extra: serde_json::Map<String, serde_json::Value>,
199}
200
201// ── Site ─────────────────────────────────────────────────────────────
202
203/// Site object from `/api/self/sites`.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct SessionSite {
206    #[serde(rename = "_id")]
207    pub id: String,
208    pub name: String,
209    #[serde(default)]
210    pub desc: Option<String>,
211    #[serde(default)]
212    pub role: Option<String>,
213    /// Catch-all for undocumented fields.
214    #[serde(flatten)]
215    pub extra: serde_json::Map<String, serde_json::Value>,
216}
217
218// ── Event ────────────────────────────────────────────────────────────
219
220/// Event object from `stat/event`.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct SessionEvent {
223    #[serde(rename = "_id")]
224    pub id: String,
225    #[serde(default)]
226    pub key: Option<String>,
227    #[serde(default)]
228    pub msg: Option<String>,
229    #[serde(default)]
230    pub datetime: Option<String>,
231    #[serde(default)]
232    pub subsystem: Option<String>,
233    #[serde(default)]
234    pub site_id: Option<String>,
235    /// Catch-all for undocumented fields.
236    #[serde(flatten)]
237    pub extra: serde_json::Map<String, serde_json::Value>,
238}
239
240// ── Alarm ────────────────────────────────────────────────────────────
241
242/// Alarm object from `stat/alarm`.
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct SessionAlarm {
245    #[serde(rename = "_id")]
246    pub id: String,
247    #[serde(default)]
248    pub key: Option<String>,
249    #[serde(default)]
250    pub msg: Option<String>,
251    #[serde(default)]
252    pub datetime: Option<String>,
253    #[serde(default)]
254    pub archived: Option<bool>,
255    /// Catch-all for undocumented fields.
256    #[serde(flatten)]
257    pub extra: serde_json::Map<String, serde_json::Value>,
258}
259
260// ── Wi-Fi Observability ─────────────────────────────────────────────
261
262/// Neighboring / rogue access point from `stat/rogueap`.
263///
264/// Each entry represents a foreign AP detected by one of your APs.
265/// Note: `stat/rogueap` uses Unix epoch **seconds** for query params,
266/// unlike many other UniFi stats endpoints.
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct RogueAp {
269    pub bssid: String,
270    #[serde(default)]
271    pub essid: Option<String>,
272    #[serde(default)]
273    pub channel: Option<i32>,
274    #[serde(default)]
275    pub freq: Option<i32>,
276    #[serde(default)]
277    pub signal: Option<i32>,
278    #[serde(default)]
279    pub rssi: Option<i32>,
280    #[serde(default)]
281    pub noise: Option<i32>,
282    #[serde(default)]
283    pub security: Option<String>,
284    #[serde(default)]
285    pub radio: Option<String>,
286    #[serde(default)]
287    pub age: Option<i64>,
288    #[serde(default)]
289    pub is_rogue: bool,
290    /// MAC of your AP that observed this neighbor.
291    #[serde(default)]
292    pub ap_mac: Option<String>,
293    /// Catch-all for undocumented fields.
294    #[serde(flatten)]
295    pub extra: serde_json::Map<String, serde_json::Value>,
296}
297
298/// Country-level regulatory channel data from `stat/current-channel`.
299///
300/// The UniFi API returns one record per country with per-band channel lists
301/// (e.g. `channels_ng`, `channels_na`, `channels_6e`) rather than per-radio
302/// rows. The typed fields cover the most common bands; the `extra` map
303/// captures width-specific and AFC lists.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct ChannelAvailability {
306    /// ISO 3166-1 numeric country code (e.g. `"840"` for the US).
307    #[serde(default)]
308    pub code: Option<String>,
309    /// Two-letter country key (e.g. `"US"`).
310    #[serde(default)]
311    pub key: Option<String>,
312    /// Human-readable country name.
313    #[serde(default)]
314    pub name: Option<String>,
315    /// 2.4 GHz channels.
316    #[serde(default)]
317    pub channels_ng: Option<Vec<i32>>,
318    /// 5 GHz channels.
319    #[serde(default)]
320    pub channels_na: Option<Vec<i32>>,
321    /// 5 GHz DFS channels.
322    #[serde(default)]
323    pub channels_na_dfs: Option<Vec<i32>>,
324    /// 6 GHz channels.
325    #[serde(default)]
326    pub channels_6e: Option<Vec<i32>>,
327    /// Catch-all for width-specific lists, AFC data, etc.
328    #[serde(flatten)]
329    pub extra: serde_json::Map<String, serde_json::Value>,
330}