api.rs
1 use serde::{Deserialize, Serialize}; 2 3 pub const DEFAULT_DEVICE_URL: &str = "http://ceratina.local"; 4 5 // ─── /api/system/device/status ────────────────────────────────────────────── 6 7 #[derive(Debug, Clone, Deserialize)] 8 pub struct DeviceStatusEnvelope { 9 pub data: DeviceStatusData, 10 pub time: String, 11 } 12 13 #[derive(Debug, Clone, Deserialize)] 14 pub struct DeviceStatusData { 15 pub device: DeviceIdentity, 16 pub network: DeviceNetwork, 17 pub runtime: DeviceRuntime, 18 pub sleep: DeviceSleep, 19 pub storage: DeviceStorage, 20 } 21 22 #[derive(Debug, Clone, Deserialize)] 23 pub struct DeviceIdentity { 24 pub chip_model: String, 25 pub chip_cores: u32, 26 pub chip_revision: u32, 27 } 28 29 #[derive(Debug, Clone, Deserialize)] 30 pub struct DeviceNetwork { 31 pub ipv4_address: String, 32 pub wifi_rssi: i32, 33 } 34 35 #[derive(Debug, Clone, Deserialize)] 36 pub struct DeviceRuntime { 37 pub uptime: String, 38 pub uptime_seconds: u64, 39 #[serde(default)] 40 pub temperature_celsius: f64, 41 pub memory_heap_free: u64, 42 #[serde(default)] 43 pub memory_heap_total: u64, 44 #[serde(default)] 45 pub memory_heap_min_free: u64, 46 #[serde(default)] 47 pub memory_heap_max_alloc: u64, 48 #[serde(default)] 49 pub memory_psram_total: u64, 50 #[serde(default)] 51 pub memory_psram_free: u64, 52 } 53 54 #[derive(Debug, Clone, Deserialize)] 55 pub struct DeviceStorage { 56 pub location: String, 57 pub total_bytes: u64, 58 pub used_bytes: u64, 59 pub free_bytes: u64, 60 } 61 62 impl DeviceStorage { 63 pub fn percent_used(&self) -> f64 { 64 if self.total_bytes == 0 { 65 0.0 66 } else { 67 (self.used_bytes as f64 / self.total_bytes as f64) * 100.0 68 } 69 } 70 } 71 72 #[derive(Debug, Clone, Deserialize)] 73 pub struct DeviceSleep { 74 #[serde(default)] 75 pub pending: bool, 76 #[serde(default)] 77 pub requested_duration_seconds: u64, 78 #[serde(default)] 79 pub wake_cause: String, 80 #[serde(default)] 81 pub timer_wakeup_enabled: bool, 82 #[serde(default)] 83 pub timer_wakeup_us: u64, 84 #[serde(default)] 85 pub enabled: bool, 86 #[serde(default)] 87 pub default_duration_seconds: u64, 88 } 89 90 #[derive(Debug, Clone, Deserialize)] 91 pub struct SleepConfigResponse { 92 pub ok: bool, 93 pub data: SleepConfigData, 94 } 95 96 #[derive(Debug, Clone, Deserialize, Serialize)] 97 pub struct SleepConfigData { 98 pub enabled: bool, 99 pub duration_seconds: u64, 100 } 101 102 // ─── /api/cloudevents ─────────────────────────────────────────────────────── 103 104 #[derive(Debug, Clone, Deserialize, Serialize)] 105 pub struct CloudEvent { 106 pub id: String, 107 #[serde(rename = "type")] 108 pub event_type: String, 109 pub time: String, 110 pub data: serde_json::Value, 111 } 112 113 // ─── /api/wireless/status ─────────────────────────────────────────────────── 114 115 #[derive(Debug, Clone, Deserialize)] 116 pub struct WirelessStatusResponse { 117 pub ok: bool, 118 pub data: WirelessStatusData, 119 } 120 121 #[derive(Debug, Clone, Deserialize)] 122 pub struct WirelessStatusData { 123 pub connected: bool, 124 pub sta_ssid: String, 125 pub sta_ipv4: String, 126 pub wifi_rssi: i32, 127 pub ap_active: bool, 128 pub ap_ssid: String, 129 pub ap_ipv4: String, 130 } 131 132 // ─── /api/wireless/actions/scan ───────────────────────────────────────────── 133 134 #[derive(Debug, Clone, Deserialize)] 135 pub struct WifiScanResponse { 136 pub ok: bool, 137 pub data: WifiScanData, 138 } 139 140 #[derive(Debug, Clone, Deserialize)] 141 pub struct WifiScanData { 142 pub scan_count: u32, 143 pub networks: Vec<WifiNetwork>, 144 } 145 146 #[derive(Debug, Clone, Deserialize)] 147 pub struct WifiNetwork { 148 pub ssid: String, 149 pub rssi: i32, 150 pub channel: u8, 151 pub encryption: String, 152 pub open: bool, 153 } 154 155 // ─── /api/filesystem/list ─────────────────────────────────────────────────── 156 157 #[derive(Debug, Clone, Deserialize, PartialEq)] 158 pub struct FileEntry { 159 pub name: String, 160 pub size: u64, 161 } 162 163 // ─── CO2 control ──────────────────────────────────────────────────────────── 164 165 #[derive(Debug, Clone, Deserialize)] 166 pub struct Co2ConfigResponse { 167 pub ok: bool, 168 pub data: Co2ConfigData, 169 } 170 171 #[derive(Debug, Clone, Deserialize)] 172 pub struct Co2ConfigData { 173 pub model: String, 174 pub measuring: bool, 175 pub measurement_interval_seconds: u16, 176 pub auto_calibration_enabled: bool, 177 pub temperature_offset_celsius: f64, 178 pub altitude_meters: u16, 179 } 180 181 // ─── Utilities ────────────────────────────────────────────────────────────── 182 183 pub fn format_file_size(bytes: u64) -> String { 184 if bytes < 1024 { 185 format!("{} B", bytes) 186 } else if bytes < 1024 * 1024 { 187 format!("{:.1} KB", bytes as f64 / 1024.0) 188 } else { 189 format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0)) 190 } 191 } 192 193 pub fn format_storage_pair(used: u64, total: u64) -> String { 194 let (used_val, total_val, unit) = if total < 1024 { 195 (used as f64, total as f64, "B") 196 } else if total < 1024 * 1024 { 197 (used as f64 / 1024.0, total as f64 / 1024.0, "KB") 198 } else { 199 ( 200 used as f64 / (1024.0 * 1024.0), 201 total as f64 / (1024.0 * 1024.0), 202 "MB", 203 ) 204 }; 205 format!("{used_val:.1} / {total_val:.1} {unit}") 206 }