Switch println! to log::info in water.rs; add dynamic deep-sleep adjustment based on plant state; update heap metrics and OTA UI.
This commit is contained in:
@@ -11,6 +11,7 @@ use esp_hal::pcnt::unit::Unit;
|
|||||||
use esp_hal::peripherals::GPIO5;
|
use esp_hal::peripherals::GPIO5;
|
||||||
use esp_hal::Async;
|
use esp_hal::Async;
|
||||||
use esp_println::println;
|
use esp_println::println;
|
||||||
|
use log::info;
|
||||||
use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20};
|
use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20};
|
||||||
|
|
||||||
unsafe impl Send for TankSensor<'_> {}
|
unsafe impl Send for TankSensor<'_> {}
|
||||||
@@ -90,9 +91,9 @@ impl<'a> TankSensor<'a> {
|
|||||||
let mut delay = Delay::new();
|
let mut delay = Delay::new();
|
||||||
|
|
||||||
let presence = self.one_wire_bus.reset(&mut delay)?;
|
let presence = self.one_wire_bus.reset(&mut delay)?;
|
||||||
println!("OneWire: reset presence pulse = {}", presence);
|
info!("OneWire: reset presence pulse = {}", presence);
|
||||||
if !presence {
|
if !presence {
|
||||||
println!("OneWire: no device responded to reset — check pull-up resistor and wiring");
|
info!("OneWire: no device responded to reset — check pull-up resistor and wiring");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut search = DeviceSearch::new();
|
let mut search = DeviceSearch::new();
|
||||||
@@ -100,7 +101,7 @@ impl<'a> TankSensor<'a> {
|
|||||||
let mut devices_found = 0u8;
|
let mut devices_found = 0u8;
|
||||||
while let Some(device) = self.one_wire_bus.search_next(&mut search, &mut delay)? {
|
while let Some(device) = self.one_wire_bus.search_next(&mut search, &mut delay)? {
|
||||||
devices_found += 1;
|
devices_found += 1;
|
||||||
println!(
|
info!(
|
||||||
"OneWire: found device #{} family=0x{:02X} addr={:02X?}",
|
"OneWire: found device #{} family=0x{:02X} addr={:02X?}",
|
||||||
devices_found, device.address[0], device.address
|
devices_found, device.address[0], device.address
|
||||||
);
|
);
|
||||||
@@ -108,16 +109,16 @@ impl<'a> TankSensor<'a> {
|
|||||||
water_temp_sensor = Some(device);
|
water_temp_sensor = Some(device);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
println!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE);
|
info!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if devices_found == 0 {
|
if devices_found == 0 {
|
||||||
println!("OneWire: search found zero devices on the bus");
|
info!("OneWire: search found zero devices on the bus");
|
||||||
}
|
}
|
||||||
|
|
||||||
match water_temp_sensor {
|
match water_temp_sensor {
|
||||||
Some(device) => {
|
Some(device) => {
|
||||||
println!("Found one wire device: {:?}", device);
|
info!("Found one wire device: {:?}", device);
|
||||||
let mut water_temp_sensor = DS18B20::new(device)?;
|
let mut water_temp_sensor = DS18B20::new(device)?;
|
||||||
|
|
||||||
let water_temp: Result<f32, FatError> = loop {
|
let water_temp: Result<f32, FatError> = loop {
|
||||||
|
|||||||
@@ -641,18 +641,44 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/deepsleep", "low Volt 12h").await;
|
.mqtt_publish("/deepsleep", "low Volt 12h").await;
|
||||||
12 * 60
|
12 * 60
|
||||||
} else if is_day {
|
|
||||||
let _ = board
|
|
||||||
.board_hal
|
|
||||||
.get_esp()
|
|
||||||
.mqtt_publish("/deepsleep", "normal 20m").await;
|
|
||||||
20
|
|
||||||
} else {
|
} else {
|
||||||
let _ = board
|
let base_duration: u32 = if is_day { 20 } else { 60 };
|
||||||
.board_hal
|
|
||||||
.get_esp()
|
// Shorten sleep if any plant has a pending action sooner than the base duration
|
||||||
.mqtt_publish("/deepsleep", "night 1h").await;
|
let min_plant_wakeup: Option<u32> = plantstate
|
||||||
60
|
.iter()
|
||||||
|
.zip(&board.board_hal.get_config().plants)
|
||||||
|
.filter_map(|(state, conf)| {
|
||||||
|
state.minutes_until_actionable(conf, &timezone_time)
|
||||||
|
})
|
||||||
|
.min();
|
||||||
|
|
||||||
|
let duration = min_plant_wakeup
|
||||||
|
.map(|m| base_duration.min(m.max(1)))
|
||||||
|
.unwrap_or(base_duration);
|
||||||
|
|
||||||
|
if duration < base_duration {
|
||||||
|
let msg = format!("reduced {}m", duration);
|
||||||
|
let _ = board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.mqtt_publish("/deepsleep", &msg)
|
||||||
|
.await;
|
||||||
|
} else if is_day {
|
||||||
|
let _ = board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.mqtt_publish("/deepsleep", "normal 20m")
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
let _ = board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.mqtt_publish("/deepsleep", "night 1h")
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = board
|
let _ = board
|
||||||
@@ -1336,7 +1362,6 @@ async fn get_version(
|
|||||||
slot1_state: format!("{:?}", board.slot1_state),
|
slot1_state: format!("{:?}", board.slot1_state),
|
||||||
heap_total: heap.size,
|
heap_total: heap.size,
|
||||||
heap_used: heap.current_usage,
|
heap_used: heap.current_usage,
|
||||||
heap_free: heap.size.saturating_sub(heap.current_usage),
|
|
||||||
heap_max_used: heap.max_usage,
|
heap_max_used: heap.max_usage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1350,6 +1375,5 @@ struct VersionInfo {
|
|||||||
slot1_state: String,
|
slot1_state: String,
|
||||||
heap_total: usize,
|
heap_total: usize,
|
||||||
heap_used: usize,
|
heap_used: usize,
|
||||||
heap_free: usize,
|
|
||||||
heap_max_used: usize,
|
heap_max_used: usize,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::hal::Moistures;
|
use crate::hal::Moistures;
|
||||||
use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Timelike, Utc};
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -192,6 +192,67 @@ impl PlantState {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns how many minutes until this plant becomes actionable (could be watered),
|
||||||
|
/// or `None` if no action is pending within any reasonable horizon.
|
||||||
|
///
|
||||||
|
/// Used to dynamically shorten deep-sleep so cooldowns are not prolonged.
|
||||||
|
pub fn minutes_until_actionable(
|
||||||
|
&self,
|
||||||
|
plant_conf: &PlantConfig,
|
||||||
|
current_time: &DateTime<Tz>,
|
||||||
|
) -> Option<u32> {
|
||||||
|
if matches!(plant_conf.mode, PlantWateringMode::Off) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut earliest: Option<u32> = None;
|
||||||
|
|
||||||
|
// When does the current cooldown expire?
|
||||||
|
if self.pump_in_timeout(plant_conf, current_time) {
|
||||||
|
if let Some(last_pump) = self.pump.previous_pump {
|
||||||
|
if let Some(expiry) = last_pump
|
||||||
|
.checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
|
||||||
|
{
|
||||||
|
let diff = expiry.signed_duration_since(current_time.with_timezone(&Utc));
|
||||||
|
let mins = diff.num_minutes().max(0) as u32;
|
||||||
|
earliest = Some(earliest.map_or(mins, |e| e.min(mins)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For moisture-based modes: when does the watering window open, if the plant is dry
|
||||||
|
// but currently outside its time window (and not in cooldown)?
|
||||||
|
if matches!(
|
||||||
|
plant_conf.mode,
|
||||||
|
PlantWateringMode::TargetMoisture | PlantWateringMode::MinMoisture
|
||||||
|
) {
|
||||||
|
let (moisture, _) = self.plant_moisture();
|
||||||
|
if let Some(moisture) = moisture {
|
||||||
|
if moisture < plant_conf.target_moisture
|
||||||
|
&& !self.pump_in_timeout(plant_conf, current_time)
|
||||||
|
&& !in_time_range(
|
||||||
|
current_time,
|
||||||
|
plant_conf.pump_hour_start,
|
||||||
|
plant_conf.pump_hour_end,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// in_time_range uses `hour > start`, so the window opens at start+1:00
|
||||||
|
let cur_hour = current_time.hour() as u32;
|
||||||
|
let cur_minute = current_time.minute() as u32;
|
||||||
|
let open_at_hour = (plant_conf.pump_hour_start as u32 + 1) % 24;
|
||||||
|
let mins = if open_at_hour > cur_hour {
|
||||||
|
(open_at_hour - cur_hour) * 60 - cur_minute
|
||||||
|
} else {
|
||||||
|
(24 - cur_hour + open_at_hour) * 60 - cur_minute
|
||||||
|
};
|
||||||
|
earliest = Some(earliest.map_or(mins, |e| e.min(mins)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
earliest
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_err(&self) -> bool {
|
pub fn is_err(&self) -> bool {
|
||||||
self.sensor_a.is_err().is_some() || self.sensor_b.is_err().is_some()
|
self.sensor_a.is_err().is_some() || self.sensor_b.is_err().is_some()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ export interface VersionInfo {
|
|||||||
slot1_state: string,
|
slot1_state: string,
|
||||||
heap_total: number,
|
heap_total: number,
|
||||||
heap_used: number,
|
heap_used: number,
|
||||||
heap_free: number,
|
|
||||||
heap_max_used: number,
|
heap_max_used: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<span class="otakey">Free:</span>
|
<span class="otakey">Minimum Free:</span>
|
||||||
<span class="otavalue" id="heap_free"></span>
|
<span class="otavalue" id="heap_min_free"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<span class="otakey">Used:</span>
|
<span class="otakey">Used:</span>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class OTAView {
|
|||||||
readonly firmware_partition: HTMLDivElement;
|
readonly firmware_partition: HTMLDivElement;
|
||||||
readonly firmware_state0: HTMLDivElement;
|
readonly firmware_state0: HTMLDivElement;
|
||||||
readonly firmware_state1: HTMLDivElement;
|
readonly firmware_state1: HTMLDivElement;
|
||||||
readonly heap_free: HTMLDivElement;
|
readonly heap_min_free: HTMLDivElement;
|
||||||
readonly heap_used: HTMLDivElement;
|
readonly heap_used: HTMLDivElement;
|
||||||
readonly heap_total: HTMLDivElement;
|
readonly heap_total: HTMLDivElement;
|
||||||
readonly heap_max_used: HTMLDivElement;
|
readonly heap_max_used: HTMLDivElement;
|
||||||
@@ -28,7 +28,7 @@ export class OTAView {
|
|||||||
this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement;
|
this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement;
|
||||||
this.firmware_state0 = document.getElementById("firmware_state0") as HTMLDivElement;
|
this.firmware_state0 = document.getElementById("firmware_state0") as HTMLDivElement;
|
||||||
this.firmware_state1 = document.getElementById("firmware_state1") as HTMLDivElement;
|
this.firmware_state1 = document.getElementById("firmware_state1") as HTMLDivElement;
|
||||||
this.heap_free = document.getElementById("heap_free") as HTMLDivElement;
|
this.heap_min_free = document.getElementById("heap_min_free") as HTMLDivElement;
|
||||||
this.heap_used = document.getElementById("heap_used") as HTMLDivElement;
|
this.heap_used = document.getElementById("heap_used") as HTMLDivElement;
|
||||||
this.heap_total = document.getElementById("heap_total") as HTMLDivElement;
|
this.heap_total = document.getElementById("heap_total") as HTMLDivElement;
|
||||||
this.heap_max_used = document.getElementById("heap_max_used") as HTMLDivElement;
|
this.heap_max_used = document.getElementById("heap_max_used") as HTMLDivElement;
|
||||||
@@ -59,7 +59,7 @@ export class OTAView {
|
|||||||
this.firmware_partition.innerText = versionInfo.current;
|
this.firmware_partition.innerText = versionInfo.current;
|
||||||
this.firmware_state0.innerText = versionInfo.slot0_state;
|
this.firmware_state0.innerText = versionInfo.slot0_state;
|
||||||
this.firmware_state1.innerText = versionInfo.slot1_state;
|
this.firmware_state1.innerText = versionInfo.slot1_state;
|
||||||
this.heap_free.innerText = fmtBytes(versionInfo.heap_free);
|
this.heap_min_free.innerText = fmtBytes(versionInfo.heap_total - versionInfo.heap_max_used);
|
||||||
this.heap_used.innerText = fmtBytes(versionInfo.heap_used);
|
this.heap_used.innerText = fmtBytes(versionInfo.heap_used);
|
||||||
this.heap_total.innerText = fmtBytes(versionInfo.heap_total);
|
this.heap_total.innerText = fmtBytes(versionInfo.heap_total);
|
||||||
this.heap_max_used.innerText = fmtBytes(versionInfo.heap_max_used);
|
this.heap_max_used.innerText = fmtBytes(versionInfo.heap_max_used);
|
||||||
|
|||||||
Reference in New Issue
Block a user