clippy happier
This commit is contained in:
parent
a401d4de7b
commit
26da6b39cc
7
rust/.idea/dictionaries/project.xml
generated
Normal file
7
rust/.idea/dictionaries/project.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="project">
|
||||||
|
<words>
|
||||||
|
<w>sntp</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
@ -65,7 +65,7 @@ struct LightState {
|
|||||||
active: bool,
|
active: bool,
|
||||||
/// led should not be on at this time of day
|
/// led should not be on at this time of day
|
||||||
out_of_work_hour: bool,
|
out_of_work_hour: bool,
|
||||||
/// battery is low so do not use led
|
/// the battery is low so do not use led
|
||||||
battery_low: bool,
|
battery_low: bool,
|
||||||
/// the sun is up
|
/// the sun is up
|
||||||
is_day: bool,
|
is_day: bool,
|
||||||
@ -79,8 +79,8 @@ enum SensorError {
|
|||||||
OpenCircuit { hz: f32, min: f32 },
|
OpenCircuit { hz: f32, min: f32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
fn safe_main() -> anyhow::Result<()> {
|
fn safe_main() -> Result<()> {
|
||||||
// It is necessary to call this function once. Otherwise some patches to the runtime
|
// It is necessary to call this function once. Otherwise, some patches to the runtime
|
||||||
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
// 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();
|
esp_idf_svc::sys::link_patches();
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let version = get_version();
|
let version = get_version();
|
||||||
println!(
|
println!(
|
||||||
"Version useing git has {} build on {}",
|
"Version using git has {} build on {}",
|
||||||
version.git_hash, version.build_time
|
version.git_hash, version.build_time
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -129,16 +129,16 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
&format!("unknown {ota_state}")
|
&format!("unknown {ota_state}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
log(log::LogMessage::PartitionState, 0, 0, "", ota_state_string);
|
log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
|
||||||
|
|
||||||
let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap();
|
let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap();
|
||||||
board.general_fault(false);
|
board.general_fault(false);
|
||||||
|
|
||||||
log(log::LogMessage::MountingFilesystem, 0, 0, "", "");
|
log(LogMessage::MountingFilesystem, 0, 0, "", "");
|
||||||
board.mount_file_system()?;
|
board.mount_file_system()?;
|
||||||
let free_space = board.file_system_size()?;
|
let free_space = board.file_system_size()?;
|
||||||
log(
|
log(
|
||||||
log::LogMessage::FilesystemMount,
|
LogMessage::FilesystemMount,
|
||||||
free_space.free_size as u32,
|
free_space.free_size as u32,
|
||||||
free_space.total_size as u32,
|
free_space.total_size as u32,
|
||||||
&free_space.used_size.to_string(),
|
&free_space.used_size.to_string(),
|
||||||
@ -157,17 +157,17 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
//check if we know the time current > 2020 (plausibility check, this code is newer than 2020)
|
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
|
||||||
if cur.year() < 2020 {
|
if cur.year() < 2020 {
|
||||||
to_config = true;
|
to_config = true;
|
||||||
log(log::LogMessage::YearInplausibleForceConfig, 0, 0, "", "");
|
log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("cur is {}", cur);
|
println!("cur is {}", cur);
|
||||||
board.update_charge_indicator();
|
board.update_charge_indicator();
|
||||||
|
|
||||||
if board.get_restart_to_conf() {
|
if board.get_restart_to_conf() {
|
||||||
log(log::LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
|
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
|
||||||
for _i in 0..2 {
|
for _i in 0..2 {
|
||||||
board.general_fault(true);
|
board.general_fault(true);
|
||||||
Delay::new_default().delay_ms(100);
|
Delay::new_default().delay_ms(100);
|
||||||
@ -177,9 +177,9 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
to_config = true;
|
to_config = true;
|
||||||
board.general_fault(true);
|
board.general_fault(true);
|
||||||
board.set_restart_to_conf(false);
|
board.set_restart_to_conf(false);
|
||||||
} else if board.is_mode_override() {
|
} else if board.mode_override_pressed() {
|
||||||
board.general_fault(true);
|
board.general_fault(true);
|
||||||
log(log::LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
|
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
|
||||||
for _i in 0..5 {
|
for _i in 0..5 {
|
||||||
board.general_fault(true);
|
board.general_fault(true);
|
||||||
Delay::new_default().delay_ms(100);
|
Delay::new_default().delay_ms(100);
|
||||||
@ -187,7 +187,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
Delay::new_default().delay_ms(100);
|
Delay::new_default().delay_ms(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if board.is_mode_override() {
|
if board.mode_override_pressed() {
|
||||||
board.general_fault(true);
|
board.general_fault(true);
|
||||||
to_config = true;
|
to_config = true;
|
||||||
} else {
|
} else {
|
||||||
@ -195,27 +195,26 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let config: PlantControllerConfig;
|
let config: PlantControllerConfig = match board.get_config() {
|
||||||
match board.get_config() {
|
|
||||||
Ok(valid) => {
|
Ok(valid) => {
|
||||||
config = valid;
|
valid
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log(
|
log(
|
||||||
log::LogMessage::ConfigModeMissingConfig,
|
LogMessage::ConfigModeMissingConfig,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
&err.to_string(),
|
&err.to_string(),
|
||||||
);
|
);
|
||||||
//config upload will trigger reboot!
|
//config upload will trigger reboot!
|
||||||
let _ = board.wifi_ap(Option::None);
|
let _ = board.wifi_ap(None);
|
||||||
drop(board);
|
drop(board);
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
let _webserver = httpd(reboot_now.clone());
|
let _webserver = httpd(reboot_now.clone());
|
||||||
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
|
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut wifi = false;
|
let mut wifi = false;
|
||||||
let mut mqtt = false;
|
let mut mqtt = false;
|
||||||
@ -273,7 +272,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
Err(err) => println!(
|
Err(err) => println!(
|
||||||
"Could not start config override ap mode due to {}",
|
"Could not start config override ap mode due to {}",
|
||||||
err.to_string()
|
err
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,7 +320,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log(
|
log(
|
||||||
log::LogMessage::StartupInfo,
|
LogMessage::StartupInfo,
|
||||||
wifi as u32,
|
wifi as u32,
|
||||||
sntp as u32,
|
sntp as u32,
|
||||||
&mqtt.to_string(),
|
&mqtt.to_string(),
|
||||||
@ -329,7 +328,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if to_config {
|
if to_config {
|
||||||
//check if client or ap mode and init wifi
|
//check if client or ap mode and init Wi-Fi
|
||||||
println!("executing config mode override");
|
println!("executing config mode override");
|
||||||
//config upload will trigger reboot!
|
//config upload will trigger reboot!
|
||||||
drop(board);
|
drop(board);
|
||||||
@ -337,7 +336,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
let _webserver = httpd(reboot_now.clone());
|
let _webserver = httpd(reboot_now.clone());
|
||||||
wait_infinity(WaitType::ConfigButton, reboot_now.clone());
|
wait_infinity(WaitType::ConfigButton, reboot_now.clone());
|
||||||
} else {
|
} else {
|
||||||
log(log::LogMessage::NormalRun, 0, 0, "", "");
|
log(LogMessage::NormalRun, 0, 0, "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
let dry_run = false;
|
let dry_run = false;
|
||||||
@ -367,7 +366,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
&format!("{}", &err.to_string()),
|
&err.to_string()
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
// disabled cannot trigger this because of wrapping if is_enabled
|
// disabled cannot trigger this because of wrapping if is_enabled
|
||||||
@ -432,15 +431,15 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
let pump_required = plantstate
|
let pump_required = plantstate
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&config.plants)
|
.zip(&config.plants)
|
||||||
.any(|(it, conf)| it.needs_to_be_watered(&conf, &timezone_time))
|
.any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time))
|
||||||
&& !water_frozen;
|
&& !water_frozen;
|
||||||
if pump_required {
|
if pump_required {
|
||||||
log(log::LogMessage::EnableMain, dry_run as u32, 0, "", "");
|
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
|
||||||
if !dry_run {
|
if !dry_run {
|
||||||
board.any_pump(true)?; // what does this do? Does it need to be reset?
|
board.any_pump(true)?; // what does this do? Does it need to be reset?
|
||||||
}
|
}
|
||||||
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&config.plants).enumerate() {
|
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&config.plants).enumerate() {
|
||||||
if state.needs_to_be_watered(&plant_config, &timezone_time) {
|
if state.needs_to_be_watered(plant_config, &timezone_time) {
|
||||||
let pump_count = board.consecutive_pump_count(plant_id) + 1;
|
let pump_count = board.consecutive_pump_count(plant_id) + 1;
|
||||||
board.store_consecutive_pump_count(plant_id, pump_count);
|
board.store_consecutive_pump_count(plant_id, pump_count);
|
||||||
//TODO(judge) where to put this?
|
//TODO(judge) where to put this?
|
||||||
@ -456,7 +455,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
// board.fault(plant, true);
|
// board.fault(plant, true);
|
||||||
//}
|
//}
|
||||||
log(
|
log(
|
||||||
log::LogMessage::PumpPlant,
|
LogMessage::PumpPlant,
|
||||||
(plant_id + 1) as u32,
|
(plant_id + 1) as u32,
|
||||||
plant_config.pump_time_s as u32,
|
plant_config.pump_time_s as u32,
|
||||||
&dry_run.to_string(),
|
&dry_run.to_string(),
|
||||||
@ -470,7 +469,7 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32);
|
Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32);
|
||||||
board.pump(plant_id, false)?;
|
board.pump(plant_id, false)?;
|
||||||
}
|
}
|
||||||
} else if !state.pump_in_timeout(&plant_config, &timezone_time) {
|
} else if !state.pump_in_timeout(plant_config, &timezone_time) {
|
||||||
// plant does not need to be watered and is not in timeout
|
// plant does not need to be watered and is not in timeout
|
||||||
// -> reset consecutive pump count
|
// -> reset consecutive pump count
|
||||||
board.store_consecutive_pump_count(plant_id, 0);
|
board.store_consecutive_pump_count(plant_id, 0);
|
||||||
@ -504,23 +503,21 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
if config.night_lamp.night_lamp_only_when_dark {
|
if config.night_lamp.night_lamp_only_when_dark {
|
||||||
if !light_state.is_day {
|
if !light_state.is_day {
|
||||||
if light_state.battery_low {
|
if light_state.battery_low {
|
||||||
board.light(false).unwrap();
|
board.light(false)?;
|
||||||
} else {
|
} else {
|
||||||
light_state.active = true;
|
light_state.active = true;
|
||||||
board.light(true).unwrap();
|
board.light(true)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if light_state.battery_low {
|
||||||
if light_state.battery_low {
|
board.light(false)?;
|
||||||
board.light(false).unwrap();
|
|
||||||
} else {
|
} else {
|
||||||
light_state.active = true;
|
light_state.active = true;
|
||||||
board.light(true).unwrap();
|
board.light(true)?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
light_state.active = false;
|
light_state.active = false;
|
||||||
board.light(false).unwrap();
|
board.light(false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Lightstate is {:?}", light_state);
|
println!("Lightstate is {:?}", light_state);
|
||||||
@ -547,10 +544,6 @@ fn safe_main() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
let _ = board.mqtt_publish(&config, "/state", "sleep".as_bytes());
|
let _ = board.mqtt_publish(&config, "/state", "sleep".as_bytes());
|
||||||
|
|
||||||
//determine next event
|
|
||||||
//is light out of work trigger soon?
|
|
||||||
//is battery low ??
|
|
||||||
//is deep sleep
|
|
||||||
mark_app_valid();
|
mark_app_valid();
|
||||||
|
|
||||||
let stay_alive_mqtt = STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed);
|
let stay_alive_mqtt = STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
@ -576,7 +569,7 @@ fn publish_battery_state(
|
|||||||
let bat = board.get_battery_state();
|
let bat = board.get_battery_state();
|
||||||
match serde_json::to_string(&bat) {
|
match serde_json::to_string(&bat) {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
let _ = board.mqtt_publish(&config, "/battery", state.as_bytes());
|
let _ = board.mqtt_publish(config, "/battery", state.as_bytes());
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Error publishing battery_state {}", err);
|
println!("Error publishing battery_state {}", err);
|
||||||
@ -631,11 +624,9 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
|||||||
drop(lock);
|
drop(lock);
|
||||||
vTaskDelay(delay);
|
vTaskDelay(delay);
|
||||||
|
|
||||||
if wait_type == WaitType::MqttConfig {
|
if wait_type == WaitType::MqttConfig && !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);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
|
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
//ensure clean http answer
|
//ensure clean http answer
|
||||||
Delay::new_default().delay_ms(500);
|
Delay::new_default().delay_ms(500);
|
||||||
@ -655,7 +646,7 @@ fn main() {
|
|||||||
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(false);
|
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(false);
|
||||||
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
|
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
|
||||||
}
|
}
|
||||||
// if safe_main exists with error, rollback to known good ota version
|
// if safe_main exists with an error, rollback to a known good ota version
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Failed main {}", err);
|
println!("Failed main {}", err);
|
||||||
let _rollback_successful = rollback_and_reboot();
|
let _rollback_successful = rollback_and_reboot();
|
||||||
@ -665,22 +656,22 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn to_string<T: Display>(value: Result<T>) -> String {
|
fn to_string<T: Display>(value: Result<T>) -> String {
|
||||||
return match value {
|
match value {
|
||||||
Ok(v) => v.to_string(),
|
Ok(v) => v.to_string(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
format!("{:?}", err)
|
format!("{:?}", err)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn in_time_range(cur: &DateTime<Tz>, start: u8, end: u8) -> bool {
|
pub fn in_time_range(cur: &DateTime<Tz>, start: u8, end: u8) -> bool {
|
||||||
let curhour = cur.hour() as u8;
|
let curhour = cur.hour() as u8;
|
||||||
//eg 10-14
|
//eg 10-14
|
||||||
if start < end {
|
if start < end {
|
||||||
return curhour > start && curhour < end;
|
curhour > start && curhour < end
|
||||||
} else {
|
} else {
|
||||||
//eg 20-05
|
//eg 20-05
|
||||||
return curhour > start || curhour < end;
|
curhour > start || curhour < end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,11 +686,11 @@ fn get_version() -> VersionInfo {
|
|||||||
} else {
|
} else {
|
||||||
"ota_0 @ "
|
"ota_0 @ "
|
||||||
};
|
};
|
||||||
return VersionInfo {
|
VersionInfo {
|
||||||
git_hash: (branch + "@" + hash),
|
git_hash: branch + "@" + hash,
|
||||||
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
|
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
|
||||||
partition: partition.to_owned() + &address.to_string(),
|
partition: partition.to_owned() + &address.to_string(),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
|
@ -60,9 +60,9 @@ use esp_idf_svc::systime::EspSystemTime;
|
|||||||
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
||||||
use one_wire_bus::OneWire;
|
use one_wire_bus::OneWire;
|
||||||
|
|
||||||
use crate::config::{self, PlantControllerConfig};
|
use crate::config::PlantControllerConfig;
|
||||||
use crate::log::log;
|
use crate::log::log;
|
||||||
use crate::{plant_hal, to_string, STAY_ALIVE};
|
use crate::{to_string, STAY_ALIVE};
|
||||||
|
|
||||||
//Only support for 8 right now!
|
//Only support for 8 right now!
|
||||||
pub const PLANT_COUNT: usize = 8;
|
pub const PLANT_COUNT: usize = 8;
|
||||||
@ -168,7 +168,7 @@ pub struct PlantCtrlBoard<'a> {
|
|||||||
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||||
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||||
wifi_driver: EspWifi<'a>,
|
wifi_driver: EspWifi<'a>,
|
||||||
one_wire_bus: OneWire<PinDriver<'a, Gpio18, esp_idf_hal::gpio::InputOutput>>,
|
one_wire_bus: OneWire<PinDriver<'a, Gpio18, InputOutput>>,
|
||||||
mqtt_client: Option<EspMqttClient<'a>>,
|
mqtt_client: Option<EspMqttClient<'a>>,
|
||||||
battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>,
|
battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>,
|
||||||
rtc:
|
rtc:
|
||||||
@ -229,9 +229,9 @@ impl PlantCtrlBoard<'_> {
|
|||||||
pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
||||||
self.shift_register.decompose()[AWAKE].set_low().unwrap();
|
self.shift_register.decompose()[AWAKE].set_low().unwrap();
|
||||||
unsafe {
|
unsafe {
|
||||||
//if we dont do this here, we might just revert a newly flashed firmeware
|
//if we don't do this here, we might just revert newly flashed firmware
|
||||||
mark_app_valid();
|
mark_app_valid();
|
||||||
//allow early wakup by pressing the boot button
|
//allow early wakeup by pressing the boot button
|
||||||
if duration_in_ms == 0 {
|
if duration_in_ms == 0 {
|
||||||
esp_restart();
|
esp_restart();
|
||||||
} else {
|
} else {
|
||||||
@ -278,7 +278,8 @@ impl PlantCtrlBoard<'_> {
|
|||||||
};
|
};
|
||||||
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
|
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
|
||||||
|
|
||||||
let data_start_address = 1 * self.eeprom.page_size() as u32;
|
//skip page 0, used by the header
|
||||||
|
let data_start_address = self.eeprom.page_size() as u32;
|
||||||
let mut data_buffer = vec![0_u8; header.size];
|
let mut data_buffer = vec![0_u8; header.size];
|
||||||
match self.eeprom.read_data(data_start_address, &mut data_buffer) {
|
match self.eeprom.read_data(data_start_address, &mut data_buffer) {
|
||||||
OkStd(_) => {}
|
OkStd(_) => {}
|
||||||
@ -337,9 +338,9 @@ impl PlantCtrlBoard<'_> {
|
|||||||
OkStd(_) => {}
|
OkStd(_) => {}
|
||||||
Err(err) => bail!("Error writing eeprom {:?}", err),
|
Err(err) => bail!("Error writing eeprom {:?}", err),
|
||||||
};
|
};
|
||||||
current_page = current_page + 1;
|
current_page += 1;
|
||||||
|
|
||||||
let iter = ((current_page / 1) % 8) as usize;
|
let iter = (current_page % 8) as usize;
|
||||||
if iter != lastiter {
|
if iter != lastiter {
|
||||||
for i in 0..PLANT_COUNT {
|
for i in 0..PLANT_COUNT {
|
||||||
self.fault(i, iter == i);
|
self.fault(i, iter == i);
|
||||||
@ -350,11 +351,11 @@ impl PlantCtrlBoard<'_> {
|
|||||||
//update led here?
|
//update led here?
|
||||||
delay.delay_ms(5);
|
delay.delay_ms(5);
|
||||||
}
|
}
|
||||||
return Ok(());
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_battery_state(&mut self) -> BatteryState {
|
pub fn get_battery_state(&mut self) -> BatteryState {
|
||||||
let bat = BatteryState {
|
BatteryState {
|
||||||
voltage_milli_volt: to_string(self.voltage_milli_volt()),
|
voltage_milli_volt: to_string(self.voltage_milli_volt()),
|
||||||
current_milli_ampere: to_string(self.average_current_milli_ampere()),
|
current_milli_ampere: to_string(self.average_current_milli_ampere()),
|
||||||
cycle_count: to_string(self.cycle_count()),
|
cycle_count: to_string(self.cycle_count()),
|
||||||
@ -363,14 +364,13 @@ impl PlantCtrlBoard<'_> {
|
|||||||
state_of_charge: to_string(self.state_charge_percent()),
|
state_of_charge: to_string(self.state_charge_percent()),
|
||||||
state_of_health: to_string(self.state_health_percent()),
|
state_of_health: to_string(self.state_health_percent()),
|
||||||
temperature: to_string(self.bat_temperature()),
|
temperature: to_string(self.bat_temperature()),
|
||||||
};
|
}
|
||||||
return bat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_files(&self) -> FileList {
|
pub fn list_files(&self) -> FileList {
|
||||||
let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();
|
let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();
|
||||||
|
|
||||||
let mut file_system_corrupt = Option::None;
|
let mut file_system_corrupt = None;
|
||||||
|
|
||||||
let mut iter_error = None;
|
let mut iter_error = None;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
@ -386,7 +386,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
filename: file.file_name().into_string().unwrap(),
|
filename: file.file_name().into_string().unwrap(),
|
||||||
size: file
|
size: file
|
||||||
.metadata()
|
.metadata()
|
||||||
.and_then(|it| core::result::Result::Ok(it.len()))
|
.and_then(|it| Result::Ok(it.len()))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
as usize,
|
as usize,
|
||||||
};
|
};
|
||||||
@ -409,13 +409,13 @@ impl PlantCtrlBoard<'_> {
|
|||||||
esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
|
esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FileList {
|
FileList {
|
||||||
total,
|
total,
|
||||||
used,
|
used,
|
||||||
file_system_corrupt,
|
file_system_corrupt,
|
||||||
files: result,
|
files: result,
|
||||||
iter_error,
|
iter_error,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_file(&self, filename: &str) -> Result<()> {
|
pub fn delete_file(&self, filename: &str) -> Result<()> {
|
||||||
@ -430,11 +430,11 @@ impl PlantCtrlBoard<'_> {
|
|||||||
|
|
||||||
pub fn get_file_handle(&self, filename: &str, write: bool) -> Result<File> {
|
pub fn get_file_handle(&self, filename: &str, write: bool) -> Result<File> {
|
||||||
let filepath = Path::new(BASE_PATH).join(Path::new(filename));
|
let filepath = Path::new(BASE_PATH).join(Path::new(filename));
|
||||||
return Ok(if write {
|
Ok(if write {
|
||||||
File::create(filepath)?
|
File::create(filepath)?
|
||||||
} else {
|
} else {
|
||||||
File::open(filepath)?
|
File::open(filepath)?
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_day(&self) -> bool {
|
pub fn is_day(&self) -> bool {
|
||||||
@ -522,17 +522,17 @@ impl PlantCtrlBoard<'_> {
|
|||||||
7 => PUMP8_BIT,
|
7 => PUMP8_BIT,
|
||||||
_ => bail!("Invalid pump {plant}",),
|
_ => bail!("Invalid pump {plant}",),
|
||||||
};
|
};
|
||||||
//currently infailable error, keep for future as result anyway
|
//currently infallible error, keep for future as result anyway
|
||||||
self.shift_register.decompose()[index].set_state(enable.into())?;
|
self.shift_register.decompose()[index].set_state(enable.into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_pump_time(&self, plant: usize) -> Option<chrono::DateTime<Utc>> {
|
pub fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
|
||||||
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
|
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
|
||||||
return Some(DateTime::from_timestamp_millis(ts)?);
|
DateTime::from_timestamp_millis(ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>) {
|
pub fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
|
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
|
||||||
}
|
}
|
||||||
@ -546,7 +546,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
|
|
||||||
pub fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
|
pub fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
return CONSECUTIVE_WATERING_PLANT[plant];
|
CONSECUTIVE_WATERING_PLANT[plant]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,18 +569,18 @@ impl PlantCtrlBoard<'_> {
|
|||||||
|
|
||||||
pub fn low_voltage_in_cycle(&mut self) -> bool {
|
pub fn low_voltage_in_cycle(&mut self) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
return LOW_VOLTAGE_DETECTED;
|
LOW_VOLTAGE_DETECTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn any_pump(&mut self, enable: bool) -> Result<()> {
|
pub fn any_pump(&mut self, enable: bool) -> Result<()> {
|
||||||
{
|
{
|
||||||
self.main_pump.set_state(enable.into()).unwrap();
|
self.main_pump.set_state(enable.into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time(&mut self) -> Result<chrono::DateTime<Utc>> {
|
pub fn time(&mut self) -> Result<DateTime<Utc>> {
|
||||||
let time = EspSystemTime {}.now().as_millis();
|
let time = EspSystemTime {}.now().as_millis();
|
||||||
let smaller_time = time as i64;
|
let smaller_time = time as i64;
|
||||||
let local_time = DateTime::from_timestamp_millis(smaller_time)
|
let local_time = DateTime::from_timestamp_millis(smaller_time)
|
||||||
@ -588,7 +588,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
Ok(local_time)
|
Ok(local_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sntp(&mut self, max_wait_ms: u32) -> Result<chrono::DateTime<Utc>> {
|
pub fn sntp(&mut self, max_wait_ms: u32) -> Result<DateTime<Utc>> {
|
||||||
let sntp = sntp::EspSntp::new_default()?;
|
let sntp = sntp::EspSntp::new_default()?;
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
while sntp.get_sync_status() != SyncStatus::Completed {
|
while sntp.get_sync_status() != SyncStatus::Completed {
|
||||||
@ -634,28 +634,26 @@ impl PlantCtrlBoard<'_> {
|
|||||||
self.signal_counter.counter_pause()?;
|
self.signal_counter.counter_pause()?;
|
||||||
self.signal_counter.counter_clear()?;
|
self.signal_counter.counter_clear()?;
|
||||||
//Disable all
|
//Disable all
|
||||||
self.shift_register.decompose()[MS_4].set_high().unwrap();
|
self.shift_register.decompose()[MS_4].set_high()?;
|
||||||
|
|
||||||
self.sensor_multiplexer(sensor_channel)?;
|
self.sensor_multiplexer(sensor_channel)?;
|
||||||
|
|
||||||
self.shift_register.decompose()[MS_4].set_low().unwrap();
|
self.shift_register.decompose()[MS_4].set_low()?;
|
||||||
self.shift_register.decompose()[SENSOR_ON]
|
self.shift_register.decompose()[SENSOR_ON]
|
||||||
.set_high()
|
.set_high()?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let delay = Delay::new_default();
|
let delay = Delay::new_default();
|
||||||
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
|
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
|
||||||
let factor = 1000 as f32 / measurement as f32;
|
let factor = 1000f32 / measurement as f32;
|
||||||
|
|
||||||
//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()[MS_4].set_high().unwrap();
|
self.shift_register.decompose()[MS_4].set_high()?;
|
||||||
self.shift_register.decompose()[SENSOR_ON]
|
self.shift_register.decompose()[SENSOR_ON]
|
||||||
.set_low()
|
.set_low()?;
|
||||||
.unwrap();
|
|
||||||
delay.delay_ms(10);
|
delay.delay_ms(10);
|
||||||
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
||||||
let hz = unscaled as f32 * factor;
|
let hz = unscaled as f32 * factor;
|
||||||
@ -707,7 +705,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
//TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
|
//TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
|
||||||
self.wifi_driver.set_configuration(&Configuration::Client(
|
self.wifi_driver.set_configuration(&Configuration::Client(
|
||||||
ClientConfiguration {
|
ClientConfiguration {
|
||||||
ssid: ssid,
|
ssid,
|
||||||
password: pw,
|
password: pw,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@ -716,7 +714,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
None => {
|
None => {
|
||||||
self.wifi_driver.set_configuration(&Configuration::Client(
|
self.wifi_driver.set_configuration(&Configuration::Client(
|
||||||
ClientConfiguration {
|
ClientConfiguration {
|
||||||
ssid: ssid,
|
ssid,
|
||||||
auth_method: AuthMethod::None,
|
auth_method: AuthMethod::None,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@ -733,7 +731,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
delay.delay_ms(250);
|
delay.delay_ms(250);
|
||||||
counter += 250;
|
counter += 250;
|
||||||
if counter > max_wait {
|
if counter > max_wait {
|
||||||
//ignore these errors, wifi will not be used this
|
//ignore these errors, Wi-Fi will not be used this
|
||||||
self.wifi_driver.disconnect().unwrap_or(());
|
self.wifi_driver.disconnect().unwrap_or(());
|
||||||
self.wifi_driver.stop().unwrap_or(());
|
self.wifi_driver.stop().unwrap_or(());
|
||||||
bail!("Did not manage wifi connection within timeout");
|
bail!("Did not manage wifi connection within timeout");
|
||||||
@ -745,7 +743,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
delay.delay_ms(250);
|
delay.delay_ms(250);
|
||||||
counter += 250;
|
counter += 250;
|
||||||
if counter > max_wait {
|
if counter > max_wait {
|
||||||
//ignore these errors, wifi will not be used this
|
//ignore these errors, Wi-Fi will not be used this
|
||||||
self.wifi_driver.disconnect().unwrap_or(());
|
self.wifi_driver.disconnect().unwrap_or(());
|
||||||
self.wifi_driver.stop().unwrap_or(());
|
self.wifi_driver.stop().unwrap_or(());
|
||||||
bail!("Did not manage wifi connection within timeout");
|
bail!("Did not manage wifi connection within timeout");
|
||||||
@ -780,7 +778,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
let mut total_size = 0;
|
let mut total_size = 0;
|
||||||
let mut used_size = 0;
|
let mut used_size = 0;
|
||||||
unsafe {
|
unsafe {
|
||||||
esp_idf_sys::esp!(esp_idf_sys::esp_spiffs_info(
|
esp_idf_sys::esp!(esp_spiffs_info(
|
||||||
storage.as_ptr(),
|
storage.as_ptr(),
|
||||||
&mut total_size,
|
&mut total_size,
|
||||||
&mut used_size
|
&mut used_size
|
||||||
@ -793,7 +791,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_mode_override(&mut self) -> bool {
|
pub fn mode_override_pressed(&mut self) -> bool {
|
||||||
self.boot_button.get_level() == Level::Low
|
self.boot_button.get_level() == Level::Low
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,16 +800,20 @@ impl PlantCtrlBoard<'_> {
|
|||||||
let config = Path::new(CONFIG_FILE);
|
let config = Path::new(CONFIG_FILE);
|
||||||
if config.exists() {
|
if config.exists() {
|
||||||
println!("Removing config");
|
println!("Removing config");
|
||||||
std::fs::remove_file(config)?;
|
fs::remove_file(config)?;
|
||||||
}
|
}
|
||||||
//TODO clear eeprom
|
|
||||||
|
//destroy backup header
|
||||||
|
let dummy : [u8;0] = [];
|
||||||
|
self.backup_config(&dummy)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
|
pub fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
|
||||||
match self.rtc.datetime() {
|
match self.rtc.datetime() {
|
||||||
OkStd(rtc_time) => {
|
OkStd(rtc_time) => {
|
||||||
return Ok(rtc_time.and_utc());
|
Ok(rtc_time.and_utc())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
bail!("Error getting rtc time {:?}", err)
|
bail!("Error getting rtc time {:?}", err)
|
||||||
@ -829,7 +831,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config(&mut self) -> Result<config::PlantControllerConfig> {
|
pub fn get_config(&mut self) -> Result<PlantControllerConfig> {
|
||||||
let cfg = File::open(CONFIG_FILE)?;
|
let cfg = File::open(CONFIG_FILE)?;
|
||||||
let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
|
let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
@ -889,8 +891,8 @@ impl PlantCtrlBoard<'_> {
|
|||||||
unsafe { vTaskDelay(100) };
|
unsafe { vTaskDelay(100) };
|
||||||
}
|
}
|
||||||
for plant in 0..PLANT_COUNT {
|
for plant in 0..PLANT_COUNT {
|
||||||
let a = self.measure_moisture_hz(plant, plant_hal::Sensor::A);
|
let a = self.measure_moisture_hz(plant, Sensor::A);
|
||||||
let b = self.measure_moisture_hz(plant, plant_hal::Sensor::B);
|
let b = self.measure_moisture_hz(plant, Sensor::B);
|
||||||
let aa = match a {
|
let aa = match a {
|
||||||
OkStd(a) => a as u32,
|
OkStd(a) => a as u32,
|
||||||
Err(_) => u32::MAX,
|
Err(_) => u32::MAX,
|
||||||
@ -905,11 +907,6 @@ impl PlantCtrlBoard<'_> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_wifi_config_file_existant(&mut self) -> bool {
|
|
||||||
let config = Path::new(CONFIG_FILE);
|
|
||||||
config.exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> {
|
pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> {
|
||||||
let base_topic = config
|
let base_topic = config
|
||||||
.network
|
.network
|
||||||
@ -956,8 +953,8 @@ impl PlantCtrlBoard<'_> {
|
|||||||
let round_trip_topic_copy = round_trip_topic.clone();
|
let round_trip_topic_copy = round_trip_topic.clone();
|
||||||
let round_trip_ok_copy = round_trip_ok.clone();
|
let round_trip_ok_copy = round_trip_ok.clone();
|
||||||
let client_id = mqtt_client_config.client_id.unwrap_or("not set");
|
let client_id = mqtt_client_config.client_id.unwrap_or("not set");
|
||||||
log(LogMessage::MqttInfo, 0, 0, client_id, &mqtt_url);
|
log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url);
|
||||||
let mut client = EspMqttClient::new_cb(&mqtt_url, &mqtt_client_config, move |event| {
|
let mut client = EspMqttClient::new_cb(mqtt_url, &mqtt_client_config, move |event| {
|
||||||
let payload = event.payload();
|
let payload = event.payload();
|
||||||
match payload {
|
match payload {
|
||||||
embedded_svc::mqtt::client::EventPayload::Received {
|
embedded_svc::mqtt::client::EventPayload::Received {
|
||||||
@ -977,7 +974,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
|
log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
|
||||||
STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
|
STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
|
||||||
} else {
|
} else {
|
||||||
log(LogMessage::UnknownTopic, 0, 0, "", &topic);
|
log(LogMessage::UnknownTopic, 0, 0, "", topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1018,8 +1015,9 @@ impl PlantCtrlBoard<'_> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let wait_for_connections_event = 0;
|
let mut wait_for_connections_event = 0;
|
||||||
while wait_for_connections_event < 100 {
|
while wait_for_connections_event < 100 {
|
||||||
|
wait_for_connections_event += 1;
|
||||||
match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
|
match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
true => {
|
true => {
|
||||||
println!("Mqtt connection callback received, progressing");
|
println!("Mqtt connection callback received, progressing");
|
||||||
@ -1037,8 +1035,9 @@ impl PlantCtrlBoard<'_> {
|
|||||||
"online_test".as_bytes(),
|
"online_test".as_bytes(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let wait_for_roundtrip = 0;
|
let mut wait_for_roundtrip = 0;
|
||||||
while wait_for_roundtrip < 100 {
|
while wait_for_roundtrip < 100 {
|
||||||
|
wait_for_roundtrip+=1;
|
||||||
match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
true => {
|
true => {
|
||||||
println!("Round trip registered, proceeding");
|
println!("Round trip registered, proceeding");
|
||||||
@ -1085,7 +1084,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
let client = self.mqtt_client.as_mut().unwrap();
|
let client = self.mqtt_client.as_mut().unwrap();
|
||||||
let mut full_topic: heapless::String<256> = heapless::String::new();
|
let mut full_topic: heapless::String<256> = heapless::String::new();
|
||||||
if full_topic
|
if full_topic
|
||||||
.push_str(&config.network.base_topic.as_ref().unwrap())
|
.push_str(config.network.base_topic.as_ref().unwrap())
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
println!("Some error assembling full_topic 1");
|
println!("Some error assembling full_topic 1");
|
||||||
@ -1097,7 +1096,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
};
|
};
|
||||||
let publish = client.publish(
|
let publish = client.publish(
|
||||||
&full_topic,
|
&full_topic,
|
||||||
embedded_svc::mqtt::client::QoS::ExactlyOnce,
|
ExactlyOnce,
|
||||||
true,
|
true,
|
||||||
message,
|
message,
|
||||||
);
|
);
|
||||||
@ -1110,7 +1109,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
String::from_utf8_lossy(message),
|
String::from_utf8_lossy(message),
|
||||||
message_id
|
message_id
|
||||||
);
|
);
|
||||||
return Ok(());
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!(
|
println!(
|
||||||
@ -1119,13 +1118,13 @@ impl PlantCtrlBoard<'_> {
|
|||||||
String::from_utf8_lossy(message),
|
String::from_utf8_lossy(message),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
return Err(err)?;
|
Err(err)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_restart_to_conf(&mut self) -> bool {
|
pub fn get_restart_to_conf(&mut self) -> bool {
|
||||||
return unsafe { RESTART_TO_CONF };
|
unsafe { RESTART_TO_CONF }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_restart_to_conf(&mut self, to_conf: bool) {
|
pub fn set_restart_to_conf(&mut self, to_conf: bool) {
|
||||||
@ -1192,7 +1191,7 @@ impl PlantCtrlBoard<'_> {
|
|||||||
|
|
||||||
pub fn bat_temperature(&mut self) -> Result<u16> {
|
pub fn bat_temperature(&mut self) -> Result<u16> {
|
||||||
match self.battery_driver.temperature() {
|
match self.battery_driver.temperature() {
|
||||||
OkStd(r) => Ok(r as u16),
|
OkStd(r) => Ok(r),
|
||||||
Err(err) => bail!("Error reading Temperature {:?}", err),
|
Err(err) => bail!("Error reading Temperature {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1241,7 +1240,7 @@ fn print_battery(
|
|||||||
) -> Result<(), Bq34Z100Error<I2cError>> {
|
) -> Result<(), Bq34Z100Error<I2cError>> {
|
||||||
println!("Try communicating with battery");
|
println!("Try communicating with battery");
|
||||||
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
|
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
|
||||||
println!("Firmeware {:?}", e);
|
println!("Firmware {:?}", e);
|
||||||
0
|
0
|
||||||
});
|
});
|
||||||
println!("fw version is {}", fwversion);
|
println!("fw version is {}", fwversion);
|
||||||
@ -1291,10 +1290,10 @@ fn print_battery(
|
|||||||
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
|
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
|
||||||
let _ = battery_driver.unsealed();
|
let _ = battery_driver.unsealed();
|
||||||
let _ = battery_driver.it_enable();
|
let _ = battery_driver.it_enable();
|
||||||
return Result::Ok(());
|
Result::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(|| PlantHal::create_i2c());
|
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c);
|
||||||
impl PlantHal {
|
impl PlantHal {
|
||||||
fn create_i2c() -> Mutex<I2cDriver<'static>> {
|
fn create_i2c() -> Mutex<I2cDriver<'static>> {
|
||||||
let peripherals = unsafe { Peripherals::new() };
|
let peripherals = unsafe { Peripherals::new() };
|
||||||
@ -1315,15 +1314,15 @@ impl PlantHal {
|
|||||||
let peripherals = Peripherals::take()?;
|
let peripherals = Peripherals::take()?;
|
||||||
|
|
||||||
let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?;
|
let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?;
|
||||||
clock.set_pull(Pull::Floating).unwrap();
|
clock.set_pull(Pull::Floating)?;
|
||||||
let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?;
|
let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?;
|
||||||
latch.set_pull(Pull::Floating).unwrap();
|
latch.set_pull(Pull::Floating)?;
|
||||||
let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?;
|
let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?;
|
||||||
data.set_pull(Pull::Floating).unwrap();
|
data.set_pull(Pull::Floating)?;
|
||||||
let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into());
|
let shift_register = ShiftRegister40::new(clock, latch, data);
|
||||||
//disable all
|
//disable all
|
||||||
for mut pin in shift_register.decompose() {
|
for mut pin in shift_register.decompose() {
|
||||||
pin.set_low().unwrap();
|
pin.set_low()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let awake = &mut shift_register.decompose()[AWAKE];
|
let awake = &mut shift_register.decompose()[AWAKE];
|
||||||
@ -1363,7 +1362,7 @@ impl PlantHal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?;
|
let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?;
|
||||||
one_wire_pin.set_pull(Pull::Floating).unwrap();
|
one_wire_pin.set_pull(Pull::Floating)?;
|
||||||
|
|
||||||
let rtc_time = rtc.datetime();
|
let rtc_time = rtc.datetime();
|
||||||
match rtc_time {
|
match rtc_time {
|
||||||
@ -1498,7 +1497,7 @@ impl PlantHal {
|
|||||||
boot_button.set_pull(Pull::Floating)?;
|
boot_button.set_pull(Pull::Floating)?;
|
||||||
|
|
||||||
let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?;
|
let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?;
|
||||||
light.set_pull(Pull::Floating).unwrap();
|
light.set_pull(Pull::Floating)?;
|
||||||
|
|
||||||
let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?;
|
let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?;
|
||||||
main_pump.set_pull(Pull::Floating)?;
|
main_pump.set_pull(Pull::Floating)?;
|
||||||
@ -1518,7 +1517,7 @@ impl PlantHal {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
log(
|
log(
|
||||||
LogMessage::BatteryCommunicationError,
|
LogMessage::BatteryCommunicationError,
|
||||||
0 as u32,
|
0u32,
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
&format!("{err:?})"),
|
&format!("{err:?})"),
|
||||||
@ -1541,16 +1540,16 @@ impl PlantHal {
|
|||||||
signal_counter: counter_unit1,
|
signal_counter: counter_unit1,
|
||||||
wifi_driver,
|
wifi_driver,
|
||||||
mqtt_client: None,
|
mqtt_client: None,
|
||||||
battery_driver: battery_driver,
|
battery_driver,
|
||||||
rtc: rtc,
|
rtc,
|
||||||
eeprom: eeprom,
|
eeprom,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = rv.lock().is_ok_and(|mut board| {
|
let _ = rv.lock().is_ok_and(|mut board| {
|
||||||
unsafe { gpio_hold_dis(board.shift_register_enable_invert.pin()) };
|
unsafe { gpio_hold_dis(board.shift_register_enable_invert.pin()) };
|
||||||
board.shift_register_enable_invert.set_low().unwrap();
|
board.shift_register_enable_invert.set_low().unwrap();
|
||||||
unsafe { gpio_hold_en(board.shift_register_enable_invert.pin()) };
|
unsafe { gpio_hold_en(board.shift_register_enable_invert.pin()) };
|
||||||
return true;
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(rv)
|
Ok(rv)
|
||||||
|
@ -3,12 +3,12 @@ use chrono_tz::Tz;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{self, PlantConfig},
|
config::PlantConfig,
|
||||||
in_time_range, plant_hal,
|
in_time_range, plant_hal,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 6500.; // 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)]
|
||||||
pub enum MoistureSensorError {
|
pub enum MoistureSensorError {
|
||||||
@ -117,7 +117,7 @@ impl PlantState {
|
|||||||
pub fn read_hardware_state(
|
pub fn read_hardware_state(
|
||||||
plant_id: usize,
|
plant_id: usize,
|
||||||
board: &mut plant_hal::PlantCtrlBoard,
|
board: &mut plant_hal::PlantCtrlBoard,
|
||||||
config: &config::PlantConfig,
|
config: &PlantConfig,
|
||||||
) -> 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) {
|
||||||
@ -225,8 +225,7 @@ impl PlantState {
|
|||||||
if let Some(moisture_percent) = moisture_percent {
|
if let Some(moisture_percent) = moisture_percent {
|
||||||
if self.pump_in_timeout(plant_conf, current_time) {
|
if self.pump_in_timeout(plant_conf, current_time) {
|
||||||
false
|
false
|
||||||
} else {
|
} else if moisture_percent < plant_conf.target_moisture {
|
||||||
if moisture_percent < plant_conf.target_moisture {
|
|
||||||
in_time_range(
|
in_time_range(
|
||||||
current_time,
|
current_time,
|
||||||
plant_conf.pump_hour_start,
|
plant_conf.pump_hour_start,
|
||||||
@ -235,18 +234,13 @@ impl PlantState {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// in case no moisture can be determined do not water plant
|
// in case no moisture can be determined, do not water the plant
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlantWateringMode::TimerOnly => {
|
PlantWateringMode::TimerOnly => {
|
||||||
if self.pump_in_timeout(plant_conf, current_time) {
|
!self.pump_in_timeout(plant_conf, current_time)
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,9 +293,9 @@ pub struct PlantInfo<'a> {
|
|||||||
sensor_b: &'a MoistureSensorState,
|
sensor_b: &'a MoistureSensorState,
|
||||||
/// configured plant watering mode
|
/// configured plant watering mode
|
||||||
mode: PlantWateringMode,
|
mode: PlantWateringMode,
|
||||||
/// plant needs to be watered
|
/// the plant needs to be watered
|
||||||
do_water: bool,
|
do_water: bool,
|
||||||
/// is plant considerd to be dry according to settings
|
/// plant is considered to be dry according to settings
|
||||||
dry: bool,
|
dry: bool,
|
||||||
/// plant irrigation cooldown is active
|
/// plant irrigation cooldown is active
|
||||||
cooldown: bool,
|
cooldown: bool,
|
||||||
@ -310,7 +304,7 @@ pub struct PlantInfo<'a> {
|
|||||||
/// how often has the pump been watered without reaching target moisture
|
/// how often has the pump been watered without reaching target moisture
|
||||||
consecutive_pump_count: u32,
|
consecutive_pump_count: u32,
|
||||||
pump_error: Option<PumpError>,
|
pump_error: Option<PumpError>,
|
||||||
/// last time when pump was active
|
/// last time when the pump was active
|
||||||
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>>,
|
||||||
|
@ -17,12 +17,12 @@ pub enum TankError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum TankState {
|
pub enum TankState {
|
||||||
TankSensorPresent(f32),
|
Present(f32),
|
||||||
TankSensorError(TankError),
|
Error(TankError),
|
||||||
TankSensorDisabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raw_volatge_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
|
fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
|
||||||
if raw_value_mv > OPEN_TANK_VOLTAGE {
|
if raw_value_mv > OPEN_TANK_VOLTAGE {
|
||||||
return Err(TankError::SensorMissing(raw_value_mv));
|
return Err(TankError::SensorMissing(raw_value_mv));
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ fn raw_voltage_to_tank_fill_percent(
|
|||||||
raw_value_mv: f32,
|
raw_value_mv: f32,
|
||||||
config: &TankConfig,
|
config: &TankConfig,
|
||||||
) -> Result<f32, TankError> {
|
) -> Result<f32, TankError> {
|
||||||
let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?;
|
let divider_percent = raw_voltage_to_divider_percent(raw_value_mv)?;
|
||||||
if divider_percent < config.tank_empty_percent.into()
|
if divider_percent < config.tank_empty_percent.into()
|
||||||
|| divider_percent > config.tank_full_percent.into()
|
|| divider_percent > config.tank_full_percent.into()
|
||||||
{
|
{
|
||||||
@ -56,9 +56,9 @@ fn raw_voltage_to_tank_fill_percent(
|
|||||||
impl TankState {
|
impl TankState {
|
||||||
pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> {
|
pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> {
|
||||||
match self {
|
match self {
|
||||||
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
|
TankState::Disabled => Err(TankError::SensorDisabled),
|
||||||
TankState::TankSensorError(err) => Err(err.clone()),
|
TankState::Error(err) => Err(err.clone()),
|
||||||
TankState::TankSensorPresent(raw_value_mv) => {
|
TankState::Present(raw_value_mv) => {
|
||||||
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
|
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
|
||||||
Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.)
|
Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.)
|
||||||
}
|
}
|
||||||
@ -66,9 +66,9 @@ impl TankState {
|
|||||||
}
|
}
|
||||||
pub fn enough_water(&self, config: &TankConfig) -> Result<bool, TankError> {
|
pub fn enough_water(&self, config: &TankConfig) -> Result<bool, TankError> {
|
||||||
match self {
|
match self {
|
||||||
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
|
TankState::Disabled => Err(TankError::SensorDisabled),
|
||||||
TankState::TankSensorError(err) => Err(err.clone()),
|
TankState::Error(err) => Err(err.clone()),
|
||||||
TankState::TankSensorPresent(raw_value_mv) => {
|
TankState::Present(raw_value_mv) => {
|
||||||
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
|
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
|
||||||
if tank_fill_percent > config.tank_empty_percent.into() {
|
if tank_fill_percent > config.tank_empty_percent.into() {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@ -80,14 +80,14 @@ impl TankState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(&self) -> bool {
|
||||||
matches!(self, TankState::TankSensorDisabled)
|
matches!(self, TankState::Disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
|
pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
|
||||||
match self {
|
match self {
|
||||||
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
|
TankState::Disabled => Err(TankError::SensorDisabled),
|
||||||
TankState::TankSensorError(err) => Err(err.clone()),
|
TankState::Error(err) => Err(err.clone()),
|
||||||
TankState::TankSensorPresent(raw_value_mv) => {
|
TankState::Present(raw_value_mv) => {
|
||||||
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config);
|
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config);
|
||||||
match tank_fill_percent {
|
match tank_fill_percent {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@ -108,11 +108,11 @@ impl TankState {
|
|||||||
|
|
||||||
pub fn got_error(&self, config: &TankConfig) -> Option<TankError> {
|
pub fn got_error(&self, config: &TankConfig) -> Option<TankError> {
|
||||||
match self {
|
match self {
|
||||||
TankState::TankSensorPresent(raw_value_mv) => {
|
TankState::Present(raw_value_mv) => {
|
||||||
raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
|
raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
|
||||||
}
|
}
|
||||||
TankState::TankSensorError(err) => Some(err.clone()),
|
TankState::Error(err) => Some(err.clone()),
|
||||||
TankState::TankSensorDisabled => Some(TankError::SensorDisabled),
|
TankState::Disabled => Some(TankError::SensorDisabled),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +130,10 @@ impl TankState {
|
|||||||
Ok(left_ml) => Some(left_ml),
|
Ok(left_ml) => Some(left_ml),
|
||||||
};
|
};
|
||||||
let enough_water = self.enough_water(config).unwrap_or(false); //NOTE: is this correct if there is an error assume not enough water?
|
let enough_water = self.enough_water(config).unwrap_or(false); //NOTE: is this correct if there is an error assume not enough water?
|
||||||
let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should no warn level be triggered if there is an error?
|
let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should warn level be triggered if there is an error?
|
||||||
let raw = match self {
|
let raw = match self {
|
||||||
TankState::TankSensorDisabled | TankState::TankSensorError(_) => None,
|
TankState::Disabled | TankState::Error(_) => None,
|
||||||
TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv),
|
TankState::Present(raw_value_mv) => Some(*raw_value_mv),
|
||||||
};
|
};
|
||||||
|
|
||||||
let percent = match raw {
|
let percent = match raw {
|
||||||
@ -163,30 +163,30 @@ pub fn determine_tank_state(
|
|||||||
) -> TankState {
|
) -> TankState {
|
||||||
if config.tank.tank_sensor_enabled {
|
if config.tank.tank_sensor_enabled {
|
||||||
match board.tank_sensor_voltage() {
|
match board.tank_sensor_voltage() {
|
||||||
Ok(raw_sensor_value_mv) => TankState::TankSensorPresent(raw_sensor_value_mv),
|
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
|
||||||
Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())),
|
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TankState::TankSensorDisabled
|
TankState::Disabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
/// Information structure send to mqtt for monitoring purposes
|
/// Information structure send to mqtt for monitoring purposes
|
||||||
pub struct TankInfo {
|
pub struct TankInfo {
|
||||||
/// is there enough water in the tank
|
/// there is enough water in the tank
|
||||||
enough_water: bool,
|
enough_water: bool,
|
||||||
/// warning that water needs to be refilled soon
|
/// warning that water needs to be refilled soon
|
||||||
warn_level: bool,
|
warn_level: bool,
|
||||||
/// estimation how many ml are still in tank
|
/// estimation how many ml are still in the tank
|
||||||
left_ml: Option<f32>,
|
left_ml: Option<f32>,
|
||||||
/// if there is was an issue with the water level sensor
|
/// if there is an issue with the water level sensor
|
||||||
sensor_error: Option<TankError>,
|
sensor_error: Option<TankError>,
|
||||||
/// raw water sensor value
|
/// raw water sensor value
|
||||||
raw: Option<f32>,
|
raw: Option<f32>,
|
||||||
/// percent value
|
/// percent value
|
||||||
percent: Option<f32>,
|
percent: Option<f32>,
|
||||||
/// water in tank might be frozen
|
/// water in the tank might be frozen
|
||||||
water_frozen: bool,
|
water_frozen: bool,
|
||||||
/// water temperature
|
/// water temperature
|
||||||
water_temp: Option<f32>,
|
water_temp: Option<f32>,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
pub trait LimitPrecision {
|
pub trait LimitPrecision {
|
||||||
fn to_precision(self, presision: i32) -> Self;
|
fn to_precision(self, precision: i32) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LimitPrecision for f32 {
|
impl LimitPrecision for f32 {
|
||||||
fn to_precision(self, precision: i32) -> Self {
|
fn to_precision(self, precision: i32) -> Self {
|
||||||
(self * (10_f32).powi(precision)).round() / (10_f32).powi(precision)
|
let factor = 10_f32.powi(precision);
|
||||||
|
(self * factor).round() / factor
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -82,12 +82,10 @@ fn get_time(
|
|||||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||||
let native = board
|
let native = board
|
||||||
.time()
|
.time().map(|t| t.to_rfc3339())
|
||||||
.and_then(|t| Ok(t.to_rfc3339()))
|
|
||||||
.unwrap_or("error".to_string());
|
.unwrap_or("error".to_string());
|
||||||
let rtc = board
|
let rtc = board
|
||||||
.get_rtc_time()
|
.get_rtc_time().map(|t| t.to_rfc3339())
|
||||||
.and_then(|t| Ok(t.to_rfc3339()))
|
|
||||||
.unwrap_or("error".to_string());
|
.unwrap_or("error".to_string());
|
||||||
|
|
||||||
let data = LoadData {
|
let data = LoadData {
|
||||||
@ -166,7 +164,7 @@ fn get_backup_config(
|
|||||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||||
let json = match board.get_backup_config() {
|
let json = match board.get_backup_config() {
|
||||||
Ok(config) => std::str::from_utf8(&config)?.to_owned(),
|
Ok(config) => from_utf8(&config)?.to_owned(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Error get backup config {:?}", err);
|
println!("Error get backup config {:?}", err);
|
||||||
err.to_string()
|
err.to_string()
|
||||||
@ -290,7 +288,7 @@ fn list_files(
|
|||||||
let board = BOARD_ACCESS.lock().unwrap();
|
let board = BOARD_ACCESS.lock().unwrap();
|
||||||
let result = board.list_files();
|
let result = board.list_files();
|
||||||
let file_list_json = serde_json::to_string(&result)?;
|
let file_list_json = serde_json::to_string(&result)?;
|
||||||
return anyhow::Ok(Some(file_list_json));
|
anyhow::Ok(Some(file_list_json))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ota(
|
fn ota(
|
||||||
@ -333,7 +331,7 @@ fn ota(
|
|||||||
board.set_restart_to_conf(true);
|
board.set_restart_to_conf(true);
|
||||||
drop(board);
|
drop(board);
|
||||||
finalizer.set_as_boot_partition()?;
|
finalizer.set_as_boot_partition()?;
|
||||||
return anyhow::Ok(None);
|
anyhow::Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
||||||
@ -372,7 +370,7 @@ fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
println!("Finished flashing file {line} lines processed");
|
println!("Finished flashing file {line} lines processed");
|
||||||
board.general_fault(false);
|
board.general_fault(false);
|
||||||
return anyhow::Ok(());
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -380,12 +378,12 @@ fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
|||||||
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();
|
||||||
let value = parsed.query_pairs().filter(|it| it.0 == param_name).next();
|
let value = parsed.query_pairs().find(|it| it.0 == param_name);
|
||||||
match value {
|
match value {
|
||||||
Some(found) => {
|
Some(found) => {
|
||||||
return Some(found.1.into_owned());
|
Some(found.1.into_owned())
|
||||||
}
|
}
|
||||||
None => return None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,7 +692,7 @@ fn cors_response(
|
|||||||
let mut response = request.into_response(status, None, &headers)?;
|
let mut response = request.into_response(status, None, &headers)?;
|
||||||
response.write(body.as_bytes())?;
|
response.write(body.as_bytes())?;
|
||||||
response.flush()?;
|
response.flush()?;
|
||||||
return anyhow::Ok(());
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnyhowHandler =
|
type AnyhowHandler =
|
||||||
@ -719,7 +717,7 @@ fn handle_error_to500(
|
|||||||
cors_response(request, 500, &error_text)?;
|
cors_response(request, 500, &error_text)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return anyhow::Ok(());
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_up_to_bytes_from_request(
|
fn read_up_to_bytes_from_request(
|
||||||
|
@ -24,7 +24,7 @@ export class FileView {
|
|||||||
let fileuploadname = document.getElementById("fileuploadname") as HTMLInputElement
|
let fileuploadname = document.getElementById("fileuploadname") as HTMLInputElement
|
||||||
let fileuploadbtn = document.getElementById("fileuploadbtn") as HTMLInputElement
|
let fileuploadbtn = document.getElementById("fileuploadbtn") as HTMLInputElement
|
||||||
fileuploadfile.onchange = () => {
|
fileuploadfile.onchange = () => {
|
||||||
var selectedFile = fileuploadfile.files?.[0];
|
const selectedFile = fileuploadfile.files?.[0];
|
||||||
if (selectedFile == null) {
|
if (selectedFile == null) {
|
||||||
//TODO error dialog here
|
//TODO error dialog here
|
||||||
return
|
return
|
||||||
@ -42,7 +42,7 @@ export class FileView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileuploadbtn.onclick = () => {
|
fileuploadbtn.onclick = () => {
|
||||||
var selectedFile = fileuploadfile.files?.[0];
|
const selectedFile = fileuploadfile.files?.[0];
|
||||||
if (selectedFile == null) {
|
if (selectedFile == null) {
|
||||||
//TODO error dialog here
|
//TODO error dialog here
|
||||||
return
|
return
|
||||||
@ -77,8 +77,7 @@ class FileEntry {
|
|||||||
this.view.classList.add("fileentryouter")
|
this.view.classList.add("fileentryouter")
|
||||||
|
|
||||||
const template = require('./fileviewentry.html') as string;
|
const template = require('./fileviewentry.html') as string;
|
||||||
const fileRaw = template.replaceAll("${fileid}", String(fileid));
|
this.view.innerHTML = template.replaceAll("${fileid}", String(fileid))
|
||||||
this.view.innerHTML = fileRaw
|
|
||||||
|
|
||||||
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
|
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
|
||||||
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;
|
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
animation: indeterminateAnimation 1s infinite linear;
|
animation: indeterminateAnimation 1s infinite linear;
|
||||||
transform-origin: 0% 50%;
|
transform-origin: 0 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { deepEqual } from 'fast-equals';
|
import { deepEqual } from 'fast-equals';
|
||||||
|
|
||||||
declare var PUBLIC_URL: string;
|
declare var PUBLIC_URL: string;
|
||||||
@ -8,7 +7,7 @@ document.body.innerHTML = require('./main.html') as string;
|
|||||||
|
|
||||||
|
|
||||||
import { TimeView } from "./timeview";
|
import { TimeView } from "./timeview";
|
||||||
import { PlantView, PlantViews } from "./plant";
|
import { PlantViews } from "./plant";
|
||||||
import { NetworkConfigView } from "./network";
|
import { NetworkConfigView } from "./network";
|
||||||
import { NightLampView } from "./nightlightview";
|
import { NightLampView } from "./nightlightview";
|
||||||
import { TankConfigView } from "./tankview";
|
import { TankConfigView } from "./tankview";
|
||||||
@ -261,7 +260,6 @@ export class Controller {
|
|||||||
configChanged() {
|
configChanged() {
|
||||||
const current = controller.getConfig();
|
const current = controller.getConfig();
|
||||||
var pretty = JSON.stringify(current, undefined, 0);
|
var pretty = JSON.stringify(current, undefined, 0);
|
||||||
var initial = JSON.stringify(this.initialConfig, undefined, 0);
|
|
||||||
controller.submitView.setJson(pretty);
|
controller.submitView.setJson(pretty);
|
||||||
|
|
||||||
if (deepEqual(current, controller.initialConfig)) {
|
if (deepEqual(current, controller.initialConfig)) {
|
||||||
@ -307,7 +305,7 @@ export class Controller {
|
|||||||
})
|
})
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(
|
.then(
|
||||||
text => {
|
_ => {
|
||||||
clearTimeout(timerId);
|
clearTimeout(timerId);
|
||||||
controller.progressview.removeProgress("test_pump");
|
controller.progressview.removeProgress("test_pump");
|
||||||
}
|
}
|
||||||
@ -507,3 +505,16 @@ controller.populateTimezones().then(_ => {
|
|||||||
|
|
||||||
controller.progressview.removeProgress("rebooting");
|
controller.progressview.removeProgress("rebooting");
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", (event) => {
|
||||||
|
const currentConfig = controller.getConfig();
|
||||||
|
|
||||||
|
// Check if the current state differs from the initial configuration
|
||||||
|
if (!deepEqual(currentConfig, controller.initialConfig)) {
|
||||||
|
const confirmationMessage = "You have unsaved changes. Are you sure you want to leave this page?";
|
||||||
|
|
||||||
|
// Standard behavior for displaying the confirmation dialog
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = confirmationMessage; // This will trigger the browser's default dialog
|
||||||
|
return confirmationMessage;
|
||||||
|
}
|
||||||
|
});
|
@ -3,8 +3,8 @@ import { Controller } from "./main";
|
|||||||
export class NetworkConfigView {
|
export class NetworkConfigView {
|
||||||
setScanResult(ssidList: SSIDList) {
|
setScanResult(ssidList: SSIDList) {
|
||||||
this.ssidlist.innerHTML = ''
|
this.ssidlist.innerHTML = ''
|
||||||
for (var ssid of ssidList.ssids) {
|
for (const ssid of ssidList.ssids) {
|
||||||
var wi = document.createElement("option");
|
const wi = document.createElement("option");
|
||||||
wi.value = ssid;
|
wi.value = ssid;
|
||||||
this.ssidlist.appendChild(wi);
|
this.ssidlist.appendChild(wi);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export class OTAView {
|
|||||||
const file = document.getElementById("firmware_file") as HTMLInputElement;
|
const file = document.getElementById("firmware_file") as HTMLInputElement;
|
||||||
this.file1Upload = file
|
this.file1Upload = file
|
||||||
this.file1Upload.onchange = () => {
|
this.file1Upload.onchange = () => {
|
||||||
var selectedFile = file.files?.[0];
|
const selectedFile = file.files?.[0];
|
||||||
if (selectedFile == null) {
|
if (selectedFile == null) {
|
||||||
//TODO error dialog here
|
//TODO error dialog here
|
||||||
return
|
return
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
.plantcheckbox{
|
.plantcheckbox{
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
margin: 0px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
</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="flexcontainer">
|
||||||
|
@ -61,12 +61,10 @@ export class PlantView {
|
|||||||
|
|
||||||
|
|
||||||
constructor(plantId: number, parent:HTMLDivElement, controller:Controller) {
|
constructor(plantId: number, parent:HTMLDivElement, controller:Controller) {
|
||||||
const dummy = this;
|
|
||||||
this.plantId = plantId;
|
this.plantId = plantId;
|
||||||
this.plantDiv = document.createElement("div")! as HTMLDivElement
|
this.plantDiv = document.createElement("div")! as HTMLDivElement
|
||||||
const template = require('./plant.html') as string;
|
const template = require('./plant.html') as string;
|
||||||
const plantRaw = template.replaceAll("${plantId}", String(plantId));
|
this.plantDiv.innerHTML = template.replaceAll("${plantId}", String(plantId))
|
||||||
this.plantDiv.innerHTML = plantRaw
|
|
||||||
|
|
||||||
this.plantDiv.classList.add("plantcontainer")
|
this.plantDiv.classList.add("plantcontainer")
|
||||||
parent.appendChild(this.plantDiv)
|
parent.appendChild(this.plantDiv)
|
||||||
@ -184,7 +182,7 @@ export class PlantView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getConfig(): PlantConfig {
|
getConfig(): PlantConfig {
|
||||||
const rv: PlantConfig = {
|
return {
|
||||||
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,
|
||||||
@ -196,14 +194,5 @@ export class PlantView {
|
|||||||
moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || undefined,
|
moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || undefined,
|
||||||
moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || undefined,
|
moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || undefined,
|
||||||
};
|
};
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoistureA(a: number) {
|
|
||||||
this.moistureA.innerText = String(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoistureB(b: number) {
|
|
||||||
this.moistureB.innerText = String(b);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
<style>
|
<style>
|
||||||
.tankcheckbox {
|
.tankcheckbox {
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
margin: 0px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.tankkey{
|
.tankkey{
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
}
|
}
|
||||||
.tankvalue{
|
.tankvalue{
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 0px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -38,7 +38,7 @@ export class TimeView {
|
|||||||
update(native: string, rtc: string) {
|
update(native: string, rtc: string) {
|
||||||
this.esp_time.innerText = native;
|
this.esp_time.innerText = native;
|
||||||
this.rtc_time.innerText = rtc;
|
this.rtc_time.innerText = rtc;
|
||||||
var date = new Date();
|
const date = new Date();
|
||||||
this.browser_time.innerText = date.toISOString();
|
this.browser_time.innerText = date.toISOString();
|
||||||
if(this.auto_refresh.checked){
|
if(this.auto_refresh.checked){
|
||||||
this.timer = setTimeout(this.controller.updateRTCData, 1000);
|
this.timer = setTimeout(this.controller.updateRTCData, 1000);
|
||||||
|
9
website/.idea/website.iml
generated
Normal file
9
website/.idea/website.iml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
Loading…
x
Reference in New Issue
Block a user