chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

espupを使うことですんなりビルドが通った。感謝→Lチカをやってみる

やりたいこと:Rustを使ってESP32C3(RISC-V)でMQTTで通信するCO2センサを開発する
課題:普通のソースでビルドが通らない
取り組み:
理解不足というのと、いろんな方法があるようでもあり、環境構築にまずさに起因してRISC-V(ESP32C3)用のRustでのコンパイルが通らなかった。たまたま見かけた記事にespupを使うと良いとあったので、それを試した。すると環境構築からビルドまでエラーなく一発で通った。ありがたい。
espup
Rust (std) on ESP32-C3 で OSC からシリアル LED (WS2812 / SK6812) を動かす
GitHub - esp-rs/esp-rust-board: Open Hardware with ESP32-C3 compatible with Feather specification designed in KiCad

次はLED点滅をやらせたい。NetPixelではなくてGPIOを1/0操作して素のLEDを光らせたい。
作りたいアプリはMQTT通信できるCO2センサのため、WiFiを使うのが前提なので、std環境必須であり、std環境版のLチカを試す。
ありがたいことに、std版Lチカを説明してくれている記事がありそれを参考にする
ESP32でstdなRust開発入門 その2 Lチカ | Lang-ship
ソースを租借して自分なりに書きなおす力量がないので(力量がないから丸写しにしかならないので)、詳細は上記を見てください。
元のソースが?以下のようである。
esp-idf-hal/examples/blinky.rs at master · esp-rs/esp-idf-hal · GitHub

    let peripherals = Peripherals::take().unwrap();
    let mut led = PinDriver::output(peripherals.pins.gpio4)?;

MicroPythonの場合だと、2行目相当の操作だけでGPIO操作できるのだが、組み込みRustの場合、peripheralsを生成してから、led用ポートを作るらしい。。うーん、分からん。ソースをよく読んだらなぜ2段階になっているのか分かるのだろうか。Peripherals::take()ということで、「周辺IOを自分は使います」と排他的に宣言するのだろうか??

