add ability to override frequency per plant and adjust timezone, fix missing workhour for plants
This commit is contained in:
parent
f8274ea7a8
commit
5fe1dc8f40
@ -79,6 +79,7 @@ pub struct PlantControllerConfig {
|
|||||||
pub tank: TankConfig,
|
pub tank: TankConfig,
|
||||||
pub night_lamp: NightLampConfig,
|
pub night_lamp: NightLampConfig,
|
||||||
pub plants: [PlantConfig; PLANT_COUNT],
|
pub plants: [PlantConfig; PLANT_COUNT],
|
||||||
|
pub timezone: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
@ -93,7 +94,10 @@ pub struct PlantConfig {
|
|||||||
pub sensor_a: bool,
|
pub sensor_a: bool,
|
||||||
pub sensor_b: bool,
|
pub sensor_b: bool,
|
||||||
pub max_consecutive_pump_count: u8,
|
pub max_consecutive_pump_count: u8,
|
||||||
|
pub moisture_sensor_min_frequency: Option<f32>, // Optional min frequency
|
||||||
|
pub moisture_sensor_max_frequency: Option<f32>, // Optional max frequency
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlantConfig {
|
impl Default for PlantConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -106,6 +110,8 @@ impl Default for PlantConfig {
|
|||||||
sensor_a: true,
|
sensor_a: true,
|
||||||
sensor_b: false,
|
sensor_b: false,
|
||||||
max_consecutive_pump_count: 10,
|
max_consecutive_pump_count: 10,
|
||||||
|
moisture_sensor_min_frequency: None, // No override by default
|
||||||
|
moisture_sensor_max_frequency: None, // No override by default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,8 +5,8 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use chrono::{DateTime, Datelike, Timelike};
|
use chrono::{DateTime, Datelike, Timelike};
|
||||||
use chrono_tz::{Europe::Berlin, Tz};
|
use chrono_tz::Tz::UTC;
|
||||||
|
use chrono_tz::Tz;
|
||||||
use esp_idf_hal::delay::Delay;
|
use esp_idf_hal::delay::Delay;
|
||||||
use esp_idf_sys::{
|
use esp_idf_sys::{
|
||||||
esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition,
|
esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition,
|
||||||
@ -32,8 +32,6 @@ pub mod util;
|
|||||||
use plant_state::PlantState;
|
use plant_state::PlantState;
|
||||||
use tank::*;
|
use tank::*;
|
||||||
|
|
||||||
const TIME_ZONE: Tz = Berlin;
|
|
||||||
|
|
||||||
pub static BOARD_ACCESS: Lazy<Mutex<PlantCtrlBoard>> = Lazy::new(|| PlantHal::create().unwrap());
|
pub static BOARD_ACCESS: Lazy<Mutex<PlantCtrlBoard>> = Lazy::new(|| PlantHal::create().unwrap());
|
||||||
pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
|
pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
|
||||||
|
|
||||||
@ -280,11 +278,20 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let timezone_time = cur.with_timezone(&TIME_ZONE);
|
let timezone = match &config.timezone {
|
||||||
|
Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| {
|
||||||
|
println!("Invalid timezone '{}', falling back to UTC", tz_str);
|
||||||
|
UTC
|
||||||
|
}),
|
||||||
|
None => UTC, // Fallback to UTC if no timezone is set
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let timezone_time = cur.with_timezone(&timezone);
|
||||||
println!(
|
println!(
|
||||||
"Running logic at utc {} and {} {}",
|
"Running logic at utc {} and {} {}",
|
||||||
cur,
|
cur,
|
||||||
TIME_ZONE.name(),
|
timezone.name(),
|
||||||
timezone_time
|
timezone_time
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -579,28 +586,51 @@ fn publish_battery_state(
|
|||||||
|
|
||||||
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||||
let delay = wait_type.blink_pattern();
|
let delay = wait_type.blink_pattern();
|
||||||
|
|
||||||
let mut led_count = 8;
|
let mut led_count = 8;
|
||||||
|
let mut pattern_step = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// TODO implement actually different blink patterns instead of modulating blink duration
|
unsafe {
|
||||||
if wait_type == WaitType::MissingConfig {
|
let mut lock = BOARD_ACCESS.lock().unwrap();
|
||||||
|
lock.update_charge_indicator();
|
||||||
|
match wait_type {
|
||||||
|
WaitType::MissingConfig => {
|
||||||
|
// Keep existing behavior: circular filling pattern
|
||||||
led_count %= 8;
|
led_count %= 8;
|
||||||
led_count += 1;
|
led_count += 1;
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
BOARD_ACCESS.lock().unwrap().update_charge_indicator();
|
|
||||||
//do not trigger watchdog
|
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
BOARD_ACCESS.lock().unwrap().fault(i, i < led_count);
|
lock.fault(i, i < led_count);
|
||||||
}
|
}
|
||||||
BOARD_ACCESS.lock().unwrap().general_fault(true);
|
}
|
||||||
vTaskDelay(delay);
|
WaitType::ConfigButton => {
|
||||||
BOARD_ACCESS.lock().unwrap().general_fault(false);
|
// Alternating pattern: 1010 1010 -> 0101 0101
|
||||||
//TODO move locking outside of loop and drop afterwards
|
pattern_step = (pattern_step + 1) % 2;
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
BOARD_ACCESS.lock().unwrap().fault(i, false);
|
lock.fault(i, (i + pattern_step) % 2 == 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
WaitType::MqttConfig => {
|
||||||
|
// Moving dot pattern
|
||||||
|
pattern_step = (pattern_step + 1) % 8;
|
||||||
|
for i in 0..8 {
|
||||||
|
lock.fault(i, i == pattern_step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.general_fault(true);
|
||||||
|
drop(lock);
|
||||||
vTaskDelay(delay);
|
vTaskDelay(delay);
|
||||||
|
let mut lock = BOARD_ACCESS.lock().unwrap();
|
||||||
|
lock.general_fault(false);
|
||||||
|
|
||||||
|
// Clear all LEDs
|
||||||
|
for i in 0..8 {
|
||||||
|
lock.fault(i, false);
|
||||||
|
}
|
||||||
|
drop(lock);
|
||||||
|
vTaskDelay(delay);
|
||||||
|
|
||||||
if wait_type == WaitType::MqttConfig {
|
if wait_type == WaitType::MqttConfig {
|
||||||
if !STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed) {
|
if !STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
|
reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
in_time_range, plant_hal,
|
in_time_range, plant_hal,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 5500.; // 60kHz (500Hz margin)
|
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 6500.; // 60kHz (500Hz margin)
|
||||||
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really really dry, think like cactus levels
|
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really really dry, think like cactus levels
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize)]
|
#[derive(Debug, PartialEq, Serialize)]
|
||||||
@ -87,23 +87,30 @@ pub struct PlantState {
|
|||||||
pub pump: PumpState,
|
pub pump: PumpState,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_range_moisture(s: f32) -> Result<f32, MoistureSensorError> {
|
fn map_range_moisture(
|
||||||
if s < MOIST_SENSOR_MIN_FREQUENCY {
|
s: f32,
|
||||||
|
min_frequency: Option<f32>,
|
||||||
|
max_frequency: Option<f32>,
|
||||||
|
) -> Result<f32, MoistureSensorError> {
|
||||||
|
// Use overrides if provided, otherwise fallback to defaults
|
||||||
|
let min_freq = min_frequency.unwrap_or(MOIST_SENSOR_MIN_FREQUENCY);
|
||||||
|
let max_freq = max_frequency.unwrap_or(MOIST_SENSOR_MAX_FREQUENCY);
|
||||||
|
|
||||||
|
if s < min_freq {
|
||||||
return Err(MoistureSensorError::OpenLoop {
|
return Err(MoistureSensorError::OpenLoop {
|
||||||
hz: s,
|
hz: s,
|
||||||
min: MOIST_SENSOR_MIN_FREQUENCY,
|
min: min_freq,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if s > MOIST_SENSOR_MAX_FREQUENCY {
|
if s > max_freq {
|
||||||
return Err(MoistureSensorError::ShortCircuit {
|
return Err(MoistureSensorError::ShortCircuit {
|
||||||
hz: s,
|
hz: s,
|
||||||
max: MOIST_SENSOR_MAX_FREQUENCY,
|
max: max_freq,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let moisture_percent = (s - MOIST_SENSOR_MIN_FREQUENCY) * 100.0
|
let moisture_percent = (s - min_freq) * 100.0 / (max_freq - min_freq);
|
||||||
/ (MOIST_SENSOR_MAX_FREQUENCY - MOIST_SENSOR_MIN_FREQUENCY);
|
|
||||||
|
|
||||||
return Ok(moisture_percent);
|
Ok(moisture_percent)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlantState {
|
impl PlantState {
|
||||||
@ -114,7 +121,11 @@ impl PlantState {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let sensor_a = if config.sensor_a {
|
let sensor_a = if config.sensor_a {
|
||||||
match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) {
|
match board.measure_moisture_hz(plant_id, plant_hal::Sensor::A) {
|
||||||
Ok(raw) => match map_range_moisture(raw) {
|
Ok(raw) => match map_range_moisture(
|
||||||
|
raw,
|
||||||
|
config.moisture_sensor_min_frequency,
|
||||||
|
config.moisture_sensor_max_frequency,
|
||||||
|
) {
|
||||||
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
|
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
|
||||||
raw_hz: raw,
|
raw_hz: raw,
|
||||||
moisture_percent,
|
moisture_percent,
|
||||||
@ -128,9 +139,14 @@ impl PlantState {
|
|||||||
} else {
|
} else {
|
||||||
MoistureSensorState::Disabled
|
MoistureSensorState::Disabled
|
||||||
};
|
};
|
||||||
|
|
||||||
let sensor_b = if config.sensor_b {
|
let sensor_b = if config.sensor_b {
|
||||||
match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) {
|
match board.measure_moisture_hz(plant_id, plant_hal::Sensor::B) {
|
||||||
Ok(raw) => match map_range_moisture(raw) {
|
Ok(raw) => match map_range_moisture(
|
||||||
|
raw,
|
||||||
|
config.moisture_sensor_min_frequency,
|
||||||
|
config.moisture_sensor_max_frequency,
|
||||||
|
) {
|
||||||
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
|
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
|
||||||
raw_hz: raw,
|
raw_hz: raw,
|
||||||
moisture_percent,
|
moisture_percent,
|
||||||
@ -144,6 +160,7 @@ impl PlantState {
|
|||||||
} else {
|
} else {
|
||||||
MoistureSensorState::Disabled
|
MoistureSensorState::Disabled
|
||||||
};
|
};
|
||||||
|
|
||||||
let previous_pump = board.last_pump_time(plant_id);
|
let previous_pump = board.last_pump_time(plant_id);
|
||||||
let consecutive_pump_count = board.consecutive_pump_count(plant_id);
|
let consecutive_pump_count = board.consecutive_pump_count(plant_id);
|
||||||
let state = Self {
|
let state = Self {
|
||||||
@ -210,7 +227,11 @@ impl PlantState {
|
|||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
if moisture_percent < plant_conf.target_moisture {
|
if moisture_percent < plant_conf.target_moisture {
|
||||||
true
|
in_time_range(
|
||||||
|
current_time,
|
||||||
|
plant_conf.pump_hour_start,
|
||||||
|
plant_conf.pump_hour_end,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,39 @@ fn write_time(
|
|||||||
anyhow::Ok(None)
|
anyhow::Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_time(
|
||||||
|
_request: &mut Request<&mut EspHttpConnection>,
|
||||||
|
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||||
|
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||||
|
let native = board
|
||||||
|
.time()
|
||||||
|
.and_then(|t| Ok(t.to_rfc3339()))
|
||||||
|
.unwrap_or("error".to_string());
|
||||||
|
let rtc = board
|
||||||
|
.get_rtc_time()
|
||||||
|
.and_then(|t| Ok(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<Option<std::string::String>, 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(
|
fn get_live_moisture(
|
||||||
_request: &mut Request<&mut EspHttpConnection>,
|
_request: &mut Request<&mut EspHttpConnection>,
|
||||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||||
@ -106,27 +139,7 @@ fn get_live_moisture(
|
|||||||
anyhow::Ok(Some(json))
|
anyhow::Ok(Some(json))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_data(
|
|
||||||
_request: &mut Request<&mut EspHttpConnection>,
|
|
||||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
|
||||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
|
||||||
let native = board
|
|
||||||
.time()
|
|
||||||
.and_then(|t| Ok(t.to_rfc3339()))
|
|
||||||
.unwrap_or("error".to_string());
|
|
||||||
let rtc = board
|
|
||||||
.get_rtc_time()
|
|
||||||
.and_then(|t| Ok(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_config(
|
fn get_config(
|
||||||
_request: &mut Request<&mut EspHttpConnection>,
|
_request: &mut Request<&mut EspHttpConnection>,
|
||||||
@ -362,6 +375,8 @@ fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
|||||||
return anyhow::Ok(());
|
return anyhow::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
|
fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
|
||||||
println!("{uri} get {param_name}");
|
println!("{uri} get {param_name}");
|
||||||
let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap();
|
let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap();
|
||||||
@ -403,7 +418,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
server
|
server
|
||||||
.fn_handler("/time", Method::Get, |request| {
|
.fn_handler("/time", Method::Get, |request| {
|
||||||
handle_error_to500(request, get_data)
|
handle_error_to500(request, get_time)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
server
|
server
|
||||||
@ -658,8 +673,14 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
server
|
server
|
||||||
|
.fn_handler("/timezones", Method::Get, move |request| {
|
||||||
|
handle_error_to500(request, get_timezones)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn cors_response(
|
fn cors_response(
|
||||||
request: Request<&mut EspHttpConnection>,
|
request: Request<&mut EspHttpConnection>,
|
||||||
status: u16,
|
status: u16,
|
||||||
|
@ -69,6 +69,7 @@ interface PlantControllerConfig {
|
|||||||
tank: TankConfig,
|
tank: TankConfig,
|
||||||
night_lamp: NightLampConfig,
|
night_lamp: NightLampConfig,
|
||||||
plants: PlantConfig[]
|
plants: PlantConfig[]
|
||||||
|
timezone?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlantConfig {
|
interface PlantConfig {
|
||||||
@ -80,6 +81,9 @@ interface PlantConfig {
|
|||||||
pump_hour_end: number,
|
pump_hour_end: number,
|
||||||
sensor_b: boolean,
|
sensor_b: boolean,
|
||||||
max_consecutive_pump_count: number,
|
max_consecutive_pump_count: number,
|
||||||
|
moisture_sensor_min_frequency?: number;
|
||||||
|
moisture_sensor_max_frequency?: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +65,17 @@ export class Controller {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populateTimezones(): Promise<void> {
|
||||||
|
return fetch('/timezones')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => json as string[])
|
||||||
|
.then(timezones => {
|
||||||
|
controller.timeView.timezones(timezones)
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching timezones:', error));
|
||||||
|
}
|
||||||
|
|
||||||
updateFileList() : Promise<void> {
|
updateFileList() : Promise<void> {
|
||||||
return fetch(PUBLIC_URL + "/files")
|
return fetch(PUBLIC_URL + "/files")
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@ -308,7 +319,8 @@ export class Controller {
|
|||||||
network: controller.networkView.getConfig(),
|
network: controller.networkView.getConfig(),
|
||||||
tank: controller.tankView.getConfig(),
|
tank: controller.tankView.getConfig(),
|
||||||
night_lamp: controller.nightLampView.getConfig(),
|
night_lamp: controller.nightLampView.getConfig(),
|
||||||
plants: controller.plantViews.getConfig()
|
plants: controller.plantViews.getConfig(),
|
||||||
|
timezone: controller.timeView.getTimeZone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,6 +362,7 @@ export class Controller {
|
|||||||
this.networkView.setConfig(current.network);
|
this.networkView.setConfig(current.network);
|
||||||
this.nightLampView.setConfig(current.night_lamp);
|
this.nightLampView.setConfig(current.night_lamp);
|
||||||
this.plantViews.setConfig(current.plants);
|
this.plantViews.setConfig(current.plants);
|
||||||
|
this.timeView.setTimeZone(current.timezone);
|
||||||
}
|
}
|
||||||
|
|
||||||
measure_moisture() {
|
measure_moisture() {
|
||||||
@ -459,8 +472,11 @@ export class Controller {
|
|||||||
}
|
}
|
||||||
const controller = new Controller();
|
const controller = new Controller();
|
||||||
controller.progressview.removeProgress("rebooting");
|
controller.progressview.removeProgress("rebooting");
|
||||||
controller.progressview.addProgress("initial", 0, "read rtc");
|
|
||||||
controller.updateRTCData().then(_ => {
|
controller.progressview.addProgress("initial", 0, "read timezones");
|
||||||
|
controller.populateTimezones().then(_ => {
|
||||||
|
controller.progressview.addProgress("initial", 10, "read rtc");
|
||||||
|
controller.updateRTCData().then(_ => {
|
||||||
controller.progressview.addProgress("initial", 20, "read battery");
|
controller.progressview.addProgress("initial", 20, "read battery");
|
||||||
controller.updateBatteryData().then(_ => {
|
controller.updateBatteryData().then(_ => {
|
||||||
controller.progressview.addProgress("initial", 40, "read config");
|
controller.progressview.addProgress("initial", 40, "read config");
|
||||||
@ -480,9 +496,10 @@ controller.updateRTCData().then(_ => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
;
|
});
|
||||||
|
|
||||||
|
|
||||||
//controller.measure_moisture();
|
//controller.measure_moisture();
|
||||||
|
|
||||||
|
@ -59,9 +59,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="plantkey">Warn Pump Count:</div>
|
<div class="plantkey">Warn Pump Count:</div>
|
||||||
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" , max="50" ,
|
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" max="50" ,
|
||||||
placeholder="10">
|
placeholder="10">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="plantkey">Min Frequency Override</div>
|
||||||
|
<input class="plantvalue" id="plant_${plantId}_min_frequency" type="number" min="1000" max="25000">
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="plantkey">Max Frequency Override</div>
|
||||||
|
<input class="plantvalue" id="plant_${plantId}_max_frequency" type="number" min="1000" max="25000" >
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="plantkey">Sensor B installed:</div>
|
<div class="plantkey">Sensor B installed:</div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const PLANT_COUNT = 8;
|
const PLANT_COUNT = 8;
|
||||||
|
|
||||||
|
|
||||||
@ -43,6 +42,8 @@ export class PlantViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PlantView {
|
export class PlantView {
|
||||||
|
private readonly moistureSensorMinFrequency: HTMLInputElement;
|
||||||
|
private readonly moistureSensorMaxFrequency: HTMLInputElement;
|
||||||
private readonly plantId: number;
|
private readonly plantId: number;
|
||||||
private readonly plantDiv: HTMLDivElement;
|
private readonly plantDiv: HTMLDivElement;
|
||||||
private readonly header: HTMLElement;
|
private readonly header: HTMLElement;
|
||||||
@ -136,6 +137,19 @@ export class PlantView {
|
|||||||
this.maxConsecutivePumpCount.onchange = function(){
|
this.maxConsecutivePumpCount.onchange = function(){
|
||||||
controller.configChanged()
|
controller.configChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.moistureSensorMinFrequency = document.getElementById("plant_"+plantId+"_min_frequency") as HTMLInputElement;
|
||||||
|
this.moistureSensorMinFrequency.onchange = function(){
|
||||||
|
controller.configChanged()
|
||||||
|
}
|
||||||
|
this.moistureSensorMinFrequency.onchange = () => {
|
||||||
|
controller.configChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.moistureSensorMaxFrequency = document.getElementById("plant_"+plantId+"_max_frequency") as HTMLInputElement;
|
||||||
|
this.moistureSensorMaxFrequency.onchange = () => {
|
||||||
|
controller.configChanged();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
update(a: number, b: number) {
|
update(a: number, b: number) {
|
||||||
@ -159,12 +173,18 @@ export class PlantView {
|
|||||||
this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString();
|
this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString();
|
||||||
this.pumpHourStart.value = plantConfig.pump_hour_start.toString();
|
this.pumpHourStart.value = plantConfig.pump_hour_start.toString();
|
||||||
this.pumpHourEnd.value = plantConfig.pump_hour_end.toString();
|
this.pumpHourEnd.value = plantConfig.pump_hour_end.toString();
|
||||||
this.sensorBInstalled.checked = plantConfig.sensor_b
|
this.sensorBInstalled.checked = plantConfig.sensor_b;
|
||||||
this.maxConsecutivePumpCount.value = plantConfig.max_consecutive_pump_count.toString();
|
this.maxConsecutivePumpCount.value = plantConfig.max_consecutive_pump_count.toString();
|
||||||
|
|
||||||
|
// Set new fields
|
||||||
|
this.moistureSensorMinFrequency.value =
|
||||||
|
plantConfig.moisture_sensor_min_frequency?.toString() || "";
|
||||||
|
this.moistureSensorMaxFrequency.value =
|
||||||
|
plantConfig.moisture_sensor_max_frequency?.toString() || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig() :PlantConfig {
|
getConfig(): PlantConfig {
|
||||||
const rv:PlantConfig = {
|
const rv: PlantConfig = {
|
||||||
mode: this.mode.value,
|
mode: this.mode.value,
|
||||||
target_moisture: this.targetMoisture.valueAsNumber,
|
target_moisture: this.targetMoisture.valueAsNumber,
|
||||||
pump_time_s: this.pumpTimeS.valueAsNumber,
|
pump_time_s: this.pumpTimeS.valueAsNumber,
|
||||||
@ -172,9 +192,11 @@ export class PlantView {
|
|||||||
pump_hour_start: +this.pumpHourStart.value,
|
pump_hour_start: +this.pumpHourStart.value,
|
||||||
pump_hour_end: +this.pumpHourEnd.value,
|
pump_hour_end: +this.pumpHourEnd.value,
|
||||||
sensor_b: this.sensorBInstalled.checked,
|
sensor_b: this.sensorBInstalled.checked,
|
||||||
max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber
|
max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber,
|
||||||
}
|
moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || undefined,
|
||||||
return rv
|
moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || undefined,
|
||||||
|
};
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMoistureA(a: number) {
|
setMoistureA(a: number) {
|
||||||
|
@ -18,4 +18,11 @@
|
|||||||
<div id="timeview_browser_time" style="text-wrap: nowrap; flex-grow: 1;">Local time</div>
|
<div id="timeview_browser_time" style="text-wrap: nowrap; flex-grow: 1;">Local time</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex">
|
||||||
|
<span style="min-width: 50px;">Timezone:</span>
|
||||||
|
<select id="timezone_select" style="text-wrap: nowrap; flex-grow: 1;">
|
||||||
|
<option value="" disabled selected>Select Timezone</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button id="timeview_time_upload">Store Browser time into esp and rtc</button>
|
<button id="timeview_time_upload">Store Browser time into esp and rtc</button>
|
||||||
|
@ -8,9 +8,14 @@ export class TimeView {
|
|||||||
auto_refresh: HTMLInputElement;
|
auto_refresh: HTMLInputElement;
|
||||||
controller: Controller;
|
controller: Controller;
|
||||||
timer: NodeJS.Timeout | undefined;
|
timer: NodeJS.Timeout | undefined;
|
||||||
|
timezoneSelect: HTMLSelectElement;
|
||||||
|
|
||||||
constructor(controller:Controller) {
|
constructor(controller:Controller) {
|
||||||
(document.getElementById("timeview") as HTMLElement).innerHTML = require("./timeview.html")
|
(document.getElementById("timeview") as HTMLElement).innerHTML = require("./timeview.html")
|
||||||
|
this.timezoneSelect = document.getElementById('timezone_select') as HTMLSelectElement;
|
||||||
|
this.timezoneSelect.onchange = function(){
|
||||||
|
controller.configChanged()
|
||||||
|
}
|
||||||
|
|
||||||
this.auto_refresh = document.getElementById("timeview_auto_refresh") as HTMLInputElement;
|
this.auto_refresh = document.getElementById("timeview_auto_refresh") as HTMLInputElement;
|
||||||
this.esp_time = document.getElementById("timeview_esp_time") as HTMLDivElement;
|
this.esp_time = document.getElementById("timeview_esp_time") as HTMLDivElement;
|
||||||
@ -44,4 +49,26 @@ export class TimeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timezones(timezones: string[]) {
|
||||||
|
timezones.forEach(tz => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = tz;
|
||||||
|
option.textContent = tz;
|
||||||
|
this.timezoneSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTimeZone() {
|
||||||
|
return this.timezoneSelect.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeZone(timezone: string | undefined) {
|
||||||
|
if (timezone != undefined) {
|
||||||
|
this.timezoneSelect.value = timezone;
|
||||||
|
} else {
|
||||||
|
this.timezoneSelect.value = "UTC";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user