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