file up & download and delete
This commit is contained in:
		@@ -6,7 +6,7 @@ use anyhow::{anyhow, bail, Context};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
use crate::hal::LittleFS2StorageAdapter::LittleFs2Filesystem;
 | 
			
		||||
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
 | 
			
		||||
use alloc::string::ToString;
 | 
			
		||||
use alloc::sync::Arc;
 | 
			
		||||
use alloc::{format, string::String, vec::Vec};
 | 
			
		||||
@@ -34,7 +34,7 @@ use esp_wifi::wifi::{
 | 
			
		||||
    ScanTypeConfig, WifiController, WifiDevice, WifiEvent, WifiState,
 | 
			
		||||
};
 | 
			
		||||
use littlefs2::fs::Filesystem;
 | 
			
		||||
use littlefs2_core::{FileType, PathBuf};
 | 
			
		||||
use littlefs2_core::{DynFile, FileType, OpenSeekFrom, Path, PathBuf, SeekFrom};
 | 
			
		||||
use log::{info, warn};
 | 
			
		||||
 | 
			
		||||
#[link_section = ".rtc.data"]
 | 
			
		||||
@@ -113,6 +113,79 @@ macro_rules! mk_static {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Esp<'_> {
 | 
			
		||||
    pub(crate) async fn delete_file(&self, filename: String) -> anyhow::Result<()> {
 | 
			
		||||
        let file = PathBuf::try_from(filename.as_str()).unwrap();
 | 
			
		||||
        let access = self.fs.lock().await;
 | 
			
		||||
        access
 | 
			
		||||
            .remove(&*file)
 | 
			
		||||
            .map_err(|err| anyhow!("Could not delete file: {:?}", err))?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    pub(crate) async fn write_file(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        filename: String,
 | 
			
		||||
        offset: u32,
 | 
			
		||||
        buf: &[u8],
 | 
			
		||||
    ) -> anyhow::Result<()> {
 | 
			
		||||
        let file = PathBuf::try_from(filename.as_str()).unwrap();
 | 
			
		||||
        let access = self.fs.lock().await;
 | 
			
		||||
        info!("write file {} at offset {}", filename, offset);
 | 
			
		||||
        match access.open_file_with_options_and_then(
 | 
			
		||||
            |options| options.read(true).write(true).create(true),
 | 
			
		||||
            &*file,
 | 
			
		||||
            |file| {
 | 
			
		||||
                file.seek(SeekFrom::Start(offset))?;
 | 
			
		||||
                file.write(buf)?;
 | 
			
		||||
                Ok(())
 | 
			
		||||
            },
 | 
			
		||||
        ) {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                bail!(format!("{err:?}"))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub(crate) async fn get_file(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        filename: String,
 | 
			
		||||
        chunk: u32,
 | 
			
		||||
    ) -> anyhow::Result<([u8; 128], usize)> {
 | 
			
		||||
        use littlefs2::io::Error as lfs2Error;
 | 
			
		||||
 | 
			
		||||
        let file = PathBuf::try_from(filename.as_str()).unwrap();
 | 
			
		||||
        let access = self.fs.lock().await;
 | 
			
		||||
        let mut buf = [0_u8; 128];
 | 
			
		||||
        let mut read = 0;
 | 
			
		||||
        let offset = chunk * 128;
 | 
			
		||||
        info!("read file {} at offset {}", filename, offset);
 | 
			
		||||
        match access.open_file_with_options_and_then(
 | 
			
		||||
            |options| options.read(true),
 | 
			
		||||
            &*file,
 | 
			
		||||
            |file| {
 | 
			
		||||
                let length = file.len()? as u32;
 | 
			
		||||
                info!("file length {}", length);
 | 
			
		||||
                if length == 0 {
 | 
			
		||||
                    Err(lfs2Error::IO)
 | 
			
		||||
                } else if length > offset {
 | 
			
		||||
                    file.seek(SeekFrom::Start(offset))?;
 | 
			
		||||
                    info!("seek to {}", offset);
 | 
			
		||||
                    read = file.read(&mut buf)?;
 | 
			
		||||
                    info!("read {} bytes", read);
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                } else {
 | 
			
		||||
                    //exactly at end, do nothing
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        ) {
 | 
			
		||||
            Ok(_) => {}
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                bail!(format!("{err:?}"))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok((buf, read))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn get_ota_slot(&mut self) -> String {
 | 
			
		||||
        match self.ota.current_slot() {
 | 
			
		||||
            Ok(slot) => {
 | 
			
		||||
@@ -435,11 +508,12 @@ impl Esp<'_> {
 | 
			
		||||
        match self.fs.lock().await.read_dir_and_then(&path, |dir| {
 | 
			
		||||
            for entry in dir {
 | 
			
		||||
                let e = entry?;
 | 
			
		||||
 | 
			
		||||
                result.files.push(FileInfo {
 | 
			
		||||
                    filename: e.path().to_string(),
 | 
			
		||||
                    size: e.metadata().len(),
 | 
			
		||||
                });
 | 
			
		||||
                if e.file_type() == FileType::File {
 | 
			
		||||
                    result.files.push(FileInfo {
 | 
			
		||||
                        filename: e.path().to_string(),
 | 
			
		||||
                        size: e.metadata().len(),
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Result::Ok(())
 | 
			
		||||
        }) {
 | 
			
		||||
@@ -450,16 +524,7 @@ impl Esp<'_> {
 | 
			
		||||
        }
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
    pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> {
 | 
			
		||||
        bail!("todo");
 | 
			
		||||
        // let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
 | 
			
		||||
        // match fs::remove_file(filepath) {
 | 
			
		||||
        //     OkStd(_) => anyhow::Ok(()),
 | 
			
		||||
        //     Err(err) => {
 | 
			
		||||
        //         bail!(format!("{err:?}"))
 | 
			
		||||
        //     }
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // pub(crate) async fn get_file_handle(
 | 
			
		||||
    //     &self,
 | 
			
		||||
    //     filename: &str,
 | 
			
		||||
@@ -473,13 +538,17 @@ impl Esp<'_> {
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn init_rtc_deepsleep_memory(&self, init_rtc_store: bool, to_config_mode: bool) {
 | 
			
		||||
    pub(crate) async fn init_rtc_deepsleep_memory(
 | 
			
		||||
        &self,
 | 
			
		||||
        init_rtc_store: bool,
 | 
			
		||||
        to_config_mode: bool,
 | 
			
		||||
    ) {
 | 
			
		||||
        if init_rtc_store {
 | 
			
		||||
            unsafe {
 | 
			
		||||
                LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT];
 | 
			
		||||
                CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT];
 | 
			
		||||
                LOW_VOLTAGE_DETECTED = false;
 | 
			
		||||
                crate::log::init();
 | 
			
		||||
                crate::log::init().await;
 | 
			
		||||
                RESTART_TO_CONF = to_config_mode;
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -493,14 +562,16 @@ impl Esp<'_> {
 | 
			
		||||
                    0,
 | 
			
		||||
                    "",
 | 
			
		||||
                    "",
 | 
			
		||||
                );
 | 
			
		||||
                )
 | 
			
		||||
                .await;
 | 
			
		||||
                log(
 | 
			
		||||
                    LogMessage::LowVoltage,
 | 
			
		||||
                    LOW_VOLTAGE_DETECTED as u32,
 | 
			
		||||
                    0,
 | 
			
		||||
                    "",
 | 
			
		||||
                    "",
 | 
			
		||||
                );
 | 
			
		||||
                )
 | 
			
		||||
                .await;
 | 
			
		||||
                for i in 0..PLANT_COUNT {
 | 
			
		||||
                    log::info!(
 | 
			
		||||
                        "LAST_WATERING_TIMESTAMP[{}] = UTC {}",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
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::consts::U512 as lfsCache;
 | 
			
		||||
use littlefs2::consts::U512 as lfsLookahead;
 | 
			
		||||
use littlefs2::driver::Storage as lfs2Storage;
 | 
			
		||||
use littlefs2::fs::Filesystem as lfs2Filesystem;
 | 
			
		||||
use littlefs2::io::Error as lfs2Error;
 | 
			
		||||
@@ -14,20 +14,15 @@ pub struct LittleFs2Filesystem {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl lfs2Storage for LittleFs2Filesystem {
 | 
			
		||||
    const READ_SIZE: usize = 512;
 | 
			
		||||
    const READ_SIZE: usize = 256;
 | 
			
		||||
    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_SIZE: usize = 512; //usually optimal for flash access
 | 
			
		||||
    const BLOCK_COUNT: usize = 8 * 1024 * 1024 / 512; //8mb in 32kb blocks
 | 
			
		||||
    const BLOCK_CYCLES: isize = 100;
 | 
			
		||||
    type CACHE_SIZE = lfs2Array512;
 | 
			
		||||
    type LOOKAHEAD_SIZE = lfs2Array1;
 | 
			
		||||
    type CACHE_SIZE = lfsCache;
 | 
			
		||||
    type LOOKAHEAD_SIZE = lfsLookahead;
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
mod LittleFS2StorageAdapter;
 | 
			
		||||
pub(crate) mod battery;
 | 
			
		||||
pub mod esp;
 | 
			
		||||
mod initial_hal;
 | 
			
		||||
mod little_fs2storage_adapter;
 | 
			
		||||
mod rtc;
 | 
			
		||||
//mod water;
 | 
			
		||||
 | 
			
		||||
@@ -34,12 +34,10 @@ use esp_bootloader_esp_idf::partitions::{
 | 
			
		||||
};
 | 
			
		||||
use esp_hal::clock::CpuClock;
 | 
			
		||||
use esp_hal::gpio::{Input, InputConfig, Pull};
 | 
			
		||||
use esp_println::println;
 | 
			
		||||
use measurements::{Current, Voltage};
 | 
			
		||||
 | 
			
		||||
use crate::hal::LittleFS2StorageAdapter::LittleFs2Filesystem;
 | 
			
		||||
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
 | 
			
		||||
use embassy_sync::mutex::Mutex;
 | 
			
		||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
 | 
			
		||||
use esp_alloc as _;
 | 
			
		||||
use esp_backtrace as _;
 | 
			
		||||
use esp_bootloader_esp_idf::ota::Slot;
 | 
			
		||||
@@ -182,6 +180,7 @@ impl PlantHal {
 | 
			
		||||
        let peripherals: Peripherals = esp_hal::init(config);
 | 
			
		||||
 | 
			
		||||
        esp_alloc::heap_allocator!(size: 64 * 1024);
 | 
			
		||||
        esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
 | 
			
		||||
        let systimer = SystemTimer::new(peripherals.SYSTIMER);
 | 
			
		||||
 | 
			
		||||
        let boot_button = Input::new(
 | 
			
		||||
@@ -364,9 +363,11 @@ impl PlantHal {
 | 
			
		||||
            to_config_mode as u32,
 | 
			
		||||
            "",
 | 
			
		||||
            &format!("{reasons:?}"),
 | 
			
		||||
        );
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
        esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode);
 | 
			
		||||
        esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode)
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
        let config = esp.load_config().await;
 | 
			
		||||
 | 
			
		||||
@@ -453,7 +454,8 @@ impl PlantHal {
 | 
			
		||||
                    0,
 | 
			
		||||
                    "",
 | 
			
		||||
                    &err.to_string(),
 | 
			
		||||
                );
 | 
			
		||||
                )
 | 
			
		||||
                .await;
 | 
			
		||||
                HAL {
 | 
			
		||||
                    board_hal: initial_hal::create_initial_board(
 | 
			
		||||
                        free_pins,
 | 
			
		||||
 
 | 
			
		||||
@@ -31,8 +31,6 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 | 
			
		||||
use embassy_sync::mutex::{Mutex, MutexGuard};
 | 
			
		||||
use embassy_sync::once_lock::OnceLock;
 | 
			
		||||
use embassy_time::Timer;
 | 
			
		||||
use esp_alloc::heap_allocator;
 | 
			
		||||
use esp_bootloader_esp_idf::ota::OtaImageState;
 | 
			
		||||
use esp_hal::rom::ets_delay_us;
 | 
			
		||||
use esp_hal::system::software_reset;
 | 
			
		||||
use esp_println::{logger, println};
 | 
			
		||||
@@ -317,8 +315,7 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
 | 
			
		||||
        info!("executing config mode override");
 | 
			
		||||
        //config upload will trigger reboot!
 | 
			
		||||
        let reboot_now = Arc::new(AtomicBool::new(false));
 | 
			
		||||
        //TODO
 | 
			
		||||
        //let _webserver = httpd(reboot_now.clone());
 | 
			
		||||
        //spawner.spawn(httpd(reboot_now.clone(), stack))?;
 | 
			
		||||
        let board = BOARD_ACCESS.get().await.lock().await;
 | 
			
		||||
        wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -667,7 +664,8 @@ pub async fn do_secure_pump(
 | 
			
		||||
                                current_ma as u32,
 | 
			
		||||
                                plant_config.max_pump_current_ma.to_string().as_str(),
 | 
			
		||||
                                step.to_string().as_str(),
 | 
			
		||||
                            );
 | 
			
		||||
                            )
 | 
			
		||||
                            .await;
 | 
			
		||||
                            board.board_hal.general_fault(true).await;
 | 
			
		||||
                            board.board_hal.fault(plant_id, true).await?;
 | 
			
		||||
                            if !plant_config.ignore_current_error {
 | 
			
		||||
@@ -686,7 +684,8 @@ pub async fn do_secure_pump(
 | 
			
		||||
                                current_ma as u32,
 | 
			
		||||
                                plant_config.min_pump_current_ma.to_string().as_str(),
 | 
			
		||||
                                step.to_string().as_str(),
 | 
			
		||||
                            );
 | 
			
		||||
                            )
 | 
			
		||||
                            .await;
 | 
			
		||||
                            board.board_hal.general_fault(true).await;
 | 
			
		||||
                            board.board_hal.fault(plant_id, true).await?;
 | 
			
		||||
                            if !plant_config.ignore_current_error {
 | 
			
		||||
@@ -706,7 +705,8 @@ pub async fn do_secure_pump(
 | 
			
		||||
                            0,
 | 
			
		||||
                            "",
 | 
			
		||||
                            "",
 | 
			
		||||
                        );
 | 
			
		||||
                        )
 | 
			
		||||
                        .await;
 | 
			
		||||
                        error = true;
 | 
			
		||||
                        break;
 | 
			
		||||
                    } else {
 | 
			
		||||
@@ -840,7 +840,7 @@ async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMu
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
async fn publish_firmware_info(
 | 
			
		||||
    mut board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
 | 
			
		||||
    board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
 | 
			
		||||
    version: VersionInfo,
 | 
			
		||||
    ip_address: &String,
 | 
			
		||||
    timezone_time: &String,
 | 
			
		||||
@@ -867,17 +867,6 @@ async fn publish_firmware_info(
 | 
			
		||||
    let _ = esp.mqtt_publish("/state", "online".as_bytes()).await;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn state_to_string(state: OtaImageState) -> &'static str {
 | 
			
		||||
    match state {
 | 
			
		||||
        OtaImageState::New => "New",
 | 
			
		||||
        OtaImageState::PendingVerify => "PendingVerify",
 | 
			
		||||
        OtaImageState::Valid => "Valid",
 | 
			
		||||
        OtaImageState::Invalid => "Invalid",
 | 
			
		||||
        OtaImageState::Aborted => "Aborted",
 | 
			
		||||
        OtaImageState::Undefined => "Undefined",
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn try_connect_wifi_sntp_mqtt() -> NetworkMode {
 | 
			
		||||
    let board = &mut BOARD_ACCESS.get().await.lock().await;
 | 
			
		||||
    let nw_conf = &board.board_hal.get_config().network.clone();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
use crate::config::PlantControllerConfig;
 | 
			
		||||
use crate::{get_version, log::LogMessage, BOARD_ACCESS};
 | 
			
		||||
use alloc::borrow::ToOwned;
 | 
			
		||||
use alloc::fmt::format;
 | 
			
		||||
use alloc::format;
 | 
			
		||||
use alloc::string::{String, ToString};
 | 
			
		||||
use alloc::sync::Arc;
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
@@ -21,6 +23,7 @@ use embassy_net::Stack;
 | 
			
		||||
use embassy_time::Instant;
 | 
			
		||||
use embedded_io_async::{Read, Write};
 | 
			
		||||
use esp_println::println;
 | 
			
		||||
use littlefs2_core::Path;
 | 
			
		||||
use log::info;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
@@ -294,15 +297,6 @@ pub struct NightLampCommand {
 | 
			
		||||
//     anyhow::Ok(None)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
 | 
			
		||||
//     log::info!("{uri} get {param_name}");
 | 
			
		||||
//     let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap();
 | 
			
		||||
//     let value = parsed.query_pairs().find(|it| it.0 == param_name);
 | 
			
		||||
//     match value {
 | 
			
		||||
//         Some(found) => Some(found.1.into_owned()),
 | 
			
		||||
//         None => None,
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
struct HttpHandler {
 | 
			
		||||
    reboot_now: Arc<AtomicBool>,
 | 
			
		||||
@@ -324,43 +318,147 @@ impl Handler for HttpHandler {
 | 
			
		||||
        let method = headers.method;
 | 
			
		||||
        let path = headers.path;
 | 
			
		||||
 | 
			
		||||
        let status = match method {
 | 
			
		||||
            Method::Get => match path {
 | 
			
		||||
                "/favicon.ico" => {
 | 
			
		||||
                    conn.initiate_response(200, Some("OK"), &[("Content-Type", "image/x-icon")])
 | 
			
		||||
        let prefix = "/file?filename=";
 | 
			
		||||
        let status = if path.starts_with(prefix) {
 | 
			
		||||
            let filename = &path[prefix.len()..];
 | 
			
		||||
            let mut board = BOARD_ACCESS.get().await.lock().await;
 | 
			
		||||
            info!("file request for {} with method {}", filename, method);
 | 
			
		||||
            match method {
 | 
			
		||||
                Method::Delete => {
 | 
			
		||||
                    board
 | 
			
		||||
                        .board_hal
 | 
			
		||||
                        .get_esp()
 | 
			
		||||
                        .delete_file(filename.to_owned())
 | 
			
		||||
                        .await
 | 
			
		||||
                        .unwrap();
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                Method::Get => {
 | 
			
		||||
                    let disp = format!("attachment; filename=\"{filename}\"");
 | 
			
		||||
                    conn.initiate_response(
 | 
			
		||||
                        200,
 | 
			
		||||
                        Some("OK"),
 | 
			
		||||
                        &[
 | 
			
		||||
                            ("Content-Type", "application/octet-stream"),
 | 
			
		||||
                            ("Content-Disposition", disp.as_str()),
 | 
			
		||||
                        ],
 | 
			
		||||
                    )
 | 
			
		||||
                    .await?;
 | 
			
		||||
                    let mut chunk = 0;
 | 
			
		||||
                    loop {
 | 
			
		||||
                        let read_chunk = board
 | 
			
		||||
                            .board_hal
 | 
			
		||||
                            .get_esp()
 | 
			
		||||
                            .get_file(filename.to_owned(), chunk)
 | 
			
		||||
                            .await
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                        let length = read_chunk.1;
 | 
			
		||||
                        info!("read {} bytes for file request for {}", length, filename);
 | 
			
		||||
                        if length == 0 {
 | 
			
		||||
                            info!("file request for {} finished", filename);
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                        let data = &read_chunk.0[0..length];
 | 
			
		||||
                        conn.write_all(data).await?;
 | 
			
		||||
                        if length < 128 {
 | 
			
		||||
                            info!("file request for {} finished", filename);
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                        chunk = chunk + 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                Method::Post => {
 | 
			
		||||
                    //ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming
 | 
			
		||||
                    let _ = board
 | 
			
		||||
                        .board_hal
 | 
			
		||||
                        .get_esp()
 | 
			
		||||
                        .delete_file(filename.to_owned())
 | 
			
		||||
                        .await;
 | 
			
		||||
                    let mut offset = 0_usize;
 | 
			
		||||
                    loop {
 | 
			
		||||
                        let mut buf = [0_u8; 1024];
 | 
			
		||||
                        let to_write = conn.read(&mut buf).await?;
 | 
			
		||||
                        if to_write == 0 {
 | 
			
		||||
                            info!("file request for {} finished", filename);
 | 
			
		||||
                            break;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            info!(
 | 
			
		||||
                                "writing {} bytes for file request for {}",
 | 
			
		||||
                                to_write, filename
 | 
			
		||||
                            );
 | 
			
		||||
                            board
 | 
			
		||||
                                .board_hal
 | 
			
		||||
                                .get_esp()
 | 
			
		||||
                                .write_file(filename.to_owned(), offset as u32, &buf[0..to_write])
 | 
			
		||||
                                .await
 | 
			
		||||
                                .unwrap();
 | 
			
		||||
                        }
 | 
			
		||||
                        offset = offset + to_write
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                _ => None,
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            match method {
 | 
			
		||||
                Method::Get => match path {
 | 
			
		||||
                    "/favicon.ico" => {
 | 
			
		||||
                        conn.initiate_response(
 | 
			
		||||
                            200,
 | 
			
		||||
                            Some("OK"),
 | 
			
		||||
                            &[("Content-Type", "image/x-icon")],
 | 
			
		||||
                        )
 | 
			
		||||
                        .await?;
 | 
			
		||||
                    conn.write_all(include_bytes!("favicon.ico")).await?;
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                "/" => {
 | 
			
		||||
                    conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/html")])
 | 
			
		||||
                        conn.write_all(include_bytes!("favicon.ico")).await?;
 | 
			
		||||
                        Some(200)
 | 
			
		||||
                    }
 | 
			
		||||
                    "/" => {
 | 
			
		||||
                        conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/html")])
 | 
			
		||||
                            .await?;
 | 
			
		||||
                        conn.write_all(include_bytes!("index.html")).await?;
 | 
			
		||||
                        Some(200)
 | 
			
		||||
                    }
 | 
			
		||||
                    "/bundle.js" => {
 | 
			
		||||
                        conn.initiate_response(
 | 
			
		||||
                            200,
 | 
			
		||||
                            Some("OK"),
 | 
			
		||||
                            &[("Content-Type", "text/javascript")],
 | 
			
		||||
                        )
 | 
			
		||||
                        .await?;
 | 
			
		||||
                    conn.write_all(include_bytes!("index.html")).await?;
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                "/bundle.js" => {
 | 
			
		||||
                    conn.initiate_response(200, Some("OK"), &[("Content-Type", "text/javascript")])
 | 
			
		||||
                        .await?;
 | 
			
		||||
                    conn.write_all(include_bytes!("bundle.js")).await?;
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                "/reboot" => {
 | 
			
		||||
                    let mut board = BOARD_ACCESS.get().await.lock().await;
 | 
			
		||||
                    board.board_hal.get_esp().set_restart_to_conf(true);
 | 
			
		||||
                    self.reboot_now.store(true, Ordering::Relaxed);
 | 
			
		||||
                    Some(200)
 | 
			
		||||
                }
 | 
			
		||||
                &_ => {
 | 
			
		||||
                        conn.write_all(include_bytes!("bundle.js")).await?;
 | 
			
		||||
                        Some(200)
 | 
			
		||||
                    }
 | 
			
		||||
                    "/reboot" => {
 | 
			
		||||
                        let mut board = BOARD_ACCESS.get().await.lock().await;
 | 
			
		||||
                        board.board_hal.get_esp().set_restart_to_conf(true);
 | 
			
		||||
                        self.reboot_now.store(true, Ordering::Relaxed);
 | 
			
		||||
                        Some(200)
 | 
			
		||||
                    }
 | 
			
		||||
                    &_ => {
 | 
			
		||||
                        let json = match path {
 | 
			
		||||
                            "/version" => Some(get_version_web(conn).await),
 | 
			
		||||
                            "/time" => Some(get_time(conn).await),
 | 
			
		||||
                            "/battery" => Some(get_battery_state(conn).await),
 | 
			
		||||
                            "/solar" => Some(get_solar_state(conn).await),
 | 
			
		||||
                            "/get_config" => Some(get_config(conn).await),
 | 
			
		||||
                            "/files" => Some(list_files(conn).await),
 | 
			
		||||
                            "/log_localization" => Some(get_log_localization_config(conn).await),
 | 
			
		||||
                            "/log" => Some(get_log(conn).await),
 | 
			
		||||
                            "/wifiscan" => Some(wifi_scan(conn).await),
 | 
			
		||||
                            _ => None,
 | 
			
		||||
                        };
 | 
			
		||||
                        match json {
 | 
			
		||||
                            None => None,
 | 
			
		||||
                            Some(json) => Some(handle_json(conn, json).await?),
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                Method::Post => {
 | 
			
		||||
                    let json = match path {
 | 
			
		||||
                        "/version" => Some(get_version_web(conn).await),
 | 
			
		||||
                        "/time" => Some(get_time(conn).await),
 | 
			
		||||
                        "/battery" => Some(get_battery_state(conn).await),
 | 
			
		||||
                        "/solar" => Some(get_solar_state(conn).await),
 | 
			
		||||
                        "/get_config" => Some(get_config(conn).await),
 | 
			
		||||
                        "/files" => Some(list_files(conn).await),
 | 
			
		||||
                        "/log_localization" => Some(get_log_localization_config(conn).await),
 | 
			
		||||
                        "/log" => Some(get_log(conn).await),
 | 
			
		||||
                        "/wifiscan" => Some(wifi_scan(conn).await),
 | 
			
		||||
                        "/set_config" => Some(set_config(conn).await),
 | 
			
		||||
                        _ => None,
 | 
			
		||||
                    };
 | 
			
		||||
                    match json {
 | 
			
		||||
@@ -368,20 +466,9 @@ impl Handler for HttpHandler {
 | 
			
		||||
                        Some(json) => Some(handle_json(conn, json).await?),
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Method::Post => {
 | 
			
		||||
                let json = match path {
 | 
			
		||||
                    "/wifiscan" => Some(wifi_scan(conn).await),
 | 
			
		||||
                    "/set_config" => Some(set_config(conn).await),
 | 
			
		||||
                    _ => None,
 | 
			
		||||
                };
 | 
			
		||||
                match json {
 | 
			
		||||
                    None => None,
 | 
			
		||||
                    Some(json) => Some(handle_json(conn, json).await?),
 | 
			
		||||
                }
 | 
			
		||||
                Method::Options | Method::Delete | Method::Head | Method::Put => None,
 | 
			
		||||
                _ => None,
 | 
			
		||||
            }
 | 
			
		||||
            Method::Options | Method::Delete | Method::Head | Method::Put => None,
 | 
			
		||||
            _ => None,
 | 
			
		||||
        };
 | 
			
		||||
        let code = match status {
 | 
			
		||||
            None => {
 | 
			
		||||
@@ -390,6 +477,7 @@ impl Handler for HttpHandler {
 | 
			
		||||
            }
 | 
			
		||||
            Some(code) => code,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        conn.complete().await?;
 | 
			
		||||
        let response_time = Instant::now().duration_since(start).as_millis();
 | 
			
		||||
 | 
			
		||||
@@ -398,16 +486,87 @@ impl Handler for HttpHandler {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)?;
 | 
			
		||||
//     .fn_handler("/file", Method::Get, move |request| {
 | 
			
		||||
//         let filename = query_param(request.uri(), "filename").unwrap();
 | 
			
		||||
//         let file_handle = BOARD_ACCESS
 | 
			
		||||
//             .lock()
 | 
			
		||||
//             .unwrap()
 | 
			
		||||
//             .board_hal
 | 
			
		||||
//             .get_esp()
 | 
			
		||||
//             .get_file_handle(&filename, false);
 | 
			
		||||
//         match file_handle {
 | 
			
		||||
//             Ok(mut file_handle) => {
 | 
			
		||||
//                 let headers = [("Access-Control-Allow-Origin", "*")];
 | 
			
		||||
//                 let mut response = request.into_response(200, None, &headers)?;
 | 
			
		||||
//                 const BUFFER_SIZE: usize = 512;
 | 
			
		||||
//                 let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
 | 
			
		||||
//                 let mut total_read: usize = 0;
 | 
			
		||||
//                 loop {
 | 
			
		||||
//                     unsafe { vTaskDelay(1) };
 | 
			
		||||
//                     let read = std::io::Read::read(&mut file_handle, &mut buffer)?;
 | 
			
		||||
//                     total_read += read;
 | 
			
		||||
//                     let to_write = &buffer[0..read];
 | 
			
		||||
//                     response.write(to_write)?;
 | 
			
		||||
//                     if read == 0 {
 | 
			
		||||
//                         break;
 | 
			
		||||
//                     }
 | 
			
		||||
//                 }
 | 
			
		||||
//                 log::info!("wrote {total_read} for file {filename}");
 | 
			
		||||
//                 drop(file_handle);
 | 
			
		||||
//                 response.flush()?;
 | 
			
		||||
//             }
 | 
			
		||||
//             Err(err) => {
 | 
			
		||||
//                 //todo set headers here for filename to be error
 | 
			
		||||
//                 let error_text = err.to_string();
 | 
			
		||||
//                 log::info!("error handling get file {}", error_text);
 | 
			
		||||
//                 cors_response(request, 500, &error_text)?;
 | 
			
		||||
//             }
 | 
			
		||||
//         }
 | 
			
		||||
//         anyhow::Ok(())
 | 
			
		||||
//     })
 | 
			
		||||
//     .unwrap();
 | 
			
		||||
// server
 | 
			
		||||
//     .fn_handler("/file", Method::Post, move |mut request| {
 | 
			
		||||
//         let filename = query_param(request.uri(), "filename").unwrap();
 | 
			
		||||
//         let mut board = BOARD_ACCESS.lock().unwrap();
 | 
			
		||||
//         let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true);
 | 
			
		||||
//         match file_handle {
 | 
			
		||||
//             //TODO get free filesystem size, check against during write if not to large
 | 
			
		||||
//             Ok(mut file_handle) => {
 | 
			
		||||
//                 const BUFFER_SIZE: usize = 512;
 | 
			
		||||
//                 let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
 | 
			
		||||
//                 let mut total_read: usize = 0;
 | 
			
		||||
//                 let mut lastiter = 0;
 | 
			
		||||
//                 loop {
 | 
			
		||||
//                     let iter = (total_read / 1024) % 8;
 | 
			
		||||
//                     if iter != lastiter {
 | 
			
		||||
//                         for i in 0..PLANT_COUNT {
 | 
			
		||||
//                             let _ = board.board_hal.fault(i, iter == i);
 | 
			
		||||
//                         }
 | 
			
		||||
//                         lastiter = iter;
 | 
			
		||||
//                     }
 | 
			
		||||
//
 | 
			
		||||
//     let mut board = BOARD_ACCESS.lock().expect("board access");
 | 
			
		||||
//     board.board_hal.set_config(config)?;
 | 
			
		||||
//     anyhow::Ok(Some("saved".to_owned()))
 | 
			
		||||
// }
 | 
			
		||||
//                     let read = request.read(&mut buffer)?;
 | 
			
		||||
//                     total_read += read;
 | 
			
		||||
//                     let to_write = &buffer[0..read];
 | 
			
		||||
//                     std::io::Write::write(&mut file_handle, to_write)?;
 | 
			
		||||
//                     if read == 0 {
 | 
			
		||||
//                         break;
 | 
			
		||||
//                     }
 | 
			
		||||
//                 }
 | 
			
		||||
//                 cors_response(request, 200, &format!("saved {total_read} bytes"))?;
 | 
			
		||||
//             }
 | 
			
		||||
//             Err(err) => {
 | 
			
		||||
//                 //todo set headers here for filename to be error
 | 
			
		||||
//                 let error_text = err.to_string();
 | 
			
		||||
//                 log::info!("error handling get file {}", error_text);
 | 
			
		||||
//                 cors_response(request, 500, &error_text)?;
 | 
			
		||||
//             }
 | 
			
		||||
//         }
 | 
			
		||||
//         drop(board);
 | 
			
		||||
//         anyhow::Ok(())
 | 
			
		||||
//     })
 | 
			
		||||
//     .unwrap();
 | 
			
		||||
 | 
			
		||||
async fn set_config<T, const N: usize>(
 | 
			
		||||
    request: &mut Connection<'_, T, N>,
 | 
			
		||||
@@ -703,87 +862,7 @@ pub async fn httpd(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
 | 
			
		||||
    //     })
 | 
			
		||||
    //     .unwrap();
 | 
			
		||||
    // server
 | 
			
		||||
    //     .fn_handler("/file", Method::Get, move |request| {
 | 
			
		||||
    //         let filename = query_param(request.uri(), "filename").unwrap();
 | 
			
		||||
    //         let file_handle = BOARD_ACCESS
 | 
			
		||||
    //             .lock()
 | 
			
		||||
    //             .unwrap()
 | 
			
		||||
    //             .board_hal
 | 
			
		||||
    //             .get_esp()
 | 
			
		||||
    //             .get_file_handle(&filename, false);
 | 
			
		||||
    //         match file_handle {
 | 
			
		||||
    //             Ok(mut file_handle) => {
 | 
			
		||||
    //                 let headers = [("Access-Control-Allow-Origin", "*")];
 | 
			
		||||
    //                 let mut response = request.into_response(200, None, &headers)?;
 | 
			
		||||
    //                 const BUFFER_SIZE: usize = 512;
 | 
			
		||||
    //                 let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
 | 
			
		||||
    //                 let mut total_read: usize = 0;
 | 
			
		||||
    //                 loop {
 | 
			
		||||
    //                     unsafe { vTaskDelay(1) };
 | 
			
		||||
    //                     let read = std::io::Read::read(&mut file_handle, &mut buffer)?;
 | 
			
		||||
    //                     total_read += read;
 | 
			
		||||
    //                     let to_write = &buffer[0..read];
 | 
			
		||||
    //                     response.write(to_write)?;
 | 
			
		||||
    //                     if read == 0 {
 | 
			
		||||
    //                         break;
 | 
			
		||||
    //                     }
 | 
			
		||||
    //                 }
 | 
			
		||||
    //                 log::info!("wrote {total_read} for file {filename}");
 | 
			
		||||
    //                 drop(file_handle);
 | 
			
		||||
    //                 response.flush()?;
 | 
			
		||||
    //             }
 | 
			
		||||
    //             Err(err) => {
 | 
			
		||||
    //                 //todo set headers here for filename to be error
 | 
			
		||||
    //                 let error_text = err.to_string();
 | 
			
		||||
    //                 log::info!("error handling get file {}", error_text);
 | 
			
		||||
    //                 cors_response(request, 500, &error_text)?;
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //         anyhow::Ok(())
 | 
			
		||||
    //     })
 | 
			
		||||
    //     .unwrap();
 | 
			
		||||
    // server
 | 
			
		||||
    //     .fn_handler("/file", Method::Post, move |mut request| {
 | 
			
		||||
    //         let filename = query_param(request.uri(), "filename").unwrap();
 | 
			
		||||
    //         let mut board = BOARD_ACCESS.lock().unwrap();
 | 
			
		||||
    //         let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true);
 | 
			
		||||
    //         match file_handle {
 | 
			
		||||
    //             //TODO get free filesystem size, check against during write if not to large
 | 
			
		||||
    //             Ok(mut file_handle) => {
 | 
			
		||||
    //                 const BUFFER_SIZE: usize = 512;
 | 
			
		||||
    //                 let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
 | 
			
		||||
    //                 let mut total_read: usize = 0;
 | 
			
		||||
    //                 let mut lastiter = 0;
 | 
			
		||||
    //                 loop {
 | 
			
		||||
    //                     let iter = (total_read / 1024) % 8;
 | 
			
		||||
    //                     if iter != lastiter {
 | 
			
		||||
    //                         for i in 0..PLANT_COUNT {
 | 
			
		||||
    //                             let _ = board.board_hal.fault(i, iter == i);
 | 
			
		||||
    //                         }
 | 
			
		||||
    //                         lastiter = iter;
 | 
			
		||||
    //                     }
 | 
			
		||||
    //
 | 
			
		||||
    //                     let read = request.read(&mut buffer)?;
 | 
			
		||||
    //                     total_read += read;
 | 
			
		||||
    //                     let to_write = &buffer[0..read];
 | 
			
		||||
    //                     std::io::Write::write(&mut file_handle, to_write)?;
 | 
			
		||||
    //                     if read == 0 {
 | 
			
		||||
    //                         break;
 | 
			
		||||
    //                     }
 | 
			
		||||
    //                 }
 | 
			
		||||
    //                 cors_response(request, 200, &format!("saved {total_read} bytes"))?;
 | 
			
		||||
    //             }
 | 
			
		||||
    //             Err(err) => {
 | 
			
		||||
    //                 //todo set headers here for filename to be error
 | 
			
		||||
    //                 let error_text = err.to_string();
 | 
			
		||||
    //                 log::info!("error handling get file {}", error_text);
 | 
			
		||||
    //                 cors_response(request, 500, &error_text)?;
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //         drop(board);
 | 
			
		||||
    //         anyhow::Ok(())
 | 
			
		||||
    //     })
 | 
			
		||||
    //     .unwrap();
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // server
 | 
			
		||||
    //     .fn_handler("/file", Method::Delete, move |request| {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user