add ability to override frequency per plant and adjust timezone, fix missing workhour for plants

This commit is contained in:
Empire Phoenix 2025-05-06 22:33:33 +02:00
parent f8274ea7a8
commit 5fe1dc8f40
10 changed files with 257 additions and 94 deletions

View File

@ -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
} }
} }
} }

View File

@ -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
if wait_type == WaitType::MissingConfig {
led_count %= 8;
led_count += 1;
};
unsafe { unsafe {
BOARD_ACCESS.lock().unwrap().update_charge_indicator(); let mut lock = BOARD_ACCESS.lock().unwrap();
//do not trigger watchdog lock.update_charge_indicator();
for i in 0..8 { match wait_type {
BOARD_ACCESS.lock().unwrap().fault(i, i < led_count); 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); vTaskDelay(delay);
BOARD_ACCESS.lock().unwrap().general_fault(false); let mut lock = BOARD_ACCESS.lock().unwrap();
//TODO move locking outside of loop and drop afterwards lock.general_fault(false);
// Clear all LEDs
for i in 0..8 { for i in 0..8 {
BOARD_ACCESS.lock().unwrap().fault(i, false); lock.fault(i, false);
} }
drop(lock);
vTaskDelay(delay); 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);

View File

@ -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
} }
@ -293,4 +314,4 @@ pub struct PlantInfo<'a> {
last_pump: Option<DateTime<Tz>>, last_pump: Option<DateTime<Tz>>,
/// next time when pump should activate /// next time when pump should activate
next_pump: Option<DateTime<Tz>>, next_pump: Option<DateTime<Tz>>,
} }

View File

@ -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,
@ -724,4 +745,4 @@ fn read_up_to_bytes_from_request(
let allvec = data_store.concat(); let allvec = data_store.concat();
println!("Raw data {}", from_utf8(&allvec)?); println!("Raw data {}", from_utf8(&allvec)?);
Ok(allvec) Ok(allvec)
} }

View File

@ -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;
} }

View File

@ -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,30 +472,34 @@ 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.progressview.addProgress("initial", 20, "read battery"); controller.populateTimezones().then(_ => {
controller.updateBatteryData().then(_ => { controller.progressview.addProgress("initial", 10, "read rtc");
controller.progressview.addProgress("initial", 40, "read config"); controller.updateRTCData().then(_ => {
controller.downloadConfig().then(_ => { controller.progressview.addProgress("initial", 20, "read battery");
controller.progressview.addProgress("initial", 50, "read version"); controller.updateBatteryData().then(_ => {
controller.version().then(_ => { controller.progressview.addProgress("initial", 40, "read config");
controller.progressview.addProgress("initial", 70, "read filelist"); controller.downloadConfig().then(_ => {
controller.updateFileList().then(_ => { controller.progressview.addProgress("initial", 50, "read version");
controller.progressview.addProgress("initial", 90, "read backupinfo"); controller.version().then(_ => {
controller.getBackupInfo().then(_ => { controller.progressview.addProgress("initial", 70, "read filelist");
controller.loadLogLocaleConfig().then(_ => { controller.updateFileList().then(_ => {
controller.loadTankInfo().then(_ => { controller.progressview.addProgress("initial", 90, "read backupinfo");
controller.progressview.removeProgress("initial") controller.getBackupInfo().then(_ => {
controller.loadLogLocaleConfig().then(_ => {
controller.loadTankInfo().then(_ => {
controller.progressview.removeProgress("initial")
})
}) })
}) })
}) })
}) })
}) });
}); });
}) });
}) });
;
//controller.measure_moisture(); //controller.measure_moisture();

View File

@ -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>

View File

@ -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,23 +173,31 @@ 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,
pump_cooldown_min: this.pumpCooldown.valueAsNumber, pump_cooldown_min: this.pumpCooldown.valueAsNumber,
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) {
this.moistureA.innerText = String(a); this.moistureA.innerText = String(a);

View File

@ -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>

View File

@ -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";
}
}
}