move to typescript, testmode, shiftregister revival
This commit is contained in:
parent
fd823217aa
commit
1e40e2e3ba
4
.gitignore
vendored
4
.gitignore
vendored
@ -6,3 +6,7 @@ board/production/PlantCtrlESP32_2023-11-08_00-45-35/netlist.ipc
|
|||||||
.embuild/
|
.embuild/
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
node_modules/
|
||||||
|
rust/src/webserver/bundle.js
|
||||||
|
|
||||||
|
rust/image.bin
|
||||||
|
24410
board/PlantCtrlESP32.kicad_sch-bak
Normal file
24410
board/PlantCtrlESP32.kicad_sch-bak
Normal file
File diff suppressed because it is too large
Load Diff
7
board/PlantCtrlESP32.round-tracks-config
Normal file
7
board/PlantCtrlESP32.round-tracks-config
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Default False 2.0 3
|
||||||
|
12V True 2.0 3
|
||||||
|
3V True 2.0 3
|
||||||
|
BAT+ True 2.0 3
|
||||||
|
BAT- True 2.0 3
|
||||||
|
GND True 2.0 3
|
||||||
|
False True False
|
4562
board/bom/ibom.html
Normal file
4562
board/bom/ibom.html
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
1704
board/production/PlantCtrlESP32_2023-10-26_23-11-30/netlist.ipc
Normal file
1704
board/production/PlantCtrlESP32_2023-10-26_23-11-30/netlist.ipc
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ target = "xtensa-esp32-espidf"
|
|||||||
[target.xtensa-esp32-espidf]
|
[target.xtensa-esp32-espidf]
|
||||||
linker = "ldproxy"
|
linker = "ldproxy"
|
||||||
runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv" # Select this runner for espflash v2.x.x
|
runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv" # Select this runner for espflash v2.x.x
|
||||||
# runner = "cargo runner"
|
#runner = "cargo runner"
|
||||||
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
|
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
|
||||||
|
|
||||||
[unstable]
|
[unstable]
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use vergen::EmitBuilder;
|
use vergen::EmitBuilder;
|
||||||
fn main() {
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=./src/src_webpack");
|
||||||
|
Command::new("rm").arg("./src/webserver/bundle.js").output().expect("failed to execute process");
|
||||||
|
|
||||||
|
let output = Command::new("npx").arg("webpack").current_dir("./src_webpack").output().expect("failed to execute process");
|
||||||
|
|
||||||
|
println!("status: {}", output.status);
|
||||||
|
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||||
|
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
|
||||||
|
assert!(output.status.success());
|
||||||
|
|
||||||
embuild::espidf::sysenv::output();
|
embuild::espidf::sysenv::output();
|
||||||
let _ = EmitBuilder::builder().all_git().emit();
|
let _ = EmitBuilder::builder().all_git().emit();
|
||||||
}
|
}
|
@ -13,14 +13,20 @@ pub struct Config {
|
|||||||
|
|
||||||
plantcount: u16,
|
plantcount: u16,
|
||||||
|
|
||||||
pump_duration_ms: [u16;PLANT_COUNT],
|
|
||||||
pump_cooldown_min: [u16;PLANT_COUNT],
|
|
||||||
pump_hour_start: [u8;PLANT_COUNT],
|
|
||||||
pump_hour_end: [u8;PLANT_COUNT],
|
|
||||||
|
|
||||||
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: u8,
|
||||||
|
|
||||||
|
plants: [Plant;PLANT_COUNT]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Plant{
|
||||||
|
target_moisture: u8,
|
||||||
|
pump_time_s: u16,
|
||||||
|
pump_cooldown_min: u16,
|
||||||
|
pump_hour_start: heapless::String<5>,
|
||||||
|
pump_hour_end: heapless::String<5>
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -35,17 +35,17 @@ fn wait_infinity(board_access: Arc<Mutex<PlantCtrlBoard<'_>>>, wait_type:WaitTyp
|
|||||||
WaitType::InitialConfig => 250_u32,
|
WaitType::InitialConfig => 250_u32,
|
||||||
WaitType::FlashError => 100_u32,
|
WaitType::FlashError => 100_u32,
|
||||||
};
|
};
|
||||||
board_access.lock().unwrap().light(true);
|
board_access.lock().unwrap().light(true).unwrap();
|
||||||
loop {
|
loop {
|
||||||
unsafe {
|
unsafe {
|
||||||
//do not trigger watchdog
|
//do not trigger watchdog
|
||||||
for i in 0..7 {
|
for i in 0..8 {
|
||||||
board_access.lock().unwrap().fault(i, true);
|
board_access.lock().unwrap().fault(i, true);
|
||||||
}
|
}
|
||||||
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..7 {
|
for i in 0..8 {
|
||||||
board_access.lock().unwrap().fault(i, false);
|
board_access.lock().unwrap().fault(i, false);
|
||||||
}
|
}
|
||||||
vTaskDelay(delay);
|
vTaskDelay(delay);
|
||||||
@ -130,7 +130,7 @@ 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);
|
let _webserver = httpd_initial(board_access.clone());
|
||||||
wait_infinity(board_access.clone(), WaitType::InitialConfig);
|
wait_infinity(board_access.clone(), WaitType::InitialConfig);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use ds18b20::Ds18b20;
|
use ds18b20::Ds18b20;
|
||||||
use embedded_hal::digital::v1_compat::OldOutputPin;
|
|
||||||
use embedded_hal::digital::v2::OutputPin;
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
use esp_idf_hal::adc::config::Config;
|
use esp_idf_hal::adc::config::Config;
|
||||||
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
|
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
|
||||||
@ -34,7 +34,7 @@ use esp_idf_hal::prelude::Peripherals;
|
|||||||
use esp_idf_hal::reset::ResetReason;
|
use esp_idf_hal::reset::ResetReason;
|
||||||
use esp_idf_svc::sntp::{self, SyncStatus};
|
use esp_idf_svc::sntp::{self, SyncStatus};
|
||||||
use esp_idf_svc::systime::EspSystemTime;
|
use esp_idf_svc::systime::EspSystemTime;
|
||||||
use esp_idf_sys::EspError;
|
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};
|
||||||
@ -127,6 +127,7 @@ pub trait PlantCtrlBoardInteraction {
|
|||||||
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<()>;
|
||||||
fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>>;
|
fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>>;
|
||||||
|
fn test(&mut self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CreatePlantHal<'a> {
|
pub trait CreatePlantHal<'a> {
|
||||||
@ -143,37 +144,6 @@ impl CreatePlantHal<'_> for PlantHal {
|
|||||||
let mut latch = PinDriver::output(peripherals.pins.gpio22)?;
|
let mut latch = PinDriver::output(peripherals.pins.gpio22)?;
|
||||||
let mut data = PinDriver::output(peripherals.pins.gpio19)?;
|
let mut data = PinDriver::output(peripherals.pins.gpio19)?;
|
||||||
|
|
||||||
// loop {
|
|
||||||
// unsafe {
|
|
||||||
// let delay = Delay::new_default();
|
|
||||||
|
|
||||||
// latch.set_low().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// for i in 1..2 {
|
|
||||||
// data.set_high().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// clock.set_high().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// clock.set_low().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// }
|
|
||||||
// latch.set_high().unwrap();
|
|
||||||
|
|
||||||
// latch.set_low().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// for i in 1..2 {
|
|
||||||
// data.set_low().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// clock.set_high().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// clock.set_low().unwrap();
|
|
||||||
// delay.delay_ms(1);
|
|
||||||
// }
|
|
||||||
// latch.set_high().unwrap();
|
|
||||||
// vTaskDelay(5);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
let one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio4)?;
|
let one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio4)?;
|
||||||
//TODO make to none if not possible to init
|
//TODO make to none if not possible to init
|
||||||
|
|
||||||
@ -259,7 +229,7 @@ impl CreatePlantHal<'_> for PlantHal {
|
|||||||
|
|
||||||
|
|
||||||
let rv = Arc::new(Mutex::new(PlantCtrlBoard {
|
let rv = Arc::new(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,
|
||||||
low_voltage_detected: low_voltage_detected,
|
low_voltage_detected: low_voltage_detected,
|
||||||
@ -280,7 +250,7 @@ impl CreatePlantHal<'_> for PlantHal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PlantCtrlBoard<'a> {
|
pub struct PlantCtrlBoard<'a> {
|
||||||
//shift_register: ShiftRegister40<OldOutputPin<PinDriver<'a, esp_idf_hal::gpio::Gpio21, esp_idf_hal::gpio::Output>>, OldOutputPin<PinDriver<'a, esp_idf_hal::gpio::Gpio22, esp_idf_hal::gpio::Output>>, OldOutputPin<PinDriver<'a, esp_idf_hal::gpio::Gpio19, esp_idf_hal::gpio::Output>>>,
|
shift_register: ShiftRegister40<PinDriver<'a, esp_idf_hal::gpio::Gpio21, esp_idf_hal::gpio::Output>, PinDriver<'a, esp_idf_hal::gpio::Gpio22, esp_idf_hal::gpio::Output>, PinDriver<'a, esp_idf_hal::gpio::Gpio19, esp_idf_hal::gpio::Output>>,
|
||||||
consecutive_watering_plant: Mutex<[u32; PLANT_COUNT]>,
|
consecutive_watering_plant: Mutex<[u32; PLANT_COUNT]>,
|
||||||
last_watering_timestamp: Mutex<[i64; PLANT_COUNT]>,
|
last_watering_timestamp: Mutex<[i64; PLANT_COUNT]>,
|
||||||
low_voltage_detected: Mutex<bool>,
|
low_voltage_detected: Mutex<bool>,
|
||||||
@ -362,7 +332,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
|||||||
fn pump(&self, plant: usize, enable: bool) -> Result<()> {
|
fn pump(&self, plant: usize, enable: bool) -> Result<()> {
|
||||||
let index = plant * PINS_PER_PLANT + PLANT_PUMP_OFFSET;
|
let index = plant * PINS_PER_PLANT + PLANT_PUMP_OFFSET;
|
||||||
//currently infailable error, keep for future as result anyway
|
//currently infailable error, keep for future as result anyway
|
||||||
//self.shift_register.decompose()[index].set_state(enable.into()).unwrap();
|
self.shift_register.decompose()[index].set_state(enable.into()).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +357,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
|||||||
|
|
||||||
fn fault(&self, plant: usize, enable: bool) {
|
fn fault(&self, plant: usize, enable: bool) {
|
||||||
let index = plant * PINS_PER_PLANT + PLANT_FAULT_OFFSET;
|
let index = plant * PINS_PER_PLANT + PLANT_FAULT_OFFSET;
|
||||||
//self.shift_register.decompose()[index].set_state(enable.into()).unwrap()
|
self.shift_register.decompose()[index].set_state(enable.into()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn low_voltage_in_cycle(&mut self) -> bool {
|
fn low_voltage_in_cycle(&mut self) -> bool {
|
||||||
@ -436,13 +406,13 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
|||||||
let measurement = 100;
|
let measurement = 100;
|
||||||
let factor = 1000 / 100;
|
let factor = 1000 / 100;
|
||||||
|
|
||||||
//self.shift_register.decompose()[index].set_high().unwrap();
|
self.shift_register.decompose()[index].set_high().unwrap();
|
||||||
//give some time to stabilize
|
//give some time to stabilize
|
||||||
delay.delay_ms(10);
|
delay.delay_ms(10);
|
||||||
self.signal_counter.counter_resume()?;
|
self.signal_counter.counter_resume()?;
|
||||||
delay.delay_ms(measurement);
|
delay.delay_ms(measurement);
|
||||||
self.signal_counter.counter_pause()?;
|
self.signal_counter.counter_pause()?;
|
||||||
//self.shift_register.decompose()[index].set_low().unwrap();
|
self.shift_register.decompose()[index].set_low().unwrap();
|
||||||
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
||||||
let hz = unscaled * factor;
|
let hz = unscaled * factor;
|
||||||
println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
|
println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
|
||||||
@ -624,4 +594,45 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
|||||||
}, true)?;
|
}, true)?;
|
||||||
return Ok(self.wifi_driver.get_scan_result()?);
|
return Ok(self.wifi_driver.get_scan_result()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test(&mut self) -> Result<()> {
|
||||||
|
self.general_fault(true);
|
||||||
|
unsafe { vTaskDelay(100) };
|
||||||
|
self.general_fault(false);
|
||||||
|
unsafe { vTaskDelay(100) };
|
||||||
|
self.any_pump(true)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
self.any_pump(false)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
self.light(true)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
self.light(false)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
for i in 0 .. 8{
|
||||||
|
self.fault(i, true);
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
self.fault(i, false);
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
}
|
||||||
|
for i in 0 .. 8{
|
||||||
|
self.pump(i, true)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
self.pump(i, false)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
}
|
||||||
|
for i in 0 .. 8{
|
||||||
|
self.measure_moisture_hz(i, Sensor::A)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
}
|
||||||
|
for i in 0 .. 8{
|
||||||
|
self.measure_moisture_hz(i, Sensor::B)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
}
|
||||||
|
for i in 0 .. 8{
|
||||||
|
self.measure_moisture_hz(i, Sensor::PUMP)?;
|
||||||
|
unsafe { vTaskDelay(500) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
use anyhow::{Context, Error};
|
|
||||||
use esp_idf_hal::gpio::AnyInputPin;
|
|
||||||
use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex};
|
|
||||||
use esp_idf_hal::peripherals::Peripherals;
|
|
||||||
|
|
||||||
#[link_section = ".rtc.data"]
|
|
||||||
static mut MY_FANCY_VARIABLE: u32 = 0;
|
|
||||||
|
|
||||||
|
|
||||||
fn read_moisture() -> Result<i16,Error> {
|
|
||||||
|
|
||||||
let p = Peripherals::take().unwrap();
|
|
||||||
|
|
||||||
let counter1 = p.pcnt0;
|
|
||||||
let mut counter_unit1 = PcntDriver::new(
|
|
||||||
counter1,
|
|
||||||
Some(p.pins.gpio19),
|
|
||||||
Option::<AnyInputPin>::None,
|
|
||||||
Option::<AnyInputPin>::None,
|
|
||||||
Option::<AnyInputPin>::None,
|
|
||||||
).context("Could not obtain counter unit")?;
|
|
||||||
|
|
||||||
counter_unit1.channel_config(
|
|
||||||
PcntChannel::Channel0,
|
|
||||||
PinIndex::Pin0,
|
|
||||||
PinIndex::Pin1,
|
|
||||||
&PcntChannelConfig {
|
|
||||||
lctrl_mode: PcntControlMode::Reverse,
|
|
||||||
hctrl_mode: PcntControlMode::Keep,
|
|
||||||
pos_mode: PcntCountMode::Decrement,
|
|
||||||
neg_mode: PcntCountMode::Increment,
|
|
||||||
counter_h_lim: i16::MAX,
|
|
||||||
counter_l_lim: 0,
|
|
||||||
},
|
|
||||||
).context("Failed to configure pulse counter")?;
|
|
||||||
|
|
||||||
counter_unit1.set_filter_value(u16::min(10 * 80, 1023))?;
|
|
||||||
counter_unit1.filter_enable().context("Failed to enable pulse counter filter")?;
|
|
||||||
|
|
||||||
counter_unit1.counter_pause().context("Failed to pause pulse counter")?;
|
|
||||||
counter_unit1.counter_clear().context("Failed to clear pulse counter")?;
|
|
||||||
counter_unit1.counter_resume().context("Failed to start pulse counter")?;
|
|
||||||
|
|
||||||
let measurement = 100;
|
|
||||||
let waitFor = 1000/100;
|
|
||||||
//delay(measurement);
|
|
||||||
|
|
||||||
counter_unit1.counter_pause().context("Failed to end pulse counter measurement")?;
|
|
||||||
return Ok(counter_unit1.get_counter_value().context("Failed to read pulse counter value")?*waitFor);
|
|
||||||
}
|
|
||||||
fn main() {
|
|
||||||
// It is necessary to call this function once. Otherwise some patches to the runtime
|
|
||||||
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
|
||||||
esp_idf_svc::sys::link_patches();
|
|
||||||
|
|
||||||
// Bind the log crate to the ESP Logging facilities
|
|
||||||
esp_idf_svc::log::EspLogger::initialize_default();
|
|
||||||
|
|
||||||
log::info!("Hello, world!");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +1,8 @@
|
|||||||
<html>
|
<html>
|
||||||
<meta>
|
|
||||||
<script src="ota.js"></script>
|
|
||||||
<script src="form.js"></script>
|
|
||||||
</meta>
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2>firmeware OTA v3</h2>
|
<h2>firmeware OTA v3</h2>
|
||||||
<form id="upload_form" method="post">
|
<form id="upload_form" method="post">
|
||||||
<input type="file" name="file1" id="file1" onchange="uploadFile()"><br>
|
<input type="file" name="file1" id="file1"><br>
|
||||||
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
|
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
|
||||||
<h3 id="status"></h3>
|
<h3 id="status"></h3>
|
||||||
<h3 id="answer"></h3>
|
<h3 id="answer"></h3>
|
||||||
@ -17,41 +12,42 @@
|
|||||||
<h2>config</h2>
|
<h2>config</h2>
|
||||||
|
|
||||||
|
|
||||||
<button id="submit" onclick="createForm()">Create</button>
|
<button id="dummy">Create</button>
|
||||||
<div id="configform">
|
<div id="configform">
|
||||||
<h3>Tank:</h3>
|
<h3>Tank:</h3>
|
||||||
<div>
|
<div>
|
||||||
<input type="checkbox" id="tank_sensor_enabled" onchange="submitForm()">
|
<input type="checkbox" id="tank_sensor_enabled">
|
||||||
Enable Tank Sensor
|
Enable Tank Sensor
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="number" min="2" max="500000" id="tank_full_ml" onchange="submitForm()">
|
<input type="number" min="2" max="500000" id="tank_full_ml">
|
||||||
Tank Size mL
|
Tank Size mL
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="number" min="1" max="500000" id="tank_warn_percent" onchange="submitForm()">
|
<input type="number" min="1" max="500000" id="tank_warn_percent">
|
||||||
Tank Warn below mL
|
Tank Warn below mL
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Light:</h3>
|
<h3>Light:</h3>
|
||||||
<div>
|
<div>
|
||||||
Start
|
Start
|
||||||
<input type="time" id="night_lamp_time_start" onchange="submitForm()">
|
<input type="time" id="night_lamp_time_start">
|
||||||
Stop
|
Stop
|
||||||
<input type="time" id="night_lamp_time_end" onchange="submitForm()">
|
<input type="time" id="night_lamp_time_end">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="checkbox" id="night_lamp_only_when_dark" onchange="submitForm()">
|
<input type="checkbox" id="night_lamp_only_when_dark">
|
||||||
Light only when dark
|
Light only when dark
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Plants:</h3>
|
<h3>Plants:</h3>
|
||||||
<div id="plants"></div>
|
<div id="plants"></div>
|
||||||
</div>
|
</div>
|
||||||
<button id="submit" onclick="submitForm()">Submit</button>
|
<button id="submit">Submit</button>
|
||||||
<br>
|
<br>
|
||||||
<textarea id="json" cols=50 rows=10></textarea>
|
<textarea id="json" cols=50 rows=10></textarea>
|
||||||
|
<script src="bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,109 +0,0 @@
|
|||||||
var plantcount = 1;
|
|
||||||
|
|
||||||
function createForm(){
|
|
||||||
var current = {}
|
|
||||||
current.tank_sensor_enabled = true;
|
|
||||||
current.tank_full_ml = 400;
|
|
||||||
current.tank_warn_percent = 200;
|
|
||||||
current.night_lamp_time_start = "18:00";
|
|
||||||
current.night_lamp_time_end = "02:00";
|
|
||||||
current.night_lamp_only_when_dark = true;
|
|
||||||
current.plants = [
|
|
||||||
{
|
|
||||||
target_moisture: 40,
|
|
||||||
pump_time_s:60
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
current.plantcount = 1;
|
|
||||||
|
|
||||||
plantcount = current.plantcount;
|
|
||||||
|
|
||||||
|
|
||||||
for(i=0;i<plantcount;i++){
|
|
||||||
var plant = document.createElement("div");
|
|
||||||
plants.appendChild(plant);
|
|
||||||
|
|
||||||
var header = document.createElement("h4");
|
|
||||||
header.textContent = "Plant " + (i+1);
|
|
||||||
plant.appendChild(header);
|
|
||||||
|
|
||||||
{
|
|
||||||
var holder = document.createElement("div");
|
|
||||||
plant.appendChild(holder);
|
|
||||||
var inputf = document.createElement("input");
|
|
||||||
inputf.id = "plant_"+i+"_target_moisture";
|
|
||||||
inputf.onchange = function() {submitForm()};
|
|
||||||
inputf.type = "number";
|
|
||||||
inputf.min = 0;
|
|
||||||
inputf.max = 100;
|
|
||||||
holder.appendChild(inputf)
|
|
||||||
|
|
||||||
var text = document.createElement("span");
|
|
||||||
holder.appendChild(text)
|
|
||||||
text.innerHTML += "Target Moisture"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var holder = document.createElement("div");
|
|
||||||
plant.appendChild(holder);
|
|
||||||
var input = document.createElement("input");
|
|
||||||
input.id = "plant_"+i+"_pump_time_s";
|
|
||||||
input.onchange = function() {submitForm()};
|
|
||||||
input.type = "number";
|
|
||||||
input.min = 0;
|
|
||||||
input.max = 600;
|
|
||||||
holder.appendChild(input)
|
|
||||||
|
|
||||||
var text = document.createElement("span");
|
|
||||||
holder.appendChild(text)
|
|
||||||
text.innerHTML += "Pump Time (s)"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sync(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sync(current){
|
|
||||||
document.getElementById("tank_full_ml").disabled = !current.tank_sensor_enabled;
|
|
||||||
document.getElementById("tank_warn_percent").disabled = !current.tank_sensor_enabled;
|
|
||||||
|
|
||||||
document.getElementById("tank_sensor_enabled").checked = current.tank_sensor_enabled;
|
|
||||||
document.getElementById("tank_full_ml").value = current.tank_full_ml;
|
|
||||||
document.getElementById("tank_warn_percent").value = current.tank_warn_percent;
|
|
||||||
document.getElementById("night_lamp_time_start").value = current.night_lamp_time_start;
|
|
||||||
document.getElementById("night_lamp_time_end").value = current.night_lamp_time_end;
|
|
||||||
document.getElementById("tank_warn_percent").value = current.tank_warn_percent;
|
|
||||||
|
|
||||||
for(i=0;i<plantcount;i++){
|
|
||||||
document.getElementById("plant_"+i+"_target_moisture").value = current.plants[i].target_moisture;
|
|
||||||
document.getElementById("plant_"+i+"_pump_time_s").value = current.plants[i].pump_time_s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitForm() {
|
|
||||||
var current = {}
|
|
||||||
current.plantcount = plantcount;
|
|
||||||
|
|
||||||
current.tank_sensor_enabled = document.getElementById("tank_sensor_enabled").checked;
|
|
||||||
current.tank_full_ml = document.getElementById("tank_full_ml").value;
|
|
||||||
current.tank_warn_percent = document.getElementById("tank_warn_percent").value;
|
|
||||||
current.night_lamp_time_start = document.getElementById("night_lamp_time_start").value;
|
|
||||||
current.night_lamp_time_end = document.getElementById("night_lamp_time_end").value;
|
|
||||||
current.night_lamp_only_when_dark = document.getElementById("night_lamp_only_when_dark").checked;
|
|
||||||
|
|
||||||
current.plants = []
|
|
||||||
for(i=0;i<plantcount;i++){
|
|
||||||
console.log("Adding plant " + i)
|
|
||||||
current.plants[i] = {}
|
|
||||||
current.plants[i].target_moisture = document.getElementById("plant_"+i+"_target_moisture").value;
|
|
||||||
current.plants[i].pump_time_s = document.getElementById("plant_"+i+"_pump_time_s").value;
|
|
||||||
}
|
|
||||||
|
|
||||||
sync(current);
|
|
||||||
console.log(current);
|
|
||||||
|
|
||||||
var pretty = JSON.stringify(current, undefined, 4);
|
|
||||||
document.getElementById('json').value = pretty;
|
|
||||||
}
|
|
@ -1,13 +1,11 @@
|
|||||||
<html>
|
<html>
|
||||||
<meta>
|
|
||||||
<script src="ota.js"></script>
|
|
||||||
<script src="wifi.js"></script>
|
|
||||||
</meta>
|
|
||||||
<body>
|
<body>
|
||||||
|
<input type="button" id="test" value="Test">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>firmeware OTA v3</h2>
|
<h2>firmeware OTA v3</h2>
|
||||||
<form id="upload_form" method="post">
|
<form id="upload_form" method="post">
|
||||||
<input type="file" name="file1" id="file1" onchange="uploadFile()"><br>
|
<input type="file" name="file1" id="file1"><br>
|
||||||
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
|
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
|
||||||
<h3 id="status"></h3>
|
<h3 id="status"></h3>
|
||||||
<h3 id="answer"></h3>
|
<h3 id="answer"></h3>
|
||||||
@ -17,7 +15,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>WIFI</h2>
|
<h2>WIFI</h2>
|
||||||
<input type="button" id="scan" onclick="scanWifi()" value="Scan">
|
<input type="button" id="scan" value="Scan">
|
||||||
<br>
|
<br>
|
||||||
<label for="ssid">SSID:</label>
|
<label for="ssid">SSID:</label>
|
||||||
<input type="text" id="ssid" list="ssidlist">
|
<input type="text" id="ssid" list="ssidlist">
|
||||||
@ -26,9 +24,10 @@
|
|||||||
</datalist>
|
</datalist>
|
||||||
<label for="ssid">Password:</label>
|
<label for="ssid">Password:</label>
|
||||||
<input type="text" id="password" list="ssidlist">
|
<input type="text" id="password" list="ssidlist">
|
||||||
<input type="button" id="save" onclick="saveWifi()" value="Save & Restart">
|
<input type="button" id="save" value="Save & Restart">
|
||||||
<div id="wifistatus"></div>
|
<div id="wifistatus"></div>
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,40 +0,0 @@
|
|||||||
function _(el) {
|
|
||||||
return document.getElementById(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadFile() {
|
|
||||||
var file = _("file1").files[0];
|
|
||||||
// alert(file.name+" | "+file.size+" | "+file.type);
|
|
||||||
var ajax = new XMLHttpRequest();
|
|
||||||
ajax.upload.addEventListener("progress", progressHandler, false);
|
|
||||||
ajax.addEventListener("load", completeHandler, false);
|
|
||||||
ajax.addEventListener("error", errorHandler, false);
|
|
||||||
ajax.addEventListener("abort", abortHandler, false);
|
|
||||||
ajax.open("POST", "/ota"); // http://www.developphp.com/video/JavaScript/File-Upload-Progress-Bar-Meter-Tutorial-Ajax-PHP
|
|
||||||
//use file_upload_parser.php from above url
|
|
||||||
ajax.send(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
function progressHandler(event) {
|
|
||||||
_("loaded_n_total").innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
|
|
||||||
var percent = (event.loaded / event.total) * 100;
|
|
||||||
_("progressBar").value = Math.round(percent);
|
|
||||||
_("status").innerHTML = Math.round(percent) + "%";
|
|
||||||
_("answer").innerHTML = "in progress";
|
|
||||||
}
|
|
||||||
|
|
||||||
function completeHandler(event) {
|
|
||||||
_("status").innerHTML = event.target.responseText;
|
|
||||||
_("answer").innerHTML = "finished";
|
|
||||||
_("progressBar").value = 0; //wil clear progress bar after successful upload
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorHandler(event) {
|
|
||||||
_("status").innerHTML = event.target.responseText;
|
|
||||||
_("answer").innerHTML = "failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
function abortHandler(event) {
|
|
||||||
_("status").innerHTML = event.target.responseText;
|
|
||||||
_("answer").innerHTML = "aborted";
|
|
||||||
}
|
|
@ -16,23 +16,18 @@ 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>>>) -> 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()?;
|
||||||
response.write(include_bytes!("initial_config.html"))?;
|
response.write(include_bytes!("initial_config.html"))?;
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
server
|
|
||||||
.fn_handler("/wifi.js",Method::Get, |request| {
|
let board_access_for_scan = board_access.clone();
|
||||||
let mut response = request.into_ok_response()?;
|
|
||||||
response.write(include_bytes!("wifi.js"))?;
|
|
||||||
return Ok(())
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
server.fn_handler("/wifiscan",Method::Post, move |request| {
|
server.fn_handler("/wifiscan",Method::Post, move |request| {
|
||||||
let mut response = request.into_ok_response()?;
|
let mut response = request.into_ok_response()?;
|
||||||
let mut board = board_access.lock().unwrap();
|
let mut board = board_access_for_scan.lock().unwrap();
|
||||||
match board.wifi_scan() {
|
match board.wifi_scan() {
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
response.write(format!("Error scanning wifi: {}", error).as_bytes())?;
|
response.write(format!("Error scanning wifi: {}", error).as_bytes())?;
|
||||||
@ -50,11 +45,14 @@ pub fn httpd_initial(board_access:&Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<E
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
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(){
|
||||||
let error_text = read.unwrap_err().to_string();
|
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())?;
|
request.into_status_response(500)?.write(error_text.as_bytes())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -62,16 +60,23 @@ pub fn httpd_initial(board_access:&Arc<Mutex<PlantCtrlBoard<'static>>>) -> Box<E
|
|||||||
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();
|
||||||
|
println!("Could not parse wificonfig {}", error_text);
|
||||||
request.into_status_response(500)?.write(error_text.as_bytes())?;
|
request.into_status_response(500)?.write(error_text.as_bytes())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut board = board_access.lock().unwrap();
|
let mut board = board_access_for_save.lock().unwrap();
|
||||||
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())?;
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
let board_access_for_test= board_access.clone();
|
||||||
|
server.fn_handler("/boardtest",Method::Post, move |request| {
|
||||||
|
let mut board = board_access_for_test.lock().unwrap();
|
||||||
|
board.test();
|
||||||
|
return Ok(())
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
@ -100,9 +105,9 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
server
|
server
|
||||||
.fn_handler("/ota.js",Method::Get, |request| {
|
.fn_handler("/bundle.js",Method::Get, |request| {
|
||||||
let mut response = request.into_ok_response()?;
|
let mut response = request.into_ok_response()?;
|
||||||
response.write(include_bytes!("ota.js"))?;
|
response.write(include_bytes!("bundle.js"))?;
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
server
|
server
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
function saveWifi(){
|
|
||||||
document.getElementById("save").disabled = true;
|
|
||||||
var wificonfig = {}
|
|
||||||
wificonfig.ssid = document.getElementById("ssid").value
|
|
||||||
wificonfig.password = document.getElementById("password").value
|
|
||||||
var pretty = JSON.stringify(wificonfig, undefined, 4);
|
|
||||||
console.log("Sending config " + pretty)
|
|
||||||
|
|
||||||
var ajax = new XMLHttpRequest();
|
|
||||||
ajax.upload.addEventListener("progress", progressHandler, false);
|
|
||||||
ajax.onreadystatechange = () => {
|
|
||||||
_("wifistatus").innerText = ajax.responseText
|
|
||||||
};
|
|
||||||
ajax.onerror = (evt) => {
|
|
||||||
console.log(evt)
|
|
||||||
_("wifistatus").innerText = ajax.responseText
|
|
||||||
document.getElementById("save").disabled = false;
|
|
||||||
alert("Failed to save config see console")
|
|
||||||
}
|
|
||||||
ajax.open("POST", "/wifisave");
|
|
||||||
ajax.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function scanWifi(){
|
|
||||||
document.getElementById("scan").disabled = true;
|
|
||||||
|
|
||||||
var ajax = new XMLHttpRequest();
|
|
||||||
ajax.upload.addEventListener("progress", progressHandler, false);
|
|
||||||
ajax.responseType = 'json';
|
|
||||||
ajax.onreadystatechange = () => {
|
|
||||||
if (ajax.readyState === 4) {
|
|
||||||
callback(ajax.response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ajax.onerror = (evt) => {
|
|
||||||
console.log(evt)
|
|
||||||
document.getElementById("scan").disabled = false;
|
|
||||||
alert("Failed to start see console")
|
|
||||||
}
|
|
||||||
ajax.open("POST", "/wifiscan");
|
|
||||||
ajax.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function callback(data){
|
|
||||||
document.getElementById("scan").disabled = false;
|
|
||||||
|
|
||||||
var ssidlist = document.getElementById("ssidlist")
|
|
||||||
ssidlist.innerHTML = ''
|
|
||||||
|
|
||||||
for (ssid of data.ssids) {
|
|
||||||
var wi = document.createElement("option");
|
|
||||||
wi.value = ssid;
|
|
||||||
ssidlist.appendChild(wi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
1540
rust/src_webpack/package-lock.json
generated
Normal file
1540
rust/src_webpack/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
rust/src_webpack/package.json
Normal file
11
rust/src_webpack/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"webpack": "^5.89.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"source-map-loader": "^4.0.1"
|
||||||
|
}
|
||||||
|
}
|
175
rust/src_webpack/src/form.ts
Normal file
175
rust/src_webpack/src/form.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
interface PlantConfig {
|
||||||
|
tank_sensor_enabled:boolean,
|
||||||
|
tank_full_ml:number,
|
||||||
|
tank_warn_percent: number,
|
||||||
|
night_lamp_time_start: string,
|
||||||
|
night_lamp_time_end: string,
|
||||||
|
night_lamp_only_when_dark: boolean,
|
||||||
|
plants: {
|
||||||
|
target_moisture: number,
|
||||||
|
pump_time_s: number,
|
||||||
|
pump_cooldown_min: number,
|
||||||
|
pump_hour_start: string,
|
||||||
|
pump_hour_end: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
let plants = document.getElementById("plants") as HTMLInputElement;
|
||||||
|
|
||||||
|
let fromWrapper = (() => {
|
||||||
|
|
||||||
|
let plantcount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
let tank_full_ml = document.getElementById("tank_full_ml") as HTMLInputElement;
|
||||||
|
tank_full_ml.onchange = submitForm
|
||||||
|
let tank_warn_percent = document.getElementById("tank_warn_percent") as HTMLInputElement;
|
||||||
|
tank_warn_percent.onchange = submitForm
|
||||||
|
let tank_sensor_enabled = document.getElementById("tank_sensor_enabled") as HTMLInputElement;
|
||||||
|
tank_sensor_enabled.onchange = submitForm
|
||||||
|
let night_lamp_only_when_dark = document.getElementById("night_lamp_only_when_dark") as HTMLInputElement;
|
||||||
|
night_lamp_only_when_dark.onchange = submitForm
|
||||||
|
let night_lamp_time_start = document.getElementById("night_lamp_time_start") as HTMLInputElement;
|
||||||
|
night_lamp_time_start.onchange = submitForm
|
||||||
|
let night_lamp_time_end = document.getElementById("night_lamp_time_end") as HTMLInputElement;
|
||||||
|
night_lamp_time_end.onchange = submitForm
|
||||||
|
|
||||||
|
let json = document.getElementById('json') as HTMLInputElement
|
||||||
|
|
||||||
|
function createForm(){
|
||||||
|
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++){
|
||||||
|
var plant = document.createElement("div");
|
||||||
|
plants.appendChild(plant);
|
||||||
|
var header = document.createElement("h4");
|
||||||
|
header.textContent = "Plant " + (i+1);
|
||||||
|
plant.appendChild(header);
|
||||||
|
|
||||||
|
{
|
||||||
|
var holder = document.createElement("div");
|
||||||
|
plant.appendChild(holder);
|
||||||
|
var inputf = document.createElement("input");
|
||||||
|
inputf.id = "plant_"+i+"_target_moisture";
|
||||||
|
inputf.onchange = submitForm;
|
||||||
|
inputf.type = "number";
|
||||||
|
inputf.min = "0";
|
||||||
|
inputf.max = "100";
|
||||||
|
holder.appendChild(inputf)
|
||||||
|
|
||||||
|
var text = document.createElement("span");
|
||||||
|
holder.appendChild(text)
|
||||||
|
text.innerHTML += "Target Moisture"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var holder = document.createElement("div");
|
||||||
|
plant.appendChild(holder);
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.id = "plant_"+i+"_pump_time_s";
|
||||||
|
input.onchange = submitForm;
|
||||||
|
input.type = "number";
|
||||||
|
input.min = "0";
|
||||||
|
input.max = "600";
|
||||||
|
holder.appendChild(input)
|
||||||
|
|
||||||
|
var text = document.createElement("span");
|
||||||
|
holder.appendChild(text)
|
||||||
|
text.innerHTML += "Pump Time (s)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sync(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sync(current:PlantConfig){
|
||||||
|
plantcount = current.plants.length
|
||||||
|
tank_full_ml.disabled = !current.tank_sensor_enabled;
|
||||||
|
tank_warn_percent.disabled = !current.tank_sensor_enabled;
|
||||||
|
|
||||||
|
tank_sensor_enabled.checked = current.tank_sensor_enabled;
|
||||||
|
tank_full_ml.value = current.tank_full_ml.toString();
|
||||||
|
tank_warn_percent.value = current.tank_warn_percent.toString();
|
||||||
|
night_lamp_time_start.value = current.night_lamp_time_start;
|
||||||
|
night_lamp_time_end.value = current.night_lamp_time_end;
|
||||||
|
|
||||||
|
for(let i=0;i<current.plants.length;i++){
|
||||||
|
let plant_target_moisture = document.getElementById("plant_"+i+"_target_moisture") as HTMLInputElement;
|
||||||
|
plant_target_moisture.value = current.plants[i].target_moisture.toString();
|
||||||
|
let plant_pump_time_s = document.getElementById("plant_"+i+"_pump_time_s") as HTMLInputElement;
|
||||||
|
plant_pump_time_s.value = current.plants[i].pump_time_s.toString();
|
||||||
|
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();
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitForm() {
|
||||||
|
var current: PlantConfig = {
|
||||||
|
tank_sensor_enabled: tank_sensor_enabled.checked,
|
||||||
|
tank_full_ml: +tank_full_ml.value,
|
||||||
|
tank_warn_percent: +tank_warn_percent.value,
|
||||||
|
night_lamp_time_start: night_lamp_time_start.value,
|
||||||
|
night_lamp_time_end: night_lamp_time_end.value,
|
||||||
|
night_lamp_only_when_dark: night_lamp_only_when_dark.checked,
|
||||||
|
plants: []
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let i=0;i<plantcount;i++){
|
||||||
|
console.log("Adding plant " + i)
|
||||||
|
let plant_target_moisture = document.getElementById("plant_"+i+"_target_moisture") as HTMLInputElement;
|
||||||
|
let plant_pump_time_s = document.getElementById("plant_"+i+"_pump_time_s") as HTMLInputElement;
|
||||||
|
let plant_pump_cooldown_min = document.getElementById("plant_"+i+"_pump_cooldown_min") as HTMLInputElement;
|
||||||
|
let plant_pump_hour_start = document.getElementById("plant_"+i+"_pump_hour_start") as HTMLInputElement;
|
||||||
|
let plant_pump_hour_end = document.getElementById("plant_"+i+"_pump_hour_end") as HTMLInputElement;
|
||||||
|
|
||||||
|
current.plants[i] = {
|
||||||
|
target_moisture : +plant_target_moisture.value,
|
||||||
|
pump_time_s: +plant_pump_time_s.value,
|
||||||
|
pump_cooldown_min: +plant_pump_cooldown_min.value,
|
||||||
|
pump_hour_start: plant_pump_hour_start.value,
|
||||||
|
pump_hour_end: plant_pump_hour_end.value
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sync(current);
|
||||||
|
console.log(current);
|
||||||
|
|
||||||
|
var pretty = JSON.stringify(current, undefined, 4);
|
||||||
|
json.value = pretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
let createDocumentBtn = document.getElementById("create") as HTMLButtonElement
|
||||||
|
if(createDocumentBtn){
|
||||||
|
createDocumentBtn.onclick = createForm;
|
||||||
|
}
|
||||||
|
let submitFormBtn = document.getElementById("submit") as HTMLButtonElement
|
||||||
|
if(submitFormBtn){
|
||||||
|
submitFormBtn.onclick = submitForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
if(plants){
|
||||||
|
fromWrapper()
|
||||||
|
}
|
||||||
|
|
37
rust/src_webpack/src/ota.ts
Normal file
37
rust/src_webpack/src/ota.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export function uploadFile() {
|
||||||
|
var file1 = document.getElementById("file1") as HTMLInputElement;
|
||||||
|
var loaded_n_total = document.getElementById("loaded_n_total");
|
||||||
|
var progressBar = document.getElementById("progressBar") as HTMLProgressElement;
|
||||||
|
var status = document.getElementById("status");
|
||||||
|
var answer = document.getElementById("answer");
|
||||||
|
|
||||||
|
var file = file1.files[0];
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
|
||||||
|
|
||||||
|
ajax.upload.addEventListener("progress", event => {
|
||||||
|
loaded_n_total.innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
|
||||||
|
var percent = (event.loaded / event.total) * 100;
|
||||||
|
progressBar.value = Math.round(percent);
|
||||||
|
status.innerHTML = Math.round(percent) + "%";
|
||||||
|
answer.innerHTML = "in progress";
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("load", () => {
|
||||||
|
status.innerHTML = ajax.responseText;
|
||||||
|
answer.innerHTML = "finished";
|
||||||
|
progressBar.value = 0;
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("error", () => {
|
||||||
|
status.innerHTML = ajax.responseText;
|
||||||
|
answer.innerHTML = "failed";
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("abort", () => {
|
||||||
|
status.innerHTML = ajax.responseText;
|
||||||
|
answer.innerHTML = "aborted";
|
||||||
|
}, false);
|
||||||
|
ajax.open("POST", "/ota");
|
||||||
|
ajax.send(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
let file1Upload = document.getElementById("file1") as HTMLInputElement;
|
||||||
|
file1Upload.onchange = uploadFile;
|
93
rust/src_webpack/src/wifi.ts
Normal file
93
rust/src_webpack/src/wifi.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
interface WifiConfig {
|
||||||
|
ssid: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SSIDList {
|
||||||
|
ssids : [string]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveWifi(){
|
||||||
|
var saveButton = (document.getElementById("save") as HTMLButtonElement);
|
||||||
|
saveButton.disabled = true;
|
||||||
|
var ssid = (document.getElementById("ssid") as HTMLInputElement).value
|
||||||
|
var password = (document.getElementById("password") as HTMLInputElement).value
|
||||||
|
|
||||||
|
var wifistatus = document.getElementById("wifistatus")
|
||||||
|
|
||||||
|
var wificonfig:WifiConfig = {ssid, password}
|
||||||
|
var pretty = JSON.stringify(wificonfig, undefined, 4);
|
||||||
|
console.log("Sending config " + pretty)
|
||||||
|
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
ajax.onreadystatechange = () => {
|
||||||
|
wifistatus.innerText = ajax.responseText
|
||||||
|
};
|
||||||
|
ajax.onerror = (evt) => {
|
||||||
|
console.log(evt)
|
||||||
|
wifistatus.innerText = ajax.responseText
|
||||||
|
saveButton.disabled = false;
|
||||||
|
alert("Failed to save config see console")
|
||||||
|
}
|
||||||
|
ajax.open("POST", "/wifisave");
|
||||||
|
ajax.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scanWifi(){
|
||||||
|
var scanButton = (document.getElementById("scan") as HTMLButtonElement);
|
||||||
|
scanButton.disabled = true;
|
||||||
|
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
ajax.responseType = 'json';
|
||||||
|
ajax.onreadystatechange = () => {
|
||||||
|
if (ajax.readyState === 4) {
|
||||||
|
callback(ajax.response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ajax.onerror = (evt) => {
|
||||||
|
console.log(evt)
|
||||||
|
scanButton.disabled = false;
|
||||||
|
alert("Failed to start see console")
|
||||||
|
}
|
||||||
|
ajax.open("POST", "/wifiscan");
|
||||||
|
ajax.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(){
|
||||||
|
var testButton = (document.getElementById("test") as HTMLButtonElement);
|
||||||
|
testButton.disabled = true;
|
||||||
|
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
ajax.responseType = 'json';
|
||||||
|
ajax.onerror = (evt) => {
|
||||||
|
console.log(evt)
|
||||||
|
testButton.disabled = false;
|
||||||
|
alert("Failed to start see console")
|
||||||
|
}
|
||||||
|
ajax.open("POST", "/boardtest");
|
||||||
|
ajax.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function callback(data:SSIDList){
|
||||||
|
var ssidlist = document.getElementById("ssidlist")
|
||||||
|
ssidlist.innerHTML = ''
|
||||||
|
|
||||||
|
for (var ssid of data.ssids) {
|
||||||
|
var wi = document.createElement("option");
|
||||||
|
wi.value = ssid;
|
||||||
|
ssidlist.appendChild(wi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testBtn = document.getElementById("test") as HTMLButtonElement;
|
||||||
|
if(testBtn){
|
||||||
|
testBtn.onclick = test;
|
||||||
|
}
|
||||||
|
let scanWifiBtn = document.getElementById("scan") as HTMLButtonElement;
|
||||||
|
if(scanWifiBtn){
|
||||||
|
scanWifiBtn.onclick = scanWifi;
|
||||||
|
}
|
||||||
|
let saveWifiBtn = document.getElementById("save") as HTMLButtonElement;
|
||||||
|
if(saveWifiBtn){
|
||||||
|
saveWifiBtn.onclick = saveWifi;
|
||||||
|
}
|
13
rust/src_webpack/tsconfig.json
Normal file
13
rust/src_webpack/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "es6",
|
||||||
|
"target": "es5",
|
||||||
|
"jsx": "react",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
}
|
24
rust/src_webpack/webpack.config.js
Normal file
24
rust/src_webpack/webpack.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: "development",
|
||||||
|
entry: ['./src/form.ts','./src/ota.ts','./src/wifi.ts'],
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: 'bundle.js',
|
||||||
|
path: path.resolve(__dirname, '../src/webserver'),
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user