Skip to main content

unifly_api/model/
device.rs

1// ── Device domain types ──
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::net::IpAddr;
6
7use super::common::{Bandwidth, DataSource, EntityOrigin};
8use super::entity_id::{EntityId, MacAddress};
9
10/// Canonical device type -- normalized from both API surfaces.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[non_exhaustive]
13pub enum DeviceType {
14    Gateway,
15    Switch,
16    AccessPoint,
17    Other,
18}
19
20/// Device operational state.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[non_exhaustive]
23pub enum DeviceState {
24    Online,
25    Offline,
26    PendingAdoption,
27    Updating,
28    GettingReady,
29    Adopting,
30    Deleting,
31    ConnectionInterrupted,
32    Isolated,
33    Unknown,
34}
35
36impl DeviceState {
37    pub fn is_online(&self) -> bool {
38        matches!(self, Self::Online)
39    }
40
41    pub fn is_transitional(&self) -> bool {
42        matches!(
43            self,
44            Self::Updating | Self::GettingReady | Self::Adopting | Self::PendingAdoption
45        )
46    }
47}
48
49/// Port on a switch or gateway.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Port {
52    pub index: u32,
53    pub name: Option<String>,
54    pub state: PortState,
55    pub speed_mbps: Option<u32>,
56    pub max_speed_mbps: Option<u32>,
57    pub connector: Option<PortConnector>,
58    pub poe: Option<PoeInfo>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub enum PortState {
63    Up,
64    Down,
65    Unknown,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69pub enum PortConnector {
70    Rj45,
71    Sfp,
72    SfpPlus,
73    Sfp28,
74    Qsfp28,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PoeInfo {
79    pub standard: Option<String>,
80    pub enabled: bool,
81    pub state: PortState,
82}
83
84/// High-level operational mode of a switch port's VLAN profile.
85///
86/// Derived from the Session API's `port_table` / `port_overrides` fields
87/// (`tagged_vlan_mgmt`, `op_mode`) into a single normalized value.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase")]
90pub enum PortMode {
91    /// Single untagged VLAN (no tagged VLANs allowed).
92    Access,
93    /// Untagged native VLAN plus one or more tagged VLANs.
94    Trunk,
95    /// Port mirrors another port (SPAN/RSPAN).
96    Mirror,
97    /// Mode could not be determined from the available data.
98    Unknown,
99}
100
101/// Spanning-Tree Protocol state for a port, as reported by the switch.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(rename_all = "lowercase")]
104pub enum StpState {
105    Disabled,
106    Blocking,
107    Listening,
108    Learning,
109    Forwarding,
110    Broken,
111    Unknown,
112}
113
114/// PoE operating mode for a switch port (configuration, not live state).
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "lowercase")]
117pub enum PoeMode {
118    /// Automatic negotiation (802.3af/at/bt).
119    Auto,
120    /// PoE explicitly disabled on this port.
121    Off,
122    /// Passive 24V (legacy).
123    Passive24V,
124    /// PoE passthrough (for specific switches).
125    Passthrough,
126    /// Unknown or vendor-specific mode.
127    Other,
128}
129
130/// Configured auto-negotiation / link speed for a port.
131///
132/// `Auto` means negotiate; other variants pin the link to a fixed speed.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "lowercase")]
135pub enum PortSpeedSetting {
136    Auto,
137    Mbps10,
138    Mbps100,
139    Mbps1000,
140    Mbps2500,
141    Mbps5000,
142    Mbps10000,
143}
144
145impl PortSpeedSetting {
146    /// Numeric link speed in Mbps, or `None` for `Auto`.
147    ///
148    /// Useful for comparing a configured pinned speed against the live
149    /// negotiated speed without round-tripping through strings.
150    pub fn as_mbps(self) -> Option<u32> {
151        match self {
152            Self::Auto => None,
153            Self::Mbps10 => Some(10),
154            Self::Mbps100 => Some(100),
155            Self::Mbps1000 => Some(1000),
156            Self::Mbps2500 => Some(2500),
157            Self::Mbps5000 => Some(5000),
158            Self::Mbps10000 => Some(10000),
159        }
160    }
161}
162
163/// VLAN and physical profile for a switch port, merged from the Session API's
164/// `port_table` (live state) and `port_overrides` (user configuration).
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct PortProfile {
167    /// 1-based port index as shown in the UniFi UI.
168    pub index: u32,
169    /// User-configured port label (from overrides) or auto-generated name.
170    pub name: Option<String>,
171    /// Link state (up/down/unknown).
172    pub link_state: PortState,
173    /// Operational mode (access / trunk / mirror / unknown).
174    pub mode: PortMode,
175    /// Session `_id` of the native (untagged) network, if any.
176    pub native_network_id: Option<String>,
177    /// Resolved VLAN id of the native network, if known.
178    pub native_vlan_id: Option<u16>,
179    /// Resolved display name of the native network, if known.
180    pub native_network_name: Option<String>,
181    /// Session `_id`s of explicitly tagged networks.
182    pub tagged_network_ids: Vec<String>,
183    /// Resolved VLAN ids of explicitly tagged networks (best-effort).
184    pub tagged_vlan_ids: Vec<u16>,
185    /// Resolved display names of explicitly tagged networks (best-effort).
186    pub tagged_network_names: Vec<String>,
187    /// Whether the trunk carries all tagged VLANs (UniFi "tagged_vlan_mgmt=auto").
188    pub tagged_all: bool,
189    /// Configured PoE mode, if the port supports PoE.
190    pub poe_mode: Option<PoeMode>,
191    /// Configured link speed setting.
192    pub speed_setting: Option<PortSpeedSetting>,
193    /// Current negotiated link speed in Mbps, from live state.
194    pub link_speed_mbps: Option<u32>,
195    /// STP state reported by the switch.
196    pub stp_state: StpState,
197    /// Reference to a named port profile (portconf), if one is applied.
198    pub port_profile_id: Option<String>,
199}
200
201/// Radio on an access point.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct Radio {
204    pub frequency_ghz: f32,
205    pub channel: Option<u32>,
206    pub channel_width_mhz: Option<u32>,
207    pub wlan_standard: Option<String>,
208    pub tx_retries_pct: Option<f64>,
209    pub channel_utilization_pct: Option<f64>,
210}
211
212/// Real-time device statistics.
213#[derive(Debug, Clone, Default, Serialize, Deserialize)]
214pub struct DeviceStats {
215    pub uptime_secs: Option<u64>,
216    pub cpu_utilization_pct: Option<f64>,
217    pub memory_utilization_pct: Option<f64>,
218    pub load_average_1m: Option<f64>,
219    pub load_average_5m: Option<f64>,
220    pub load_average_15m: Option<f64>,
221    pub uplink_bandwidth: Option<Bandwidth>,
222    pub last_heartbeat: Option<DateTime<Utc>>,
223    pub next_heartbeat: Option<DateTime<Utc>>,
224}
225
226/// The canonical Device type. Merges data from Integration + Session API.
227#[derive(Debug, Clone, Serialize, Deserialize)]
228#[allow(clippy::struct_excessive_bools)]
229pub struct Device {
230    pub id: EntityId,
231    pub mac: MacAddress,
232    pub ip: Option<IpAddr>,
233    pub wan_ipv6: Option<String>,
234    pub name: Option<String>,
235    pub model: Option<String>,
236    pub device_type: DeviceType,
237    pub state: DeviceState,
238
239    // Firmware
240    pub firmware_version: Option<String>,
241    pub firmware_updatable: bool,
242
243    // Lifecycle
244    pub adopted_at: Option<DateTime<Utc>>,
245    pub provisioned_at: Option<DateTime<Utc>>,
246    pub last_seen: Option<DateTime<Utc>>,
247
248    // Hardware
249    pub serial: Option<String>,
250    pub supported: bool,
251
252    // Interfaces
253    pub ports: Vec<Port>,
254    pub radios: Vec<Radio>,
255
256    // Uplink
257    pub uplink_device_id: Option<EntityId>,
258    pub uplink_device_mac: Option<MacAddress>,
259    /// 1-based switch port index this device is uplinked through, if the
260    /// uplink is wired and the upstream switch reported the remote port.
261    /// `None` for wireless uplinks and root devices.
262    pub uplink_port_idx: Option<u32>,
263
264    // Features (from Integration API)
265    pub has_switching: bool,
266    pub has_access_point: bool,
267
268    // Real-time stats (populated from statistics endpoint or WebSocket)
269    pub stats: DeviceStats,
270
271    // Client count (if known)
272    pub client_count: Option<u32>,
273
274    // Metadata
275    pub origin: Option<EntityOrigin>,
276
277    #[serde(skip)]
278    #[allow(dead_code)]
279    pub(crate) source: DataSource,
280    #[serde(skip)]
281    #[allow(dead_code)]
282    pub(crate) updated_at: DateTime<Utc>,
283}