eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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:
MCargo.lock | 34++++++++++++++++++++++++++++++++++
Mclient/ble.py | 3+++
Mecu-esp32/Cargo.toml | 1+
Mecu-esp32/src/main.rs | 75++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mecu-esp32/src/ota.rs | 39++++++++++++++++++++++++++++++++++-----
Mecu-esp32/src/wibble.rs | 25+++++++++++++++++--------
Mecu-esp32/src/wifi.rs | 18------------------
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!"),