Getting started with ESP32 and Rust

It was very easy to get the very basics of ESP32 and Rust up -- to the point where I could compile and run code on the ESP32.

But then the next step, where it connects to WiFi, took a few evenings. So here's my documentation.

This is all current as of 2022-12-04, and as I have the impression the APIs are not yet very stable (from the various demo code that didn't compile for me), YMMV.

I'm only targeting developing with std, for now.

The book is rather good at explaining setting up the enviroment.

Setting up the environment

This is rather easy, assuming you already have Rust set up:

cargo install ldproxy espup espflash cargo-generate
espup install
apt install libtinfo5
source ~/export-esp.sh

Project setup

Generate with cargo generate from a template, like so (run from the parent directory of the project-to-be):

cargo generate --git https://github.com/esp-rs/esp-idf-template cargo

(I chose "4.4"/"stable" as the ESP-IDF version).

A cargo run should now download a lot of stuff (a project directory has about 3GB for me), compile, upload, and show the serial console.

Getting WiFi running

This is where the various demo projects on GitHub didn't work for me, besides from being way too complicated for my taste.

So here is what gets the ESP32 connected to my WiFi, waits for it to be up, and pings the gateway.

Cargo.toml

[dependencies]
esp-idf-sys = { version = "0.31.11", features = ["binstart"] }
esp-idf-svc = { version="0.43.4", features = ["std", "experimental"] }
esp-idf-hal = "0.39"
embedded-svc = "0.23"
#embedded-hal = "0.2"
anyhow = {version = "1", features = ["backtrace"]}
log = "0.4"

src/main.rs

use anyhow;
use anyhow::bail;
use embedded_svc::wifi::*;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::ping::EspPing;
use esp_idf_svc::wifi::EspWifi;
use esp_idf_sys::esp_restart;
use log::*;
use std::net::Ipv4Addr;

const WIFI_SSID: &str = "...";
const WIFI_PSK: &str = "...";
const WIFI_TIMEOUT: u64 = 10;

fn main() -> anyhow::Result<()> {
    esp_idf_sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();

    info!("Hello, world!");

    let peripherals = Peripherals::take().unwrap();
    let sysloop = EspSystemEventLoop::take().unwrap();

    let mut wifi = EspWifi::new(peripherals.modem, sysloop.clone(), None).unwrap();
    dbg!(wifi.get_capabilities());

    let wifi_cfg = Configuration::Client(ClientConfiguration {
        ssid: WIFI_SSID.into(),
        password: WIFI_PSK.into(),
        // WPA2WPA3Personal doesn't work with WPA2-only AP
        auth_method: AuthMethod::WPA2WPA3Personal,
        bssid: None,
        channel: None,
    });

    wifi.set_configuration(&wifi_cfg).unwrap();

    wifi.start()?;
    wifi.connect()?;

    // there is  esp_idf_svc::netif::EspNetifWait, but it doesn't work
    // for me...
    let mut have_ip = false;
    for _ in 0..WIFI_TIMEOUT {
        if wifi.is_connected()? && wifi.sta_netif().get_ip_info()?.ip != Ipv4Addr::UNSPECIFIED {
            have_ip = true;
            break;
        }
        FreeRtos::delay_ms(1000);
    }

    // I've run into that issue where it only connects every second boot, so:
    if !have_ip {
        println!("REBOOTING");
        unsafe { esp_restart() };
    }

    let ip_info = wifi.sta_netif().get_ip_info()?;

    info!("Wifi DHCP info: {:?}", ip_info);

    ping(ip_info.subnet.gateway)?;

    info!("DONE (for now)");
    loop {
        println!(
            "up? {}, connected? {}",
            wifi.is_up().unwrap(),
            wifi.is_connected().unwrap()
        );
        FreeRtos::delay_ms(1000);
    }
}

// stolen from https://github.com/ivmarkov/rust-esp32-std-demo
fn ping(ip: Ipv4Addr) -> anyhow::Result<()> {
    info!("About to do some pings for {:?}", ip);

    let ping_summary = EspPing::default().ping(ip, &Default::default())?;
    if ping_summary.transmitted != ping_summary.received {
        bail!("Pinging IP {} resulted in timeouts", ip);
    }

    info!("Pinging done");

    Ok(())
}