webserver improvements

This commit is contained in:
Empire 2023-12-27 17:33:11 +01:00
parent 1e40e2e3ba
commit 4c02b99ea7
11 changed files with 361 additions and 202 deletions

View File

@ -75,6 +75,7 @@ anyhow = { version = "1.0.75", features = ["std", "backtrace"] }
schemars = "0.8.16" schemars = "0.8.16"
heapless = { version = "0.7", features = ["serde"] } heapless = { version = "0.7", features = ["serde"] }
serde_json = "1.0.108" serde_json = "1.0.108"
strum = { version = "0.25.0", features = ["derive"] }
#?bq34z100 required #?bq34z100 required
[build-dependencies] [build-dependencies]

View File

@ -1,6 +1,6 @@
nvs, data, nvs, , 16k, nvs, data, nvs, , 16k,
otadata, data, ota, , 8k, otadata, data, ota, , 8k,
phy_init, data, phy, , 4k, phy_init, data, phy, , 4k,
factory, app, ota_0, , 1792K, ota_0, app, ota_0, , 1792K,
ota_1, app, ota_1, , 1792K, ota_1, app, ota_1, , 1792K,
storage, data, spiffs, , 400K, storage, data, spiffs, , 400K,
1 nvs data nvs 16k
2 otadata data ota 8k
3 phy_init data phy 4k
4 factory ota_0 app ota_0 1792K
5 ota_1 app ota_1 1792K
6 storage data spiffs 400K

View File

