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 night_lamp: NightLampConfig,
|
||||
pub plants: [PlantConfig; PLANT_COUNT],
|
||||
pub timezone: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
@ -93,7 +94,10 @@ pub struct PlantConfig {
|
||||
pub sensor_a: bool,
|
||||
pub sensor_b: bool,
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -106,6 +110,8 @@ impl Default for PlantConfig {
|
||||
sensor_a: true,
|
||||
sensor_b: false,
|
||||
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 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_sys::{
|
||||
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 tank::*;
|
||||
|
||||
const TIME_ZONE: Tz = Berlin;
|
||||
|
||||
pub static BOARD_ACCESS: Lazy<Mutex<PlantCtrlBoard>> = Lazy::new(|| PlantHal::create().unwrap());
|
||||
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!(
|
||||
"Running logic at utc {} and {} {}",
|
||||
cur,
|
||||
TIME_ZONE.name(),
|
||||
timezone.name(),
|
||||
timezone_time
|
||||
);
|
||||
|
||||
@ -579,28 +586,51 @@ fn publish_battery_state(
|
||||
|
||||
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
let delay = wait_type.blink_pattern();
|
||||
|
||||
let mut led_count = 8;
|
||||
let mut pattern_step = 0;
|
||||
|
||||
loop {
|
||||
// TODO implement actually different blink patterns instead of modulating blink duration
|
||||
if wait_type == WaitType::MissingConfig {
|
||||
led_count %= 8;
|
||||
led_count += 1;
|
||||
};
|
||||
unsafe {
|
||||
BOARD_ACCESS.lock().unwrap().update_charge_indicator();
|
||||
//do not trigger watchdog
|
||||
for i in 0..8 {
|
||||
BOARD_ACCESS.lock().unwrap().fault(i, i < led_count);
|
||||
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 += 1;
|
||||
for i in 0..8 {
|
||||
lock.fault(i, i < led_count);
|
||||
}
|
||||
}
|
||||
WaitType::ConfigButton => {
|
||||
// Alternating pattern: 1010 1010 -> 0101 0101
|
||||
pattern_step = (pattern_step + 1) % 2;
|
||||
for i in 0..8 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
BOARD_ACCESS.lock().unwrap().general_fault(true);
|
||||
|
||||
lock.general_fault(true);
|
||||
drop(lock);
|
||||
vTaskDelay(delay);
|
||||
BOARD_ACCESS.lock().unwrap().general_fault(false);
|
||||
//TODO move locking outside of loop and drop afterwards
|
||||
let mut lock = BOARD_ACCESS.lock().unwrap();
|
||||
lock.general_fault(false);
|
||||
|
||||
// Clear all LEDs
|
||||
for i in 0..8 {
|
||||
BOARD_ACCESS.lock().unwrap().fault(i, false);
|
||||
lock.fault(i, false);
|
||||
}
|
||||
drop(lock);
|
||||
vTaskDelay(delay);
|
||||
|
||||
if wait_type == WaitType::MqttConfig {
|
||||
if !STAY_ALIVE.load(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,
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
@ -87,23 +87,30 @@ pub struct PlantState {
|
||||
pub pump: PumpState,
|
||||
}
|
||||
|
||||
fn map_range_moisture(s: f32) -> Result<f32, MoistureSensorError> {
|
||||
if s < MOIST_SENSOR_MIN_FREQUENCY {
|
||||
fn map_range_moisture(
|
||||
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 {
|
||||
hz: s,
|
||||
min: MOIST_SENSOR_MIN_FREQUENCY,
|
||||
min: min_freq,
|
||||
});
|
||||
}
|
||||
if s > MOIST_SENSOR_MAX_FREQUENCY {
|
||||
if s > max_freq {
|
||||
return Err(MoistureSensorError::ShortCircuit {
|
||||
hz: s,
|
||||
max: MOIST_SENSOR_MAX_FREQUENCY,
|
||||
max: max_freq,
|
||||
});
|
||||
}
|
||||
let moisture_percent = (s - MOIST_SENSOR_MIN_FREQUENCY) * 100.0
|
||||
/ (MOIST_SENSOR_MAX_FREQUENCY - MOIST_SENSOR_MIN_FREQUENCY);
|
||||
let moisture_percent = (s - min_freq) * 100.0 / (max_freq - min_freq);
|
||||
|
||||
return Ok(moisture_percent);
|
||||
Ok(moisture_percent)
|
||||
}
|
||||
|
||||
impl PlantState {
|
||||
@ -114,7 +121,11 @@ impl PlantState {
|
||||
) -> Self {
|
||||
let sensor_a = if config.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 {
|
||||
raw_hz: raw,
|
||||
moisture_percent,
|
||||
@ -128,9 +139,14 @@ impl PlantState {
|
||||
} else {
|
||||
MoistureSensorState::Disabled
|
||||
};
|
||||
|
||||
let sensor_b = if config.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 {
|
||||
raw_hz: raw,
|
||||
moisture_percent,
|
||||
@ -144,6 +160,7 @@ impl PlantState {
|
||||
} else {
|
||||
MoistureSensorState::Disabled
|
||||
};
|
||||
|
||||
let previous_pump = board.last_pump_time(plant_id);
|
||||
let consecutive_pump_count = board.consecutive_pump_count(plant_id);
|
||||
let state = Self {
|
||||
@ -210,7 +227,11 @@ impl PlantState {
|
||||
false
|
||||
} else {
|
||||
if moisture_percent < plant_conf.target_moisture {
|
||||
true
|
||||
in_time_range(
|
||||
current_time,
|
||||
plant_conf.pump_hour_start,
|
||||
plant_conf.pump_hour_end,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@ -293,4 +314,4 @@ pub struct PlantInfo<'a> {
|
||||
last_pump: Option<DateTime<Tz>>,
|
||||
/// next time when pump should activate
|
||||
next_pump: Option<DateTime<Tz>>,
|
||||
}
|
||||
}
|
@ -77,6 +77,39 @@ fn write_time(
|
||||
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(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
@ -106,27 +139,7 @@ fn get_live_moisture(
|
||||
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(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
@ -362,6 +375,8 @@ fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
|
||||
println!("{uri} get {param_name}");
|
||||
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();
|
||||
server
|
||||
.fn_handler("/time", Method::Get, |request| {
|
||||
handle_error_to500(request, get_data)
|
||||
handle_error_to500(request, get_time)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
@ -658,8 +673,14 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/timezones", Method::Get, move |request| {
|
||||
handle_error_to500(request, get_timezones)
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
}
|
||||
|
||||
|
||||
fn cors_response(
|
||||
request: Request<&mut EspHttpConnection>,
|
||||
status: u16,
|
||||
@ -724,4 +745,4 @@ fn read_up_to_bytes_from_request(
|
||||
let allvec = data_store.concat();
|
||||
println!("Raw data {}", from_utf8(&allvec)?);
|
||||
Ok(allvec)
|
||||
}
|
||||
}
|
@ -69,6 +69,7 @@ interface PlantControllerConfig {
|
||||
tank: TankConfig,
|
||||
night_lamp: NightLampConfig,
|
||||
plants: PlantConfig[]
|
||||
timezone?: string,
|
||||
}
|
||||
|
||||
interface PlantConfig {
|
||||
@ -80,6 +81,9 @@ interface PlantConfig {
|
||||
pump_hour_end: number,
|
||||
sensor_b: boolean,
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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> {
|
||||
return fetch(PUBLIC_URL + "/files")
|
||||
.then(response => response.json())
|
||||
@ -308,7 +319,8 @@ export class Controller {
|
||||
network: controller.networkView.getConfig(),
|
||||
tank: controller.tankView.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.nightLampView.setConfig(current.night_lamp);
|
||||
this.plantViews.setConfig(current.plants);
|
||||
this.timeView.setTimeZone(current.timezone);
|
||||
}
|
||||
|
||||
measure_moisture() {
|
||||
@ -459,30 +472,34 @@ export class Controller {
|
||||
}
|
||||
const controller = new Controller();
|
||||
controller.progressview.removeProgress("rebooting");
|
||||
controller.progressview.addProgress("initial", 0, "read rtc");
|
||||
controller.updateRTCData().then(_ => {
|
||||
controller.progressview.addProgress("initial", 20, "read battery");
|
||||
controller.updateBatteryData().then(_ => {
|
||||
controller.progressview.addProgress("initial", 40, "read config");
|
||||
controller.downloadConfig().then(_ => {
|
||||
controller.progressview.addProgress("initial", 50, "read version");
|
||||
controller.version().then(_ => {
|
||||
controller.progressview.addProgress("initial", 70, "read filelist");
|
||||
controller.updateFileList().then(_ => {
|
||||
controller.progressview.addProgress("initial", 90, "read backupinfo");
|
||||
controller.getBackupInfo().then(_ => {
|
||||
controller.loadLogLocaleConfig().then(_ => {
|
||||
controller.loadTankInfo().then(_ => {
|
||||
controller.progressview.removeProgress("initial")
|
||||
|
||||
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.updateBatteryData().then(_ => {
|
||||
controller.progressview.addProgress("initial", 40, "read config");
|
||||
controller.downloadConfig().then(_ => {
|
||||
controller.progressview.addProgress("initial", 50, "read version");
|
||||
controller.version().then(_ => {
|
||||
controller.progressview.addProgress("initial", 70, "read filelist");
|
||||
controller.updateFileList().then(_ => {
|
||||
controller.progressview.addProgress("initial", 90, "read backupinfo");
|
||||
controller.getBackupInfo().then(_ => {
|
||||
controller.loadLogLocaleConfig().then(_ => {
|
||||
controller.loadTankInfo().then(_ => {
|
||||
controller.progressview.removeProgress("initial")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
})
|
||||
;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//controller.measure_moisture();
|
||||
|
||||
|
@ -59,9 +59,17 @@
|
||||
</div>
|
||||
<div class="flexcontainer">
|
||||
<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">
|
||||
</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="plantkey">Sensor B installed:</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
const PLANT_COUNT = 8;
|
||||
|
||||
|
||||
@ -43,6 +42,8 @@ export class PlantViews {
|
||||
}
|
||||
|
||||
export class PlantView {
|
||||
private readonly moistureSensorMinFrequency: HTMLInputElement;
|
||||
private readonly moistureSensorMaxFrequency: HTMLInputElement;
|
||||
private readonly plantId: number;
|
||||
private readonly plantDiv: HTMLDivElement;
|
||||
private readonly header: HTMLElement;
|
||||
@ -136,6 +137,19 @@ export class PlantView {
|
||||
this.maxConsecutivePumpCount.onchange = function(){
|
||||
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) {
|
||||
@ -159,23 +173,31 @@ export class PlantView {
|
||||
this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString();
|
||||
this.pumpHourStart.value = plantConfig.pump_hour_start.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();
|
||||
|
||||
// Set new fields
|
||||
this.moistureSensorMinFrequency.value =
|
||||
plantConfig.moisture_sensor_min_frequency?.toString() || "";
|
||||
this.moistureSensorMaxFrequency.value =
|
||||
plantConfig.moisture_sensor_max_frequency?.toString() || "";
|
||||
}
|
||||
|
||||
getConfig() :PlantConfig {
|
||||
const rv:PlantConfig = {
|
||||
mode: this.mode.value,
|
||||
target_moisture: this.targetMoisture.valueAsNumber,
|
||||
pump_time_s: this.pumpTimeS.valueAsNumber,
|
||||
pump_cooldown_min: this.pumpCooldown.valueAsNumber,
|
||||
pump_hour_start: +this.pumpHourStart.value,
|
||||
pump_hour_end: +this.pumpHourEnd.value,
|
||||
sensor_b: this.sensorBInstalled.checked,
|
||||
max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber
|
||||
}
|
||||
return rv
|
||||
}
|
||||
getConfig(): PlantConfig {
|
||||
const rv: PlantConfig = {
|
||||
mode: this.mode.value,
|
||||
target_moisture: this.targetMoisture.valueAsNumber,
|
||||
pump_time_s: this.pumpTimeS.valueAsNumber,
|
||||
pump_cooldown_min: this.pumpCooldown.valueAsNumber,
|
||||
pump_hour_start: +this.pumpHourStart.value,
|
||||
pump_hour_end: +this.pumpHourEnd.value,
|
||||
sensor_b: this.sensorBInstalled.checked,
|
||||
max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber,
|
||||
moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || undefined,
|
||||
moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || undefined,
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
|
||||
setMoistureA(a: number) {
|
||||
this.moistureA.innerText = String(a);
|
||||
|
@ -18,4 +18,11 @@
|
||||
<div id="timeview_browser_time" style="text-wrap: nowrap; flex-grow: 1;">Local time</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>
|
||||
|
@ -8,9 +8,14 @@ export class TimeView {
|
||||
auto_refresh: HTMLInputElement;
|
||||
controller: Controller;
|
||||
timer: NodeJS.Timeout | undefined;
|
||||
timezoneSelect: HTMLSelectElement;
|
||||
|
||||
constructor(controller:Controller) {
|
||||
(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.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