Merge branch 'feature/12v' of ssh://git.mannheim.ccc.de:1337/C3MA/PlantCtrl into feature/12v
This commit is contained in:
commit
3e31b4c041
@ -6,20 +6,37 @@ fn main() {
|
||||
Command::new("rm")
|
||||
.arg("./src/webserver/bundle.js")
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
.unwrap();
|
||||
|
||||
let output = Command::new("npx")
|
||||
match Command::new("cmd").spawn() {
|
||||
Ok(_) => {
|
||||
println!("Assuming build on windows");
|
||||
let output = Command::new("cmd")
|
||||
.arg("/K")
|
||||
.arg("npx")
|
||||
.arg("webpack")
|
||||
.current_dir("./src_webpack")
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
.unwrap();
|
||||
println!("status: {}", output.status);
|
||||
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||
|
||||
assert!(output.status.success());
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Assuming build on linux");
|
||||
let output = Command::new("bash")
|
||||
.arg("webpack")
|
||||
.current_dir("./src_webpack")
|
||||
.output()
|
||||
.unwrap();
|
||||
println!("status: {}", output.status);
|
||||
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||
assert!(output.status.success());
|
||||
}
|
||||
}
|
||||
|
||||
embuild::espidf::sysenv::output();
|
||||
let _ = EmitBuilder::builder().all_git().emit();
|
||||
let _ = EmitBuilder::builder().all_git().all_build().emit();
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
|
||||
toolchain = "esp"
|
||||
|
@ -5,4 +5,4 @@ CONFIG_ESP_MAIN_TASK_STACK_SIZE=20000
|
||||
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=true
|
||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
@ -60,7 +60,7 @@ pub struct Plant {
|
||||
pub pump_hour_start: u8,
|
||||
pub pump_hour_end: u8,
|
||||
pub sensor_b: bool,
|
||||
pub sensor_p: bool
|
||||
pub sensor_p: bool,
|
||||
}
|
||||
impl Default for Plant {
|
||||
fn default() -> Self {
|
||||
@ -72,7 +72,7 @@ impl Default for Plant {
|
||||
pump_hour_end: 20,
|
||||
mode: Mode::OFF,
|
||||
sensor_b: false,
|
||||
sensor_p : false
|
||||
sensor_p: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
182
rust/src/main.rs
182
rust/src/main.rs
@ -1,16 +1,11 @@
|
||||
use std::{
|
||||
env,
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
};
|
||||
use std::sync::{atomic::AtomicBool, Arc, Mutex};
|
||||
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike};
|
||||
use chrono_tz::{Europe::Berlin, Tz};
|
||||
use config::Plant;
|
||||
use embedded_svc::mqtt;
|
||||
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_sys::{
|
||||
esp_deep_sleep, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay,
|
||||
CONFIG_FREERTOS_HZ,
|
||||
esp_deep_sleep, esp_ota_get_app_partition_count, esp_ota_get_running_partition, esp_ota_get_state_partition, esp_ota_img_states_t, esp_ota_img_states_t_ESP_OTA_IMG_ABORTED, esp_ota_img_states_t_ESP_OTA_IMG_INVALID, esp_ota_img_states_t_ESP_OTA_IMG_NEW, esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY, esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED, esp_ota_img_states_t_ESP_OTA_IMG_VALID, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay, CONFIG_FREERTOS_HZ
|
||||
};
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
@ -19,7 +14,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::{Config, WifiConfig},
|
||||
espota::rollback_and_reboot,
|
||||
espota::{mark_app_valid, rollback_and_reboot},
|
||||
webserver::webserver::{httpd, httpd_initial},
|
||||
};
|
||||
mod config;
|
||||
@ -84,7 +79,7 @@ struct PlantState {
|
||||
sensor_error_b: Option<SensorError>,
|
||||
sensor_error_p: Option<SensorError>,
|
||||
out_of_work_hour: bool,
|
||||
next_pump: Option<DateTime<Tz>>
|
||||
next_pump: Option<DateTime<Tz>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
@ -121,28 +116,41 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
log::info!("Startup Rust");
|
||||
|
||||
let git_hash = env!("VERGEN_GIT_DESCRIBE");
|
||||
println!("Version useing git has {}", git_hash);
|
||||
let build_timestamp = env!("VERGEN_BUILD_TIMESTAMP");
|
||||
println!(
|
||||
"Version useing git has {} build on {}",
|
||||
git_hash, build_timestamp
|
||||
);
|
||||
|
||||
let partition_state: embedded_svc::ota::SlotState = embedded_svc::ota::SlotState::Unknown;
|
||||
match esp_idf_svc::ota::EspOta::new() {
|
||||
Ok(ota) => {
|
||||
//match ota.get_running_slot(){
|
||||
// Ok(slot) => {
|
||||
// partition_state = slot.state;
|
||||
// println!(
|
||||
// "Booting from {} with state {:?}",
|
||||
// slot.label, partition_state
|
||||
// );
|
||||
//},
|
||||
// Err(err) => {
|
||||
// println!("Error getting running slot {}", err);
|
||||
// },
|
||||
//}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error obtaining ota info {}", err);
|
||||
}
|
||||
let count = unsafe { esp_ota_get_app_partition_count() };
|
||||
println!("Partition count is {}", count);
|
||||
let mut ota_state: esp_ota_img_states_t = 0;
|
||||
let running_partition = unsafe { esp_ota_get_running_partition() };
|
||||
let address = unsafe {
|
||||
(*running_partition).address
|
||||
|
||||
};
|
||||
println!("Partition address is {}", address);
|
||||
|
||||
let ota_state_string = unsafe {
|
||||
esp_ota_get_state_partition(running_partition, &mut ota_state);
|
||||
if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_NEW {
|
||||
format!("Partition state is {}", "ESP_OTA_IMG_NEW")
|
||||
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY {
|
||||
format!("Partition state is {}", "ESP_OTA_IMG_PENDING_VERIFY")
|
||||
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_VALID {
|
||||
format!("Partition state is {}", "ESP_OTA_IMG_VALID")
|
||||
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_INVALID {
|
||||
format!("Partition state is {}", "ESP_OTA_IMG_INVALID")
|
||||
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_ABORTED {
|
||||
format!("Partition state is {}", "ESP_OTA_IMG_ABORTED")
|
||||
} else if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED {
|
||||
format!("Partition state is {}", "ESP_OTA_IMG_UNDEFINED")
|
||||
} else {
|
||||
format!("Partition state is {}", ota_state)
|
||||
}
|
||||
};
|
||||
println!("{}", ota_state_string);
|
||||
|
||||
println!("Board hal init");
|
||||
let mut board: std::sync::MutexGuard<'_, PlantCtrlBoard<'_>> = BOARD_ACCESS.lock().unwrap();
|
||||
@ -212,14 +220,10 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
}
|
||||
Err(err) => {
|
||||
if board.is_wifi_config_file_existant() {
|
||||
match partition_state {
|
||||
embedded_svc::ota::SlotState::Invalid
|
||||
| embedded_svc::ota::SlotState::Unverified => {
|
||||
if ota_state == esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY {
|
||||
println!("Config seem to be unparsable after upgrade, reverting");
|
||||
rollback_and_reboot()?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
println!("Missing wifi config, entering initial config mode {}", err);
|
||||
board.wifi_ap().unwrap();
|
||||
@ -289,15 +293,26 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
|
||||
if online_mode == OnlineMode::Online {
|
||||
let _ = board.mqtt_publish(&config, "/firmware/githash", git_hash.as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/buildtime", build_timestamp.as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/last_online", europe_time.to_rfc3339().as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/ota_state", ota_state_string.as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/partition_address", format!("{:#06x}",address).as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/state", "online".as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/last_online", europe_time.to_rfc3339().as_bytes());
|
||||
|
||||
|
||||
|
||||
publish_battery_state(&mut board, &config);
|
||||
}
|
||||
|
||||
let tank_state = determine_tank_state(&mut board, &config);
|
||||
if online_mode == OnlineMode::Online {
|
||||
if tank_state.sensor_error {
|
||||
let _ = board.mqtt_publish(&config, "/water/ml", "error".to_string().as_bytes());
|
||||
} else {
|
||||
let _ = board.mqtt_publish(&config, "/water/ml", tank_state.left_ml.to_string().as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/water/enough_water", tank_state.enough_water.to_string().as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/water/raw", tank_state.raw.to_string().as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut water_frozen = false;
|
||||
for _attempt in 0..5 {
|
||||
@ -354,10 +369,9 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
board.store_consecutive_pump_count(plant, consecutive_pump_count);
|
||||
let plant_config = config.plants[plant];
|
||||
|
||||
|
||||
if plant_config.sensor_p {
|
||||
match map_range_moisture(
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32,
|
||||
) {
|
||||
Ok(p) => state.p = Some(p),
|
||||
Err(err) => {
|
||||
@ -377,19 +391,18 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
board.pump(plant, true)?;
|
||||
board.last_pump_time(plant);
|
||||
state.active = true;
|
||||
for t in 0..plant_config.pump_time_s {
|
||||
//FIXME do periodic pump test here and state update
|
||||
for _ in 0..plant_config.pump_time_s {
|
||||
unsafe { vTaskDelay(CONFIG_FREERTOS_HZ) };
|
||||
if plant_config.sensor_p {
|
||||
let moist = map_range_moisture(
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32);
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32,
|
||||
);
|
||||
if online_mode == OnlineMode::Online {
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
format!("/plant{}/Sensor P after", plant + 1).as_str(),
|
||||
option_to_string(moist.ok()).as_bytes(),
|
||||
);
|
||||
|
||||
}
|
||||
} else {
|
||||
if online_mode == OnlineMode::Online {
|
||||
@ -400,13 +413,12 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
board.pump(plant, false)?;
|
||||
if plant_config.sensor_p {
|
||||
match map_range_moisture(
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32,
|
||||
) {
|
||||
Ok(p) => state.after_p = Some(p),
|
||||
Err(err) => {
|
||||
@ -423,7 +435,6 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
//mqtt sync pump error value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
None => {
|
||||
println!("Nothing to do");
|
||||
@ -435,7 +446,8 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
let mut light_state = LightState {
|
||||
..Default::default()
|
||||
};
|
||||
light_state.is_day = board.is_day();
|
||||
let is_day = board.is_day();
|
||||
light_state.is_day = is_day;
|
||||
light_state.out_of_work_hour = !in_time_range(
|
||||
europe_time,
|
||||
config.night_lamp_hour_start,
|
||||
@ -490,12 +502,42 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
unsafe { gpio_deep_sleep_hold_dis() };
|
||||
unsafe { gpio_deep_sleep_hold_en() };
|
||||
|
||||
let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 {
|
||||
if online_mode == OnlineMode::Online {
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/deepsleep",
|
||||
"Entering low voltage long deep sleep".as_bytes(),
|
||||
);
|
||||
}
|
||||
12 * 60
|
||||
} else if is_day {
|
||||
if online_mode == OnlineMode::Online {
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/deepsleep",
|
||||
"Entering normal mode 20m deep sleep".as_bytes(),
|
||||
);
|
||||
}
|
||||
20
|
||||
} else {
|
||||
if online_mode == OnlineMode::Online {
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/deepsleep",
|
||||
"Entering night mode 1h deep sleep".as_bytes(),
|
||||
);
|
||||
}
|
||||
60
|
||||
};
|
||||
//determine next event
|
||||
//is light out of work trigger soon?
|
||||
//is battery low ??
|
||||
//is deep sleep
|
||||
|
||||
unsafe { esp_deep_sleep(1000 * 1000 * 20) };
|
||||
mark_app_valid();
|
||||
|
||||
unsafe { esp_deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64) };
|
||||
}
|
||||
|
||||
fn publish_battery_state(
|
||||
@ -771,9 +813,10 @@ fn determine_state_target_moisture_for_plant(
|
||||
|
||||
if a_low || b_low {
|
||||
state.dry = true;
|
||||
if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error
|
||||
|| !tank_state.enough_water
|
||||
{
|
||||
if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error {
|
||||
//ignore is ok
|
||||
}
|
||||
else if !tank_state.enough_water {
|
||||
state.no_water = true;
|
||||
}
|
||||
}
|
||||
@ -901,11 +944,26 @@ fn determine_next_plant(
|
||||
return None;
|
||||
}
|
||||
|
||||
fn update_plant_state(plantstate: &mut [PlantState; PLANT_COUNT], board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>, config: &Config){
|
||||
fn update_plant_state(
|
||||
plantstate: &mut [PlantState; PLANT_COUNT],
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
config: &Config,
|
||||
) {
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let state = &plantstate[plant];
|
||||
let plant_config = config.plants[plant];
|
||||
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
format!("/plant{}/mode", plant + 1).as_str(),
|
||||
match plant_config.mode {
|
||||
config::Mode::OFF => "OFF".as_bytes(),
|
||||
config::Mode::TargetMoisture => "TargetMoisture".as_bytes(),
|
||||
config::Mode::TimerOnly => "TimerOnly".as_bytes(),
|
||||
config::Mode::TimerAndDeadzone => "TimerAndDeadzone".as_bytes(),
|
||||
},
|
||||
);
|
||||
|
||||
let last_time = board.last_pump_time(plant);
|
||||
let europe_time = last_time.with_timezone(&Berlin);
|
||||
if europe_time.year() > 2023 {
|
||||
@ -931,16 +989,15 @@ fn update_plant_state(plantstate: &mut [PlantState; PLANT_COUNT], board: &mut st
|
||||
format!("/plant{}/next pump", plant + 1).as_str(),
|
||||
time.as_bytes(),
|
||||
);
|
||||
},
|
||||
}
|
||||
None => {
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
format!("/plant{}/next pump", plant + 1).as_str(),
|
||||
"N/A".as_bytes(),
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
@ -1069,7 +1126,18 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
|
||||
fn main() {
|
||||
let result = safe_main();
|
||||
result.unwrap();
|
||||
match result {
|
||||
Ok(_) => {
|
||||
println!("Main app finished, restarting");
|
||||
unsafe { esp_restart() };
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Failed main {}", err);
|
||||
let rollback_successful = rollback_and_reboot();
|
||||
println!("Failed to rollback :(");
|
||||
rollback_successful.unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
//error codes
|
||||
//error_reading_config_after_upgrade
|
||||
|
@ -159,7 +159,6 @@ pub struct PlantCtrlBoard<'a> {
|
||||
PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
|
||||
>,
|
||||
low_voltage_detected: Mutex<bool>,
|
||||
tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
|
||||
tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
|
||||
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::Gpio25, esp_idf_hal::gpio::Input>,
|
||||
@ -963,8 +962,6 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
let nvs = EspDefaultNvsPartition::take()?;
|
||||
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
|
||||
|
||||
let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED });
|
||||
|
||||
let adc_config = esp_idf_hal::adc::config::Config {
|
||||
resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit,
|
||||
calibration: true,
|
||||
@ -1001,7 +998,6 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
}
|
||||
let rv = Mutex::new(PlantCtrlBoard {
|
||||
shift_register,
|
||||
low_voltage_detected,
|
||||
tank_driver,
|
||||
tank_channel,
|
||||
solar_is_day,
|
||||
|
Loading…
Reference in New Issue
Block a user