@ -6,28 +6,47 @@ use crate::PLANT_COUNT;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[derive(Debug)]
pub struct Config { pub struct Config {
tank_sensor_enabled: bool, tank_sensor_enabled: bool,
tank_full_ml: u32, tank_full_ml: u32,
tank_warn_percent: u8, tank_warn_percent: u8,
plantcount: u16,
night_lamp_hour_start: u8, night_lamp_hour_start: u8,
night_lamp_hour_end: u8, night_lamp_hour_end: u8,
night_lamp_only_when_dark: u8, night_lamp_only_when_dark: bool,
plants: [Plant;PLANT_COUNT] plants: [Plant;PLANT_COUNT]
} }
#[derive(Serialize, Deserialize)] impl Default for Config {
fn default() -> Self {
Self { tank_sensor_enabled: true,
tank_full_ml: 5000,
tank_warn_percent: 50,
night_lamp_hour_start: 21,
night_lamp_hour_end: 2,
night_lamp_only_when_dark: true,
plants: [Plant::default();PLANT_COUNT]
}
}
}
#[derive(Serialize, Deserialize, Copy, Clone)]
#[derive(Debug)]
pub struct Plant{ pub struct Plant{
target_moisture: u8, target_moisture: u8,
pump_time_s: u16, pump_time_s: u16,
pump_cooldown_min: u16, pump_cooldown_min: u16,
pump_hour_start: heapless::String<5>, pump_hour_start: u8,
pump_hour_end: heapless::String<5> pump_hour_end: u8
} }
impl Default for Plant {
fn default() -> Self {
Self { target_moisture: 40, pump_time_s: 60, pump_cooldown_min: 60, pump_hour_start: 8, pump_hour_end: 20 }
}
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[derive(Debug)] #[derive(Debug)]
pub struct WifiConfig { pub struct WifiConfig {

View File

@ -1,4 +1,4 @@
use std::{sync::{Arc, Mutex}, env}; use std::{sync::{Arc, Mutex, atomic::AtomicBool}, env};
use chrono::{Datelike, NaiveDateTime, Timelike}; use chrono::{Datelike, NaiveDateTime, Timelike};
@ -9,7 +9,7 @@ use esp_idf_sys::{esp_restart, vTaskDelay};
use plant_hal::{CreatePlantHal, PlantCtrlBoard, PlantCtrlBoardInteraction, PlantHal, PLANT_COUNT}; use plant_hal::{CreatePlantHal, PlantCtrlBoard, PlantCtrlBoardInteraction, PlantHal, PLANT_COUNT};
use crate::{config::{Config, WifiConfig}, webserver::webserver::httpd_initial}; use crate::{config::{Config, WifiConfig}, webserver::webserver::{httpd_initial, httpd}};
mod config; mod config;
pub mod plant_hal; pub mod plant_hal;
mod webserver { mod webserver {
@ -27,31 +27,46 @@ enum OnlineMode {
enum WaitType{ enum WaitType{
InitialConfig, InitialConfig,
FlashError FlashError,
NormalConfig
} }
fn wait_infinity(board_access: Arc<Mutex<PlantCtrlBoard<'_>>>, wait_type:WaitType) -> !{ fn wait_infinity(wait_type:WaitType, reboot_now:Arc<AtomicBool>) -> !{
let delay = match wait_type { let delay = match wait_type {
WaitType::InitialConfig => 250_u32, WaitType::InitialConfig => 250_u32,
WaitType::FlashError => 100_u32, WaitType::FlashError => 100_u32,
WaitType::NormalConfig => 500_u32
}; };
board_access.lock().unwrap().light(true).unwrap(); let led_count = match wait_type {
WaitType::InitialConfig => 8,
WaitType::FlashError => 8,
WaitType::NormalConfig => 4
};
BOARD_ACCESS.lock().unwrap().light(true).unwrap();
loop { loop {
unsafe { unsafe {
//do not trigger watchdog //do not trigger watchdog
for i in 0..8 { for i in 0..8 {
board_access.lock().unwrap().fault(i, true); BOARD_ACCESS.lock().unwrap().fault(i, i <led_count);
} }
board_access.lock().unwrap().general_fault(true); BOARD_ACCESS.lock().unwrap().general_fault(true);
vTaskDelay(delay); vTaskDelay(delay);
board_access.lock().unwrap().general_fault(false); BOARD_ACCESS.lock().unwrap().general_fault(false);
for i in 0..8 { for i in 0..8 {
board_access.lock().unwrap().fault(i, false); BOARD_ACCESS.lock().unwrap().fault(i, false);
} }
vTaskDelay(delay); vTaskDelay(delay);
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
println!("Rebooting");
esp_restart();
} }
} }
} }
}
static BOARD_ACCESS: Lazy<PlantCtrlBoard> = Lazy::new(|| {
PlantHal::create()?;
});
fn main() -> Result<()> { fn main() -> Result<()> {
// It is necessary to call this function once. Otherwise some patches to the runtime // It is necessary to call this function once. Otherwise some patches to the runtime
@ -67,8 +82,7 @@ fn main() -> Result<()> {
println!("Version useing git has {}", git_hash); println!("Version useing git has {}", git_hash);
println!("Board hal init"); println!("Board hal init");
let board_access = PlantHal::create()?; let mut board = BOARD_ACCESS.lock().unwrap();
let mut board = board_access.lock().unwrap();
println!("Mounting filesystem"); println!("Mounting filesystem");
board.mountFileSystem()?; board.mountFileSystem()?;
let free_space = board.fileSystemSize()?; let free_space = board.fileSystemSize()?;
@ -103,16 +117,14 @@ fn main() -> Result<()> {
if board.is_config_reset() { if board.is_config_reset() {
println!("Reset config is still pressed, deleting configs and reboot"); println!("Reset config is still pressed, deleting configs and reboot");
match board.remove_configs() { match board.remove_configs() {
Ok(_) => { Ok(case) => {
println!("Removed config files, restarting"); println!("Succeeded in deleting config {}", case);
unsafe {
esp_restart();
}
} }
Err(err) => { Err(err) => {
println!("Could not remove config files, system borked {}", err); println!("Could not remove config files, system borked {}", err);
//terminate main app and freeze //terminate main app and freeze
wait_infinity( board_access.clone(), WaitType::FlashError);
wait_infinity(WaitType::FlashError, Arc::new(AtomicBool::new(false)));
} }
} }
} }
@ -130,14 +142,13 @@ fn main() -> Result<()> {
board.wifi_ap().unwrap(); board.wifi_ap().unwrap();
//config upload will trigger reboot! //config upload will trigger reboot!
drop(board); drop(board);
let _webserver = httpd_initial(board_access.clone()); let reboot_now = Arc::new(AtomicBool::new(false));
wait_infinity(board_access.clone(), WaitType::InitialConfig); let _webserver = httpd_initial(BOARD_ACCESS.clone(), reboot_now.clone());
wait_infinity(WaitType::InitialConfig, reboot_now.clone());
}, },
}; };
// let proceed = config.unwrap();
//check if we have a config file //check if we have a config file
// if not found or parsing error -> error very fast blink general fault // if not found or parsing error -> error very fast blink general fault
//if this happens after a firmeware upgrade (check image state), mark as invalid //if this happens after a firmeware upgrade (check image state), mark as invalid
@ -203,6 +214,21 @@ fn main() -> Result<()> {
println!("Running logic at europe/berlin {}", europe_time); println!("Running logic at europe/berlin {}", europe_time);
} }
let config:Config;
match (board.get_config()){
Ok(valid) => {
config = valid;
},
Err(err) => {
println!("Missing normal config, entering config mode {}", err);
//config upload will trigger reboot!
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(BOARD_ACCESS.clone(),reboot_now.clone());
wait_infinity(BOARD_ACCESS.clone(), WaitType::NormalConfig, reboot_now.clone());
},
}
if online_mode == OnlineMode::SnTp { if online_mode == OnlineMode::SnTp {
//mqtt here //mqtt here
} }

View File

@ -11,6 +11,7 @@ use plant_ctrl2::sipo::ShiftRegister40;
use anyhow::anyhow; use anyhow::anyhow;
use anyhow::{bail, Ok, Result}; use anyhow::{bail, Ok, Result};
use strum::EnumString;
use std::ffi::CString; use std::ffi::CString;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
@ -23,7 +24,6 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use ds18b20::Ds18b20; use ds18b20::Ds18b20;
use embedded_hal::digital::v2::OutputPin; use embedded_hal::digital::v2::OutputPin;
use esp_idf_hal::adc::config::Config;
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver}; use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
use esp_idf_hal::delay::Delay; use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, Level, PinDriver}; use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, Level, PinDriver};
@ -37,7 +37,7 @@ use esp_idf_svc::systime::EspSystemTime;
use esp_idf_sys::{EspError, vTaskDelay}; use esp_idf_sys::{EspError, vTaskDelay};
use one_wire_bus::OneWire; use one_wire_bus::OneWire;
use crate::config::{self, WifiConfig}; use crate::config::{self, WifiConfig, Config};
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
const PINS_PER_PLANT: usize = 5; const PINS_PER_PLANT: usize = 5;
@ -80,6 +80,14 @@ pub struct FileSystemSizeInfo {
pub free_size: usize, pub free_size: usize,
} }
#[derive(strum::Display)]
pub enum ClearConfigType {
WifiConfig,
Config,
None
}
#[derive(Debug)] #[derive(Debug)]
pub enum Sensor { pub enum Sensor {
A, A,
@ -121,8 +129,9 @@ pub trait PlantCtrlBoardInteraction {
//config //config
fn is_config_reset(&mut self) -> bool; fn is_config_reset(&mut self) -> bool;
fn remove_configs(&mut self) -> Result<()>; fn remove_configs(&mut self) -> Result<ClearConfigType>;
fn get_config(&mut self) -> Result<config::Config>; fn get_config(&mut self) -> Result<config::Config>;
fn set_config(&mut self, wifi: &Config) -> Result<()>;
fn get_wifi(&mut self) -> Result<config::WifiConfig>; fn get_wifi(&mut self) -> Result<config::WifiConfig>;
fn set_wifi(&mut self, wifi: &WifiConfig) -> Result<()>; fn set_wifi(&mut self, wifi: &WifiConfig) -> Result<()>;
fn wifi_ap(&mut self) -> Result<()>; fn wifi_ap(&mut self) -> Result<()>;
@ -137,7 +146,7 @@ pub trait CreatePlantHal<'a> {
pub struct PlantHal {} pub struct PlantHal {}
impl CreatePlantHal<'_> for PlantHal { impl CreatePlantHal<'_> for PlantHal {
fn create() -> Result<Arc<Mutex<PlantCtrlBoard<'static>>>> { fn create() -> Result<Mutex<PlantCtrlBoard<'static>>> {
let peripherals = Peripherals::take()?; let peripherals = Peripherals::take()?;
let mut clock = PinDriver::output(peripherals.pins.gpio21)?; let mut clock = PinDriver::output(peripherals.pins.gpio21)?;
@ -213,7 +222,7 @@ impl CreatePlantHal<'_> for PlantHal {
let last_watering_timestamp = Mutex::new(unsafe { LAST_WATERING_TIMESTAMP }); let last_watering_timestamp = Mutex::new(unsafe { LAST_WATERING_TIMESTAMP });
let consecutive_watering_plant = Mutex::new(unsafe { CONSECUTIVE_WATERING_PLANT }); let consecutive_watering_plant = Mutex::new(unsafe { CONSECUTIVE_WATERING_PLANT });
let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED }); let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED });
let tank_driver = AdcDriver::new(peripherals.adc1, &Config::new())?; let tank_driver = AdcDriver::new(peripherals.adc1, &esp_idf_hal::adc::config::Config::new())?;
let tank_channel: AdcChannelDriver<'_, { attenuation::DB_11 }, Gpio39> = let tank_channel: AdcChannelDriver<'_, { attenuation::DB_11 }, Gpio39> =
AdcChannelDriver::new(peripherals.pins.gpio39)?; AdcChannelDriver::new(peripherals.pins.gpio39)?;
let solar_is_day = PinDriver::input(peripherals.pins.gpio25)?; let solar_is_day = PinDriver::input(peripherals.pins.gpio25)?;
@ -228,7 +237,7 @@ impl CreatePlantHal<'_> for PlantHal {
println!("After stuff"); println!("After stuff");
let rv = Arc::new(Mutex::new(PlantCtrlBoard { let rv = Mutex::new(PlantCtrlBoard {
shift_register : shift_register, shift_register : shift_register,
last_watering_timestamp: last_watering_timestamp, last_watering_timestamp: last_watering_timestamp,
consecutive_watering_plant: consecutive_watering_plant, consecutive_watering_plant: consecutive_watering_plant,
@ -244,7 +253,7 @@ impl CreatePlantHal<'_> for PlantHal {
one_wire_bus: one_wire_bus, one_wire_bus: one_wire_bus,
signal_counter: counter_unit1, signal_counter: counter_unit1,
wifi_driver: wifi_driver, wifi_driver: wifi_driver,
})); });
return Ok(rv); return Ok(rv);
} }
} }
@ -534,19 +543,26 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
return self.boot_button.get_level() == Level::Low; return self.boot_button.get_level() == Level::Low;
} }
fn remove_configs(&mut self) -> Result<()> {
let wifi_config = Path::new(WIFI_CONFIG_FILE);
if wifi_config.exists() {
println!("Removing wifi config");
std::fs::remove_file(wifi_config)?;
}
fn remove_configs(&mut self) -> Result<ClearConfigType> {
let config = Path::new(CONFIG_FILE); let config = Path::new(CONFIG_FILE);
if config.exists() { if config.exists() {
println!("Removing config"); println!("Removing config");
std::fs::remove_file(config)?; std::fs::remove_file(config)?;
return Ok(ClearConfigType::Config);
} }
Ok(())
let wifi_config = Path::new(WIFI_CONFIG_FILE);
if wifi_config.exists() {
println!("Removing wifi config");
std::fs::remove_file(wifi_config)?;
Ok(ClearConfigType::WifiConfig);
}
Ok((ClearConfigType::None));
} }
fn get_wifi(&mut self) -> Result<config::WifiConfig> { fn get_wifi(&mut self) -> Result<config::WifiConfig> {
@ -563,12 +579,16 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
} }
fn get_config(&mut self) -> Result<config::Config> { fn get_config(&mut self) -> Result<config::Config> {
let mut cfg = File::open(CONFIG_FILE)?; let cfg = File::open(CONFIG_FILE)?;
let mut data: [u8; 512] = [0; 512]; let config: Config = serde_json::from_reader(cfg)?;
let read = cfg.read(&mut data)?; return Ok(config);
println!("Read file {}", from_utf8(&data[0..read])?); }
bail!("todo") fn set_config(&mut self, config: &Config) -> Result<()> {
let mut cfg = File::create(CONFIG_FILE)?;
serde_json::to_writer(&mut cfg, &config)?;
println!("Wrote config config {:?}", config);
return Ok(());
} }
fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>> { fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>> {

View File

@ -32,9 +32,11 @@
<h3>Light:</h3> <h3>Light:</h3>
<div> <div>
Start Start
<input type="time" id="night_lamp_time_start"> <select type="time" id="night_lamp_time_start">
</select>
Stop Stop
<input type="time" id="night_lamp_time_end"> <select type="time" id="night_lamp_time_end">
</select>
</div> </div>
<div> <div>
<input type="checkbox" id="night_lamp_only_when_dark"> <input type="checkbox" id="night_lamp_only_when_dark">

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -23,7 +23,7 @@
<option value="Not scanned yet"> <option value="Not scanned yet">
</datalist> </datalist>
<label for="ssid">Password:</label> <label for="ssid">Password:</label>
<input type="text" id="password" list="ssidlist"> <input type="text" id="password">
<input type="button" id="save" value="Save & Restart"> <input type="button" id="save" value="Save & Restart">
<div id="wifistatus"></div> <div id="wifistatus"></div>
<br> <br>

View File

@ -1,14 +1,14 @@
//offer ota and config mode //offer ota and config mode
use std::sync::{Mutex, Arc}; use std::{sync::{Mutex, Arc, atomic::AtomicBool}, str::from_utf8};
use embedded_svc::http::Method; use embedded_svc::http::{Method, Headers};
use esp_idf_svc::http::server::EspHttpServer; use esp_idf_svc::http::server::EspHttpServer;
use esp_ota::OtaUpdate; use esp_ota::OtaUpdate;
use heapless::String; use heapless::String;
use serde::Serialize; use serde::Serialize;
use crate::{plant_hal::{PlantCtrlBoard, PlantCtrlBoardInteraction}, config::WifiConfig}; use crate::{plant_hal::{PlantCtrlBoard, PlantCtrlBoardInteraction, PLANT_COUNT}, config::{WifiConfig, Config, Plant}};
#[derive(Serialize)] #[derive(Serialize)]
#[derive(Debug)] #[derive(Debug)]
@ -16,7 +16,7 @@ struct SSIDList<'a> {
ssids: Vec<&'a String<32>> ssids: Vec<&'a String<32>>
} }
pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<EspHttpServer<'static>> { pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>, reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let mut server = shared(); let mut server = shared();
server.fn_handler("/",Method::Get, move |request| { server.fn_handler("/",Method::Get, move |request| {
let mut response = request.into_ok_response()?; let mut response = request.into_ok_response()?;
@ -37,9 +37,9 @@ pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<Es
scan_result.iter().for_each(|s| scan_result.iter().for_each(|s|
ssids.push(&s.ssid) ssids.push(&s.ssid)
); );
response.write( serde_json::to_string( let ssid_json = serde_json::to_string( &SSIDList{ssids})?;
&SSIDList{ssids} println!("Sending ssid list {}", &ssid_json);
)?.as_bytes())?; response.write( &ssid_json.as_bytes())?;
}, },
} }
return Ok(()) return Ok(())
@ -48,6 +48,7 @@ pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<Es
let board_access_for_save = board_access.clone(); let board_access_for_save = board_access.clone();
server.fn_handler("/wifisave",Method::Post, move |mut request| { server.fn_handler("/wifisave",Method::Post, move |mut request| {
let mut buf = [0_u8;2048]; let mut buf = [0_u8;2048];
let read = request.read(&mut buf); let read = request.read(&mut buf);
if read.is_err(){ if read.is_err(){
@ -57,6 +58,8 @@ pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<Es
return Ok(()); return Ok(());
} }
let actual_data = &buf[0..read.unwrap()]; let actual_data = &buf[0..read.unwrap()];
println!("raw {:?}", actual_data);
println!("Raw data {}", from_utf8(actual_data).unwrap());
let wifi_config: Result<WifiConfig, serde_json::Error> = serde_json::from_slice(actual_data); let wifi_config: Result<WifiConfig, serde_json::Error> = serde_json::from_slice(actual_data);
if wifi_config.is_err(){ if wifi_config.is_err(){
let error_text = wifi_config.unwrap_err().to_string(); let error_text = wifi_config.unwrap_err().to_string();
@ -68,6 +71,7 @@ pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<Es
board.set_wifi(&wifi_config.unwrap())?; board.set_wifi(&wifi_config.unwrap())?;
let mut response = request.into_status_response(202)?; let mut response = request.into_status_response(202)?;
response.write("saved".as_bytes())?; response.write("saved".as_bytes())?;
reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
return Ok(()) return Ok(())
}).unwrap(); }).unwrap();
@ -81,7 +85,7 @@ pub fn httpd_initial(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<Es
return server return server
} }
pub fn httpd(board:&mut Box<PlantCtrlBoard<'static>>) -> Box<EspHttpServer<'static>> { pub fn httpd(board_access:Arc<Mutex<PlantCtrlBoard<'static>>>, reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let mut server = shared(); let mut server = shared();
server server
@ -91,12 +95,57 @@ pub fn httpd(board:&mut Box<PlantCtrlBoard<'static>>) -> Box<EspHttpServer<'stat
return Ok(()) return Ok(())
}).unwrap(); }).unwrap();
let board_access_for_get_config= board_access.clone();
server
.fn_handler("/get_config",Method::Get, move |request| {
let mut response = request.into_ok_response()?;
let mut board = board_access_for_get_config.lock()?;
match (board.get_config()) {
Ok(config) => {
let config_json = serde_json::to_string(&config)?;
response.write(config_json.as_bytes())?;
},
Err(_) => {
let config_json = serde_json::to_string(&Config::default())?;
response.write(config_json.as_bytes())?;
},
}
return Ok(())
}).unwrap();
let board_access_for_set_config= board_access.clone();
server.fn_handler("/set_config",Method::Post, move |mut request| {
let mut buf = [0_u8;2048];
let read = request.read(&mut buf);
if read.is_err(){
let error_text = read.unwrap_err().to_string();
println!("Could not parse wificonfig {}", error_text);
request.into_status_response(500)?.write(error_text.as_bytes())?;
return Ok(());
}
let actual_data = &buf[0..read.unwrap()];
println!("raw {:?}", actual_data);
println!("Raw data {}", from_utf8(actual_data).unwrap());
let config: Result<Config, serde_json::Error> = serde_json::from_slice(actual_data);
if config.is_err(){
let error_text = config.unwrap_err().to_string();
println!("Could not parse wificonfig {}", error_text);
request.into_status_response(500)?.write(error_text.as_bytes())?;
return Ok(());
}
let mut board = board_access_for_set_config.lock().unwrap();
board.set_config(&config.unwrap())?;
let mut response = request.into_status_response(202)?;
response.write("saved".as_bytes())?;
reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
return Ok(())
}).unwrap();
return server; return server;
} }
pub fn shared() -> Box<EspHttpServer<'static>> { pub fn shared() -> Box<EspHttpServer<'static>> {
let mut server = Box::new(EspHttpServer::new(&Default::default()).unwrap()); let mut server: Box<EspHttpServer<'static>> = Box::new(EspHttpServer::new(&Default::default()).unwrap());
server server
.fn_handler("/version",Method::Get, |request| { .fn_handler("/version",Method::Get, |request| {
@ -110,6 +159,12 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
response.write(include_bytes!("bundle.js"))?; response.write(include_bytes!("bundle.js"))?;
return Ok(()) return Ok(())
}).unwrap(); }).unwrap();
server
.fn_handler("/favicon.ico",Method::Get, |request| {
let mut response = request.into_ok_response()?;
response.write(include_bytes!("favicon.ico"))?;
return Ok(())
}).unwrap();
server server
.fn_handler("/ota", Method::Post, |mut request| { .fn_handler("/ota", Method::Post, |mut request| {
let ota = OtaUpdate::begin(); let ota = OtaUpdate::begin();

View File

@ -2,15 +2,15 @@ interface PlantConfig {
tank_sensor_enabled: boolean, tank_sensor_enabled: boolean,
tank_full_ml: number, tank_full_ml: number,
tank_warn_percent: number, tank_warn_percent: number,
night_lamp_time_start: string, night_lamp_hour_start: number,
night_lamp_time_end: string, night_lamp_hour_end: number,
night_lamp_only_when_dark: boolean, night_lamp_only_when_dark: boolean,
plants: { plants: {
target_moisture: number, target_moisture: number,
pump_time_s: number, pump_time_s: number,
pump_cooldown_min: number, pump_cooldown_min: number,
pump_hour_start: string, pump_hour_start: number,
pump_hour_end: string pump_hour_end: number
}[] }[]
} }
@ -20,79 +20,110 @@ let fromWrapper = (() => {
let plantcount = 0; let plantcount = 0;
function addTimeOptions(select: HTMLSelectElement) {
for (let i = 0; i < 24; i++) {
let option = document.createElement("option");
option.innerText = i.toString();
select.appendChild(option);
}
}
let tank_full_ml = document.getElementById("tank_full_ml") as HTMLInputElement; let tank_full_ml = document.getElementById("tank_full_ml") as HTMLInputElement;
tank_full_ml.onchange = submitForm tank_full_ml.onchange = updateJson
let tank_warn_percent = document.getElementById("tank_warn_percent") as HTMLInputElement; let tank_warn_percent = document.getElementById("tank_warn_percent") as HTMLInputElement;
tank_warn_percent.onchange = submitForm tank_warn_percent.onchange = updateJson
let tank_sensor_enabled = document.getElementById("tank_sensor_enabled") as HTMLInputElement; let tank_sensor_enabled = document.getElementById("tank_sensor_enabled") as HTMLInputElement;
tank_sensor_enabled.onchange = submitForm tank_sensor_enabled.onchange = updateJson
let night_lamp_only_when_dark = document.getElementById("night_lamp_only_when_dark") as HTMLInputElement; let night_lamp_only_when_dark = document.getElementById("night_lamp_only_when_dark") as HTMLInputElement;
night_lamp_only_when_dark.onchange = submitForm night_lamp_only_when_dark.onchange = updateJson
let night_lamp_time_start = document.getElementById("night_lamp_time_start") as HTMLInputElement; let night_lamp_time_start = document.getElementById("night_lamp_time_start") as HTMLSelectElement;
night_lamp_time_start.onchange = submitForm night_lamp_time_start.onchange = updateJson
let night_lamp_time_end = document.getElementById("night_lamp_time_end") as HTMLInputElement; addTimeOptions(night_lamp_time_start);
night_lamp_time_end.onchange = submitForm let night_lamp_time_end = document.getElementById("night_lamp_time_end") as HTMLSelectElement;
night_lamp_time_end.onchange = updateJson
addTimeOptions(night_lamp_time_end);
let json = document.getElementById('json') as HTMLInputElement let json = document.getElementById('json') as HTMLInputElement
function createForm(){ function createForm(current: PlantConfig) {
var current:PlantConfig = {
tank_sensor_enabled:true,
tank_full_ml:400,
tank_warn_percent:50,
night_lamp_time_start : "18:00",
night_lamp_time_end : "02:00",
night_lamp_only_when_dark: true,
plants :[
{
target_moisture: 40,
pump_time_s: 60,
pump_cooldown_min: 60,
pump_hour_start: "10:00",
pump_hour_end: "18:00"
}
]
}
for (let i = 0; i < current.plants.length; i++) { for (let i = 0; i < current.plants.length; i++) {
var plant = document.createElement("div"); let plant = document.createElement("div");
plants.appendChild(plant); plants.appendChild(plant);
var header = document.createElement("h4"); let header = document.createElement("h4");
header.textContent = "Plant " + (i + 1); header.textContent = "Plant " + (i + 1);
plant.appendChild(header); plant.appendChild(header);
{ {
var holder = document.createElement("div"); let holder = document.createElement("div");
plant.appendChild(holder); plant.appendChild(holder);
var inputf = document.createElement("input"); let inputf = document.createElement("input");
inputf.id = "plant_" + i + "_target_moisture"; inputf.id = "plant_" + i + "_target_moisture";
inputf.onchange = submitForm; inputf.onchange = updateJson;
inputf.type = "number"; inputf.type = "number";
inputf.min = "0"; inputf.min = "0";
inputf.max = "100"; inputf.max = "100";
holder.appendChild(inputf) holder.appendChild(inputf)
var text = document.createElement("span"); let text = document.createElement("span");
holder.appendChild(text) holder.appendChild(text)
text.innerHTML += "Target Moisture" text.innerHTML += "Target Moisture"
} }
{ {
var holder = document.createElement("div"); let holder = document.createElement("div");
plant.appendChild(holder); plant.appendChild(holder);
var input = document.createElement("input"); let input = document.createElement("input");
input.id = "plant_" + i + "_pump_time_s"; input.id = "plant_" + i + "_pump_time_s";
input.onchange = submitForm; input.onchange = updateJson;
input.type = "number"; input.type = "number";
input.min = "0"; input.min = "0";
input.max = "600"; input.max = "600";
holder.appendChild(input) holder.appendChild(input)
var text = document.createElement("span"); let text = document.createElement("span");
holder.appendChild(text) holder.appendChild(text)
text.innerHTML += "Pump Time (s)" text.innerHTML += "Pump Time (s)"
} }
{
let holder = document.createElement("div");
plant.appendChild(holder);
let input = document.createElement("input");
input.id = "plant_" + i + "_pump_cooldown_min";
input.onchange = updateJson;
input.type = "number";
input.min = "0";
input.max = "600";
holder.appendChild(input)
let text = document.createElement("span");
holder.appendChild(text)
text.innerHTML += "Pump Cooldown (m)"
}
{
let holder = document.createElement("div");
plant.appendChild(holder);
let input = document.createElement("select");
input.id = "plant_" + i + "_pump_hour_start";
addTimeOptions(input);
input.onchange = updateJson;
holder.appendChild(input)
let text = document.createElement("span");
holder.appendChild(text)
text.innerHTML += "Pump Hour Start"
}
{
let holder = document.createElement("div");
plant.appendChild(holder);
let input = document.createElement("select");
input.id = "plant_" + i + "_pump_hour_end";
addTimeOptions(input);
input.onchange = updateJson;
holder.appendChild(input)
let text = document.createElement("span");
holder.appendChild(text)
text.innerHTML += "Pump Hour End"
}
} }
sync(current); sync(current);
} }
@ -105,8 +136,8 @@ function sync(current:PlantConfig){
tank_sensor_enabled.checked = current.tank_sensor_enabled; tank_sensor_enabled.checked = current.tank_sensor_enabled;
tank_full_ml.value = current.tank_full_ml.toString(); tank_full_ml.value = current.tank_full_ml.toString();
tank_warn_percent.value = current.tank_warn_percent.toString(); tank_warn_percent.value = current.tank_warn_percent.toString();
night_lamp_time_start.value = current.night_lamp_time_start; night_lamp_time_start.value = current.night_lamp_hour_start.toString();
night_lamp_time_end.value = current.night_lamp_time_end; night_lamp_time_end.value = current.night_lamp_hour_end.toString();
for (let i = 0; i < current.plants.length; i++) { for (let i = 0; i < current.plants.length; i++) {
let plant_target_moisture = document.getElementById("plant_" + i + "_target_moisture") as HTMLInputElement; let plant_target_moisture = document.getElementById("plant_" + i + "_target_moisture") as HTMLInputElement;
@ -116,19 +147,19 @@ function sync(current:PlantConfig){
let plant_pump_cooldown_min = document.getElementById("plant_" + i + "_pump_cooldown_min") as HTMLInputElement; let plant_pump_cooldown_min = document.getElementById("plant_" + i + "_pump_cooldown_min") as HTMLInputElement;
plant_pump_cooldown_min.value = current.plants[i].pump_cooldown_min.toString(); plant_pump_cooldown_min.value = current.plants[i].pump_cooldown_min.toString();
let plant_pump_hour_start = document.getElementById("plant_" + i + "_pump_hour_start") as HTMLInputElement; let plant_pump_hour_start = document.getElementById("plant_" + i + "_pump_hour_start") as HTMLInputElement;
plant_pump_hour_start.value = current.plants[i].pump_hour_start; plant_pump_hour_start.value = current.plants[i].pump_hour_start.toString();
let plant_pump_hour_end = document.getElementById("plant_" + i + "_pump_hour_end") as HTMLInputElement; let plant_pump_hour_end = document.getElementById("plant_" + i + "_pump_hour_end") as HTMLInputElement;
plant_pump_hour_end.value = current.plants[i].pump_hour_end; plant_pump_hour_end.value = current.plants[i].pump_hour_end.toString();
} }
} }
function submitForm() { function updateJson() {
var current: PlantConfig = { var current: PlantConfig = {
tank_sensor_enabled: tank_sensor_enabled.checked, tank_sensor_enabled: tank_sensor_enabled.checked,
tank_full_ml: +tank_full_ml.value, tank_full_ml: +tank_full_ml.value,
tank_warn_percent: +tank_warn_percent.value, tank_warn_percent: +tank_warn_percent.value,
night_lamp_time_start: night_lamp_time_start.value, night_lamp_hour_start: +night_lamp_time_start.value,
night_lamp_time_end: night_lamp_time_end.value, night_lamp_hour_end: +night_lamp_time_end.value,
night_lamp_only_when_dark: night_lamp_only_when_dark.checked, night_lamp_only_when_dark: night_lamp_only_when_dark.checked,
plants: [] plants: []
} }
@ -145,8 +176,8 @@ function submitForm() {
target_moisture: +plant_target_moisture.value, target_moisture: +plant_target_moisture.value,
pump_time_s: +plant_pump_time_s.value, pump_time_s: +plant_pump_time_s.value,
pump_cooldown_min: +plant_pump_cooldown_min.value, pump_cooldown_min: +plant_pump_cooldown_min.value,
pump_hour_start: plant_pump_hour_start.value, pump_hour_start: +plant_pump_hour_start.value,
pump_hour_end: plant_pump_hour_end.value pump_hour_end: +plant_pump_hour_end.value
} }
} }
@ -157,17 +188,22 @@ function submitForm() {
json.value = pretty; json.value = pretty;
} }
let createDocumentBtn = document.getElementById("create") as HTMLButtonElement
if(createDocumentBtn){
createDocumentBtn.onclick = createForm;
}
let submitFormBtn = document.getElementById("submit") as HTMLButtonElement let submitFormBtn = document.getElementById("submit") as HTMLButtonElement
if (submitFormBtn) { if (submitFormBtn) {
submitFormBtn.onclick = submitForm; submitFormBtn.onclick = function (){
updateJson()
fetch("/set_config", {
method :"POST",
body: json.value
})
};
} }
fetch("/get_config")
.then(response => response.json())
.then(json => { createForm(json as PlantConfig); }
)
}) })
if (plants) { if (plants) {
fromWrapper() fromWrapper()

View File

@ -30,7 +30,7 @@ export function saveWifi(){
alert("Failed to save config see console") alert("Failed to save config see console")
} }
ajax.open("POST", "/wifisave"); ajax.open("POST", "/wifisave");
ajax.send(); ajax.send(pretty);
} }
export function scanWifi(){ export function scanWifi(){