Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

ExampleSensor
example-bmx280BMP280 / BME280 pressure, temperature, humidity
example-scd4xSCD40 / SCD41 CO₂, temperature, humidity
example-sds011SDS011 particulate matter (PM2.5 / PM10)
example-noiseI²S microphone noise level
example-all-sensorsAll 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:

  1. The Rust compiler creates a RISC-V binary;
  2. espflash detects the ESP board and flashes the firmware;
  3. espflash connects 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.