Air Quality Terminal
This section assumes you are using the Nix package manager. If you have a different environment, don't worry—most commands will be the same, except you won't need to launch nix-shell first.
If you have successfully built the example, let's understand how it works step by step.
The Altruist board currently has a set of examples for different sensors:
altruist-sensor-temp.rs
altruist-sensor-press.rs
altruist-sensor-pm25.rs
We'll start from altruist-sensor-pm25.rs example, it's based on PM sensors.
Explore the code
no_std
#![no_std]
#![no_main]
The file starts with no_*
declarations, which is typical for bare-metal software since there's no operating system or standard library available by default.
Imports
#![allow(unused)] fn main() { use esp_backtrace as _; use log::info; use embassy_executor::Spawner; use embassy_time::Timer; use rohi_hal::Sensor; use rohi_hal::board::Altruist; }
Here we import Embassy Framework related types and SDK types like Altruist
and Sensor
.
Main task
#[esp_hal_embassy::main] async fn main(spawner: Spawner) { esp_println::logger::init_logger_from_env(); let altruist = Altruist::init().await.unwrap(); spawner.spawn(print_pm25_task(altruist)).ok(); }
The main task spawns first and handles basic initialization and child task launching. See the Embassy Docs for details.
Here, the Altruist
hardware is initialized and stored in a local variable, then passed to the child working task print_pm25_task
.
Work task
#![allow(unused)] fn main() { #[embassy_executor::task] async fn print_pm25_task(mut board: Altruist) { let mut sensor = Sensor(&mut board); loop { info!("PM25: {:?}", sensor.pm25().await); Timer::after_secs(10).await; } } }
The work task handles all operations—in this case, it simply gets PM2.5 measurements and prints them to the logs (terminal).
You'll notice multiple await
calls. This instruction puts the CPU to sleep until a waiting event occurs—whether it's a time interval
for Timer
or reading measurements from a peripheral device for sensor
. Rather than running in an infinite loop checking conditions,
the CPU enters a low-power mode to conserve energy and resources. During await
periods, other tasks can use the CPU time to execute their operations.
Flashing
Flashing firmware is quite simple—just launch it using cargo as you would run any Rust binary.
cargo run --release --bin example-altruist-sensor-pm25
Behind the scenes, several processes occur:
- The Rust compiler creates a RISC-V binary;
espflash
detects the ESP board and flashes the firmware;espflash
connects to the serial interface and displays logs.