diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml index 79c15a1..0fbaf50 100644 --- a/rust/.cargo/config.toml +++ b/rust/.cargo/config.toml @@ -1,16 +1,16 @@ [build] rustflags = [ - # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.) - # NOTE: May negatively impact performance of produced code - "-C", "force-frame-pointers", - "-Z", "stack-protector=all", - "-C", "link-arg=-Tlinkall.x", + # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.) + # NOTE: May negatively impact performance of produced code + "-C", "force-frame-pointers", + "-Z", "stack-protector=all", + "-C", "link-arg=-Tlinkall.x", ] target = "riscv32imac-unknown-none-elf" [target.riscv32imac-unknown-none-elf] -runner = "espflash flash --monitor --chip esp32c6" +runner = "espflash flash --monitor --chip esp32c6 --baud 921600 --partition-table partitions.csv" #runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl #runner = "espflash flash --monitor" #runner = "cargo runner" @@ -22,7 +22,7 @@ runner = "espflash flash --monitor --chip esp32c6" [env] CHRONO_TZ_TIMEZONE_FILTER = "UTC|America/New_York|America/Chicago|America/Los_Angeles|Europe/London|Europe/Berlin|Europe/Paris|Asia/Tokyo|Asia/Shanghai|Asia/Kolkata|Australia/Sydney|America/Sao_Paulo|Africa/Johannesburg|Asia/Dubai|Pacific/Auckland" CARGO_WORKSPACE_DIR = { value = "", relative = true } -ESP_LOG="info" +ESP_LOG = "info" [unstable] build-std = ["alloc", "core"] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e142c6b..03f7496 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -155,6 +155,7 @@ edge-dhcp = "0.6.0" edge-nal = "0.5.0" edge-nal-embassy = "0.6.0" static_cell = "2.1.1" +cfg-if = "1.0.3" [patch.crates-io] diff --git a/rust/espflash.toml b/rust/espflash.toml index e4bc93c..f2a3eb8 100644 --- a/rust/espflash.toml +++ b/rust/espflash.toml @@ -1,5 +1,6 @@ -partition_table = "partitions.csv" -[connection] -serial = "/dev/ttyACM0" +format = "EspIdf" + +[idf_format_args] + [flash] -size = "16MB" \ No newline at end of file +size = "16MB" diff --git a/rust/src/hal/esp.rs b/rust/src/hal/esp.rs index d61866e..4fe30f5 100644 --- a/rust/src/hal/esp.rs +++ b/rust/src/hal/esp.rs @@ -1,5 +1,5 @@ use crate::config::{NetworkConfig, PlantControllerConfig}; -use crate::hal::PLANT_COUNT; +use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT}; use crate::log::{log, LogMessage}; use crate::STAY_ALIVE; use anyhow::{anyhow, bail, Context}; @@ -8,12 +8,21 @@ use serde::Serialize; use alloc::{string::String, vec::Vec}; use core::marker::PhantomData; -use core::net::IpAddr; +use core::net::{IpAddr, Ipv4Addr}; use core::str::FromStr; -use embassy_time::Instant; +use embassy_executor::{SendSpawner, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; +use embassy_time::{Duration, Instant, Timer}; use esp_bootloader_esp_idf::ota::OtaImageState; use esp_hal::gpio::Input; +use esp_hal::rng::Rng; +use esp_println::{print, println}; use esp_storage::FlashStorage; +use esp_wifi::wifi::{ + AccessPointConfiguration, Configuration, Interfaces, WifiController, WifiDevice, WifiEvent, + WifiState, +}; #[link_section = ".rtc.data"] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; @@ -51,12 +60,17 @@ pub struct MqttClient<'a> { base_topic: heapless::String<64>, } pub struct Esp<'a> { - pub boot_button: Input<'a>, + pub rng: Rng, + //first starter (ap or sta will take these) + pub interfaces: Option>, + pub controller: Option>, + + //only filled, if a useable mqtt client with working roundtrip could be established pub(crate) mqtt_client: Option>, - pub(crate) dummy: PhantomData<&'a ()>, + + pub boot_button: Input<'a>, pub(crate) wall_clock_offset: u64, - //pub(crate) wifi_driver: EspWifi<'a>, - //pub(crate) boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, + pub storage: FlashStorage, pub slot: usize, pub next_slot: usize, @@ -71,6 +85,17 @@ pub struct IpInfo { struct AccessPointInfo {} +macro_rules! mk_static { + ($t:ty,$val:expr) => {{ + static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); + #[deny(unused_attributes)] + let x = STATIC_CELL.uninit().write(($val)); + x + }}; +} + +static WIFI_CONTROLLER: static_cell::StaticCell = static_cell::StaticCell::new(); + impl Esp<'_> { const SPIFFS_PARTITION_NAME: &'static str = "storage"; const CONFIG_FILE: &'static str = "/spiffs/config.cfg"; @@ -150,24 +175,58 @@ impl Esp<'_> { } } - pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<()> { + pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result { let _ssid = match self.load_config() { Ok(config) => config.network.ap_ssid.clone(), Err(_) => heapless::String::from_str("PlantCtrl Emergency Mode").unwrap(), }; - todo!("todo"); - // - // let apconfig = AccessPointConfiguration { - // ssid, - // auth_method: AuthMethod::None, - // ssid_hidden: false, - // ..Default::default() - // }; - // self.wifi_driver - // .set_configuration(&Configuration::AccessPoint(apconfig))?; - // self.wifi_driver.start()?; - // anyhow::Ok(()) + let spawner = Spawner::for_current_executor().await; + + 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 = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip"); + + let config = embassy_net::Config::ipv4_static(StaticConfigV4 { + address: Ipv4Cidr::new(gw_ip_addr, 24), + gateway: Some(gw_ip_addr), + dns_servers: Default::default(), + }); + + let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64; + + // Init network stack + let (stack, runner) = embassy_net::new( + device, + config, + mk_static!(StackResources<3>, StackResources::<3>::new()), + seed, + ); + let stack = mk_static!(Stack, stack); + + let controller = self.controller.take().unwrap(); + spawner.spawn(connection(controller)).ok(); + spawner.spawn(net_task(runner)).ok(); + spawner.spawn(run_dhcp(stack, gw_ip_addr_str)).ok(); + + loop { + if stack.is_link_up() { + break; + } + Timer::after(Duration::from_millis(500)).await; + } + println!( + "Connect to the AP `esp-wifi` and point your browser to http://{gw_ip_addr_str}:8080/" + ); + println!("DHCP is enabled so there's no need to configure a static IP, just in case:"); + while !stack.is_config_up() { + Timer::after(Duration::from_millis(100)).await + } + stack + .config_v4() + .inspect(|c| println!("ipv4 config: {c:?}")); + + anyhow::Ok(stack.clone()) } pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result { @@ -638,3 +697,74 @@ impl Esp<'_> { // } } } + +#[embassy_executor::task] +async fn run_dhcp(stack: &'static Stack<'static>, gw_ip_addr: &'static str) { + use core::net::{Ipv4Addr, SocketAddrV4}; + + use edge_dhcp::{ + io::{self, DEFAULT_SERVER_PORT}, + server::{Server, ServerOptions}, + }; + use edge_nal::UdpBind; + use edge_nal_embassy::{Udp, UdpBuffers}; + + let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip"); + + let mut buf = [0u8; 1500]; + + let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; + + let buffers = UdpBuffers::<3, 1024, 1024, 10>::new(); + let unbound_socket = Udp::new(*stack, &buffers); + let mut bound_socket = unbound_socket + .bind(core::net::SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::UNSPECIFIED, + DEFAULT_SERVER_PORT, + ))) + .await + .unwrap(); + + loop { + _ = io::server::run( + &mut Server::<_, 64>::new_with_et(ip), + &ServerOptions::new(ip, Some(&mut gw_buf)), + &mut bound_socket, + &mut buf, + ) + .await + .inspect_err(|e| log::warn!("DHCP server error: {e:?}")); + Timer::after(Duration::from_millis(500)).await; + } +} + +#[embassy_executor::task] +async fn connection(mut controller: WifiController<'static>) { + println!("start connection task"); + println!("Device capabilities: {:?}", controller.capabilities()); + loop { + match esp_wifi::wifi::wifi_state() { + WifiState::ApStarted => { + // wait until we're no longer connected + controller.wait_for_event(WifiEvent::ApStop).await; + Timer::after(Duration::from_millis(5000)).await + } + _ => {} + } + if !matches!(controller.is_started(), core::result::Result::Ok(true)) { + let client_config = Configuration::AccessPoint(AccessPointConfiguration { + ssid: "esp-wifi".try_into().unwrap(), + ..Default::default() + }); + controller.set_configuration(&client_config).unwrap(); + println!("Starting wifi"); + controller.start_async().await.unwrap(); + println!("Wifi started!"); + } + } +} + +#[embassy_executor::task] +async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) { + runner.run().await +} diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 2eaecee..cc514d0 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -1,5 +1,5 @@ pub(crate) mod battery; -mod esp; +pub mod esp; mod initial_hal; mod rtc; //mod water; @@ -19,21 +19,34 @@ use alloc::boxed::Box; use alloc::format; use anyhow::{Ok, Result}; use async_trait::async_trait; +use core::cell::OnceCell; use core::marker::PhantomData; -use embassy_executor::Spawner; +use core::net::Ipv4Addr; +use core::str::FromStr; +use embassy_executor::{SendSpawner, Spawner}; +use embassy_net::tcp::{Error, TcpSocket}; +use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; //use battery::BQ34Z100G1; //use bq34z100::Bq34z100g1Driver; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::mutex::Mutex; +use embassy_sync::once_lock::OnceLock; +use embassy_time::{Duration, Timer}; use esp_bootloader_esp_idf::partitions::DataPartitionSubType; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; use esp_hal::timer::systimer::SystemTimer; -use esp_println::println; +use esp_println::{print, println}; use measurements::{Current, Voltage}; +use embassy_sync::mutex::{Mutex, MutexGuard}; use esp_alloc as _; use esp_backtrace as _; +use esp_hal::rng::Rng; +use esp_hal::timer::timg::TimerGroup; +use esp_wifi::wifi::{ + AccessPointConfiguration, Configuration, WifiController, WifiDevice, WifiEvent, WifiState, +}; +use esp_wifi::{init, EspWifiController}; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; @@ -42,16 +55,6 @@ const TANK_MULTI_SAMPLE: usize = 11; //pub static I2C_DRIVER: LazyLock>> = LazyLock::new(PlantHal::create_i2c); -// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html -macro_rules! mk_static { - ($t:ty,$val:expr) => {{ - static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); - #[deny(unused_attributes)] - let x = STATIC_CELL.uninit().write(($val)); - x - }}; -} - fn deep_sleep(_duration_in_ms: u64) -> ! { //unsafe { // //if we don't do this here, we might just revert newly flashed firmware @@ -159,6 +162,17 @@ pub struct FreePeripherals { // pub can: CAN, } +macro_rules! mk_static { + ($t:ty,$val:expr) => {{ + static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); + #[deny(unused_attributes)] + let x = STATIC_CELL.uninit().write(($val)); + x + }}; +} + +const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP"); + impl PlantHal { // fn create_i2c() -> Mutex> { // let peripherals = unsafe { Peripherals::new() }; @@ -176,19 +190,31 @@ impl PlantHal { // Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) // } - pub fn create(spawner: Spawner) -> Result>> { + pub async fn create(spawner: Spawner) -> Result>> { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(size: 64 * 1024); let systimer = SystemTimer::new(peripherals.SYSTIMER); - esp_hal_embassy::init(systimer.alarm0); let boot_button = Input::new( peripherals.GPIO9, InputConfig::default().with_pull(Pull::None), ); + let mut rng = Rng::new(peripherals.RNG); + let timg0 = TimerGroup::new(peripherals.TIMG0); + let esp_wifi_ctrl = &*mk_static!( + EspWifiController<'static>, + init(timg0.timer0, rng.clone()).unwrap() + ); + + let (mut controller, interfaces) = + esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).unwrap(); + + use esp_hal::timer::systimer::SystemTimer; + esp_hal_embassy::init(systimer.alarm0); + // // let free_pins = FreePeripherals { // can: peripherals.can, @@ -255,15 +281,16 @@ impl PlantHal { let ota_state = ota.current_ota_state()?; let mut esp = Esp { + rng, + controller: Some(controller), + interfaces: Some(interfaces), boot_button, mqtt_client: None, storage, slot: current.number(), next_slot: current.next().number(), ota_state, - dummy: PhantomData::default(), - wall_clock_offset: 0, // wifi_driver, - // boot_button + wall_clock_offset: 0, }; //init,reset rtc memory depending on cause diff --git a/rust/src/lib.rs b/rust/src/lib.rs deleted file mode 100644 index e775991..0000000 --- a/rust/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![allow(dead_code)] -#![no_std] -extern crate embedded_hal as hal; - -pub mod sipo; diff --git a/rust/src/main.rs b/rust/src/main.rs index a20b637..93146e1 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -11,6 +11,7 @@ use esp_backtrace as _; use crate::config::PlantConfig; use crate::tank::WATER_FROZEN_THRESH; +use crate::webserver::httpd; use crate::{ config::BoardVersion::INITIAL, hal::{PlantHal, HAL, PLANT_COUNT}, @@ -23,7 +24,7 @@ use alloc::sync::Arc; use alloc::{format, vec}; use chrono::{DateTime, Datelike, Timelike, Utc}; use chrono_tz::Tz::{self}; -use core::sync::atomic::Ordering; +use core::sync::atomic::{AtomicBool, Ordering}; use embassy_executor::Spawner; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::{Mutex, MutexGuard}; @@ -36,7 +37,6 @@ use esp_println::{logger, println}; use hal::battery::BatteryState; use log::{log, LogMessage}; use plant_state::PlantState; -use portable_atomic::AtomicBool; use serde::{Deserialize, Serialize}; #[no_mangle] @@ -57,7 +57,9 @@ mod config; mod hal; mod log; mod plant_state; +mod sipo; mod tank; +mod webserver; extern crate alloc; //mod webserver; @@ -235,11 +237,11 @@ async fn safe_main() -> anyhow::Result<()> { && board.board_hal.get_config().network.ssid.is_none() { info!("No wifi configured, starting initial config mode"); - board.board_hal.get_esp().wifi_ap().await.unwrap(); - drop(board); + let stack = board.board_hal.get_esp().wifi_ap().await?; + let reboot_now = Arc::new(AtomicBool::new(false)); - //TODO - //let _webserver = httpd(reboot_now.clone()); + println!("starting webserver"); + let _webserver = httpd(reboot_now.clone(), stack).await; wait_infinity(WaitType::MissingConfig, reboot_now.clone()).await; } @@ -1055,7 +1057,7 @@ async fn main(spawner: Spawner) { logger::init_logger_from_env(); //force init here! println!("Hal init"); - match BOARD_ACCESS.init(PlantHal::create(spawner).unwrap()) { + match BOARD_ACCESS.init(PlantHal::create(spawner).await.unwrap()) { Ok(_) => {} Err(_) => { panic!("Could not set hal to static") diff --git a/rust/src/sipo.rs b/rust/src/sipo.rs index 3179789..7784b5b 100644 --- a/rust/src/sipo.rs +++ b/rust/src/sipo.rs @@ -1,12 +1,11 @@ //! Serial-in parallel-out shift register use core::cell::RefCell; -use core::mem::{self, MaybeUninit}; use core::convert::Infallible; -use core::result::{Result, Result::Ok}; use core::iter::Iterator; - -use hal::digital::OutputPin; +use core::mem::{self, MaybeUninit}; +use core::result::{Result, Result::Ok}; +use embedded_hal::digital::OutputPin; trait ShiftRegisterInternal { fn update(&self, index: usize, command: bool) -> Result<(), ()>; diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs index 48ecb12..1048a06 100644 --- a/rust/src/webserver/mod.rs +++ b/rust/src/webserver/mod.rs @@ -2,30 +2,29 @@ use crate::{ config::PlantControllerConfig, - determine_tank_state, do_secure_pump, get_version, + do_secure_pump, get_version, hal::PLANT_COUNT, log::LogMessage, plant_state::{MoistureSensorState, PlantState}, BOARD_ACCESS, }; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; use anyhow::{bail, Context}; use chrono::DateTime; use core::result::Result::Ok; -use embedded_svc::http::Method; -use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}; -use esp_idf_sys::{settimeofday, timeval, vTaskDelay}; -use esp_ota::OtaUpdate; -use heapless::String; +use core::sync::atomic::AtomicBool; +use embassy_net::tcp::TcpSocket; +use embassy_net::{IpListenEndpoint, Stack}; +use embassy_time::{Duration, Timer}; +use esp_println::{print, println}; use serde::{Deserialize, Serialize}; -use std::{ - str::from_utf8, - sync::{atomic::AtomicBool, Arc}, -}; -use url::Url; #[derive(Serialize, Debug)] struct SSIDList<'a> { - ssids: Vec<&'a String<32>>, + ssids: Vec<&'a String>, } #[derive(Serialize, Debug)] @@ -36,8 +35,8 @@ struct LoadData<'a> { #[derive(Serialize, Debug)] struct Moistures { - moisture_a: Vec, - moisture_b: Vec, + moisture_a: Vec, + moisture_b: Vec, } #[derive(Serialize, Debug)] @@ -59,7 +58,7 @@ pub struct TestPump { #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct WebBackupHeader { - timestamp: std::string::String, + timestamp: String, size: u16, } @@ -67,664 +66,743 @@ pub struct WebBackupHeader { pub struct NightLampCommand { active: bool, } +// +// fn write_time( +// request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let actual_data = read_up_to_bytes_from_request(request, None)?; +// let time: SetTime = serde_json::from_slice(&actual_data)?; +// let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?; +// let mut board = BOARD_ACCESS.lock().expect("board access"); +// +// let now = timeval { +// tv_sec: parsed.to_utc().timestamp(), +// tv_usec: 0, +// }; +// unsafe { settimeofday(&now, core::ptr::null_mut()) }; +// board +// .board_hal +// .get_rtc_module() +// .set_rtc_time(&parsed.to_utc())?; +// anyhow::Ok(None) +// } +// +// fn get_time( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("board access"); +// let native = board +// .board_hal +// .get_esp() +// .time() +// .map(|t| t.to_rfc3339()) +// .unwrap_or("error".to_string()); +// let rtc = board +// .board_hal +// .get_rtc_module() +// .get_rtc_time() +// .map(|t| t.to_rfc3339()) +// .unwrap_or("error".to_string()); +// +// let data = LoadData { +// rtc: rtc.as_str(), +// native: native.as_str(), +// }; +// let json = serde_json::to_string(&data)?; +// +// anyhow::Ok(Some(json)) +// } +// +// fn get_timezones( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// // Get all timezones using chrono-tz +// let timezones: Vec<&'static str> = chrono_tz::TZ_VARIANTS.iter().map(|tz| tz.name()).collect(); +// +// // Convert to JSON +// let json = serde_json::to_string(&timezones)?; +// anyhow::Ok(Some(json)) +// } +// +// fn get_live_moisture( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("Should never fail"); +// let plant_state = +// Vec::from_iter((0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board))); +// let a = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_a { +// MoistureSensorState::Disabled => "disabled".to_string(), +// MoistureSensorState::MoistureValue { +// raw_hz, +// moisture_percent, +// } => { +// format!("{moisture_percent:.2}% {raw_hz}hz",) +// } +// MoistureSensorState::SensorError(err) => format!("{err:?}"), +// })); +// let b = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_b { +// MoistureSensorState::Disabled => "disabled".to_string(), +// MoistureSensorState::MoistureValue { +// raw_hz, +// moisture_percent, +// } => { +// format!("{moisture_percent:.2}% {raw_hz}hz",) +// } +// MoistureSensorState::SensorError(err) => format!("{err:?}"), +// })); +// +// let data = Moistures { +// moisture_a: a, +// moisture_b: b, +// }; +// let json = serde_json::to_string(&data)?; +// +// anyhow::Ok(Some(json)) +// } +// +// fn get_config( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("Should never fail"); +// let json = serde_json::to_string(&board.board_hal.get_config())?; +// anyhow::Ok(Some(json)) +// } +// +// fn backup_config( +// request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let all = read_up_to_bytes_from_request(request, Some(3072))?; +// let mut board = BOARD_ACCESS.lock().expect("board access"); +// +// //TODO how to handle progress here? prior versions animated the fault leds while running +// board.board_hal.get_rtc_module().backup_config(&all)?; +// anyhow::Ok(Some("saved".to_owned())) +// } +// +// fn get_backup_config( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("board access"); +// let json = match board.board_hal.get_rtc_module().get_backup_config() { +// Ok(config) => from_utf8(&config)?.to_owned(), +// Err(err) => { +// log::info!("Error get backup config {:?}", err); +// err.to_string() +// } +// }; +// anyhow::Ok(Some(json)) +// } +// +// fn backup_info( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("Should never fail"); +// let header = board.board_hal.get_rtc_module().get_backup_info(); +// let json = match header { +// Ok(h) => { +// let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap(); +// let wbh = WebBackupHeader { +// timestamp: timestamp.to_rfc3339(), +// size: h.size, +// }; +// serde_json::to_string(&wbh)? +// } +// Err(err) => { +// let wbh = WebBackupHeader { +// timestamp: err.to_string(), +// size: 0, +// }; +// serde_json::to_string(&wbh)? +// } +// }; +// anyhow::Ok(Some(json)) +// } +// +// fn set_config( +// request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, 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())) +// } +// +// fn get_solar_state( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("board access"); +// let state = SolarState { +// mppt_voltage: board.board_hal.get_mptt_voltage()?.as_millivolts() as f32, +// mppt_current: board.board_hal.get_mptt_current()?.as_milliamperes() as f32, +// is_day: board.board_hal.is_day(), +// }; +// anyhow::Ok(Some(serde_json::to_string(&state)?)) +// } +// +// fn get_battery_state( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().expect("board access"); +// let battery_state = board.board_hal.get_battery_monitor().get_battery_state(); +// anyhow::Ok(Some(serde_json::to_string(&battery_state)?)) +// } +// +// fn get_log( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let output = crate::log::get_log(); +// anyhow::Ok(Some(serde_json::to_string(&output)?)) +// } +// +// fn get_log_localization_config() -> Result { +// anyhow::Ok(serde_json::to_string( +// &LogMessage::to_log_localisation_config(), +// )?) +// } +// +// fn get_version_web( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// anyhow::Ok(Some(serde_json::to_string(&get_version())?)) +// } +// +// fn pump_test( +// request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let actual_data = read_up_to_bytes_from_request(request, None)?; +// let pump_test: TestPump = serde_json::from_slice(&actual_data)?; +// let mut board = BOARD_ACCESS.lock().unwrap(); +// +// let config = &board.board_hal.get_config().plants[pump_test.pump].clone(); +// let pump_result = do_secure_pump(&mut board, pump_test.pump, config, false)?; +// board.board_hal.pump(pump_test.pump, false)?; +// anyhow::Ok(Some(serde_json::to_string(&pump_result)?)) +// } +// +// fn tank_info( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().unwrap(); +// let tank_info = determine_tank_state(&mut board); +// //should be multsampled +// +// let water_temp = board +// .board_hal +// .get_tank_sensor() +// .context("no sensor") +// .and_then(|f| f.water_temperature_c()); +// Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info( +// &board.board_hal.get_config().tank, +// &water_temp, +// ))?)) +// } +// +// fn night_lamp_test( +// request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let actual_data = read_up_to_bytes_from_request(request, None)?; +// let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?; +// let mut board = BOARD_ACCESS.lock().unwrap(); +// board.board_hal.light(light_command.active)?; +// anyhow::Ok(None) +// } +// +// fn wifi_scan( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().unwrap(); +// let scan_result = board.board_hal.get_esp().wifi_scan()?; +// let mut ssids: Vec<&String<32>> = Vec::new(); +// scan_result.iter().for_each(|s| ssids.push(&s.ssid)); +// let ssid_json = serde_json::to_string(&SSIDList { ssids })?; +// log::info!("Sending ssid list {}", &ssid_json); +// anyhow::Ok(Some(ssid_json)) +// } +// +// fn list_files( +// _request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS +// .lock() +// .expect("It should be possible to lock the board for exclusive fs access"); +// let result = board.board_hal.get_esp().list_files(); +// let file_list_json = serde_json::to_string(&result)?; +// anyhow::Ok(Some(file_list_json)) +// } +// +// fn ota( +// request: &mut Request<&mut EspHttpConnection>, +// ) -> Result, anyhow::Error> { +// let mut board = BOARD_ACCESS.lock().unwrap(); +// let mut ota = OtaUpdate::begin()?; +// log::info!("start ota"); +// +// //having a larger buffer is not really faster, requires more stack and prevents the progress bar from working ;) +// 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 read = request.read(&mut buffer)?; +// total_read += read; +// let to_write = &buffer[0..read]; +// //delay for watchdog and wifi stuff +// board.board_hal.get_esp().delay.delay_ms(1); +// +// let iter = (total_read / 1024) % 8; +// if iter != lastiter { +// board.board_hal.general_fault(iter % 5 == 0); +// for i in 0..PLANT_COUNT { +// let _ = board.board_hal.fault(i, iter == i); +// } +// lastiter = iter; +// } +// +// ota.write(to_write)?; +// if read == 0 { +// break; +// } +// } +// log::info!("wrote bytes ota {total_read}"); +// log::info!("finish ota"); +// let partition = ota.raw_partition(); +// log::info!("finalizing and changing boot partition to {partition:?}"); +// +// let mut finalizer = ota.finalize()?; +// log::info!("changing boot partition"); +// board.board_hal.get_esp().set_restart_to_conf(true); +// drop(board); +// finalizer.set_as_boot_partition()?; +// anyhow::Ok(None) +// } +// +// fn query_param(uri: &str, param_name: &str) -> Option { +// 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, +// } +// } -fn write_time( - request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let actual_data = read_up_to_bytes_from_request(request, None)?; - let time: SetTime = serde_json::from_slice(&actual_data)?; - let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?; - let mut board = BOARD_ACCESS.lock().expect("board access"); - - let now = timeval { - tv_sec: parsed.to_utc().timestamp(), - tv_usec: 0, - }; - unsafe { settimeofday(&now, core::ptr::null_mut()) }; - board - .board_hal - .get_rtc_module() - .set_rtc_time(&parsed.to_utc())?; - anyhow::Ok(None) -} - -fn get_time( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("board access"); - let native = board - .board_hal - .get_esp() - .time() - .map(|t| t.to_rfc3339()) - .unwrap_or("error".to_string()); - let rtc = board - .board_hal - .get_rtc_module() - .get_rtc_time() - .map(|t| t.to_rfc3339()) - .unwrap_or("error".to_string()); - - let data = LoadData { - rtc: rtc.as_str(), - native: native.as_str(), - }; - let json = serde_json::to_string(&data)?; - - anyhow::Ok(Some(json)) -} - -fn get_timezones( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - // Get all timezones using chrono-tz - let timezones: Vec<&'static str> = chrono_tz::TZ_VARIANTS.iter().map(|tz| tz.name()).collect(); - - // Convert to JSON - let json = serde_json::to_string(&timezones)?; - anyhow::Ok(Some(json)) -} - -fn get_live_moisture( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("Should never fail"); - let plant_state = - Vec::from_iter((0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board))); - let a = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_a { - MoistureSensorState::Disabled => "disabled".to_string(), - MoistureSensorState::MoistureValue { - raw_hz, - moisture_percent, - } => { - format!("{moisture_percent:.2}% {raw_hz}hz",) - } - MoistureSensorState::SensorError(err) => format!("{err:?}"), - })); - let b = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_b { - MoistureSensorState::Disabled => "disabled".to_string(), - MoistureSensorState::MoistureValue { - raw_hz, - moisture_percent, - } => { - format!("{moisture_percent:.2}% {raw_hz}hz",) - } - MoistureSensorState::SensorError(err) => format!("{err:?}"), - })); - - let data = Moistures { - moisture_a: a, - moisture_b: b, - }; - let json = serde_json::to_string(&data)?; - - anyhow::Ok(Some(json)) -} - -fn get_config( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("Should never fail"); - let json = serde_json::to_string(&board.board_hal.get_config())?; - anyhow::Ok(Some(json)) -} - -fn backup_config( - request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let all = read_up_to_bytes_from_request(request, Some(3072))?; - let mut board = BOARD_ACCESS.lock().expect("board access"); - - //TODO how to handle progress here? prior versions animated the fault leds while running - board.board_hal.get_rtc_module().backup_config(&all)?; - anyhow::Ok(Some("saved".to_owned())) -} - -fn get_backup_config( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("board access"); - let json = match board.board_hal.get_rtc_module().get_backup_config() { - Ok(config) => from_utf8(&config)?.to_owned(), - Err(err) => { - log::info!("Error get backup config {:?}", err); - err.to_string() - } - }; - anyhow::Ok(Some(json)) -} - -fn backup_info( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("Should never fail"); - let header = board.board_hal.get_rtc_module().get_backup_info(); - let json = match header { - Ok(h) => { - let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap(); - let wbh = WebBackupHeader { - timestamp: timestamp.to_rfc3339(), - size: h.size, - }; - serde_json::to_string(&wbh)? - } - Err(err) => { - let wbh = WebBackupHeader { - timestamp: err.to_string(), - size: 0, - }; - serde_json::to_string(&wbh)? - } - }; - anyhow::Ok(Some(json)) -} - -fn set_config( - request: &mut Request<&mut EspHttpConnection>, -) -> Result, 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())) -} - -fn get_solar_state( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("board access"); - let state = SolarState { - mppt_voltage: board.board_hal.get_mptt_voltage()?.as_millivolts() as f32, - mppt_current: board.board_hal.get_mptt_current()?.as_milliamperes() as f32, - is_day: board.board_hal.is_day(), - }; - anyhow::Ok(Some(serde_json::to_string(&state)?)) -} - -fn get_battery_state( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().expect("board access"); - let battery_state = board.board_hal.get_battery_monitor().get_battery_state(); - anyhow::Ok(Some(serde_json::to_string(&battery_state)?)) -} - -fn get_log( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let output = crate::log::get_log(); - anyhow::Ok(Some(serde_json::to_string(&output)?)) -} - -fn get_log_localization_config() -> Result { - anyhow::Ok(serde_json::to_string( - &LogMessage::to_log_localisation_config(), - )?) -} - -fn get_version_web( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - anyhow::Ok(Some(serde_json::to_string(&get_version())?)) -} - -fn pump_test( - request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let actual_data = read_up_to_bytes_from_request(request, None)?; - let pump_test: TestPump = serde_json::from_slice(&actual_data)?; - let mut board = BOARD_ACCESS.lock().unwrap(); - - let config = &board.board_hal.get_config().plants[pump_test.pump].clone(); - let pump_result = do_secure_pump(&mut board, pump_test.pump, config, false)?; - board.board_hal.pump(pump_test.pump, false)?; - anyhow::Ok(Some(serde_json::to_string(&pump_result)?)) -} - -fn tank_info( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let tank_info = determine_tank_state(&mut board); - //should be multsampled - - let water_temp = board - .board_hal - .get_tank_sensor() - .context("no sensor") - .and_then(|f| f.water_temperature_c()); - Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info( - &board.board_hal.get_config().tank, - &water_temp, - ))?)) -} - -fn night_lamp_test( - request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let actual_data = read_up_to_bytes_from_request(request, None)?; - let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?; - let mut board = BOARD_ACCESS.lock().unwrap(); - board.board_hal.light(light_command.active)?; - anyhow::Ok(None) -} - -fn wifi_scan( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let scan_result = board.board_hal.get_esp().wifi_scan()?; - let mut ssids: Vec<&String<32>> = Vec::new(); - scan_result.iter().for_each(|s| ssids.push(&s.ssid)); - let ssid_json = serde_json::to_string(&SSIDList { ssids })?; - log::info!("Sending ssid list {}", &ssid_json); - anyhow::Ok(Some(ssid_json)) -} - -fn list_files( - _request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS - .lock() - .expect("It should be possible to lock the board for exclusive fs access"); - let result = board.board_hal.get_esp().list_files(); - let file_list_json = serde_json::to_string(&result)?; - anyhow::Ok(Some(file_list_json)) -} - -fn ota( - request: &mut Request<&mut EspHttpConnection>, -) -> Result, anyhow::Error> { - let mut board = BOARD_ACCESS.lock().unwrap(); - let mut ota = OtaUpdate::begin()?; - log::info!("start ota"); - - //having a larger buffer is not really faster, requires more stack and prevents the progress bar from working ;) - const BUFFER_SIZE: usize = 512; - let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - let mut total_read: usize = 0; - let mut lastiter = 0; +pub async fn httpd(reboot_now: Arc, stack: Stack<'_>) { + let mut rx_buffer = [0; 1536]; + let mut tx_buffer = [0; 1536]; + println!("Stack {}", stack.is_config_up()); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + println!("Socket created"); + //let d = embassy_time::Duration::from_millis(1000); + //socket.set_timeout(Some(d)); loop { - let read = request.read(&mut buffer)?; - total_read += read; - let to_write = &buffer[0..read]; - //delay for watchdog and wifi stuff - board.board_hal.get_esp().delay.delay_ms(1); + println!("Wait for connection..."); + let r = socket + .accept(IpListenEndpoint { + addr: None, + port: 8080, + }) + .await; + println!("Connected..."); - let iter = (total_read / 1024) % 8; - if iter != lastiter { - board.board_hal.general_fault(iter % 5 == 0); - for i in 0..PLANT_COUNT { - let _ = board.board_hal.fault(i, iter == i); - } - lastiter = iter; + if let Err(e) = r { + println!("connect error: {:?}", e); + continue; } - ota.write(to_write)?; - if read == 0 { - break; - } - } - log::info!("wrote bytes ota {total_read}"); - log::info!("finish ota"); - let partition = ota.raw_partition(); - log::info!("finalizing and changing boot partition to {partition:?}"); + use embedded_io_async::Write; - let mut finalizer = ota.finalize()?; - log::info!("changing boot partition"); - board.board_hal.get_esp().set_restart_to_conf(true); - drop(board); - finalizer.set_as_boot_partition()?; - anyhow::Ok(None) -} + let mut buffer = [0u8; 1024]; + let mut pos = 0; + loop { + match socket.read(&mut buffer).await { + core::result::Result::Ok(len) => { + if len == 0 { + println!("read EOF"); + break; + } else { + let to_print = + unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) }; -fn query_param(uri: &str, param_name: &str) -> Option { - 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, - } -} - -pub fn httpd(reboot_now: Arc) -> Box> { - let server_config = Configuration { - stack_size: 32768, - ..Default::default() - }; - let mut server: Box> = - Box::new(EspHttpServer::new(&server_config).unwrap()); - server - .fn_handler("/version", Method::Get, |request| { - handle_error_to500(request, get_version_web) - }) - .unwrap(); - server - .fn_handler("/log", Method::Get, |request| { - handle_error_to500(request, get_log) - }) - .unwrap(); - server - .fn_handler("/log_localization", Method::Get, |request| { - cors_response(request, 200, &get_log_localization_config().unwrap()) - }) - .unwrap(); - server - .fn_handler("/battery", Method::Get, |request| { - handle_error_to500(request, get_battery_state) - }) - .unwrap(); - server - .fn_handler("/solar", Method::Get, |request| { - handle_error_to500(request, get_solar_state) - }) - .unwrap(); - server - .fn_handler("/time", Method::Get, |request| { - handle_error_to500(request, get_time) - }) - .unwrap(); - server - .fn_handler("/moisture", Method::Get, |request| { - handle_error_to500(request, get_live_moisture) - }) - .unwrap(); - server - .fn_handler("/time", Method::Post, |request| { - handle_error_to500(request, write_time) - }) - .unwrap(); - server - .fn_handler("/tank", Method::Get, |request| { - handle_error_to500(request, tank_info) - }) - .unwrap(); - server - .fn_handler("/pumptest", Method::Post, |request| { - handle_error_to500(request, pump_test) - }) - .unwrap(); - server - .fn_handler("/lamptest", Method::Post, |request| { - handle_error_to500(request, night_lamp_test) - }) - .unwrap(); - server - .fn_handler("/boardtest", Method::Post, move |_| { - BOARD_ACCESS.lock().unwrap().board_hal.test() - }) - .unwrap(); - server - .fn_handler("/wifiscan", Method::Post, move |request| { - handle_error_to500(request, wifi_scan) - }) - .unwrap(); - server - .fn_handler("/ota", Method::Post, |request| { - handle_error_to500(request, ota) - }) - .unwrap(); - server - .fn_handler("/ota", Method::Options, |request| { - cors_response(request, 200, "") - }) - .unwrap(); - server - .fn_handler("/get_config", Method::Get, move |request| { - handle_error_to500(request, get_config) - }) - .unwrap(); - server - .fn_handler("/get_backup_config", Method::Get, move |request| { - handle_error_to500(request, get_backup_config) - }) - .unwrap(); - - 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| { - handle_error_to500(request, backup_config) - }) - .unwrap(); - server - .fn_handler("/backup_info", Method::Get, move |request| { - handle_error_to500(request, backup_info) - }) - .unwrap(); - server - .fn_handler("/files", Method::Get, move |request| { - handle_error_to500(request, list_files) - }) - .unwrap(); - let reboot_now_for_reboot = reboot_now.clone(); - server - .fn_handler("/reboot", Method::Post, move |_| { - BOARD_ACCESS - .lock() - .unwrap() - .board_hal - .get_esp() - .set_restart_to_conf(true); - reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed); - anyhow::Ok(()) - }) - .unwrap(); - - unsafe { vTaskDelay(1) }; - - let reboot_now_for_exit = reboot_now.clone(); - server - .fn_handler("/exit", Method::Post, move |_| { - reboot_now_for_exit.store(true, std::sync::atomic::Ordering::Relaxed); - anyhow::Ok(()) - }) - .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 { + if to_print.contains("\r\n\r\n") { + print!("{}", to_print); + println!(); break; } + + pos += len; } - 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)?; + Err(e) => { + println!("read error: {:?}", e); + break; } - } - 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| { - let filename = query_param(request.uri(), "filename").unwrap(); - let copy = filename.clone(); - let mut board = BOARD_ACCESS.lock().unwrap(); - match board.board_hal.get_esp().delete_file(&filename) { - Ok(_) => { - let info = format!("Deleted file {copy}"); - cors_response(request, 200, &info)?; - } - Err(err) => { - let info = format!("Could not delete file {copy} {err:?}"); - cors_response(request, 400, &info)?; - } - } - anyhow::Ok(()) - }) - .unwrap(); - server - .fn_handler("/file", Method::Options, |request| { - cors_response(request, 200, "") - }) - .unwrap(); - unsafe { vTaskDelay(1) }; - server - .fn_handler("/", Method::Get, move |request| { - let mut response = request.into_ok_response()?; - response.write(include_bytes!("index.html"))?; - anyhow::Ok(()) - }) - .unwrap(); - server - .fn_handler("/favicon.ico", Method::Get, |request| { - request - .into_ok_response()? - .write(include_bytes!("favicon.ico"))?; - anyhow::Ok(()) - }) - .unwrap(); - server - .fn_handler("/bundle.js", Method::Get, |request| { - request - .into_ok_response()? - .write(include_bytes!("bundle.js"))?; - anyhow::Ok(()) - }) - .unwrap(); - server - .fn_handler("/timezones", Method::Get, move |request| { - handle_error_to500(request, get_timezones) - }) - .unwrap(); - server -} - -fn cors_response( - request: Request<&mut EspHttpConnection>, - status: u16, - body: &str, -) -> Result<(), anyhow::Error> { - let headers = [ - ("Access-Control-Allow-Origin", "*"), - ("Access-Control-Allow-Headers", "*"), - ("Access-Control-Allow-Methods", "*"), - ]; - let mut response = request.into_response(status, None, &headers)?; - response.write(body.as_bytes())?; - response.flush()?; - anyhow::Ok(()) -} - -type AnyhowHandler = - fn(&mut Request<&mut EspHttpConnection>) -> Result, anyhow::Error>; -fn handle_error_to500( - mut request: Request<&mut EspHttpConnection>, - chain: AnyhowHandler, -) -> Result<(), anyhow::Error> { - let error = chain(&mut request); - match error { - Ok(answer) => match answer { - Some(json) => { - cors_response(request, 200, &json)?; - } - None => { - cors_response(request, 200, "")?; - } - }, - Err(err) => { - let error_text = err.to_string(); - log::info!("error handling process {}", error_text); - cors_response(request, 500, &error_text)?; + }; } + + let r = socket + .write_all( + b"HTTP/1.0 200 OK\r\n\r\n\ + \ + \ +

Hello Rust! Hello esp-wifi!

\ + \ + \r\n\ + ", + ) + .await; + if let Err(e) = r { + println!("write error: {:?}", e); + } + + let r = socket.flush().await; + if let Err(e) = r { + println!("flush error: {:?}", e); + } + Timer::after(Duration::from_millis(1000)).await; + + socket.close(); + Timer::after(Duration::from_millis(1000)).await; + + socket.abort(); } - anyhow::Ok(()) -} -fn read_up_to_bytes_from_request( - request: &mut Request<&mut EspHttpConnection<'_>>, - limit: Option, -) -> Result, 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) + // let server_config = Configuration { + // stack_size: 32768, + // ..Default::default() + // }; + // let mut server: Box> = + // Box::new(EspHttpServer::new(&server_config).unwrap()); + // server + // .fn_handler("/version", Method::Get, |request| { + // handle_error_to500(request, get_version_web) + // }) + // .unwrap(); + // server + // .fn_handler("/log", Method::Get, |request| { + // handle_error_to500(request, get_log) + // }) + // .unwrap(); + // server + // .fn_handler("/log_localization", Method::Get, |request| { + // cors_response(request, 200, &get_log_localization_config().unwrap()) + // }) + // .unwrap(); + // server + // .fn_handler("/battery", Method::Get, |request| { + // handle_error_to500(request, get_battery_state) + // }) + // .unwrap(); + // server + // .fn_handler("/solar", Method::Get, |request| { + // handle_error_to500(request, get_solar_state) + // }) + // .unwrap(); + // server + // .fn_handler("/time", Method::Get, |request| { + // handle_error_to500(request, get_time) + // }) + // .unwrap(); + // server + // .fn_handler("/moisture", Method::Get, |request| { + // handle_error_to500(request, get_live_moisture) + // }) + // .unwrap(); + // server + // .fn_handler("/time", Method::Post, |request| { + // handle_error_to500(request, write_time) + // }) + // .unwrap(); + // server + // .fn_handler("/tank", Method::Get, |request| { + // handle_error_to500(request, tank_info) + // }) + // .unwrap(); + // server + // .fn_handler("/pumptest", Method::Post, |request| { + // handle_error_to500(request, pump_test) + // }) + // .unwrap(); + // server + // .fn_handler("/lamptest", Method::Post, |request| { + // handle_error_to500(request, night_lamp_test) + // }) + // .unwrap(); + // server + // .fn_handler("/boardtest", Method::Post, move |_| { + // BOARD_ACCESS.lock().unwrap().board_hal.test() + // }) + // .unwrap(); + // server + // .fn_handler("/wifiscan", Method::Post, move |request| { + // handle_error_to500(request, wifi_scan) + // }) + // .unwrap(); + // server + // .fn_handler("/ota", Method::Post, |request| { + // handle_error_to500(request, ota) + // }) + // .unwrap(); + // server + // .fn_handler("/ota", Method::Options, |request| { + // cors_response(request, 200, "") + // }) + // .unwrap(); + // server + // .fn_handler("/get_config", Method::Get, move |request| { + // handle_error_to500(request, get_config) + // }) + // .unwrap(); + // server + // .fn_handler("/get_backup_config", Method::Get, move |request| { + // handle_error_to500(request, get_backup_config) + // }) + // .unwrap(); + // + // 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| { + // handle_error_to500(request, backup_config) + // }) + // .unwrap(); + // server + // .fn_handler("/backup_info", Method::Get, move |request| { + // handle_error_to500(request, backup_info) + // }) + // .unwrap(); + // server + // .fn_handler("/files", Method::Get, move |request| { + // handle_error_to500(request, list_files) + // }) + // .unwrap(); + // let reboot_now_for_reboot = reboot_now.clone(); + // server + // .fn_handler("/reboot", Method::Post, move |_| { + // BOARD_ACCESS + // .lock() + // .unwrap() + // .board_hal + // .get_esp() + // .set_restart_to_conf(true); + // reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed); + // anyhow::Ok(()) + // }) + // .unwrap(); + // + // unsafe { vTaskDelay(1) }; + // + // let reboot_now_for_exit = reboot_now.clone(); + // server + // .fn_handler("/exit", Method::Post, move |_| { + // reboot_now_for_exit.store(true, std::sync::atomic::Ordering::Relaxed); + // anyhow::Ok(()) + // }) + // .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| { + // let filename = query_param(request.uri(), "filename").unwrap(); + // let copy = filename.clone(); + // let mut board = BOARD_ACCESS.lock().unwrap(); + // match board.board_hal.get_esp().delete_file(&filename) { + // Ok(_) => { + // let info = format!("Deleted file {copy}"); + // cors_response(request, 200, &info)?; + // } + // Err(err) => { + // let info = format!("Could not delete file {copy} {err:?}"); + // cors_response(request, 400, &info)?; + // } + // } + // anyhow::Ok(()) + // }) + // .unwrap(); + // server + // .fn_handler("/file", Method::Options, |request| { + // cors_response(request, 200, "") + // }) + // .unwrap(); + // unsafe { vTaskDelay(1) }; + // server + // .fn_handler("/", Method::Get, move |request| { + // let mut response = request.into_ok_response()?; + // response.write(include_bytes!("index.html"))?; + // anyhow::Ok(()) + // }) + // .unwrap(); + // server + // .fn_handler("/favicon.ico", Method::Get, |request| { + // request + // .into_ok_response()? + // .write(include_bytes!("favicon.ico"))?; + // anyhow::Ok(()) + // }) + // .unwrap(); + // server + // .fn_handler("/bundle.js", Method::Get, |request| { + // request + // .into_ok_response()? + // .write(include_bytes!("bundle.js"))?; + // anyhow::Ok(()) + // }) + // .unwrap(); + // server + // .fn_handler("/timezones", Method::Get, move |request| { + // handle_error_to500(request, get_timezones) + // }) + // .unwrap(); + //server } +// +// fn cors_response( +// request: Request<&mut EspHttpConnection>, +// status: u16, +// body: &str, +// ) -> Result<(), anyhow::Error> { +// let headers = [ +// ("Access-Control-Allow-Origin", "*"), +// ("Access-Control-Allow-Headers", "*"), +// ("Access-Control-Allow-Methods", "*"), +// ]; +// let mut response = request.into_response(status, None, &headers)?; +// response.write(body.as_bytes())?; +// response.flush()?; +// anyhow::Ok(()) +// } +// +// type AnyhowHandler = +// fn(&mut Request<&mut EspHttpConnection>) -> Result, anyhow::Error>; +// fn handle_error_to500( +// mut request: Request<&mut EspHttpConnection>, +// chain: AnyhowHandler, +// ) -> Result<(), anyhow::Error> { +// let error = chain(&mut request); +// match error { +// Ok(answer) => match answer { +// Some(json) => { +// cors_response(request, 200, &json)?; +// } +// None => { +// cors_response(request, 200, "")?; +// } +// }, +// Err(err) => { +// let error_text = err.to_string(); +// log::info!("error handling process {}", error_text); +// cors_response(request, 500, &error_text)?; +// } +// } +// anyhow::Ok(()) +// } +// +// fn read_up_to_bytes_from_request( +// request: &mut Request<&mut EspHttpConnection<'_>>, +// limit: Option, +// ) -> Result, 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) +// }