temperature_and_humidity.rs
1 //! Temperature and humidity sensor program. 2 //! 3 //! Reads from an SHT31-compatible I2C sensor and logs to the SD card CSV. 4 5 use defmt::info; 6 use embassy_time::{Duration, Instant, Ticker, Timer}; 7 use esp_hal::i2c::master::I2c; 8 9 use crate::hardware::i2c::{SENSOR_MEASUREMENT_COMMAND, calculate_crc8}; 10 use crate::services::data_logger; 11 use crate::sensors::manager::{self, TemperatureHumidityReading}; 12 13 // ─── Sensor reading ──────────────────────────────────────────────────────────── 14 15 fn convert_temperature(raw: u16) -> f32 { 16 -45.0 + 175.0 * (raw as f32 / 65535.0) 17 } 18 19 fn convert_humidity(raw: u16) -> f32 { 20 100.0 * (raw as f32 / 65535.0) 21 } 22 23 async fn read_once( 24 i2c_bus: &mut I2c<'static, esp_hal::Async>, 25 sensor_address: u8, 26 ) -> Result<(f32, f32), &'static str> { 27 i2c_bus 28 .write_async(sensor_address, &SENSOR_MEASUREMENT_COMMAND) 29 .await 30 .map_err(|_| "failed to send measurement command")?; 31 32 Timer::after(Duration::from_millis(60)).await; 33 34 let mut buf = [0_u8; 6]; 35 i2c_bus 36 .read_async(sensor_address, &mut buf) 37 .await 38 .map_err(|_| "failed to read measurement bytes")?; 39 40 let temp_bytes = [buf[0], buf[1]]; 41 let hum_bytes = [buf[3], buf[4]]; 42 43 if buf[2] != calculate_crc8(&temp_bytes) { 44 return Err("temperature CRC mismatch"); 45 } 46 if buf[5] != calculate_crc8(&hum_bytes) { 47 return Err("humidity CRC mismatch"); 48 } 49 50 Ok(( 51 convert_temperature(u16::from_be_bytes(temp_bytes)), 52 convert_humidity(u16::from_be_bytes(hum_bytes)), 53 )) 54 } 55 56 // ─── Data logging task ───────────────────────────────────────────────────────── 57 58 #[embassy_executor::task] 59 pub async fn task( 60 mut i2c_bus: I2c<'static, esp_hal::Async>, 61 sensor_address: u8, 62 sensor_index: usize, 63 sensor_name: &'static str, 64 ) { 65 let mut sampling_interval = Ticker::every(Duration::from_secs( 66 crate::config::app::data_logger::SAMPLING_INTERVAL_SECS, 67 )); 68 69 loop { 70 sampling_interval.next().await; 71 72 match read_once(&mut i2c_bus, sensor_address).await { 73 Ok((temperature_celsius, relative_humidity_percent)) => { 74 manager::publish_temperature_humidity_reading( 75 sensor_index, 76 TemperatureHumidityReading { 77 ok: true, 78 temperature_celsius, 79 relative_humidity_percent, 80 model: "SHT31", 81 name: sensor_name, 82 }, 83 ); 84 let timestamp_millis = Instant::now().as_millis(); 85 if let Err(msg) = data_logger::append_temperature_humidity_sample( 86 timestamp_millis, 87 temperature_celsius, 88 relative_humidity_percent, 89 ) { 90 info!("failed to append data.csv row: {}", msg); 91 } else { 92 info!( 93 "logged {} sample: temperature={}C humidity={}%%", 94 sensor_name, temperature_celsius, relative_humidity_percent 95 ); 96 } 97 } 98 Err(msg) => { 99 manager::mark_temperature_humidity_unavailable(sensor_index); 100 info!("failed to read {} sensor: {}", sensor_name, msg); 101 } 102 } 103 } 104 }