6 | fn main() -> anyhow::Result<()> {
  |              ^^^^^^ use of undeclared crate or module `anyhow`

anyhow知らんと怒られる。なぜだろうか。build設定ファイルでanyhowを宣言する必要あり?
Rust/AnyhowのTips
ちょっと調べると、追加される性質のcrateのようであり、Cargo.tomlでdependncyに書く必要があった。これで正しいのかちょっと分からないが、依存対象ということで追加

[dependencies]
anyhow = "1"

[build-dependencies]
anyhow = "1"

mainは若干個性が出たのでこちらに記載

use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::*;
use esp_idf_hal::peripherals::Peripherals;
use anyhow::Result;

fn main() -> Result<()> {

    esp_idf_sys::link_patches();
    println!("### LED Blink Test ###");

    let peripherals = Peripherals::take().unwrap();
    let mut led = PinDriver::output(peripherals.pins.gpio7)?;

    println!("** loop start! **");
    loop {
        println!("LED H");
        led.set_high()?;
        FreeRtos::delay_ms(1000);
        println!("LED L");
        led.set_low()?;
        FreeRtos::delay_ms(1000);
    }
}

delayの書き方は、Githubのサンプル(blinky.rs)を引用。おかげ様で、std環境でLチカまではできた。
以下は上記のLチカプログラムでESP Rust Board上のLEDが点滅しているところ

全部入りデモサンプルコードからWiFi部分だけを抜き出してみた。httpdを使っていないはずなのだが、以下のuse文を抜くとmainで型異常になる。なぜなのか??

use embedded_svc::httpd::*;     // <<<<?????

自力でできるミニマムなコードは以下。Rustのことをよく理解していたらもっと縮められるのかもしれない。下手に削るとエラーになるのだが、エラー原因がよくわからない(定義ファイルが欠けるからと言うのまでは分かるけど、どのcrate?で定義されているのか、crate?どおしの関係性が分からない)

#![allow(unused_imports)]
#![allow(clippy::single_component_path_imports)]

use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::env;
use std::time::*;

use anyhow::bail;
use log::*;

use embedded_svc::eth;
#[allow(deprecated)]
use embedded_svc::httpd::*;     // <<<<?????
use embedded_svc::io;
use embedded_svc::ipv4;
use embedded_svc::ping::Ping;
use embedded_svc::wifi::*;
use esp_idf_svc::eventloop::*;

use esp_idf_svc::netif::*;
use esp_idf_svc::ping;
use esp_idf_svc::wifi::*;
use esp_idf_hal::peripheral;
use esp_idf_hal::prelude::*;

use esp_idf_sys;
use esp_idf_sys::{esp, EspError};

const SSID: &str = env!("RUST_ESP32_STD_DEMO_WIFI_SSID");
const PASS: &str = env!("RUST_ESP32_STD_DEMO_WIFI_PASS");


fn main() -> Result<()> {

    esp_idf_sys::link_patches();

    #[allow(unused)]
    let peripherals = Peripherals::take().unwrap();
    let sysloop = EspSystemEventLoop::take()?;

    #[allow(clippy::redundant_clone)]
    #[allow(unused_mut)]
    let mut _wifi = wifi(peripherals.modem, sysloop.clone())?;

    test_tcp()?;

    Ok(())
}


fn wifi(
    modem: impl peripheral::Peripheral<P = esp_idf_hal::modem::Modem> + 'static,
    sysloop: EspSystemEventLoop,) -> Result<Box<EspWifi<'static>>> {

    use std::net::Ipv4Addr;
    use esp_idf_svc::handle::RawHandle;
    let mut wifi = Box::new(EspWifi::new(modem, sysloop.clone(), None)?);
    info!("Wifi created, about to scan");
    let ap_infos = wifi.scan()?;
    let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
    let channel = if let Some(ours) = ours {
        info!(
            "Found configured access point {} on channel {}",
            SSID, ours.channel
        );
        Some(ours.channel)
    } else {
        info!(
            "Configured access point {} not found during scanning, will go with unknown channel",
            SSID
        );
        None
    };

    wifi.set_configuration(&Configuration::Mixed(
        ClientConfiguration {
            ssid: SSID.into(),
            password: PASS.into(),
            channel,
            ..Default::default()
        },
        AccessPointConfiguration {
            ssid: "aptest".into(),
            channel: channel.unwrap_or(1),
            ..Default::default()
        },
    ))?;

    wifi.start()?;
    info!("Starting wifi...");
    if !WifiWait::new(&sysloop)?
        .wait_with_timeout(Duration::from_secs(20), || wifi.is_started().unwrap())
    {
        bail!("Wifi did not start");
    }
    info!("Connecting wifi...");
    wifi.connect()?;
    if !EspNetifWait::new::<EspNetif>(wifi.sta_netif(), &sysloop)?.wait_with_timeout(
        Duration::from_secs(20),
        || {
            wifi.is_connected().unwrap()
                && wifi.sta_netif().get_ip_info().unwrap().ip != Ipv4Addr::new(0, 0, 0, 0)
        },
    ) {
        bail!("Wifi did not connect or did not receive a DHCP lease");
    }
    let ip_info = wifi.sta_netif().get_ip_info()?;
    info!("Wifi DHCP info: {:?}", ip_info);
    ping(ip_info.subnet.gateway)?;
    Ok(wifi)
}


fn ping(ip: ipv4::Ipv4Addr) -> Result<()> {
    info!("About to do some pings for {:?}", ip);

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


fn test_tcp() -> Result<()> {
    info!("About to open a TCP connection to 1.1.1.1 port 80");

    let mut stream = TcpStream::connect("one.one.one.one:80")?;
    let err = stream.try_clone();
    if let Err(err) = err {
        info!(
            "Duplication of file descriptors does not work (yet) on the ESP-IDF, as expected: {}",
            err
        );
    }
    stream.write_all("GET / HTTP/1.0\n\n".as_bytes())?;
    let mut result = Vec::new();
    stream.read_to_end(&mut result)?;
    info!(
        "1.1.1.1 returned:\n=================\n{}\n=================\nSince it returned something, all is OK",
        std::str::from_utf8(&result)?);

    Ok(())
}

以下でflashにやいて、シリアルモニタで動作を確認

espflash /dev/ttyACM0  target/riscv32imc-esp-espidf/debug/test-wi-fi
espflash serial-monitor

不明な点 info!()で出力されるはずのメッセージが上記シリアルモニタでは表示されない。なぜか? print!()だと表示される。以下のような記事もあり、infoの出力先を指定してビルドする必要がある?

・RUST_LOG環境変数をINFOに設定する
RUST_LOG=INFO cargo run

■ご参考URL
GitHub - esp-rs/espup: Tool for installing and maintaining Espressif Rust ecosystem.
ほぼ全部入りデモサンプル
rust-esp32-std-demo/src/main.rs at main · ivmarkov/rust-esp32-std-demo · GitHub