From 4e07dbb461dbc8513481498390a2e84b47a96b30 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Thu, 12 Jun 2025 14:40:59 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9F=20pmme-device:=20BT=5FNIMBLE=20sup?= =?UTF-8?q?port=20for=20wifi=20provisioning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmme-device/rust-version/Cargo.toml | 4 +- pmme-device/rust-version/sdkconfig.defaults | 8 + pmme-device/rust-version/src/display.rs | 22 +- pmme-device/rust-version/src/lib.rs | 4 +- pmme-device/rust-version/src/main.rs | 54 +++-- pmme-device/rust-version/src/sensors.rs | 11 +- pmme-device/rust-version/src/wifi.rs | 237 +++++++++++++++----- 7 files changed, 243 insertions(+), 97 deletions(-) diff --git a/pmme-device/rust-version/Cargo.toml b/pmme-device/rust-version/Cargo.toml index cc2e393..6192416 100644 --- a/pmme-device/rust-version/Cargo.toml +++ b/pmme-device/rust-version/Cargo.toml @@ -19,8 +19,7 @@ opt-level = "z" [features] default = [] - -# experimental = ["esp-idf-svc/experimental"] +experimental = ["esp-idf-svc/experimental"] [dependencies] log = "0.4" @@ -28,7 +27,6 @@ esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time- anyhow = "1.0.98" embedded-graphics = "0.8.1" ssd1306 = "0.10.0" -# esp-idf-hal = { version = "0.45.2", features = ["std"] } [build-dependencies] embuild = "0.33" diff --git a/pmme-device/rust-version/sdkconfig.defaults b/pmme-device/rust-version/sdkconfig.defaults index c25b89d..1f368c8 100644 --- a/pmme-device/rust-version/sdkconfig.defaults +++ b/pmme-device/rust-version/sdkconfig.defaults @@ -1,5 +1,13 @@ # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 +# CONFIG_PARTITION_TABLE_CUSTOM=y +# CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="./partitions.csv" + +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_WIFI_PROV_SCHEME_BLE=y +# CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +# CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granularity for thread sleeps (10 ms by default). diff --git a/pmme-device/rust-version/src/display.rs b/pmme-device/rust-version/src/display.rs index 58a4851..b14ceb1 100644 --- a/pmme-device/rust-version/src/display.rs +++ b/pmme-device/rust-version/src/display.rs @@ -1,13 +1,23 @@ use crate::Pms7003Data; -use std::sync::{Arc, Mutex}; -use log::info; use anyhow::{anyhow, Result}; +use log::info; +use std::sync::{Arc, Mutex}; -use embedded_graphics::{Drawable, image::{Image, ImageRaw}, mono_font::{ascii::FONT_10X20, MonoTextStyle}, pixelcolor::BinaryColor, prelude::Point, text::Text}; use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::{ + image::{Image, ImageRaw}, + mono_font::{ascii::FONT_10X20, MonoTextStyle}, + pixelcolor::BinaryColor, + prelude::Point, + text::Text, + Drawable, +}; use esp_idf_svc::hal::{delay::FreeRtos, i2c::I2cDriver}; -use ssd1306::{mode::DisplayConfig, prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306}; +use ssd1306::{ + mode::DisplayConfig, prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, + Ssd1306, +}; pub fn oled_task(pm_data: Arc>, i2c: I2cDriver) -> Result<()> { info!("Staring OLED Task"); @@ -41,9 +51,7 @@ pub fn oled_task(pm_data: Arc>, i2c: I2cDriver) -> Result<()> .map_err(|e| anyhow!("Could not draw text! {:?}", e))?; display .flush() - .map_err(|e| { - anyhow!("Could not flush display! {:?}", e) - })?; + .map_err(|e| anyhow!("Could not flush display! {:?}", e))?; FreeRtos::delay_ms(500); } } diff --git a/pmme-device/rust-version/src/lib.rs b/pmme-device/rust-version/src/lib.rs index 0a81221..37cce9d 100644 --- a/pmme-device/rust-version/src/lib.rs +++ b/pmme-device/rust-version/src/lib.rs @@ -1,6 +1,6 @@ -pub mod wifi; -pub mod sensors; pub mod display; +pub mod sensors; +pub mod wifi; #[derive(Debug, Clone, Default)] pub struct Pms7003Data { diff --git a/pmme-device/rust-version/src/main.rs b/pmme-device/rust-version/src/main.rs index a78fb52..6fa5eb0 100644 --- a/pmme-device/rust-version/src/main.rs +++ b/pmme-device/rust-version/src/main.rs @@ -1,15 +1,23 @@ -use std::{sync::{Arc, Mutex}, thread}; use anyhow::Result; - -use esp_idf_svc::hal::{ - gpio, - i2c::{I2cConfig, I2cDriver}, - peripherals::Peripherals, - prelude::FromValueType, - uart::{config, UartDriver}, - units::Hertz, +use std::{ + sync::{Arc, Mutex}, + thread, }; -use pmme_device::{display::oled_task, sensors::pm_sensor_task, Pms7003Data}; + +use esp_idf_svc::{ + eventloop::EspSystemEventLoop, + hal::{ + gpio, + i2c::{I2cConfig, I2cDriver}, + peripherals::Peripherals, + prelude::FromValueType, + uart::{self, UartDriver}, + units::Hertz, + }, + log::EspLogger, + nvs::EspDefaultNvsPartition, +}; +use pmme_device::{display::oled_task, sensors::sensors_task, wifi::wifi_task, Pms7003Data}; fn main() -> Result<()> { // It is necessary to call this function once. Otherwise some patches to the runtime @@ -17,9 +25,8 @@ fn main() -> Result<()> { esp_idf_svc::sys::link_patches(); // Initialize NVS - // esp_idf_hal::nvs::EspDefaultNvsPartition::take() - // .context("Could not take NVS partition")?; - esp_idf_svc::log::EspLogger::initialize_default(); + EspLogger::initialize_default(); + let nvs = EspDefaultNvsPartition::take()?; let peripherals = Peripherals::take()?; let i2c = peripherals.i2c0; @@ -32,7 +39,7 @@ fn main() -> Result<()> { let tx = peripherals.pins.gpio17; let rx = peripherals.pins.gpio16; - let config = config::Config::new().baudrate(Hertz(9600)); + let config = uart::config::Config::new().baudrate(Hertz(9600)); let uart = UartDriver::new( peripherals.uart2, tx, @@ -42,19 +49,28 @@ fn main() -> Result<()> { &config, )?; - let pm_data = Arc::new(Mutex::new(Pms7003Data::default())); + let sysloop = EspSystemEventLoop::take()?; + let modem = peripherals.modem; - let pm_data1 = Arc::clone(&pm_data); - let pm_data2 = Arc::clone(&pm_data); + let pm_data = Arc::new(Mutex::new(Pms7003Data::default())); + let oled_arc = Arc::clone(&pm_data); + let sensors_arc = Arc::clone(&pm_data); + // let wifi_arc = Arc::clone(&pm_data); let handles = vec![ thread::spawn(move || { - if let Err(e) = oled_task(pm_data1, i2c) { + if let Err(e) = oled_task(oled_arc, i2c) { log::error!("OLED Task Error: {:?}", e); } }), thread::spawn(move || { - if let Err(e) = pm_sensor_task(pm_data2, &uart) { + if let Err(e) = sensors_task(sensors_arc, &uart) { + log::error!("PM Sensor Task Error: {:?}", e); + } + }), + thread::spawn(move || { + // if let Err(e) = wifi_task(wifi_arc, &uart) { + if let Err(e) = wifi_task(modem, sysloop, nvs) { log::error!("PM Sensor Task Error: {:?}", e); } }), diff --git a/pmme-device/rust-version/src/sensors.rs b/pmme-device/rust-version/src/sensors.rs index d8cb8d8..05fdad3 100644 --- a/pmme-device/rust-version/src/sensors.rs +++ b/pmme-device/rust-version/src/sensors.rs @@ -1,10 +1,13 @@ use crate::Pms7003Data; -use std::sync::{Arc, Mutex}; -use log::{info, warn}; use anyhow::Result; +use log::{info, warn}; +use std::sync::{Arc, Mutex}; -use esp_idf_svc::hal::{delay::{FreeRtos, BLOCK}, uart::UartDriver}; +use esp_idf_svc::hal::{ + delay::{FreeRtos, BLOCK}, + uart::UartDriver, +}; // u16::from_be_bytes is too verbose fn get_u16(buf: &[u8]) -> u16 { @@ -45,7 +48,7 @@ fn send_pms7003_command(uart: &UartDriver, cmd: u8, datah: u8, datal: u8) -> Res Ok(()) } -pub fn pm_sensor_task(pm_data: Arc>, uart: &UartDriver) -> Result<()> { +pub fn sensors_task(pm_data: Arc>, uart: &UartDriver) -> Result<()> { info!("Staring UART PM Sensor Task"); send_pms7003_command(uart, 0xE1, 0x00, 0x00)?; diff --git a/pmme-device/rust-version/src/wifi.rs b/pmme-device/rust-version/src/wifi.rs index a429e0d..5bb5952 100644 --- a/pmme-device/rust-version/src/wifi.rs +++ b/pmme-device/rust-version/src/wifi.rs @@ -1,79 +1,192 @@ +use anyhow::Result; use log::info; -use anyhow::bail; use esp_idf_svc::{ eventloop::EspSystemEventLoop, hal::peripheral, - wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi}, + nvs::EspDefaultNvsPartition, + sys::{ + esp, esp_err_t, wifi_prov_event_handler_t, wifi_prov_mgr_config_t, wifi_prov_mgr_deinit, wifi_prov_mgr_init, wifi_prov_mgr_is_provisioned, wifi_prov_mgr_start_provisioning, wifi_prov_mgr_stop_provisioning, wifi_prov_mgr_wait, wifi_prov_scheme_ble, wifi_prov_security_WIFI_PROV_SECURITY_1, wifi_prov_security_t, EspError + }, + wifi::{BlockingWifi, ClientConfiguration, Configuration, EspWifi}, }; +use std::ffi::c_void; +use std::ffi::CString; +use std::ptr; -pub fn wifi( - ssid: &str, - pass: &str, +pub struct WifiProvisioning; + +impl WifiProvisioning { + pub fn new() -> Result { + unsafe { + // Updated struct initialization + let config = wifi_prov_mgr_config_t { + scheme: wifi_prov_scheme_ble, // ble provisioning + scheme_event_handler: wifi_prov_event_handler_t { + event_cb: None, // No custom callback + user_data: ptr::null_mut(), + }, + app_event_handler: wifi_prov_event_handler_t { + event_cb: None, // No custom callback + user_data: ptr::null_mut(), + }, + }; + esp!(wifi_prov_mgr_init(config))?; + } + Ok(WifiProvisioning) + } + + pub fn start_provisioning( + &self, + security: wifi_prov_security_t, + pop: &str, + service_name: &str, + service_key: Option<&str>, + ) -> Result<(), EspError> { + let pop = CString::new(pop).unwrap(); + let service_name = CString::new(service_name).unwrap(); + let service_key = service_key.map(|key| CString::new(key).unwrap()); + let pop_ptr: *const c_void = pop.as_ptr() as *const c_void; + unsafe { + esp!(wifi_prov_mgr_start_provisioning( + security, + pop_ptr, + service_name.as_ptr(), + service_key.map_or(ptr::null(), |k| k.as_ptr()), + ))?; + } + Ok(()) + } + + pub fn wait(&self) { + unsafe { + wifi_prov_mgr_wait(); + } + } + + pub fn is_provisioned(&self) -> Result { + let mut provisioned: bool = false; + let result: esp_err_t = unsafe { wifi_prov_mgr_is_provisioned(&mut provisioned) }; + if result == 0 { + Ok(provisioned) + } else { + Err(EspError::from(result).unwrap()) + } + } + + pub fn stop(&self) { + unsafe { + wifi_prov_mgr_stop_provisioning(); + } + } +} + +impl Drop for WifiProvisioning { + fn drop(&mut self) { + unsafe { + wifi_prov_mgr_deinit(); + } + } +} + +pub fn wifi_task( modem: impl peripheral::Peripheral

