First device: Air Quality Terminal
This section assumes you are using the Nix package manager (
nix develop). If you have a different environment, don’t worry—most commands will be the same, except you won’t need to enter the Nix shell first.
If you have successfully installed the SDK, let’s understand how the Altruist examples work step by step. The Altruist board has one example per supported sensor:
| Example | Sensor |
|---|---|
example-bmx280 | BMP280 / BME280 pressure, temperature, humidity |
example-scd4x | SCD40 / SCD41 CO₂, temperature, humidity |
example-sds011 | SDS011 particulate matter (PM2.5 / PM10) |
example-noise | I²S microphone noise level |
example-all-sensors | All of the above, concurrently |
We’ll start from the sds011.rs example, it’s based on a particulate matter sensor. One SDS011 cycle takes about two minutes: the sensor wakes up, warms up, takes several samples, and the aggregated PM2.5 / PM10 is printed before the sensor goes back to sleep.
Explore the code
///////////////////////////////////////////////////////////////////////////////
#![no_std]
#![no_main]
//! SDS011 example: prints PM2.5 and PM10 once per duty cycle (about 2 minutes).
use altruist_urban::tasks::sensors::sds011_task;
use esp_backtrace as _;
use rohi_examples::{print_events_task, run};
esp_bootloader_esp_idf::esp_app_desc!();
#[esp_hal::main]
fn main() -> ! {
run(|spawner, board| {
spawner.spawn(print_events_task().unwrap());
spawner.spawn(sds011_task(board.sds011_uart).unwrap());
})
}
no_std
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.
Main task
main is a plain #[esp_hal::main] function. run() configures logging, the esp-rtos runtime that
provides the time base of the Embassy Framework (with a small heap for its task stacks) and the board (I²C, UART and I²S pins are defined in
crates/rohi-hal-esp/src/board/altruist_urban/), then starts the Embassy executor, which spawns the two tasks below.
See the Embassy Docs for details.
Sensor task and events
sds011_task runs the SDS011 driver in a Runner that handles probing, polling and recovery. Drivers know nothing
about networks or serialization: they publish typed SensorEvents (measurements, state changes, errors) into a
bounded queue; a successful measurement alone marks the sensor Ready. print_events_task consumes them and
prints to the terminal. The production firmware has a single task that applies the events to a MeasurementStore
instead. See Sensor HAL (rohi-hal).
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 a timer or reading measurements from a peripheral device. 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 -p rohi-examples --features esp32c3 --target riscv32imc-unknown-none-elf --bin example-sds011
Behind the scenes, several processes occur:
- The Rust compiler creates a RISC-V binary;
espflashdetects the ESP board and flashes the firmware;espflashconnects to the serial interface and displays logs.
Talk to the device
Once the firmware runs, the rohi tool finds it over Bluetooth LE and shows its status, sensors and
Wi-Fi.