csv.rs
1 use super::sensor_types::*; 2 use wasm_bindgen::JsCast; 3 4 pub fn download_csv(filename: &str, csv_content: &str) { 5 let array = js_sys::Array::new(); 6 array.push(&wasm_bindgen::JsValue::from_str(csv_content)); 7 let mut options = web_sys::BlobPropertyBag::new(); 8 options.type_("text/csv"); 9 10 let Ok(blob) = web_sys::Blob::new_with_str_sequence_and_options(&array, &options) else { 11 return; 12 }; 13 let Ok(url) = web_sys::Url::create_object_url_with_blob(&blob) else { 14 return; 15 }; 16 17 if let Some(document) = web_sys::window().and_then(|w| w.document()) { 18 if let Ok(anchor) = document.create_element("a") { 19 let _ = anchor.set_attribute("href", &url); 20 let _ = anchor.set_attribute("download", filename); 21 let anchor: web_sys::HtmlElement = anchor.unchecked_into(); 22 anchor.click(); 23 } 24 } 25 26 let _ = web_sys::Url::revoke_object_url(&url); 27 } 28 29 pub trait CsvRow { 30 fn header(sample: &[Self]) -> String 31 where 32 Self: Sized; 33 fn to_row(&self) -> String; 34 } 35 36 impl CsvRow for Co2Row { 37 fn header(_: &[Self]) -> String { 38 "#,CO2_PPM,TEMP_C,HUMIDITY_PCT,TIME".to_string() 39 } 40 41 fn to_row(&self) -> String { 42 format!( 43 "{},{},{},{},{}", 44 self.row, self.co2_ppm, self.temperature, self.humidity, self.time 45 ) 46 } 47 } 48 49 impl CsvRow for TemperatureHumidityRow { 50 fn header(sample: &[Self]) -> String { 51 let sensor_count = sample.first().map(|row| row.sensors.len()).unwrap_or(0); 52 let mut header = String::from("#,"); 53 for i in 0..sensor_count { 54 header.push_str(&format!("TEMP_{i}_C,RH_{i}_PCT,")); 55 } 56 header.push_str("TIME"); 57 header 58 } 59 60 fn to_row(&self) -> String { 61 let mut row = format!("{},", self.row); 62 for sensor in &self.sensors { 63 if sensor.read_ok { 64 row.push_str(&format!( 65 "{},{},", 66 sensor.temperature_celsius, sensor.relative_humidity_percent 67 )); 68 } else { 69 row.push_str(",,"); 70 } 71 } 72 row.push_str(&self.time); 73 row 74 } 75 } 76 77 impl CsvRow for VoltageRow { 78 fn header(_: &[Self]) -> String { 79 "#,CH0_V,CH1_V,CH2_V,CH3_V,TIME".to_string() 80 } 81 82 fn to_row(&self) -> String { 83 let mut row = format!("{},", self.row); 84 for (i, voltage) in self.channels.iter().enumerate() { 85 row.push_str(&format!("{voltage:.4}")); 86 if i < self.channels.len() - 1 { 87 row.push(','); 88 } 89 } 90 row.push_str(&format!(",{}", self.time)); 91 row 92 } 93 } 94 95 impl CsvRow for PressureRow { 96 fn header(_: &[Self]) -> String { 97 "#,MODEL,PRESSURE_HPA,TEMP_C,TIME".to_string() 98 } 99 100 fn to_row(&self) -> String { 101 format!( 102 "{},{},{:.2},{:.1},{}", 103 self.row, self.model, self.pressure_hpa, self.temperature_celsius, self.time 104 ) 105 } 106 } 107 108 pub fn build_csv<T: CsvRow>(readings: &[T]) -> String { 109 let mut csv = T::header(readings); 110 csv.push('\n'); 111 for row in readings { 112 csv.push_str(&row.to_row()); 113 csv.push('\n'); 114 } 115 csv 116 }