Compare commits

...

3 Commits

Author SHA1 Message Date
cd63e76469 set read config initial somewhat ready 2025-09-17 03:50:21 +02:00
4c54edbcea littlefs2 impl stuff 2025-09-17 01:36:53 +02:00
8b938e7687 small adjustments 2025-09-16 22:41:45 +02:00
9 changed files with 306 additions and 214 deletions

View File

@ -159,6 +159,8 @@ edge-nal-embassy = "0.6.0"
static_cell = "2.1.1" static_cell = "2.1.1"
cfg-if = "1.0.3" cfg-if = "1.0.3"
edge-http = { version = "0.6.1", features = ["log"] } edge-http = { version = "0.6.1", features = ["log"] }
littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc", "serde"] }
littlefs2-core = "0.1.1"
[patch.crates-io] [patch.crates-io]

View File

@ -1,6 +1,8 @@
format = "EspIdf" [connection]
[idf_format_args] [[usb_device]]
vid = "303a"
pid = "1001"
[flash] [flash]
size = "16MB" size = "16MB"

View File

@ -3,4 +3,4 @@ otadata, data, ota, , 8k,
phy_init, data, phy, , 4k, phy_init, data, phy, , 4k,
ota_0, app, ota_0, , 3968k, ota_0, app, ota_0, , 3968k,
ota_1, app, ota_1, , 3968k, ota_1, app, ota_1, , 3968k,
storage, data, spiffs, , 8M, storage, data, littlefs,, 8M,

1 nvs data nvs 16k
3 phy_init data phy 4k
4 ota_0 app ota_0 3968k
5 ota_1 app ota_1 3968k
6 storage data spiffs littlefs 8M

View File

@ -0,0 +1,78 @@
use embedded_storage::{ReadStorage, Storage};
use esp_bootloader_esp_idf::partitions::FlashRegion;
use esp_storage::FlashStorage;
use littlefs2::consts::U1 as lfs2Array1;
use littlefs2::consts::U512 as lfs2Array512;
use littlefs2::driver::Storage as lfs2Storage;
use littlefs2::fs::Filesystem as lfs2Filesystem;
use littlefs2::io::Error as lfs2Error;
use littlefs2::io::Result as lfs2Result;
use log::{error, info};
pub struct LittleFs2Filesystem {
pub(crate) storage: &'static mut FlashRegion<'static, FlashStorage>,
}
impl lfs2Storage for LittleFs2Filesystem {
const READ_SIZE: usize = 512;
const WRITE_SIZE: usize = 512;
const BLOCK_SIZE: usize = 1024; //usually optimal for flash access
const BLOCK_COUNT: usize = 8 * 1024 * 1024 / 1024; //8mb in 32kb blocks
const BLOCK_CYCLES: isize = 100;
type CACHE_SIZE = lfs2Array512;
type LOOKAHEAD_SIZE = lfs2Array1;
fn read(&mut self, off: usize, buf: &mut [u8]) -> lfs2Result<usize> {
info!(
"Littlefs2Filesystem read at offset {} with len {}",
off,
buf.len()
);
let read_size: usize = Self::READ_SIZE;
assert_eq!(off % read_size, 0);
assert_eq!(buf.len() % read_size, 0);
match self.storage.read(off as u32, buf) {
anyhow::Result::Ok(..) => lfs2Result::Ok(buf.len()),
Err(err) => {
error!("Littlefs2Filesystem read error: {:?}", err);
Err(lfs2Error::IO)
}
}
}
fn write(&mut self, off: usize, data: &[u8]) -> lfs2Result<usize> {
info!(
"Littlefs2Filesystem write at offset {} with len {}",
off,
data.len()
);
let write_size: usize = Self::WRITE_SIZE;
assert_eq!(off % write_size, 0);
assert_eq!(data.len() % write_size, 0);
match self.storage.write(off as u32, data) {
anyhow::Result::Ok(..) => lfs2Result::Ok(data.len()),
Err(err) => {
error!("Littlefs2Filesystem write error: {:?}", err);
Err(lfs2Error::IO)
}
}
}
fn erase(&mut self, off: usize, len: usize) -> lfs2Result<usize> {
info!(
"Littlefs2Filesystem erase at offset {} with len {}",
off, len
);
let block_size: usize = Self::BLOCK_SIZE;
debug_assert!(off % block_size == 0);
debug_assert!(len % block_size == 0);
//match self.storage.erase(off as u32, len as u32) {
//anyhow::Result::Ok(..) => lfs2Result::Ok(len),
//Err(err) => {
//error!("Littlefs2Filesystem erase error: {:?}", err);
//Err(lfs2Error::IO)
// }
//}
lfs2Result::Ok(len)
}
}

