api.rs
1 use heapless::String as HeaplessString; 2 use picoserve::response::{IntoResponse, Json}; 3 use serde::Serialize; 4 5 use crate::sensors::manager; 6 use crate::services::system; 7 8 #[derive(Serialize)] 9 struct ApiResponse<'a> { 10 co2: Co2Data, 11 status: StatusData<'a>, 12 sensors: heapless::Vec<SensorData<'a>, { manager::MAX_SENSOR_COUNT }>, 13 files: heapless::Vec<FileData, 64>, 14 } 15 16 #[derive(Serialize)] 17 struct Co2Data { 18 co2_ppm: f32, 19 temperature: f32, 20 humidity: f32, 21 model: &'static str, 22 ok: bool, 23 } 24 25 #[derive(Serialize)] 26 struct StatusData<'a> { 27 hostname: &'a str, 28 platform: &'a str, 29 uptime_seconds: u64, 30 heap_free: usize, 31 heap_used: usize, 32 sd_card_mb: u32, 33 sleep_pending: bool, 34 wake_cause: &'a str, 35 data_log_path: &'a str, 36 data_log_interval_seconds: u64, 37 } 38 39 #[derive(Serialize)] 40 struct SensorData<'a> { 41 name: &'a str, 42 model: &'a str, 43 transport: SensorTransportData, 44 live: SensorLiveData, 45 } 46 47 #[derive(Serialize)] 48 #[serde(tag = "kind", rename_all = "snake_case")] 49 enum SensorTransportData { 50 I2c { 51 bus_index: u8, 52 address: u8, 53 mux_channel: i8, 54 }, 55 Modbus { 56 channel: u8, 57 slave_id: u8, 58 register_address: u16, 59 }, 60 } 61 62 #[derive(Serialize)] 63 #[serde(tag = "kind", rename_all = "snake_case")] 64 enum SensorLiveData { 65 None, 66 Co2 { 67 ok: bool, 68 co2_ppm: f32, 69 temperature: f32, 70 humidity: f32, 71 }, 72 TemperatureHumidity { 73 ok: bool, 74 temperature_celsius: f32, 75 relative_humidity_percent: f32, 76 }, 77 } 78 79 #[derive(Serialize)] 80 struct FileData { 81 name: HeaplessString<32>, 82 size: u32, 83 } 84 85 pub async fn api_handler() -> impl IntoResponse { 86 let snapshot = system::snapshot(); 87 88 let mut sensor_list = heapless::Vec::<SensorData<'_>, { manager::MAX_SENSOR_COUNT }>::new(); 89 for sensor in snapshot.sensors.inventory.iter() { 90 let transport_summary = sensor.transport_summary(); 91 let transport = if let Some(address) = transport_summary.address { 92 SensorTransportData::I2c { 93 bus_index: transport_summary.bus_index.unwrap_or_default(), 94 address, 95 mux_channel: transport_summary.mux_channel.unwrap_or(-1), 96 } 97 } else { 98 SensorTransportData::Modbus { 99 channel: transport_summary.channel.unwrap_or_default(), 100 slave_id: transport_summary.slave_id.unwrap_or_default(), 101 register_address: transport_summary.register_address.unwrap_or_default(), 102 } 103 }; 104 105 let live = if let Some(reading) = sensor.carbon_dioxide_reading() { 106 SensorLiveData::Co2 { 107 ok: reading.ok, 108 co2_ppm: reading.co2_ppm, 109 temperature: reading.temperature, 110 humidity: reading.humidity, 111 } 112 } else if let Some(reading) = sensor.temperature_humidity_reading() { 113 SensorLiveData::TemperatureHumidity { 114 ok: reading.ok, 115 temperature_celsius: reading.temperature_celsius, 116 relative_humidity_percent: reading.relative_humidity_percent, 117 } 118 } else { 119 SensorLiveData::None 120 }; 121 122 let _ = sensor_list.push(SensorData { 123 name: sensor.name, 124 model: sensor.model, 125 transport, 126 live, 127 }); 128 } 129 130 let mut file_list = heapless::Vec::<FileData, 64>::new(); 131 if let Ok(entries) = crate::filesystems::sd::list_filesystem_entries() { 132 for entry in &entries { 133 let _ = file_list.push(FileData { 134 name: entry.name.clone(), 135 size: entry.size, 136 }); 137 } 138 } 139 140 Json(ApiResponse { 141 co2: Co2Data { 142 co2_ppm: snapshot.sensors.carbon_dioxide.co2_ppm, 143 temperature: snapshot.sensors.carbon_dioxide.temperature, 144 humidity: snapshot.sensors.carbon_dioxide.humidity, 145 model: snapshot.sensors.carbon_dioxide.model, 146 ok: snapshot.sensors.carbon_dioxide.ok, 147 }, 148 status: StatusData { 149 hostname: snapshot.hostname, 150 platform: snapshot.platform, 151 uptime_seconds: snapshot.uptime_seconds, 152 heap_free: snapshot.heap_free, 153 heap_used: snapshot.heap_used, 154 sd_card_mb: snapshot.storage.sd_card_size_mb, 155 sleep_pending: snapshot.sleep.pending, 156 wake_cause: snapshot.sleep.wake_cause, 157 data_log_path: snapshot.data_logger.path, 158 data_log_interval_seconds: snapshot.data_logger.interval_seconds, 159 }, 160 sensors: sensor_list, 161 files: file_list, 162 }) 163 .into_response() 164 .with_header("Access-Control-Allow-Origin", "*") 165 }