Skip to main content

unifly_api/legacy/
devices.rs

1// Legacy API device endpoints
2//
3// Device management via stat/device (read) and cmd/devmgr (commands).
4// Covers listing, adoption, restart, firmware upgrade, and LED locate.
5
6use serde_json::json;
7use tracing::debug;
8
9use crate::error::Error;
10use crate::legacy::client::LegacyClient;
11use crate::legacy::models::LegacyDevice;
12
13impl LegacyClient {
14    /// List all devices with full statistics.
15    ///
16    /// `GET /api/s/{site}/stat/device`
17    pub async fn list_devices(&self) -> Result<Vec<LegacyDevice>, Error> {
18        let url = self.site_url("stat/device");
19        debug!("listing devices");
20        self.get(url).await
21    }
22
23    /// Get a single device by MAC address.
24    ///
25    /// Filters the device list by MAC. Returns `None` if no device matches.
26    pub async fn get_device(&self, mac: &str) -> Result<Option<LegacyDevice>, Error> {
27        let url = self.site_url("stat/device");
28        let body = json!({ "macs": [mac.to_lowercase()] });
29        let devices: Vec<LegacyDevice> = self.post(url, &body).await?;
30        Ok(devices.into_iter().next())
31    }
32
33    /// Adopt a pending device.
34    ///
35    /// `POST /api/s/{site}/cmd/devmgr` with `{"cmd": "adopt", "mac": "..."}`
36    pub async fn adopt_device(&self, mac: &str) -> Result<(), Error> {
37        let url = self.site_url("cmd/devmgr");
38        debug!(mac, "adopting device");
39        let _: Vec<serde_json::Value> = self
40            .post(
41                url,
42                &json!({
43                    "cmd": "adopt",
44                    "mac": mac,
45                }),
46            )
47            .await?;
48        Ok(())
49    }
50
51    /// Restart a device.
52    ///
53    /// `POST /api/s/{site}/cmd/devmgr` with `{"cmd": "restart", "mac": "..."}`
54    pub async fn restart_device(&self, mac: &str) -> Result<(), Error> {
55        let url = self.site_url("cmd/devmgr");
56        debug!(mac, "restarting device");
57        let _: Vec<serde_json::Value> = self
58            .post(
59                url,
60                &json!({
61                    "cmd": "restart",
62                    "mac": mac,
63                }),
64            )
65            .await?;
66        Ok(())
67    }
68
69    /// Upgrade device firmware.
70    ///
71    /// If `url` is `Some`, upgrades from that URL (`cmd: "upgrade-external"`).
72    /// Otherwise upgrades from Ubiquiti's cloud (`cmd: "upgrade"`).
73    pub async fn upgrade_device(&self, mac: &str, firmware_url: Option<&str>) -> Result<(), Error> {
74        let api_url = self.site_url("cmd/devmgr");
75        debug!(mac, ?firmware_url, "upgrading device firmware");
76
77        let body = match firmware_url {
78            Some(fw_url) => json!({
79                "cmd": "upgrade-external",
80                "mac": mac,
81                "url": fw_url,
82            }),
83            None => json!({
84                "cmd": "upgrade",
85                "mac": mac,
86            }),
87        };
88
89        let _: Vec<serde_json::Value> = self.post(api_url, &body).await?;
90        Ok(())
91    }
92
93    /// Force re-provision a device configuration.
94    ///
95    /// `POST /api/s/{site}/cmd/devmgr` with `{"cmd": "force-provision", "mac": "..."}`
96    pub async fn provision_device(&self, mac: &str) -> Result<(), Error> {
97        let url = self.site_url("cmd/devmgr");
98        debug!(mac, "force-provisioning device");
99        let _: Vec<serde_json::Value> = self
100            .post(
101                url,
102                &json!({
103                    "cmd": "force-provision",
104                    "mac": mac,
105                }),
106            )
107            .await?;
108        Ok(())
109    }
110
111    /// Trigger a site speed test (gateway).
112    ///
113    /// `POST /api/s/{site}/cmd/devmgr` with `{"cmd": "speedtest"}`
114    pub async fn speedtest(&self) -> Result<(), Error> {
115        let url = self.site_url("cmd/devmgr");
116        debug!("starting speed test");
117        let _: Vec<serde_json::Value> = self.post(url, &json!({ "cmd": "speedtest" })).await?;
118        Ok(())
119    }
120
121    /// Toggle the LED locator on a device.
122    ///
123    /// `enable: true` sends `set-locate`, `false` sends `unset-locate`.
124    pub async fn locate_device(&self, mac: &str, enable: bool) -> Result<(), Error> {
125        let url = self.site_url("cmd/devmgr");
126        let cmd = if enable { "set-locate" } else { "unset-locate" };
127        debug!(mac, cmd, "toggling device locate LED");
128        let _: Vec<serde_json::Value> = self
129            .post(
130                url,
131                &json!({
132                    "cmd": cmd,
133                    "mac": mac,
134                }),
135            )
136            .await?;
137        Ok(())
138    }
139}