splitting wip

This commit is contained in:
2025-06-19 16:56:33 +02:00
parent c429c829b4
commit fc1991523a
9 changed files with 1553 additions and 1675 deletions

View File

@@ -28,9 +28,9 @@ mod tank;
use plant_state::PlantState;
use tank::*;
use crate::config::BoardVersion::INITIAL;
use crate::hal::{BoardInteraction, PlantHal, HAL, PLANT_COUNT};
use crate::hal::battery::BatteryInteraction;
use crate::hal::BoardHal::Initial;
pub static BOARD_ACCESS: Lazy<Mutex<HAL>> = Lazy::new(|| PlantHal::create().unwrap());
pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
@@ -151,14 +151,14 @@ fn safe_main() -> anyhow::Result<()> {
log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
let mut board = BOARD_ACCESS.lock().expect("Could not lock board no other lock should be able to exist during startup!");
board.general_fault(false);
board.board_hal.general_fault(false);
let cur = board
let cur = board.board_hal
.get_rtc_time()
.or_else(|err| {
println!("rtc module error: {:?}", err);
board.general_fault(true);
board.time()
board.board_hal.general_fault(true);
board.board_hal.get_esp().time()
})
.map_err(|err| -> Result<(), _> {
bail!("time error {}", err);
@@ -172,57 +172,52 @@ fn safe_main() -> anyhow::Result<()> {
}
println!("cur is {}", cur);
match board.battery_monitor.average_current_milli_ampere() {
match board.board_hal.get_battery_monitor().average_current_milli_ampere() {
Ok(charging) => {
board.set_charge_indicator(charging > 20)
let _ = board.board_hal.set_charge_indicator(charging > 20);
}
Err(_) => {}
}
if board.get_restart_to_conf() {
if board.board_hal.get_esp().get_restart_to_conf() {
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
for _i in 0..2 {
board.general_fault(true);
board.board_hal.general_fault(true);
Delay::new_default().delay_ms(100);
board.general_fault(false);
board.board_hal.general_fault(false);
Delay::new_default().delay_ms(100);
}
to_config = true;
board.general_fault(true);
board.set_restart_to_conf(false);
} else if board.mode_override_pressed() {
board.general_fault(true);
board.board_hal.general_fault(true);
board.board_hal.get_esp().set_restart_to_conf(false);
} else if board.board_hal.get_esp().mode_override_pressed() {
board.board_hal.general_fault(true);
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
for _i in 0..5 {
board.general_fault(true);
board.board_hal.general_fault(true);
Delay::new_default().delay_ms(100);
board.general_fault(false);
board.board_hal.general_fault(false);
Delay::new_default().delay_ms(100);
}
if board.mode_override_pressed() {
board.general_fault(true);
if board.board_hal.get_esp().mode_override_pressed() {
board.board_hal.general_fault(true);
to_config = true;
} else {
board.general_fault(false);
board.board_hal.general_fault(false);
}
}
match board.board_hal {
Initial { .. } => {
//config upload will trigger reboot and then switch to selected board_hal
let _ = board.esp.wifi_ap();
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
}
_ => {}
if board.board_hal.get_config().hardware.board == INITIAL {
let _ = board.board_hal.get_esp().wifi_ap();
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
}
println!("attempting to connect wifi");
let network_mode = if board.config.network.ssid.is_some() {
let network_mode = if board.board_hal.get_config().network.ssid.is_some() {
try_connect_wifi_sntp_mqtt(&mut board)
} else {
println!("No wifi configured");
@@ -231,7 +226,7 @@ fn safe_main() -> anyhow::Result<()> {
if matches!(network_mode, NetworkMode::OFFLINE) && to_config {
println!("Could not connect to station and config mode forced, switching to ap mode!");
match board.esp.wifi_ap() {
match board.board_hal.get_esp().wifi_ap() {
Ok(_) => {
println!("Started ap, continuing")
}
@@ -239,7 +234,7 @@ fn safe_main() -> anyhow::Result<()> {
}
}
let timezone = match & board.config.timezone {
let timezone = match & board.board_hal.get_config().timezone {
Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| {
println!("Invalid timezone '{}', falling back to UTC", tz_str);
UTC
@@ -286,7 +281,7 @@ fn safe_main() -> anyhow::Result<()> {
let tank_state = determine_tank_state(&mut board);
if tank_state.is_enabled() {
if let Some(err) = tank_state.got_error(&board.config.tank) {
if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) {
match err {
TankError::SensorDisabled => { /* unreachable */ }
TankError::SensorMissing(raw_value_mv) => log(
@@ -308,10 +303,10 @@ fn safe_main() -> anyhow::Result<()> {
}
}
// disabled cannot trigger this because of wrapping if is_enabled
board.general_fault(true);
} else if tank_state.warn_level(&board.config.tank).is_ok_and(|warn| warn) {
board.board_hal.general_fault(true);
} else if tank_state.warn_level(&board.board_hal.get_config().tank).is_ok_and(|warn| warn) {
log(LogMessage::TankWaterLevelLow, 0, 0, "", "");
board.general_fault(true);
board.board_hal.general_fault(true);
}
}
@@ -332,18 +327,15 @@ fn safe_main() -> anyhow::Result<()> {
let pump_required = plantstate
.iter()
.zip(&board.config.plants)
.zip(&board.board_hal.get_config().plants)
.any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time))
&& !water_frozen;
if pump_required {
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
if !dry_run {
board.any_pump(true)?; // enables main power output, eg for a central pump with valve setup or a main water valve for the risk affine
}
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.config.plants.clone()).enumerate() {
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.board_hal.get_config().plants.clone()).enumerate() {
if state.needs_to_be_watered(plant_config, &timezone_time) {
let pump_count = board.consecutive_pump_count(plant_id) + 1;
board.store_consecutive_pump_count(plant_id, pump_count);
let pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id) + 1;
board.board_hal.get_esp().store_consecutive_pump_count(plant_id, pump_count);
let pump_ineffective = pump_count > plant_config.max_consecutive_pump_count as u32;
if pump_ineffective {
@@ -354,7 +346,7 @@ fn safe_main() -> anyhow::Result<()> {
&(plant_id+1).to_string(),
"",
);
board.fault(plant_id, true)?;
board.board_hal.fault(plant_id, true)?;
}
log(
LogMessage::PumpPlant,
@@ -363,71 +355,68 @@ fn safe_main() -> anyhow::Result<()> {
&dry_run.to_string(),
"",
);
board.store_last_pump_time(plant_id, cur);
board.last_pump_time(plant_id);
board.board_hal.get_esp().store_last_pump_time(plant_id, cur);
board.board_hal.get_esp().last_pump_time(plant_id);
//state.active = true;
pump_info(&mut board, plant_id, true, pump_ineffective);
if !dry_run {
board.pump(plant_id, true)?;
board.board_hal.pump(plant_id, true)?;
Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32);
board.pump(plant_id, false)?;
board.board_hal.pump(plant_id, false)?;
}
pump_info(&mut board, plant_id, false, pump_ineffective);
} else if !state.pump_in_timeout(plant_config, &timezone_time) {
// plant does not need to be watered and is not in timeout
// -> reset consecutive pump count
board.store_consecutive_pump_count(plant_id, 0);
board.board_hal.get_esp().store_consecutive_pump_count(plant_id, 0);
}
}
if !dry_run {
board.any_pump(false)?; // disable main power output, eg for a central pump with valve setup or a main water valve for the risk affine
}
}
let is_day = board.is_day();
let state_of_charge = board.battery_monitor.state_charge_percent().unwrap_or(0);
let is_day = board.board_hal.is_day();
let state_of_charge = board.board_hal.get_battery_monitor().state_charge_percent().unwrap_or(0);
let mut light_state = LightState {
enabled: board.config.night_lamp.enabled,
enabled: board.board_hal.get_config().night_lamp.enabled,
..Default::default()
};
if light_state.enabled {
light_state.is_day = is_day;
light_state.out_of_work_hour = !in_time_range(
&timezone_time,
board.config.night_lamp.night_lamp_hour_start,
board.config.night_lamp.night_lamp_hour_end,
board.board_hal.get_config().night_lamp.night_lamp_hour_start,
board.board_hal.get_config().night_lamp.night_lamp_hour_end,
);
if state_of_charge < board.config.night_lamp.low_soc_cutoff {
board.set_low_voltage_in_cycle();
} else if state_of_charge > board.config.night_lamp.low_soc_restore {
board.clear_low_voltage_in_cycle();
if state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff {
board.board_hal.get_esp().set_low_voltage_in_cycle();
} else if state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore {
board.board_hal.get_esp().clear_low_voltage_in_cycle();
}
light_state.battery_low = board.low_voltage_in_cycle();
light_state.battery_low = board.board_hal.get_esp().low_voltage_in_cycle();
if !light_state.out_of_work_hour {
if board.config.night_lamp.night_lamp_only_when_dark {
if board.board_hal.get_config().night_lamp.night_lamp_only_when_dark {
if !light_state.is_day {
if light_state.battery_low {
board.light(false)?;
board.board_hal.light(false)?;
} else {
light_state.active = true;
board.light(true)?;
board.board_hal.light(true)?;
}
}
} else if light_state.battery_low {
board.light(false)?;
board.board_hal.light(false)?;
} else {
light_state.active = true;
board.light(true)?;
board.board_hal.light(true)?;
}
} else {
light_state.active = false;
board.light(false)?;
board.board_hal.light(false)?;
}
println!("Lightstate is {:?}", light_state);
@@ -435,7 +424,7 @@ fn safe_main() -> anyhow::Result<()> {
match serde_json::to_string(&light_state) {
Ok(state) => {
let _ = board.esp.mqtt_publish( "/light", state.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/light", state.as_bytes());
}
Err(err) => {
println!("Error publishing lightstate {}", err);
@@ -443,16 +432,16 @@ fn safe_main() -> anyhow::Result<()> {
};
let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 {
let _ = board.esp.mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes());
12 * 60
} else if is_day {
let _ = board.esp.mqtt_publish( "/deepsleep", "normal 20m".as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "normal 20m".as_bytes());
20
} else {
let _ = board.esp.mqtt_publish( "/deepsleep", "night 1h".as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "night 1h".as_bytes());
60
};
let _ = board.esp.mqtt_publish( "/state", "sleep".as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/state", "sleep".as_bytes());
//determine next event
//is light out of work trigger soon?
@@ -472,15 +461,15 @@ fn safe_main() -> anyhow::Result<()> {
let _webserver = httpd(reboot_now.clone());
wait_infinity(WaitType::MqttConfig, reboot_now.clone());
}
board.set_restart_to_conf(false);
board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
board.board_hal.get_esp().set_restart_to_conf(false);
board.board_hal.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
}
fn obtain_tank_temperature(board: &mut MutexGuard<HAL>) -> anyhow::Result<f32> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let water_temp: Result<f32, anyhow::Error> = loop {
let temp = board.water_temperature_c();
let temp = board.board_hal.water_temperature_c();
match &temp {
Ok(res) => {
println!("Water temp is {}", res);
@@ -499,9 +488,9 @@ fn obtain_tank_temperature(board: &mut MutexGuard<HAL>) -> anyhow::Result<f32> {
}
fn publish_tank_state(board: &mut MutexGuard<HAL>, tank_state: &TankState, water_temp: &anyhow::Result<f32>) {
match serde_json::to_string(&tank_state.as_mqtt_info(&board.config.tank, water_temp)) {
match serde_json::to_string(&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, water_temp)) {
Ok(state) => {
let _ = board.esp.mqtt_publish("/water", state.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish("/water", state.as_bytes());
}
Err(err) => {
println!("Error publishing tankstate {}", err);
@@ -510,13 +499,13 @@ fn publish_tank_state(board: &mut MutexGuard<HAL>, tank_state: &TankState, water
}
fn publish_plant_states(board: &mut MutexGuard<HAL>, timezone_time: &DateTime<Tz>, plantstate: &[PlantState; 8]) {
for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.config.plants.clone()).enumerate() {
for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.board_hal.get_config().plants.clone()).enumerate() {
match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) {
Ok(state) => {
let plant_topic = format!("/plant{}", plant_id + 1);
let _ = board.esp.mqtt_publish(&plant_topic, state.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish(&plant_topic, state.as_bytes());
//reduce speed as else messages will be dropped
board.esp.delay.delay_ms(200);
board.board_hal.get_esp().delay.delay_ms(200);
}
Err(err) => {
println!("Error publishing plant state {}", err);
@@ -526,43 +515,43 @@ fn publish_plant_states(board: &mut MutexGuard<HAL>, timezone_time: &DateTime<Tz
}
fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard<HAL>, ip_address: &String, timezone_time: DateTime<Tz>) {
let _ = board.esp.mqtt_publish("/firmware/address", ip_address.as_bytes());
let _ = board.esp.mqtt_publish( "/firmware/githash", version.git_hash.as_bytes());
let _ = board.esp.mqtt_publish(
let _ = board.board_hal.get_esp().mqtt_publish("/firmware/address", ip_address.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/githash", version.git_hash.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/buildtime",
version.build_time.as_bytes(),
);
let _ = board.esp.mqtt_publish(
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/last_online",
timezone_time.to_rfc3339().as_bytes(),
);
let _ = board.esp.mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes());
let _ = board.esp.mqtt_publish(
let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/partition_address",
format!("{:#06x}", address).as_bytes(),
);
let _ = board.esp.mqtt_publish( "/state", "online".as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish( "/state", "online".as_bytes());
}
fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode{
let nw_conf = &board.config.network.clone();
match board.esp.wifi(nw_conf) {
let nw_conf = &board.board_hal.get_config().network.clone();
match board.board_hal.get_esp().wifi(nw_conf) {
Ok(ip_info) => {
let sntp_mode: SntpMode = match board.sntp(1000 * 10) {
let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10) {
Ok(new_time) => {
println!("Using time from sntp");
let _ = board.set_rtc_time(&new_time);
let _ = board.board_hal.set_rtc_time(&new_time);
SntpMode::SYNC {current: new_time}
}
Err(err) => {
println!("sntp error: {}", err);
board.general_fault(true);
board.board_hal.general_fault(true);
SntpMode::OFFLINE
}
};
let mqtt_connected = if let Some(_) = board.config.network.mqtt_url {
let nw_config = &board.config.network.clone();
match board.esp.mqtt(nw_config) {
let mqtt_connected = if let Some(_) = board.board_hal.get_config().network.mqtt_url {
let nw_config = &board.board_hal.get_config().network.clone();
match board.board_hal.get_esp().mqtt(nw_config) {
Ok(_) => {
println!("Mqtt connection ready");
true
@@ -583,13 +572,12 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode{
}
Err(_) => {
println!("Offline mode");
board.general_fault(true);
board.board_hal.general_fault(true);
NetworkMode::OFFLINE
}
}
}
//TODO clean this up? better state
fn pump_info(board: &mut MutexGuard<HAL>, plant_id: usize, pump_active: bool, pump_ineffective: bool) {
let pump_info = PumpInfo {
enabled: pump_active,
@@ -598,7 +586,7 @@ fn pump_info(board: &mut MutexGuard<HAL>, plant_id: usize, pump_active: bool, pu
let pump_topic = format!("/pump{}", plant_id + 1);
match serde_json::to_string(&pump_info) {
Ok(state) => {
let _ = board.esp.mqtt_publish(&pump_topic, state.as_bytes());
let _ = board.board_hal.get_esp().mqtt_publish(&pump_topic, state.as_bytes());
//reduce speed as else messages will be dropped
Delay::new_default().delay_ms(200);
}
@@ -611,8 +599,8 @@ fn pump_info(board: &mut MutexGuard<HAL>, plant_id: usize, pump_active: bool, pu
fn publish_battery_state(
board: &mut MutexGuard<'_, HAL<'_>>
) {
let state = board.battery_monitor.get_battery_state();
let _ = board.esp.mqtt_publish( "/battery", state.as_bytes());
let state = board.board_hal.get_battery_monitor().get_battery_state();
let _ = board.board_hal.get_esp().mqtt_publish( "/battery", state.as_bytes());
}
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
@@ -622,9 +610,9 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
loop {
unsafe {
let mut lock = BOARD_ACCESS.lock().unwrap();
if let Ok(charging) = lock.battery_monitor.average_current_milli_ampere() {
lock.set_charge_indicator(charging > 20)
let mut board = BOARD_ACCESS.lock().unwrap();
if let Ok(charging) = board.board_hal.get_battery_monitor().average_current_milli_ampere() {
let _ = board.board_hal.set_charge_indicator(charging > 20);
}
match wait_type {
WaitType::MissingConfig => {
@@ -632,36 +620,36 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
led_count %= 8;
led_count += 1;
for i in 0..8 {
let _ = lock.fault(i, i < led_count);
let _ = board.board_hal.fault(i, i < led_count);
}
}
WaitType::ConfigButton => {
// Alternating pattern: 1010 1010 -> 0101 0101
pattern_step = (pattern_step + 1) % 2;
for i in 0..8 {
let _ = lock.fault(i, (i + pattern_step) % 2 == 0);
let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0);
}
}
WaitType::MqttConfig => {
// Moving dot pattern
pattern_step = (pattern_step + 1) % 8;
for i in 0..8 {
let _ = lock.fault(i, i == pattern_step);
let _ = board.board_hal.fault(i, i == pattern_step);
}
}
}
lock.general_fault(true);
drop(lock);
board.board_hal.general_fault(true);
drop(board);
vTaskDelay(delay);
let mut lock = BOARD_ACCESS.lock().unwrap();
lock.general_fault(false);
let mut board = BOARD_ACCESS.lock().unwrap();
board.board_hal.general_fault(false);
// Clear all LEDs
for i in 0..8 {
let _ = lock.fault(i, false);
let _ = board.board_hal.fault(i, false);
}
drop(lock);
drop(board);
vTaskDelay(delay);
if wait_type == WaitType::MqttConfig
@@ -672,7 +660,7 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
//ensure clean http answer
Delay::new_default().delay_ms(500);
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
BOARD_ACCESS.lock().unwrap().board_hal.deep_sleep(1);
}
}
}
@@ -685,8 +673,8 @@ fn main() {
// timeout, this is just a fallback
Ok(_) => {
println!("Main app finished, restarting");
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(false);
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
BOARD_ACCESS.lock().unwrap().board_hal.get_esp().set_restart_to_conf(false);
BOARD_ACCESS.lock().unwrap().board_hal.deep_sleep(1);
}
// if safe_main exists with an error, rollback to a known good ota version
Err(err) => {