diff --git a/rust/sdkconfig.defaults b/rust/sdkconfig.defaults index 6ae7e19..534c04e 100644 --- a/rust/sdkconfig.defaults +++ b/rust/sdkconfig.defaults @@ -1,5 +1,5 @@ # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) -CONFIG_ESP_MAIN_TASK_STACK_SIZE=25000 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=50000 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). diff --git a/rust/src/main.rs b/rust/src/main.rs index 59251ae..a19ef4a 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -46,7 +46,7 @@ mod webserver { pub mod webserver; } -#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] enum OnlineMode { Offline, Wifi, @@ -54,7 +54,7 @@ enum OnlineMode { Online, } -#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] enum WaitType { InitialConfig, FlashError, @@ -62,7 +62,7 @@ enum WaitType { StayAlive, } -#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Default)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] struct LightState { active: bool, out_of_work_hour: bool, @@ -70,7 +70,7 @@ struct LightState { is_day: bool, } -#[derive(Clone, Copy, Debug, PartialEq, Default)] +#[derive(Debug, PartialEq, Default)] struct PlantState { a: Option, b: Option, @@ -91,14 +91,14 @@ struct PlantState { next_pump: Option>, } -#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] enum SensorError { Unknown, ShortCircuit { hz: f32, max: f32 }, OpenCircuit { hz: f32, min: f32 }, } -#[derive(Copy, Clone, Debug, PartialEq, Default)] +#[derive(Debug, PartialEq, Default)] struct TankState { enough_water: bool, warn_level: bool, @@ -113,7 +113,7 @@ struct TankStateMQTT { left_ml: u32, sensor_error: bool, raw: u16, - water_frozen: String + water_frozen: String, } #[derive(Serialize)] @@ -365,13 +365,13 @@ fn safe_main() -> anyhow::Result<()> { } let tank_state = determine_tank_state(&mut board, &config); - let mut tank_state_mqtt = TankStateMQTT{ - enough_water : tank_state.enough_water, - left_ml : tank_state.left_ml, - warn_level : tank_state.warn_level, + let mut tank_state_mqtt = TankStateMQTT { + enough_water: tank_state.enough_water, + left_ml: tank_state.left_ml, + warn_level: tank_state.warn_level, sensor_error: tank_state.sensor_error, raw: tank_state.raw, - water_frozen: "".to_owned() + water_frozen: "".to_owned(), }; let mut water_frozen = false; @@ -396,9 +396,7 @@ fn safe_main() -> anyhow::Result<()> { } tank_state_mqtt.water_frozen = water_frozen.to_string(); } - None => { - tank_state_mqtt.water_frozen = "tank sensor error".to_owned() - } + None => tank_state_mqtt.water_frozen = "tank sensor error".to_owned(), } if online_mode == OnlineMode::Online { @@ -412,9 +410,9 @@ fn safe_main() -> anyhow::Result<()> { }; } - let mut plantstate = [PlantState { + let mut plantstate: [PlantState; PLANT_COUNT] = core::array::from_fn(|_| PlantState { ..Default::default() - }; PLANT_COUNT]; + }); let plant_to_pump = determine_next_plant( &mut plantstate, europe_time, @@ -426,12 +424,7 @@ fn safe_main() -> anyhow::Result<()> { let stay_alive = STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed); println!("Check stay alive, current state is {}", stay_alive); - if stay_alive { - drop(board); - let reboot_now = Arc::new(AtomicBool::new(false)); - let _webserver = httpd(reboot_now.clone()); - wait_infinity(WaitType::StayAlive, reboot_now.clone()); - } + let mut did_pump = false; match plant_to_pump { Some(plant) => { @@ -461,34 +454,38 @@ fn safe_main() -> anyhow::Result<()> { "Trying to pump for {}s with pump {} now", plant_config.pump_time_s, plant ); - did_pump = true; - board.any_pump(true)?; - board.store_last_pump_time(plant, cur); - board.pump(plant, true)?; - board.last_pump_time(plant); - state.active = true; - for _ in 0..plant_config.pump_time_s { - unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) }; - let p_live_topic = format!("/plant{}/p live", plant + 1); - if plant_config.sensor_p { - let moist = map_range_moisture( - board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32, - ); - if online_mode == OnlineMode::Online { - let _ = board.mqtt_publish( - &config, - &p_live_topic, - option_to_string(moist.ok()).as_bytes(), + if !stay_alive { + did_pump = true; + board.any_pump(true)?; + board.store_last_pump_time(plant, cur); + board.pump(plant, true)?; + board.last_pump_time(plant); + state.active = true; + for _ in 0..plant_config.pump_time_s { + unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) }; + let p_live_topic = format!("/plant{} p live", plant + 1); + if plant_config.sensor_p { + let moist = map_range_moisture( + board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32, ); - } - } else { - if online_mode == OnlineMode::Online { - let _ = board.mqtt_publish(&config, &p_live_topic, "disabled".as_bytes()); + if online_mode == OnlineMode::Online { + let _ = board.mqtt_publish( + &config, + &p_live_topic, + option_to_string(&moist.ok()).as_bytes(), + ); + } + } else { + if online_mode == OnlineMode::Online { + let _ = + board.mqtt_publish(&config, &p_live_topic, "disabled".as_bytes()); + } } } + + board.pump(plant, false)?; } - board.pump(plant, false)?; if plant_config.sensor_p { match map_range_moisture( board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32, @@ -522,7 +519,7 @@ fn safe_main() -> anyhow::Result<()> { let is_day = board.is_day(); light_state.is_day = is_day; light_state.out_of_work_hour = !in_time_range( - europe_time, + &europe_time, config.night_lamp_hour_start, config.night_lamp_hour_end, ); @@ -604,16 +601,14 @@ fn safe_main() -> anyhow::Result<()> { //is deep sleep mark_app_valid(); - unsafe { esp_deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64) }; -} + if stay_alive { + drop(board); + let reboot_now = Arc::new(AtomicBool::new(false)); + let _webserver = httpd(reboot_now.clone()); + wait_infinity(WaitType::StayAlive, reboot_now.clone()); + } -fn to_string(value: Result) -> String { - return match value { - Ok(v) => v.to_string(), - Err(err) => { - format!("{:?}", err) - } - }; + unsafe { esp_deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64) }; } fn publish_battery_state( @@ -621,13 +616,13 @@ fn publish_battery_state( config: &Config, ) { let bat = BatteryState { - voltage_milli_volt: &to_string(board.voltage_milli_volt()), - current_milli_ampere: &to_string(board.average_current_milli_ampere()), - cycle_count: &to_string(board.cycle_count()), - design_milli_ampere: &to_string(board.design_milli_ampere_hour()), - remaining_milli_ampere: &to_string(board.remaining_milli_ampere_hour()), - state_of_charge: &to_string(board.state_charge_percent()), - state_of_health: &to_string(board.state_health_percent()), + voltage_milli_volt: &to_string(&board.voltage_milli_volt()), + current_milli_ampere: &to_string(&board.average_current_milli_ampere()), + cycle_count: &to_string(&board.cycle_count()), + design_milli_ampere: &to_string(&board.design_milli_ampere_hour()), + remaining_milli_ampere: &to_string(&board.remaining_milli_ampere_hour()), + state_of_charge: &to_string(&board.state_charge_percent()), + state_of_health: &to_string(&board.state_health_percent()), }; match serde_json::to_string(&bat) { Ok(state) => { @@ -701,54 +696,6 @@ fn determine_tank_state( }; } -fn map_range(from_range: (f32, f32), s: f32) -> anyhow::Result { - if s < from_range.0 { - anyhow::bail!( - "Value out of range, min {} but current is {}", - from_range.0, - s - ); - } - if s > from_range.1 { - anyhow::bail!( - "Value out of range, max {} but current is {}", - from_range.1, - s - ); - } - return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0)); -} - -fn map_range_moisture(s: f32) -> Result { - if s < FROM.0 { - return Err(SensorError::OpenCircuit { hz: s, min: FROM.0 }); - } - if s > FROM.1 { - return Err(SensorError::ShortCircuit { hz: s, max: FROM.1 }); - } - let tmp = TO.0 + (s - FROM.0) * (TO.1 - TO.0) / (FROM.1 - FROM.0); - - return Ok(tmp as u8); -} - -fn in_time_range(cur: DateTime, start: u8, end: u8) -> bool { - let curhour = cur.hour() as u8; - //eg 10-14 - if start < end { - return curhour > start && curhour < end; - } else { - //eg 20-05 - return curhour > start || curhour < end; - } -} - -fn option_to_string(value: Option) -> String { - match value { - Some(v) => v.to_string(), - None => "Error".to_owned(), - } -} - fn determine_state_target_moisture_for_plant( board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, plant: usize, @@ -761,7 +708,20 @@ fn determine_state_target_moisture_for_plant( if plant_config.mode == Mode::OFF { return; } - + match board.measure_moisture_hz(plant, plant_hal::Sensor::A) { + Ok(a) => { + let mapped = map_range_moisture(a as f32); + match mapped { + Ok(result) => state.a = Some(result), + Err(err) => { + state.sensor_error_a = Some(err); + } + } + } + Err(_) => { + state.sensor_error_a = Some(SensorError::Unknown); + } + } if plant_config.sensor_b { match board.measure_moisture_hz(plant, plant_hal::Sensor::B) { Ok(b) => { @@ -813,7 +773,7 @@ fn determine_state_target_moisture_for_plant( } if !in_time_range( - cur, + &cur, plant_config.pump_hour_start, plant_config.pump_hour_end, ) { @@ -883,7 +843,7 @@ fn determine_state_timer_and_deadzone_for_plant( state.cooldown = true; } if !in_time_range( - cur, + &cur, plant_config.pump_hour_start, plant_config.pump_hour_end, ) { @@ -976,13 +936,17 @@ fn update_plant_state( let mode = format!("{:?}", plant_config.mode); let plant_dto = PlantStateMQTT { - p_start: &sensor_to_string(state.p, state.sensor_error_p, plant_config.sensor_p), - p_end: &sensor_to_string(state.after_p, state.sensor_error_p, plant_config.sensor_p), - a: &sensor_to_string(state.a, state.sensor_error_a, true), - b: &sensor_to_string(state.b, state.sensor_error_b, plant_config.sensor_b), + p_start: &sensor_to_string(&state.p, &state.sensor_error_p, plant_config.sensor_p), + p_end: &sensor_to_string(&state.after_p, &state.sensor_error_p, plant_config.sensor_p), + a: &sensor_to_string( + &state.a, + &state.sensor_error_a, + plant_config.mode != Mode::OFF, + ), + b: &sensor_to_string(&state.b, &state.sensor_error_b, plant_config.sensor_b), active: state.active, mode: &mode, - last_pump: &&time_to_string_utc(board.last_pump_time(plant)), + last_pump: &time_to_string_utc(board.last_pump_time(plant)), next_pump: &time_to_string(state.next_pump), consecutive_pump_count: state.consecutive_pump_count, cooldown: state.cooldown, @@ -996,6 +960,8 @@ fn update_plant_state( Ok(state) => { let plant_topic = format!("/plant{}", plant + 1); let _ = board.mqtt_publish(&config, &plant_topic, state.as_bytes()); + //reduce speed as else messages will be dropped + Delay::new_default().delay_ms(200); } Err(err) => { println!("Error publishing lightstate {}", err); @@ -1004,39 +970,6 @@ fn update_plant_state( } } -fn time_to_string_utc(value_option: Option>) -> String { - let converted = value_option.and_then(|utc| Some(utc.with_timezone(&Berlin))); - return time_to_string(converted); -} - -fn time_to_string(value_option: Option>) -> String { - match value_option { - Some(value) => { - let europe_time = value.with_timezone(&Berlin); - if europe_time.year() > 2023 { - return europe_time.to_rfc3339(); - } else { - return "smtp error".to_owned(); - } - } - None => return "N/A".to_owned(), - }; -} - -fn sensor_to_string(value: Option, error: Option, enabled: bool) -> String { - if enabled { - match error { - Some(error) => return format!("{:?}", error), - None => match value { - Some(v) => return v.to_string(), - None => return "Error".to_owned(), - }, - } - } else { - return "disabled".to_owned(); - }; -} - fn wait_infinity(wait_type: WaitType, reboot_now: Arc) -> ! { let delay = match wait_type { WaitType::InitialConfig => 250_u32, @@ -1091,7 +1024,94 @@ fn main() { } } } -//error codes -//error_reading_config_after_upgrade -//error_no_config_after_upgrade -//error_tank_sensor_fault + +fn time_to_string_utc(value_option: Option>) -> String { + let converted = value_option.and_then(|utc| Some(utc.with_timezone(&Berlin))); + return time_to_string(converted); +} + +fn time_to_string(value_option: Option>) -> String { + match value_option { + Some(value) => { + let europe_time = value.with_timezone(&Berlin); + if europe_time.year() > 2023 { + return europe_time.to_rfc3339(); + } else { + //initial value of 0 in rtc memory + return "N/A".to_owned(); + } + } + None => return "N/A".to_owned(), + }; +} + +fn sensor_to_string(value: &Option, error: &Option, enabled: bool) -> String { + if enabled { + match error { + Some(error) => return format!("{:?}", error), + None => match value { + Some(v) => return v.to_string(), + None => return "Error".to_owned(), + }, + } + } else { + return "disabled".to_owned(); + }; +} + +fn to_string(value: &Result) -> String { + return match value { + Ok(v) => v.to_string(), + Err(err) => { + format!("{:?}", err) + } + }; +} + +fn map_range(from_range: (f32, f32), s: f32) -> anyhow::Result { + if s < from_range.0 { + anyhow::bail!( + "Value out of range, min {} but current is {}", + from_range.0, + s + ); + } + if s > from_range.1 { + anyhow::bail!( + "Value out of range, max {} but current is {}", + from_range.1, + s + ); + } + return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0)); +} + +fn map_range_moisture(s: f32) -> Result { + if s < FROM.0 { + return Err(SensorError::OpenCircuit { hz: s, min: FROM.0 }); + } + if s > FROM.1 { + return Err(SensorError::ShortCircuit { hz: s, max: FROM.1 }); + } + let tmp = TO.0 + (s - FROM.0) * (TO.1 - TO.0) / (FROM.1 - FROM.0); + + return Ok(tmp as u8); +} + +fn in_time_range(cur: &DateTime, start: u8, end: u8) -> bool { + let curhour = cur.hour() as u8; + //eg 10-14 + if start < end { + return curhour > start && curhour < end; + } else { + //eg 20-05 + return curhour > start || curhour < end; + } +} + +fn option_to_string(value: &Option) -> String { + match value { + Some(v) => v.to_string(), + None => "Error".to_owned(), + } +} diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 4ef49b9..0a902ec 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -17,7 +17,7 @@ use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfi use esp_idf_svc::nvs::EspDefaultNvsPartition; use esp_idf_svc::wifi::config::{ScanConfig, ScanType}; use esp_idf_svc::wifi::EspWifi; -use measurements::{Frequency, Temperature}; +use measurements::Temperature; use plant_ctrl2::sipo::ShiftRegister40; use anyhow::anyhow;