+ 'static, sysloop: EspSystemEventLoop, -) -> anyhow::Result>> { - let mut auth_method = AuthMethod::WPA2Personal; - if ssid.is_empty() { - bail!("Missing WiFi name") - } - if pass.is_empty() { - auth_method = AuthMethod::None; - info!("Wifi password is empty"); - } - let mut esp_wifi = EspWifi::new(modem, sysloop.clone(), None)?; + nvs: EspDefaultNvsPartition, +) -> Result<()> { + info!("Provisioning device!"); + return Ok(()); + let wifi = EspWifi::new(modem, sysloop.clone(), Some(nvs))?; + let mut wifi = BlockingWifi::wrap(wifi, sysloop)?; + let prov = WifiProvisioning::new()?; + if !prov.is_provisioned()? { + let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration { + ..Default::default() + }); + wifi.set_configuration(&wifi_configuration)?; + wifi.start()?; + prov.start_provisioning( + wifi_prov_security_WIFI_PROV_SECURITY_1, + "abcd1234", // Proof of Possession (POP) + "PROV_ESP32", // Service Name + None, // No Service Key + )?; - let mut wifi = BlockingWifi::wrap(&mut esp_wifi, sysloop)?; + println!("Waiting for Wi-Fi provisioning..."); + prov.wait(); - wifi.set_configuration(&Configuration::Client(ClientConfiguration::default()))?; - - info!("Starting wifi..."); - - wifi.start()?; - - info!("Scanning..."); - - 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) + println!("Provisioning completed. Stopping..."); + prov.stop(); } else { - info!( - "Configured access point {} not found during scanning, will go with unknown channel", - ssid - ); - None - }; - - wifi.set_configuration(&Configuration::Client(ClientConfiguration { - ssid: ssid - .try_into() - .expect("Could not parse the given SSID into WiFi config"), - password: pass - .try_into() - .expect("Could not parse the given password into WiFi config"), - channel, - auth_method, - ..Default::default() - }))?; - - info!("Connecting wifi..."); - - wifi.connect()?; - - info!("Waiting for DHCP lease..."); - + wifi.start()?; + wifi.connect()?; + } wifi.wait_netif_up()?; - let ip_info = wifi.wifi().sta_netif().get_ip_info()?; + println!("Wifi DHCP info: {:?}", ip_info); - info!("Wifi DHCP info: {:?}", ip_info); + // let mut auth_method = AuthMethod::WPA2Personal; + // if ssid.is_empty() { + // bail!("Missing WiFi name") + // } + // if pass.is_empty() { + // auth_method = AuthMethod::None; + // info!("Wifi password is empty"); + // } + // let mut esp_wifi = EspWifi::new(modem, sysloop.clone(), None)?; - Ok(Box::new(esp_wifi)) + // let mut wifi = BlockingWifi::wrap(&mut esp_wifi, sysloop)?; + + // wifi.set_configuration(&Configuration::Client(ClientConfiguration::default()))?; + + // info!("Starting wifi..."); + + // wifi.start()?; + + // info!("Scanning..."); + + // 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::Client(ClientConfiguration { + // ssid: ssid + // .try_into() + // .expect("Could not parse the given SSID into WiFi config"), + // password: pass + // .try_into() + // .expect("Could not parse the given password into WiFi config"), + // channel, + // auth_method, + // ..Default::default() + // }))?; + + // info!("Connecting wifi..."); + + // wifi.connect()?; + + // info!("Waiting for DHCP lease..."); + + // wifi.wait_netif_up()?; + + // let ip_info = wifi.wifi().sta_netif().get_ip_info()?; + + // info!("Wifi DHCP info: {:?}", ip_info); + + // Ok(Box::new(esp_wifi)) + Ok(()) }