diff --git a/pmme-device/rust-version/src/display.rs b/pmme-device/rust-version/src/display.rs new file mode 100644 index 0000000..58a4851 --- /dev/null +++ b/pmme-device/rust-version/src/display.rs @@ -0,0 +1,49 @@ +use crate::Pms7003Data; + +use std::sync::{Arc, Mutex}; +use log::info; +use anyhow::{anyhow, Result}; + +use embedded_graphics::{Drawable, image::{Image, ImageRaw}, mono_font::{ascii::FONT_10X20, MonoTextStyle}, pixelcolor::BinaryColor, prelude::Point, text::Text}; +use embedded_graphics::draw_target::DrawTarget; +use esp_idf_svc::hal::{delay::FreeRtos, i2c::I2cDriver}; +use ssd1306::{mode::DisplayConfig, prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306}; + +pub fn oled_task(pm_data: Arc>, i2c: I2cDriver) -> Result<()> { + info!("Staring OLED Task"); + + let interface = I2CDisplayInterface::new(i2c); + let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + + display.init().unwrap(); + + let raw: ImageRaw = ImageRaw::new(include_bytes!("../rust.raw"), 64); + let im = Image::new(&raw, Point::new(32, 0)); + + im.draw(&mut display).unwrap(); + display + .flush() + .map_err(|e| anyhow!("Could not flush display! {:?}", e))?; + + FreeRtos::delay_ms(2000); + loop { + let current_pm_data = pm_data.lock().unwrap().clone(); + display + .clear(BinaryColor::Off) + .map_err(|e| anyhow!("Could not clear display! {:?}", e))?; + Text::new( + &format!("PM 2.5: {}", current_pm_data.pm2_5_atm), + Point::new(20, 40), + MonoTextStyle::new(&FONT_10X20, BinaryColor::On), + ) + .draw(&mut display) + .map_err(|e| anyhow!("Could not draw text! {:?}", e))?; + display + .flush() + .map_err(|e| { + anyhow!("Could not flush display! {:?}", e) + })?; + FreeRtos::delay_ms(500); + } +} diff --git a/pmme-device/rust-version/src/lib.rs b/pmme-device/rust-version/src/lib.rs index 3b0e25e..0a81221 100644 --- a/pmme-device/rust-version/src/lib.rs +++ b/pmme-device/rust-version/src/lib.rs @@ -1 +1,24 @@ pub mod wifi; +pub mod sensors; +pub mod display; + +#[derive(Debug, Clone, Default)] +pub struct Pms7003Data { + // PM concentrations (CF=1, standard particle) in μg/m³ + pub pm1_0_cf1: u16, + pub pm2_5_cf1: u16, + pub pm10_cf1: u16, + + // PM concentrations (atmospheric environment) in μg/m³ + pub pm1_0_atm: u16, + pub pm2_5_atm: u16, + pub pm10_atm: u16, + + // Particle counts (particles per 0.1L air) + pub particles_0_3um: u16, + pub particles_0_5um: u16, + pub particles_1_0um: u16, + pub particles_2_5um: u16, + pub particles_5_0um: u16, + pub particles_10um: u16, +} diff --git a/pmme-device/rust-version/src/main.rs b/pmme-device/rust-version/src/main.rs index 73531b7..a78fb52 100644 --- a/pmme-device/rust-version/src/main.rs +++ b/pmme-device/rust-version/src/main.rs @@ -1,6 +1,7 @@ -use anyhow::{anyhow, Result}; +use std::{sync::{Arc, Mutex}, thread}; +use anyhow::Result; + use esp_idf_svc::hal::{ - delay::{FreeRtos, BLOCK}, gpio, i2c::{I2cConfig, I2cDriver}, peripherals::Peripherals, @@ -8,140 +9,7 @@ use esp_idf_svc::hal::{ uart::{config, UartDriver}, units::Hertz, }; - -use embedded_graphics::{ - image::{Image, ImageRaw}, - mono_font::{ascii::FONT_10X20, MonoTextStyle}, - pixelcolor::BinaryColor, - prelude::*, - text::Text, -}; -use ssd1306::mode::DisplayConfig; -use ssd1306::{rotation::*, size::*, I2CDisplayInterface, Ssd1306}; - -use log::{info, warn}; -use std::sync::{Arc, Mutex}; -use std::thread; - -#[derive(Debug, Clone, Default)] -pub struct Pms7003Data { - // PM concentrations (CF=1, standard particle) in μg/m³ - pub pm1_0_cf1: u16, - pub pm2_5_cf1: u16, - pub pm10_cf1: u16, - - // PM concentrations (atmospheric environment) in μg/m³ - pub pm1_0_atm: u16, - pub pm2_5_atm: u16, - pub pm10_atm: u16, - - // Particle counts (particles per 0.1L air) - pub particles_0_3um: u16, - pub particles_0_5um: u16, - pub particles_1_0um: u16, - pub particles_2_5um: u16, - pub particles_5_0um: u16, - pub particles_10um: u16, -} - -// u16::from_be_bytes is too verbose -fn get_u16(buf: &[u8]) -> u16 { - ((buf[0] as u16) << 8) | buf[1] as u16 -} - -fn read_pm_data(buf: &[u8; 32]) -> Option { - let checksum: u16 = buf.iter().take(29).copied().map(u16::from).sum(); - let csum_target = get_u16(&buf[30..32]); - if checksum != csum_target { - warn!("Checksum from PMS7003 READ does not match, skipping read"); - return None; - } - Some(Pms7003Data { - pm1_0_cf1: get_u16(&buf[ 4..6 ]), - pm2_5_cf1: get_u16(&buf[ 6..8 ]), - pm10_cf1: get_u16(&buf[ 8..10]), - pm1_0_atm: get_u16(&buf[10..12]), - pm2_5_atm: get_u16(&buf[12..14]), - pm10_atm: get_u16(&buf[14..16]), - particles_0_3um: get_u16(&buf[16..18]), - particles_0_5um: get_u16(&buf[18..20]), - particles_1_0um: get_u16(&buf[20..22]), - particles_2_5um: get_u16(&buf[22..24]), - particles_5_0um: get_u16(&buf[24..26]), - particles_10um: get_u16(&buf[26..28]), - }) -} - -fn send_pms7003_command(uart: &UartDriver, cmd: u8, datah: u8, datal: u8) -> Result<()> { - let mut command = [0x42u8, 0x4D, cmd, datah, datal, 0, 0]; - - let checksum: u16 = command.iter().take(5).copied().map(u16::from).sum(); - command[5] = ((checksum >> 8) & 0xFF) as u8; - command[6] = (checksum & 0xFF) as u8; - - uart.write(&command)?; - Ok(()) -} - -fn pm_sensor_task(pm_data: Arc>, uart: &UartDriver) -> Result<()> { - info!("Staring UART PM Sensor Task"); - - send_pms7003_command(uart, 0xE1, 0x00, 0x00)?; - - loop { - send_pms7003_command(uart, 0xE2, 0x00, 0x00)?; - - let mut buf = [0_u8; 32]; - let len = uart.read(&mut buf, BLOCK)?; - if len > 0 { - if let Some(data) = read_pm_data(&buf) { - let mut pm_data = pm_data.lock().unwrap(); - *pm_data = data; - } - } else { - // info!("No bytes read"); - } - uart.clear_rx()?; - FreeRtos::delay_ms(5000); - } -} - -fn oled_task(pm_data: Arc>, i2c: I2cDriver) -> Result<()> { - info!("Staring OLED Task"); - - let interface = I2CDisplayInterface::new(i2c); - let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) - .into_buffered_graphics_mode(); - - display.init().unwrap(); - - let raw: ImageRaw = ImageRaw::new(include_bytes!("../rust.raw"), 64); - let im = Image::new(&raw, Point::new(32, 0)); - - im.draw(&mut display).unwrap(); - display - .flush() - .map_err(|e| anyhow!("Could not flush display! {:?}", e))?; - - FreeRtos::delay_ms(2000); - loop { - let current_pm_data = pm_data.lock().unwrap().clone(); - display - .clear(BinaryColor::Off) - .map_err(|e| anyhow!("Could not clear display! {:?}", e))?; - Text::new( - &format!("PM 2.5: {}", current_pm_data.pm2_5_atm), - Point::new(20, 40), - MonoTextStyle::new(&FONT_10X20, BinaryColor::On), - ) - .draw(&mut display) - .map_err(|e| anyhow!("Could not draw text! {:?}", e))?; - display - .flush() - .map_err(|e| anyhow!("Could not flush display! {:?}", e))?; - FreeRtos::delay_ms(500); - } -} +use pmme_device::{display::oled_task, sensors::pm_sensor_task, Pms7003Data}; fn main() -> Result<()> { // It is necessary to call this function once. Otherwise some patches to the runtime diff --git a/pmme-device/rust-version/src/sensors.rs b/pmme-device/rust-version/src/sensors.rs new file mode 100644 index 0000000..d8cb8d8 --- /dev/null +++ b/pmme-device/rust-version/src/sensors.rs @@ -0,0 +1,69 @@ +use crate::Pms7003Data; + +use std::sync::{Arc, Mutex}; +use log::{info, warn}; +use anyhow::Result; + +use esp_idf_svc::hal::{delay::{FreeRtos, BLOCK}, uart::UartDriver}; + +// u16::from_be_bytes is too verbose +fn get_u16(buf: &[u8]) -> u16 { + ((buf[0] as u16) << 8) | buf[1] as u16 +} + +fn read_pm_data(buf: &[u8; 32]) -> Option { + let checksum: u16 = buf.iter().take(29).copied().map(u16::from).sum(); + let csum_target = get_u16(&buf[30..32]); + if checksum != csum_target { + warn!("Checksum from PMS7003 READ does not match, skipping read"); + return None; + } + Some(Pms7003Data { + pm1_0_cf1: get_u16(&buf[ 4..6 ]), + pm2_5_cf1: get_u16(&buf[ 6..8 ]), + pm10_cf1: get_u16(&buf[ 8..10]), + pm1_0_atm: get_u16(&buf[10..12]), + pm2_5_atm: get_u16(&buf[12..14]), + pm10_atm: get_u16(&buf[14..16]), + particles_0_3um: get_u16(&buf[16..18]), + particles_0_5um: get_u16(&buf[18..20]), + particles_1_0um: get_u16(&buf[20..22]), + particles_2_5um: get_u16(&buf[22..24]), + particles_5_0um: get_u16(&buf[24..26]), + particles_10um: get_u16(&buf[26..28]), + }) +} + +fn send_pms7003_command(uart: &UartDriver, cmd: u8, datah: u8, datal: u8) -> Result<()> { + let mut command = [0x42u8, 0x4D, cmd, datah, datal, 0, 0]; + + let checksum: u16 = command.iter().take(5).copied().map(u16::from).sum(); + command[5] = ((checksum >> 8) & 0xFF) as u8; + command[6] = (checksum & 0xFF) as u8; + + uart.write(&command)?; + Ok(()) +} + +pub fn pm_sensor_task(pm_data: Arc>, uart: &UartDriver) -> Result<()> { + info!("Staring UART PM Sensor Task"); + + send_pms7003_command(uart, 0xE1, 0x00, 0x00)?; + + loop { + send_pms7003_command(uart, 0xE2, 0x00, 0x00)?; + + let mut buf = [0_u8; 32]; + let len = uart.read(&mut buf, BLOCK)?; + if len > 0 { + if let Some(data) = read_pm_data(&buf) { + let mut pm_data = pm_data.lock().unwrap(); + *pm_data = data; + } + } else { + // info!("No bytes read"); + } + uart.clear_rx()?; + FreeRtos::delay_ms(5000); + } +} diff --git a/pmme-device/rust-version/src/wifi.rs b/pmme-device/rust-version/src/wifi.rs index 7c58fda..a429e0d 100644 --- a/pmme-device/rust-version/src/wifi.rs +++ b/pmme-device/rust-version/src/wifi.rs @@ -1,10 +1,11 @@ +use log::info; use anyhow::bail; + use esp_idf_svc::{ eventloop::EspSystemEventLoop, hal::peripheral, wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi}, }; -use log::info; pub fn wifi( ssid: &str,