commit 601c7391a3715f7777a4bf56718f5cdfbdd70c2b
parent 2cfe436ac9e02c9bea7588041ace96ccb48580b0
Author: Daniel Barlow <dan@telent.net>
Date: Wed, 4 Feb 2026 00:37:24 +0000
write selected wifi credentials to flash nvs
todo
- reboot after writing
- delete bad networks after failing to connect
Diffstat:
7 files changed, 137 insertions(+), 58 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -430,6 +430,7 @@ dependencies = [
"esp-bootloader-esp-idf",
"esp-hal",
"esp-hal-ota",
+ "esp-nvs",
"esp-println",
"esp-radio",
"esp-rtos",
@@ -909,6 +910,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a93e39c8ad8d390d248dc7b9f4b59a873f313bf535218b8e2351356972399e3"
[[package]]
+name = "esp-nvs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14136d6d8690464e3ffe596634b45d21a3ae2314060da520994b68c74b9ab6b1"
+dependencies = [
+ "embedded-storage",
+ "esp-hal",
+ "esp-storage",
+ "strum",
+ "thiserror",
+]
+
+[[package]]
name = "esp-phy"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1965,6 +1979,26 @@ dependencies = [
]
[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
+[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/client/ble.py b/client/ble.py
@@ -4,6 +4,7 @@ import rlcompleter
import asyncio
from bleak import BleakClient,BleakScanner
import sys
+import os
readline.parse_and_bind("tab: complete")
@@ -43,6 +44,8 @@ async def choose_network(client, name):
await client.write_gatt_char(UUIDS['current_index'], bytes([chosen]))
new_index = await client.read_gatt_char(UUIDS['current_index'])
print(f"set to {new_index[0]}")
+ secret = os.getenv('PASSWORD')
+ await client.write_gatt_char(UUIDS['secret'], bytearray(secret.encode('utf-8')))
async def main(address):
diff --git a/ecu-esp32/Cargo.toml b/ecu-esp32/Cargo.toml
@@ -77,3 +77,4 @@ trouble-host = { version = "0.5.1", default-features = false, features = [
"default-packet-pool-mtu-64",
"dev-disable-csprng-seed-requirement" # !!!!
]}
+esp-nvs = { version = "0.2.0", features = ["esp32c3"] }
diff --git a/ecu-esp32/src/main.rs b/ecu-esp32/src/main.rs
@@ -11,6 +11,7 @@ use esp_println as _;
use esp_alloc as _;
use alloc::vec::Vec;
+use alloc::string::String;
pub mod ecu;
pub mod wifi;
@@ -48,6 +49,8 @@ extern crate alloc;
esp_bootloader_esp_idf::esp_app_desc!();
const BUILD_ID: &str = env!("BUILD_ID");
+const FORCE_WIFI_CHOOSER : bool = option_env!("FORCE_WIFI_CHOOSER").is_some();
+
macro_rules! mk_static {
($t:ty,$val:expr) => {{
@@ -58,6 +61,16 @@ macro_rules! mk_static {
}};
}
+fn wifi_creds_from_nvs(flasher: &mut Flasher) -> Option<(String, String)> {
+ let ssid = flasher.var("ssid");
+ let secret = flasher.var("secret");
+ if let (Some(ssid), Some(secret)) = (ssid, secret) {
+ Some((ssid, secret))
+ } else {
+ None
+ }
+}
+
// #[main]
#[esp_rtos::main]
async fn main(s : Spawner) {
@@ -86,7 +99,7 @@ async fn main(s : Spawner) {
);
- let flasher = Flasher::new(peripherals.FLASH, peripherals.SHA);
+ let mut flasher = Flasher::new(peripherals.FLASH, peripherals.SHA);
let esp_radio_ctrl = // esp_radio::init().expect("esp_radio init failed");
&*mk_static!(Controller<'static>, esp_radio::init().unwrap());
@@ -94,33 +107,41 @@ async fn main(s : Spawner) {
let (mut wifi_controller, interfaces) =
esp_radio::wifi::new(esp_radio_ctrl, peripherals.WIFI, Default::default()).unwrap();
let wifi_interface = interfaces.sta;
- let station_config = ModeConfig::Client(ClientConfig::default());
- wifi_controller.set_config(&station_config).expect("can't config wifi");
- wifi_controller.start_async().await.expect("wifi failed to start");
-
- let wifi_networks : Vec<[u8;40]> =
- crate::wifi::scan_networks(&mut wifi_controller).await.into_iter().map(
- |ap| {
- let mut s : [u8; 40] = [0u8; 40];
- s[0] = (ap.signal_strength & 0x7f) as u8;
- s[1..7].copy_from_slice(&ap.bssid);
- let name = ap.ssid.as_bytes();
- s[8..(8+name.len())].copy_from_slice(name);
- s
- }
- ).collect();
-
- join3(
+
+ let wifi_creds = wifi_creds_from_nvs(&mut flasher);
+ info!("stored wifi creds {:?}", wifi_creds);
+
+ if !FORCE_WIFI_CHOOSER && let Some((wifi_ssid, wifi_secret)) = wifi_creds {
+ info!("using stored wifi creds");
+ let station_config = ModeConfig::Client(ClientConfig::default()
+ .with_ssid(wifi_ssid)
+ .with_password(wifi_secret));
+
+ wifi_controller.set_config(&station_config).expect("can't config wifi");
+ wifi_controller.start_async().await.expect("wifi failed to start");
+
crate::wifi::start_wifi(&s,
wifi_controller, wifi_interface,
flasher,
- &mut ecu),
- wibble::run_ble(&esp_radio_ctrl, peripherals.BT, wifi_networks),
- async {
- loop {
- Timer::after_millis(1000).await;
- }
- }).await;
-
-
+ &mut ecu).await;
+ } else {
+ info!("no stored wifi creds");
+
+ let station_config = ModeConfig::Client(ClientConfig::default());
+ wifi_controller.set_config(&station_config).expect("can't config wifi");
+ wifi_controller.start_async().await.expect("wifi failed to start");
+
+ let wifi_networks : Vec<[u8;40]> =
+ crate::wifi::scan_networks(&mut wifi_controller).await.into_iter().map(
+ |ap| {
+ let mut s : [u8; 40] = [0u8; 40];
+ s[0] = (ap.signal_strength & 0x7f) as u8;
+ s[1..7].copy_from_slice(&ap.bssid);
+ let name = ap.ssid.as_bytes();
+ s[8..(8+name.len())].copy_from_slice(name);
+ s
+ }
+ ).collect();
+ wibble::run_ble(&esp_radio_ctrl, peripherals.BT, wifi_networks, flasher).await;
+ }
}
diff --git a/ecu-esp32/src/ota.rs b/ecu-esp32/src/ota.rs
@@ -13,12 +13,15 @@ use core::convert::Infallible;
use log::{warn, info};
+use alloc::string::String;
+
+
const ERASE_BLOCK_BYTES : usize = 4096;
const ED25519_SIGNATURE_BYTES : usize = 64;
const SHA256_BYTES : usize = 32;
pub struct Flasher<'a> {
- ota : Ota<FlashStorage<'a>>,
+ peripheral: esp_hal::peripherals::FLASH<'a>,
sha_engine : sha::Sha<'a>
}
@@ -52,15 +55,41 @@ impl From<Infallible> for FlashError {
impl Flasher<'_> {
pub fn new(flash_peripheral: esp_hal::peripherals::FLASH<'static>,
sha_peripheral: esp_hal::peripherals::SHA<'static>) -> Self {
- let flash = FlashStorage::new(flash_peripheral);
let sha_engine = sha::Sha::new(sha_peripheral);
- let ota = Ota::new(flash).unwrap();
- Self { ota, sha_engine }
+ Self { peripheral: flash_peripheral, sha_engine }
}
pub async fn write(&mut self, sock: &mut embassy_net::tcp::TcpSocket<'_>) -> Result<(), FlashError> {
- write_ota(&mut self.ota, &mut self.sha_engine, sock).await
+ let storage = FlashStorage::new(self.peripheral.reborrow());
+ let mut ota = Ota::new(storage).unwrap();
+ write_ota(&mut ota, &mut self.sha_engine, sock).await
+ }
+
+ pub fn var(&mut self, name: &str) -> Option<String> {
+ let storage = FlashStorage::new(self.peripheral.reborrow());
+ let mut nvs =
+ esp_nvs::Nvs::new(0x9000, 0x4000, storage).expect("failed to create nvs");
+ let ns = const { esp_nvs::Key::from_str("eculocate") };
+ match nvs.get(&ns, &esp_nvs::Key::from_str(name)) {
+ Ok(s) => Some(s),
+ Err(e) => None
+ }
+ }
+
+ pub fn set_var(&mut self, name: &str, value: Option<&str>) -> Result<(), esp_nvs::error::Error> {
+ let storage = FlashStorage::new(self.peripheral.reborrow());
+ let mut nvs =
+ esp_nvs::Nvs::new(0x9000, 0x4000, storage).expect("failed to create nvs");
+ let ns = const { esp_nvs::Key::from_str("eculocate") };
+ match value {
+ Some(v) => {
+ nvs.set(&ns, &esp_nvs::Key::from_str(name), v)
+ }
+ None => {
+ nvs.delete(&ns, &esp_nvs::Key::from_str(name))
+ }
+ }
}
}
diff --git a/ecu-esp32/src/wibble.rs b/ecu-esp32/src/wibble.rs
@@ -8,6 +8,8 @@ use embassy_futures::select::select;
use alloc::vec::Vec;
+use crate::ota::Flasher;
+
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
@@ -49,8 +51,8 @@ struct WifiProvisioningService {
pub async fn run_ble(radio_init: &RadioController<'static>,
peripheral: esp_hal::peripherals::BT<'_>,
- networks: Vec<[u8; 40]>) {
- // let radio_init = esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller");
+ networks: Vec<[u8; 40]>,
+ mut flasher: Flasher<'_>) {
let connector = BleConnector::new(&radio_init, peripheral, Default::default()).expect("connector");
let controller: ExternalController<_, 20> = ExternalController::new(connector);
@@ -79,7 +81,7 @@ pub async fn run_ble(radio_init: &RadioController<'static>,
match advertise("Eculocate", &mut peripheral, &server).await {
Ok(conn) => {
// set up tasks when the connection is established to a central, so they don't run when no one is connected.
- let a = gatt_events_task(&server, &conn, &networks);
+ let a = gatt_events_task(&server, &conn, &networks, &mut flasher);
let b = custom_task(&server, &conn, &stack);
// run until any task ends (usually because the connection has been closed),
// then return to advertising state.
@@ -110,12 +112,11 @@ async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
/// This is how we interact with read and write requests.
async fn gatt_events_task<P: PacketPool>(server: &Server<'_>,
conn: &GattConnection<'_, '_, P>,
- networks: &Vec<[u8; 40]> ) -> Result<(), Error> {
+ networks: &Vec<[u8; 40]>,
+ flasher: &mut Flasher<'_>) -> Result<(), Error> {
let wps = &server.wifi_provisioning_service;
- for ap in networks {
- info!("ap {:?}", ap)
- };
let max_index : u8 = (networks.len() & 0xff).try_into().unwrap();
+
server.set(&wps.max_network_index, &max_index);
let current_index = wps.current_network_index;
let reason = loop {
@@ -145,7 +146,15 @@ async fn gatt_events_task<P: PacketPool>(server: &Server<'_>,
} else {
warn!("index {:?} out of range", i);
}
- }
+ } else if event.handle() == wps.secret.handle {
+ info!("[gatt] got secret {:?}", event.data());
+ let n = server.get(&wps.current_network).unwrap();
+ let n_s = core::str::from_utf8(&n[8..]).unwrap();
+ let n0 =
+ if let Some(i) = n_s.find("\0") { i } else { n_s.len() };
+ flasher.set_var("ssid", Some(&n_s[..n0]));
+ flasher.set_var("secret", Some(core::str::from_utf8(event.data()).unwrap()));
+ }
},
_ => {}
};
diff --git a/ecu-esp32/src/wifi.rs b/ecu-esp32/src/wifi.rs
@@ -58,10 +58,6 @@ macro_rules! mk_static {
}};
}
-const SSID: &str = env!("SSID");
-const PASSWORD: &str = env!("PASSWORD");
-
-
fn dns_records(my_ip_address: [u8;4], serial: Option<usize>) -> Vec<mdns::Resource> {
use crate::mdns::*;
use crate::mdns::RData as R;
@@ -222,21 +218,7 @@ async fn connection(mut controller: WifiController<'static>) {
}
_ => {}
}
- if !matches!(controller.is_started(), Ok(true)) {
- let station_config = ModeConfig::Client(
- ClientConfig::default()
- );
- controller.set_config(&station_config).unwrap();
- controller.start_async().await.expect("wifi controller failed to start");
-
- }
println!("About to connect...");
- let station_config = ModeConfig::Client(
- ClientConfig::default()
- .with_ssid(SSID.into())
- .with_password(PASSWORD.into()),
- );
- controller.set_config(&station_config).unwrap();
match controller.connect_async().await {
Ok(_) => println!("Wifi connected!"),