やりたいこと: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