View File

@ -6,6 +6,7 @@ use anyhow::{anyhow, bail, Context};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::Serialize; use serde::Serialize;
use crate::hal::LittleFS2StorageAdapter::LittleFs2Filesystem;
use alloc::string::ToString; use alloc::string::ToString;
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::{format, string::String, vec::Vec}; use alloc::{format, string::String, vec::Vec};
@ -17,6 +18,7 @@ use embassy_net::tcp::TcpSocket;
use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_sync::rwlock::TryLockError;
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Instant, Timer};
use embedded_storage::nor_flash::ReadNorFlash; use embedded_storage::nor_flash::ReadNorFlash;
use embedded_storage::Storage; use embedded_storage::Storage;
@ -29,8 +31,10 @@ use esp_println::{print, println};
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use esp_wifi::wifi::{ use esp_wifi::wifi::{
AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig, AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig,
WifiController, WifiDevice, WifiEvent, WifiState, ScanTypeConfig, WifiController, WifiDevice, WifiEvent, WifiState,
}; };
use littlefs2::fs::Filesystem;
use littlefs2_core::{FileType, PathBuf};
use log::{info, warn}; use log::{info, warn};
#[link_section = ".rtc.data"] #[link_section = ".rtc.data"]
@ -42,6 +46,8 @@ static mut LOW_VOLTAGE_DETECTED: bool = false;
#[link_section = ".rtc.data"] #[link_section = ".rtc.data"]
static mut RESTART_TO_CONF: bool = false; static mut RESTART_TO_CONF: bool = false;
static CONFIG_FILE: &str = "config.json";
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct FileInfo { pub struct FileInfo {
filename: String, filename: String,
@ -53,8 +59,6 @@ pub struct FileList {
total: usize, total: usize,
used: usize, used: usize,
files: Vec<FileInfo>, files: Vec<FileInfo>,
file_system_corrupt: Option<String>,
iter_error: Option<String>,
} }
pub struct FileSystemSizeInfo { pub struct FileSystemSizeInfo {
@ -69,6 +73,7 @@ pub struct MqttClient<'a> {
base_topic: heapless::String<64>, base_topic: heapless::String<64>,
} }
pub struct Esp<'a> { pub struct Esp<'a> {
pub fs: Arc<Mutex<CriticalSectionRawMutex, Filesystem<'static, LittleFs2Filesystem>>>,
pub rng: Rng, pub rng: Rng,
//first starter (ap or sta will take these) //first starter (ap or sta will take these)
pub interfaces: Option<Interfaces<'static>>, pub interfaces: Option<Interfaces<'static>>,
@ -84,6 +89,14 @@ pub struct Esp<'a> {
pub ota_next: &'static mut FlashRegion<'static, FlashStorage>, pub ota_next: &'static mut FlashRegion<'static, FlashStorage>,
} }
// SAFETY: On this target we never move Esp across OS threads; the firmware runs single-core
// cooperative tasks with Embassy. All interior mutability of non-Send peripherals is gated
// behind &mut self or embassy_sync Mutex with CriticalSectionRawMutex, which does not rely on
// thread scheduling. Therefore it is sound to mark Esp as Send to satisfy trait object bounds
// (e.g., Box<dyn BoardInteraction + Send>). If you add fields that are accessed from multiple
// CPU cores/threads, reconsider this.
unsafe impl Send for Esp<'_> {}
pub struct IpInfo { pub struct IpInfo {
pub(crate) ip: IpAddr, pub(crate) ip: IpAddr,
netmask: IpAddr, netmask: IpAddr,
@ -99,13 +112,7 @@ macro_rules! mk_static {
}}; }};
} }
static WIFI_CONTROLLER: static_cell::StaticCell<WifiController> = static_cell::StaticCell::new();
impl Esp<'_> { impl Esp<'_> {
const SPIFFS_PARTITION_NAME: &'static str = "storage";
const CONFIG_FILE: &'static str = "/spiffs/config.cfg";
const BASE_PATH: &'static str = "/spiffs";
pub(crate) fn get_ota_slot(&mut self) -> String { pub(crate) fn get_ota_slot(&mut self) -> String {
match self.ota.current_slot() { match self.ota.current_slot() {
Ok(slot) => { Ok(slot) => {
@ -164,14 +171,26 @@ impl Esp<'_> {
DateTime::from_timestamp_millis(wall_clock as i64).unwrap() DateTime::from_timestamp_millis(wall_clock as i64).unwrap()
} }
pub(crate) async fn wifi_scan(&mut self) -> Vec<AccessPointInfo> { pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
info!("start wifi scan"); info!("start wifi scan");
let mut lock = self.controller.try_lock().unwrap(); let mut lock = self
.controller
.try_lock()
.map_err(|_| anyhow!("Could not lock wifi controller, currently in use"))?;
info!("start wifi scan lock"); info!("start wifi scan lock");
let scan_config = ScanConfig::default(); let scan_config = ScanConfig {
let rv = lock.scan_with_config_sync(scan_config).unwrap(); ssid: None,
bssid: None,
channel: None,
show_hidden: false,
scan_type: ScanTypeConfig::Passive(core::time::Duration::from_secs(2)),
};
let rv = lock
.scan_with_config_async(scan_config)
.await
.map_err(|err| anyhow!("Could not scan wifi: {:?}", err))?;
info!("end wifi scan lock"); info!("end wifi scan lock");
return rv; Ok(rv)
} }
pub(crate) fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> { pub(crate) fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
@ -215,7 +234,7 @@ impl Esp<'_> {
} }
pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<Stack<'static>> { pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<Stack<'static>> {
let ssid = match self.load_config() { let ssid = match self.load_config().await {
Ok(config) => config.network.ap_ssid.as_str().to_string(), Ok(config) => config.network.ap_ssid.as_str().to_string(),
Err(_) => "PlantCtrl Emergency Mode".to_string(), Err(_) => "PlantCtrl Emergency Mode".to_string(),
}; };
@ -223,7 +242,7 @@ impl Esp<'_> {
let spawner = Spawner::for_current_executor().await; let spawner = Spawner::for_current_executor().await;
let device = self.interfaces.take().unwrap().ap; let device = self.interfaces.take().unwrap().ap;
let gw_ip_addr_str = GW_IP_ADDR_ENV.unwrap_or("192.168.2.1"); let gw_ip_addr_str = "192.168.71.1";
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip"); let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
let config = embassy_net::Config::ipv4_static(StaticConfigV4 { let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
@ -361,50 +380,29 @@ impl Esp<'_> {
// log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}")); // log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}"));
// anyhow::Ok(address) // anyhow::Ok(address)
} }
pub(crate) fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> { pub(crate) async fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> {
bail!("todo"); let cfg = PathBuf::try_from(CONFIG_FILE).unwrap();
// let cfg = File::open(Self::CONFIG_FILE)?; match self.fs.lock().await.read::<4096>(&cfg) {
// let config: PlantControllerConfig = serde_json::from_reader(cfg)?; Ok(data) => {
// anyhow::Ok(config) let config: PlantControllerConfig = serde_json::from_slice(&data)?;
anyhow::Ok(config)
}
Err(err) => {
bail!(format!("{err:?}"))
}
}
} }
pub(crate) async fn save_config( pub(crate) async fn save_config(&mut self, config: Vec<u8>) -> anyhow::Result<()> {
&mut self, let filesystem = self.fs.lock().await;
_config: &PlantControllerConfig, let cfg = PathBuf::try_from(CONFIG_FILE).unwrap();
) -> anyhow::Result<()> {
bail!("todo");
// let mut cfg = File::create(Self::CONFIG_FILE)?;
// serde_json::to_writer(&mut cfg, &config)?;
// log::info!("Wrote config config {:?}", config);
// anyhow::Ok(())
}
pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> {
bail!("fail");
// log(LogMessage::MountingFilesystem, 0, 0, "", "");
// let base_path = String::try_from("/spiffs")?;
// let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?;
//let conf = todo!();
//let conf = esp_idf_sys::esp_vfs_spiffs_conf_t { match filesystem.write(&cfg, &*config) {
//base_path: base_path.as_ptr(), Ok(_) => {}
//partition_label: storage.as_ptr(), Err(err) => {
//max_files: 5, bail!(format!("{err:?}"))
//format_if_mount_failed: true, }
//}; }
anyhow::Ok(())
//TODO
//unsafe {
//esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?;
//}
// let free_space = self.file_system_size()?;
// log(
// LogMessage::FilesystemMount,
// free_space.free_size as u32,
// free_space.total_size as u32,
// &free_space.used_size.to_string(),
// "",
// );
// anyhow::Ok(())
} }
async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> { async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> {
bail!("fail"); bail!("fail");
@ -425,60 +423,32 @@ impl Esp<'_> {
// }) // })
} }
pub(crate) async fn list_files(&self) -> FileList { pub(crate) async fn list_files(&self) -> anyhow::Result<FileList> {
return FileList { let path = PathBuf::new();
let mut result = FileList {
total: 0, total: 0,
used: 0, used: 0,
file_system_corrupt: None,
files: Vec::new(), files: Vec::new(),
iter_error: None,
}; };
//
// let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap(); match self.fs.lock().await.read_dir_and_then(&path, |dir| {
// for entry in dir {
// let mut file_system_corrupt = None; let e = entry?;
//
// let mut iter_error = None; result.files.push(FileInfo {
// let mut result = Vec::new(); filename: e.path().to_string(),
// size: e.metadata().len(),
// let filepath = Path::new(Self::BASE_PATH); });
// let read_dir = fs::read_dir(filepath); }
// match read_dir { Result::Ok(())
// OkStd(read_dir) => { }) {
// for item in read_dir { Ok(_) => {}
// match item { Err(err) => {
// OkStd(file) => { bail!(format!("{err:?}"))
// let f = FileInfo { }
// filename: file.file_name().into_string().unwrap(), }
// size: file.metadata().map(|it| it.len()).unwrap_or_default() Ok(result)
// as usize,
// };
// result.push(f);
// }
// Err(err) => {
// iter_error = Some(format!("{err:?}"));
// break;
// }
// }
// }
// }
// Err(err) => {
// file_system_corrupt = Some(format!("{err:?}"));
// }
// }
// let mut total: usize = 0;
// let mut used: usize = 0;
// unsafe {
// esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
// }
//
// FileList {
// total,
// used,
// file_system_corrupt,
// files: result,
// iter_error,
// }
} }
pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> { pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> {
bail!("todo"); bail!("todo");

View File

@ -125,11 +125,8 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
async fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { fn set_config(&mut self, config: PlantControllerConfig) {
self.config = config; self.config = config;
//TODO
// self.esp.save_config(&self.config)?;
anyhow::Ok(())
} }
async fn get_mptt_voltage(&mut self) -> Result<Voltage> { async fn get_mptt_voltage(&mut self) -> Result<Voltage> {

View File

@ -1,3 +1,4 @@
mod LittleFS2StorageAdapter;
pub(crate) mod battery; pub(crate) mod battery;
pub mod esp; pub mod esp;
mod initial_hal; mod initial_hal;
@ -22,7 +23,7 @@ use crate::{
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::format; use alloc::format;
use alloc::sync::Arc; use alloc::sync::Arc;
use anyhow::{Ok, Result}; use anyhow::{bail, Ok, Result};
use async_trait::async_trait; use async_trait::async_trait;
use embassy_executor::Spawner; use embassy_executor::Spawner;
//use battery::BQ34Z100G1; //use battery::BQ34Z100G1;
@ -32,11 +33,13 @@ use esp_bootloader_esp_idf::partitions::{
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
}; };
use esp_hal::clock::CpuClock; use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Input, InputConfig, Io, Pull}; use esp_hal::gpio::{Input, InputConfig, Pull};
use esp_println::println; use esp_println::println;
use measurements::{Current, Voltage}; use measurements::{Current, Voltage};
use crate::hal::LittleFS2StorageAdapter::LittleFs2Filesystem;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
use esp_alloc as _; use esp_alloc as _;
use esp_backtrace as _; use esp_backtrace as _;
use esp_bootloader_esp_idf::ota::Slot; use esp_bootloader_esp_idf::ota::Slot;
@ -44,6 +47,9 @@ use esp_hal::rng::Rng;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use esp_wifi::{init, EspWifiController}; use esp_wifi::{init, EspWifiController};
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
use littlefs2::object_safe::DynStorage;
use log::{info, warn};
//Only support for 8 right now! //Only support for 8 right now!
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
@ -83,7 +89,7 @@ pub trait BoardInteraction<'a> {
async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>; async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
async fn general_fault(&mut self, enable: bool); async fn general_fault(&mut self, enable: bool);
async fn test(&mut self) -> Result<()>; async fn test(&mut self) -> Result<()>;
async fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>; fn set_config(&mut self, config: PlantControllerConfig);
async fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>; async fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
async fn get_mptt_current(&mut self) -> anyhow::Result<Current>; async fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
} }
@ -94,7 +100,12 @@ impl dyn BoardInteraction<'_> {
let even = counter % 2 == 0; let even = counter % 2 == 0;
let current = counter / (PLANT_COUNT as u32); let current = counter / (PLANT_COUNT as u32);
for led in 0..PLANT_COUNT { for led in 0..PLANT_COUNT {
self.fault(led, current == led as u32).await.unwrap(); match self.fault(led, current == led as u32).await {
Result::Ok(_) => {}
Err(err) => {
warn!("Fault on plant {}: {:?}", led, err);
}
}
} }
let _ = self.general_fault(even.into()); let _ = self.general_fault(even.into());
} }
@ -182,11 +193,11 @@ impl PlantHal {
let timg0 = TimerGroup::new(peripherals.TIMG0); let timg0 = TimerGroup::new(peripherals.TIMG0);
let esp_wifi_ctrl = &*mk_static!( let esp_wifi_ctrl = &*mk_static!(
EspWifiController<'static>, EspWifiController<'static>,
init(timg0.timer0, rng.clone()).unwrap() init(timg0.timer0, rng.clone()).expect("Could not init wifi controller")
); );
let (controller, interfaces) = let (controller, interfaces) =
esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).unwrap(); esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).expect("Could not init wifi");
use esp_hal::timer::systimer::SystemTimer; use esp_hal::timer::systimer::SystemTimer;
esp_hal_embassy::init(systimer.alarm0); esp_hal_embassy::init(systimer.alarm0);
@ -229,11 +240,9 @@ impl PlantHal {
}; };
// //
let tablebuffer: [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN] =
[0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN];
let tablebuffer = mk_static!( let tablebuffer = mk_static!(
[u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN], [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN],
tablebuffer [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN]
); );
let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); let storage_ota = mk_static!(FlashStorage, FlashStorage::new());
let pt = let pt =
@ -241,46 +250,79 @@ impl PlantHal {
// List all partitions - this is just FYI // List all partitions - this is just FYI
for i in 0..pt.len() { for i in 0..pt.len() {
println!("{:?}", pt.get_partition(i)); info!("{:?}", pt.get_partition(i));
} }
let ota_data = mk_static!(
let ota_data = pt PartitionEntry,
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
DataPartitionSubType::Ota, DataPartitionSubType::Ota,
))? ))?
.unwrap(); .expect("No OTA data partition found")
);
let ota_data = mk_static!(PartitionEntry, ota_data); let ota_data = mk_static!(
FlashRegion<FlashStorage>,
let ota_data = ota_data.as_embedded_storage(storage_ota); ota_data.as_embedded_storage(storage_ota)
let ota_data = mk_static!(FlashRegion<FlashStorage>, ota_data); );
let mut ota = esp_bootloader_esp_idf::ota::Ota::new(ota_data)?; let mut ota = esp_bootloader_esp_idf::ota::Ota::new(ota_data)?;
let ota_partition = match ota.current_slot()? { let ota_partition = match ota.current_slot()? {
Slot::None => { Slot::None => {
panic!("No OTA slot found"); panic!("No OTA slot active?");
} }
Slot::Slot0 => pt Slot::Slot0 => pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
AppPartitionSubType::Ota0, AppPartitionSubType::Ota0,
))? ))?
.unwrap(), .expect("No OTA slot0 found"),
Slot::Slot1 => pt Slot::Slot1 => pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
AppPartitionSubType::Ota1, AppPartitionSubType::Ota1,
))? ))?
.unwrap(), .expect("No OTA slot1 found"),
}; };
let ota_next = mk_static!(PartitionEntry, ota_partition); let ota_next = mk_static!(PartitionEntry, ota_partition);
let storage_ota_next = mk_static!(FlashStorage, FlashStorage::new()); let storage_ota = mk_static!(FlashStorage, FlashStorage::new());
let ota_next = mk_static!( let ota_next = mk_static!(
FlashRegion<FlashStorage>, FlashRegion<FlashStorage>,
ota_next.as_embedded_storage(storage_ota_next) ota_next.as_embedded_storage(storage_ota)
); );
let data_partition = pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
DataPartitionSubType::LittleFs,
))?
.expect("Data partition with littlefs not found");
let data_partition = mk_static!(PartitionEntry, data_partition);
let storage_data = mk_static!(FlashStorage, FlashStorage::new());
let data = mk_static!(
FlashRegion<FlashStorage>,
data_partition.as_embedded_storage(storage_data)
);
let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data });
let alloc = mk_static!(Allocation<LittleFs2Filesystem>, lfs2Filesystem::allocate());
if lfs2filesystem.is_mountable() {
log::info!("Littlefs2 filesystem is mountable");
} else {
match lfs2filesystem.format() {
Result::Ok(..) => {
log::info!("Littlefs2 filesystem is formatted");
}
Err(err) => {
bail!("Littlefs2 filesystem could not be formatted: {:?}", err);
}
}
}
let fs = Arc::new(Mutex::new(
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
));
let mut esp = Esp { let mut esp = Esp {
fs,
rng, rng,
controller: Arc::new(Mutex::new(controller)), controller: Arc::new(Mutex::new(controller)),
interfaces: Some(interfaces), interfaces: Some(interfaces),
@ -325,9 +367,8 @@ impl PlantHal {
); );
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode); esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode);
let fs_mount_error = esp.mount_file_system().is_err();
let config = esp.load_config(); let config = esp.load_config().await;
log::info!("Init rtc driver"); log::info!("Init rtc driver");
// let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); // let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));

View File

@ -232,8 +232,10 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
info!("no mode override"); info!("no mode override");
} }
if board.board_hal.get_config().hardware.board == INITIAL //TODO hack
&& board.board_hal.get_config().network.ssid.is_none() if true
|| (board.board_hal.get_config().hardware.board == INITIAL
&& board.board_hal.get_config().network.ssid.is_none())
{ {
info!("No wifi configured, starting initial config mode"); info!("No wifi configured, starting initial config mode");

View File

@ -1,17 +1,21 @@
//offer ota and config mode //offer ota and config mode
use crate::config::PlantControllerConfig;
use crate::{get_version, log::LogMessage, BOARD_ACCESS}; use crate::{get_version, log::LogMessage, BOARD_ACCESS};
use alloc::borrow::ToOwned;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::vec::Vec; use alloc::vec::Vec;
use anyhow::bail;
use core::fmt::{Debug, Display}; use core::fmt::{Debug, Display};
use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::result::Result::Ok; use core::result::Result::Ok;
use core::str::from_utf8;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use edge_http::io::server::{Connection, Handler, Server}; use edge_http::io::server::{Connection, Handler, Server};
use edge_http::io::Error; use edge_http::io::Error;
use edge_http::Method; use edge_http::Method;
use edge_nal::TcpBind; use edge_nal::{TcpBind, TcpSplit};
use edge_nal_embassy::{Tcp, TcpBuffers}; use edge_nal_embassy::{Tcp, TcpBuffers};
use embassy_net::Stack; use embassy_net::Stack;
use embassy_time::Instant; use embassy_time::Instant;
@ -185,16 +189,7 @@ pub struct NightLampCommand {
// anyhow::Ok(Some(json)) // anyhow::Ok(Some(json))
// } // }
// //
// fn set_config(
// request: &mut Request<&mut EspHttpConnection>,
// ) -> Result<Option<std::string::String>, anyhow::Error> {
// let all = read_up_to_bytes_from_request(request, Some(4096))?;
// let config: PlantControllerConfig = serde_json::from_slice(&all)?;
//
// let mut board = BOARD_ACCESS.lock().expect("board access");
// board.board_hal.set_config(config)?;
// anyhow::Ok(Some("saved".to_owned()))
// }
// //
// //
@ -314,11 +309,7 @@ struct HttpHandler {
} }
impl Handler for HttpHandler { impl Handler for HttpHandler {
type Error<E> type Error<E: core::fmt::Debug> = Error<E>;
= Error<E>
where
E: Debug;
async fn handle<'a, T, const N: usize>( async fn handle<'a, T, const N: usize>(
&self, &self,
_task_id: impl Display + Copy, _task_id: impl Display + Copy,
@ -381,6 +372,7 @@ impl Handler for HttpHandler {
Method::Post => { Method::Post => {
let json = match path { let json = match path {
"/wifiscan" => Some(wifi_scan(conn).await), "/wifiscan" => Some(wifi_scan(conn).await),
"/set_config" => Some(set_config(conn).await),
_ => None, _ => None,
}; };
match json { match json {
@ -398,7 +390,6 @@ impl Handler for HttpHandler {
} }
Some(code) => code, Some(code) => code,
}; };
conn.complete().await?; conn.complete().await?;
let response_time = Instant::now().duration_since(start).as_millis(); let response_time = Instant::now().duration_since(start).as_millis();
@ -407,38 +398,76 @@ impl Handler for HttpHandler {
} }
} }
// .fn_handler("/reboot", Method::Post, move |_| { // fn set_config(
// BOARD_ACCESS // request: &mut Request<&mut EspHttpConnection>,
// .lock()
// .unwrap()
// .board_hal
// .get_esp()
// .set_restart_to_conf(true);
// reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed);
// anyhow::Ok(())
// fn wifi_scan(
// _request: &mut Request<&mut EspHttpConnection>,
// ) -> Result<Option<std::string::String>, anyhow::Error> { // ) -> Result<Option<std::string::String>, anyhow::Error> {
// let mut board = BOARD_ACCESS.lock().unwrap(); // let all = read_up_to_bytes_from_request(request, Some(4096))?;
// let scan_result = board.board_hal.get_esp().wifi_scan()?; // let config: PlantControllerConfig = serde_json::from_slice(&all)?;
// let mut ssids: Vec<&String<32>> = Vec::new(); //
// scan_result.iter().for_each(|s| ssids.push(&s.ssid)); // let mut board = BOARD_ACCESS.lock().expect("board access");
// let ssid_json = serde_json::to_string(&SSIDList { ssids })?; // board.board_hal.set_config(config)?;
// log::info!("Sending ssid list {}", &ssid_json); // anyhow::Ok(Some("saved".to_owned()))
// anyhow::Ok(Some(ssid_json))
// } // }
async fn set_config<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error>
where
T: Read + Write,
{
let all = read_up_to_bytes_from_request(request, Some(4096)).await?;
let length = all.len();
let config: PlantControllerConfig = serde_json::from_slice(&all)?;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().save_config(all).await?;
log::info!("Wrote config config {:?} with size {}", config, length);
board.board_hal.set_config(config);
anyhow::Ok(Some("saved".to_string()))
}
async fn read_up_to_bytes_from_request<T, const N: usize>(
request: &mut Connection<'_, T, N>,
limit: Option<usize>,
) -> Result<Vec<u8>, anyhow::Error>
where
T: Read + Write,
{
let max_read = limit.unwrap_or(1024);
let mut data_store = Vec::new();
let mut total_read = 0;
loop {
let mut buf = [0_u8; 64];
let read = match request.read(&mut buf).await {
Ok(read) => read,
Err(e) => bail!("Error reading request {:?}", e),
};
if read == 0 {
break;
}
let actual_data = &buf[0..read];
total_read += read;
if total_read > max_read {
bail!("Request too large {total_read} > {max_read}");
}
data_store.push(actual_data.to_owned());
}
let allvec = data_store.concat();
log::info!("Raw data {}", from_utf8(&allvec)?);
Ok(allvec)
}
async fn wifi_scan<T, const N: usize>( async fn wifi_scan<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> Result<Option<String>, anyhow::Error> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
info!("start wifi scan"); info!("start wifi scan");
let scan_result = board.board_hal.get_esp().wifi_scan().await; //let scan_result = board.board_hal.get_esp().wifi_scan().await?
//FIXME currently panics
let mut ssids: Vec<String> = Vec::new(); let mut ssids: Vec<String> = Vec::new();
scan_result //scan_result
.iter() //.iter()
.for_each(|s| ssids.push(s.ssid.to_string())); //.for_each(|s| ssids.push(s.ssid.to_string()));
let ssid_json = serde_json::to_string(&SSIDList { ssids })?; let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
info!("Sending ssid list {}", &ssid_json); info!("Sending ssid list {}", &ssid_json);
anyhow::Ok(Some(ssid_json)) anyhow::Ok(Some(ssid_json))
@ -462,7 +491,7 @@ async fn list_files<T, const N: usize>(
_request: &mut Connection<'_, T, N>, _request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> Result<Option<String>, anyhow::Error> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
let result = board.board_hal.get_esp().list_files().await; let result = board.board_hal.get_esp().list_files().await?;
let file_list_json = serde_json::to_string(&result)?; let file_list_json = serde_json::to_string(&result)?;
anyhow::Ok(Some(file_list_json)) anyhow::Ok(Some(file_list_json))
} }
@ -532,11 +561,11 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new(); let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
let tcp = Tcp::new(stack, &buffer); let tcp = Tcp::new(stack, &buffer);
let acceptor = tcp let acceptor = tcp
.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8080)) .bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 80))
.await .await
.unwrap(); .unwrap();
let mut server: Server<2, 512, 10> = Server::new(); let mut server: Server<2, 512, 15> = Server::new();
server server
.run(Some(5000), acceptor, HttpHandler { reboot_now }) .run(Some(5000), acceptor, HttpHandler { reboot_now })
.await .await
@ -636,11 +665,6 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
// .unwrap(); // .unwrap();
// //
// server // server
// .fn_handler("/set_config", Method::Post, move |request| {
// handle_error_to500(request, set_config)
// })
// .unwrap();
// server
// .fn_handler("/backup_config", Method::Post, move |request| { // .fn_handler("/backup_config", Method::Post, move |request| {
// handle_error_to500(request, backup_config) // handle_error_to500(request, backup_config)
// }) // })
@ -816,30 +840,6 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
//server //server
} }
// //
// fn read_up_to_bytes_from_request(
// request: &mut Request<&mut EspHttpConnection<'_>>,
// limit: Option<usize>,
// ) -> Result<Vec<u8>, anyhow::Error> {
// let max_read = limit.unwrap_or(1024);
// let mut data_store = Vec::new();
// let mut total_read = 0;
// loop {
// let mut buf = [0_u8; 64];
// let read = request.read(&mut buf)?;
// if read == 0 {
// break;
// }
// let actual_data = &buf[0..read];
// total_read += read;
// if total_read > max_read {
// bail!("Request too large {total_read} > {max_read}");
// }
// data_store.push(actual_data.to_owned());
// }
// let allvec = data_store.concat();
// log::info!("Raw data {}", from_utf8(&allvec)?);
// Ok(allvec)
// }
async fn handle_json<'a, T, const N: usize>( async fn handle_json<'a, T, const N: usize>(
conn: &mut Connection<'a, T, N>, conn: &mut Connection<'a, T, N>,