startup and ota state detection working, now wifi_ap next

This commit is contained in:
Empire Phoenix 2025-09-13 20:56:11 +02:00
parent 4160202cdc
commit be3c4a5095
9 changed files with 294 additions and 225 deletions

View File

@ -4,6 +4,15 @@ name = "plant-ctrl2"
rust-version = "1.86"
version = "0.1.0"
# Explicitly configure the binary target and disable building it as a test/bench.
[[bin]]
name = "plant-ctrl2"
path = "src/main.rs"
# Prevent IDEs/Cargo from trying to compile a test harness for this no_std binary.
test = false
bench = false
doc = false
[package.metadata.cargo_runner]
# The string `$TARGET_FILE` will be replaced with the path from cargo.
command = [
@ -66,6 +75,8 @@ esp-backtrace = { version = "0.17.0", features = [
"exception-handler",
"panic-handler",
"println",
"colors",
"custom-halt"
] }
esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] }
# for more networking protocol support see https://crates.io/crates/edge-net
@ -75,14 +86,16 @@ embassy-executor = { version = "0.7.0", features = [
] }
embassy-time = { version = "0.4.0", features = ["log"] }
esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] }
#esp-wifi = { version = "0.15.0", features = [
# "builtin-scheduler",
# "esp-alloc",
# "esp32c6",
# "log-04",
# "smoltcp",
# "wifi",
#] }
esp-storage = { version = "0.7.0", features = ["esp32c6"] }
esp-wifi = { version = "0.15.0", features = [
"builtin-scheduler",
"esp-alloc",
"esp32c6",
"log-04",
"smoltcp",
"wifi",
] }
#smoltcp = { version = "0.12.0", default-features = false, features = [
# "log",
# "medium-ethernet",
@ -126,7 +139,6 @@ eeprom24x = "0.7.2"
crc = "3.2.1"
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"] }
ringbuffer = "0.15.0"
#text-template = "0.1.0"
strum_macros = "0.27.0"
#esp-ota = { version = "0.2.2", features = ["log"] }
unit-enum = "1.4.1"
@ -139,6 +151,10 @@ portable-atomic = "1.11.1"
embassy-sync = { version = "0.7.2", features = ["log"] }
async-trait = "0.1.89"
bq34z100 = { version = "0.4.0", default-features = false }
edge-dhcp = "0.6.0"
edge-nal = "0.5.0"
edge-nal-embassy = "0.6.0"
static_cell = "2.1.1"
[patch.crates-io]

View File

@ -50,13 +50,13 @@ fn linker_be_nice() {
}
fn main() {
//webpack();
//linker_be_nice();
webpack();
linker_be_nice();
let _ = EmitBuilder::builder().all_git().all_build().emit();
}
fn webpack() {
println!("cargo:rerun-if-changed=./src/src_webpack");
//println!("cargo:rerun-if-changed=./src/src_webpack");
Command::new("rm")
.arg("./src/webserver/bundle.js")
.output()

View File

@ -1,23 +1,20 @@
use alloc::string::String;
use crate::hal::Box;
use anyhow::anyhow;
use alloc::string::String;
use async_trait::async_trait;
use bq34z100::{Bq34Z100Error, Bq34z100g1Driver};
use measurements::Temperature;
use serde::Serialize;
#[async_trait]
pub trait BatteryInteraction {
async fn state_charge_percent(& mut self) -> Result<f32, BatteryError>;
async fn remaining_milli_ampere_hour(& mut self) -> Result<u16, BatteryError>;
async fn max_milli_ampere_hour(& mut self) -> Result<u16, BatteryError>;
async fn design_milli_ampere_hour(& mut self) -> Result<u16, BatteryError>;
async fn voltage_milli_volt(& mut self) -> Result<u16, BatteryError>;
async fn average_current_milli_ampere(& mut self) -> Result<i16, BatteryError>;
async fn cycle_count(& mut self) -> Result<u16, BatteryError>;
async fn state_health_percent(& mut self) -> Result<u16, BatteryError>;
async fn bat_temperature(& mut self) -> Result<u16, BatteryError>;
async fn get_battery_state(& mut self) -> Result<BatteryState, BatteryError>;
async fn state_charge_percent(&mut self) -> Result<f32, BatteryError>;
async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>;
async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>;
async fn cycle_count(&mut self) -> Result<u16, BatteryError>;
async fn state_health_percent(&mut self) -> Result<u16, BatteryError>;
async fn bat_temperature(&mut self) -> Result<u16, BatteryError>;
async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>;
}
#[derive(Debug, Serialize)]
@ -56,31 +53,31 @@ pub enum BatteryState {
pub struct NoBatteryMonitor {}
#[async_trait]
impl BatteryInteraction for NoBatteryMonitor {
async fn state_charge_percent(& mut self) -> Result<f32, BatteryError> {
async fn state_charge_percent(&mut self) -> Result<f32, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
async fn remaining_milli_ampere_hour(& mut self) -> Result<u16, BatteryError> {
async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
async fn max_milli_ampere_hour(& mut self) -> Result<u16, BatteryError> {
async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
async fn design_milli_ampere_hour(& mut self) -> Result<u16, BatteryError> {
async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
async fn voltage_milli_volt(& mut self) -> Result<u16, BatteryError> {
async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
async fn average_current_milli_ampere(& mut self) -> Result<i16, BatteryError> {
async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
async fn cycle_count(& mut self) -> Result<u16, BatteryError> {
async fn cycle_count(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
@ -92,7 +89,7 @@ impl BatteryInteraction for NoBatteryMonitor {
Err(BatteryError::NoBatteryMonitor)
}
async fn get_battery_state(& mut self) -> Result<BatteryState, BatteryError> {
async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> {
Ok(BatteryState::Unknown)
}
}

View File

@ -6,14 +6,14 @@ use anyhow::{anyhow, bail, Context};
use chrono::{DateTime, Utc};
use serde::Serialize;
use alloc::{
string::{String, ToString},
vec::Vec,
};
use alloc::{string::String, vec::Vec};
use core::marker::PhantomData;
use core::net::IpAddr;
use core::str::FromStr;
use embassy_time::{Instant, Timer};
use embassy_time::Instant;
use esp_bootloader_esp_idf::ota::OtaImageState;
use esp_hal::gpio::Input;
use esp_storage::FlashStorage;
#[link_section = ".rtc.data"]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
@ -51,11 +51,16 @@ pub struct MqttClient<'a> {
base_topic: heapless::String<64>,
}
pub struct Esp<'a> {
pub boot_button: Input<'a>,
pub(crate) mqtt_client: Option<MqttClient<'a>>,
pub(crate) dummy: PhantomData<&'a ()>,
pub(crate) wall_clock_offset: u64
pub(crate) wall_clock_offset: u64,
//pub(crate) wifi_driver: EspWifi<'a>,
//pub(crate) boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
pub storage: FlashStorage,
pub slot: usize,
pub next_slot: usize,
pub ota_state: OtaImageState,
}
pub struct IpInfo {
@ -72,10 +77,9 @@ impl Esp<'_> {
const BASE_PATH: &'static str = "/spiffs";
pub(crate) fn mode_override_pressed(&mut self) -> bool {
todo!();
//self.boot_button.get_level() == Level::Low
self.boot_button.is_low()
}
pub(crate) async fn sntp(&mut self, max_wait_ms: u32) -> anyhow::Result<DateTime<Utc>> {
pub(crate) async fn sntp(&mut self, _max_wait_ms: u32) -> anyhow::Result<DateTime<Utc>> {
//let sntp = sntp::EspSntp::new_default()?;
//let mut counter = 0;
//while sntp.get_sync_status() != SyncStatus::Completed {
@ -147,10 +151,11 @@ impl Esp<'_> {
}
pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<()> {
let ssid = match self.load_config() {
let _ssid = match self.load_config() {
Ok(config) => config.network.ap_ssid.clone(),
Err(_) => heapless::String::from_str("PlantCtrl Emergency Mode").unwrap(),
};
todo!("todo");
//
// let apconfig = AccessPointConfiguration {
@ -165,15 +170,13 @@ impl Esp<'_> {
// anyhow::Ok(())
}
pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result<IpInfo> {
let ssid = network_config
let _ssid = network_config
.ssid
.clone()
.ok_or(anyhow!("No ssid configured"))?;
let password = network_config.password.clone();
let max_wait = network_config.max_wait;
let _password = network_config.password.clone();
let _max_wait = network_config.max_wait;
bail!("todo")
// match password {
// Some(pw) => {
@ -237,7 +240,7 @@ impl Esp<'_> {
}
pub(crate) async fn save_config(
&mut self,
config: &PlantControllerConfig,
_config: &PlantControllerConfig,
) -> anyhow::Result<()> {
bail!("todo");
// let mut cfg = File::create(Self::CONFIG_FILE)?;
@ -247,9 +250,9 @@ impl Esp<'_> {
}
pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> {
bail!("fail");
log(LogMessage::MountingFilesystem, 0, 0, "", "");
let base_path = String::try_from("/spiffs")?;
let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?;
// log(LogMessage::MountingFilesystem, 0, 0, "", "");
// let base_path = String::try_from("/spiffs")?;
// let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?;
//let conf = todo!();
//let conf = esp_idf_sys::esp_vfs_spiffs_conf_t {
@ -272,7 +275,7 @@ impl Esp<'_> {
// &free_space.used_size.to_string(),
// "",
// );
anyhow::Ok(())
// anyhow::Ok(())
}
async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> {
bail!("fail");
@ -348,7 +351,7 @@ impl Esp<'_> {
// iter_error,
// }
}
pub(crate) async fn delete_file(&self, filename: &str) -> anyhow::Result<()> {
pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> {
bail!("todo");
// let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
// match fs::remove_file(filepath) {
@ -425,7 +428,7 @@ impl Esp<'_> {
if base_topic.is_empty() {
bail!("Mqtt base_topic was empty")
}
let base_topic_copy = base_topic.clone();
let _base_topic_copy = base_topic.clone();
let mqtt_url = network_config
.mqtt_url
.as_ref()
@ -583,8 +586,8 @@ impl Esp<'_> {
}
pub(crate) async fn mqtt_publish(
&mut self,
subtopic: &str,
message: &[u8],
_subtopic: &str,
_message: &[u8],
) -> anyhow::Result<()> {
bail!("todo");
//

View File

@ -1,7 +1,8 @@
use alloc::vec::Vec;
use crate::hal::esp::Esp;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
use alloc::vec::Vec;
//use crate::hal::water::TankSensor;
use crate::alloc::boxed::Box;
use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor};
use crate::{
config::PlantControllerConfig,
@ -10,9 +11,7 @@ use crate::{
use anyhow::{bail, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use embedded_hal::digital::OutputPin;
use measurements::{Current, Voltage};
use crate::alloc::boxed::Box;
pub struct Initial<'a> {
//pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
@ -49,7 +48,7 @@ impl RTCModuleInteraction for NoRTC {
pub(crate) fn create_initial_board(
//free_pins: FreePeripherals,
fs_mount_error: bool,
_fs_mount_error: bool,
config: PlantControllerConfig,
esp: Esp<'static>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>> {
@ -123,7 +122,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
bail!("Please configure board revision")
}
async fn general_fault(&mut self, enable: bool) {
async fn general_fault(&mut self, _enable: bool) {
//let _ = self.general_fault.set_state(enable.into());
}
@ -134,7 +133,7 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
async fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> {
self.config = config;
//TODO
// self.esp.save_config(&self.config)?;
// self.esp.save_config(&self.config)?;
anyhow::Ok(())
}

View File

@ -5,7 +5,7 @@ mod rtc;
//mod water;
use crate::alloc::string::ToString;
use crate::hal::rtc::{RTCModuleInteraction};
use crate::hal::rtc::RTCModuleInteraction;
//use crate::hal::water::TankSensor;
use crate::{
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
@ -17,20 +17,24 @@ use crate::{
};
use alloc::boxed::Box;
use alloc::format;
use core::marker::PhantomData;
use anyhow::{Ok, Result};
use async_trait::async_trait;
use core::marker::PhantomData;
use embassy_executor::Spawner;
//use battery::BQ34Z100G1;
use bq34z100::Bq34z100g1Driver;
use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex};
//use bq34z100::Bq34z100g1Driver;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_sync::lazy_lock::LazyLock;
use esp_bootloader_esp_idf::partitions::DataPartitionSubType;
use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Input, InputConfig, Pull};
use esp_hal::timer::systimer::SystemTimer;
use esp_println::println;
use measurements::{Current, Voltage};
use esp_alloc as _;
use esp_backtrace as _;
//Only support for 8 right now!
pub const PLANT_COUNT: usize = 8;
@ -38,7 +42,17 @@ const TANK_MULTI_SAMPLE: usize = 11;
//pub static I2C_DRIVER: LazyLock<Mutex<CriticalSectionRawMutex,I2cDriver<'static>>> = LazyLock::new(PlantHal::create_i2c);
fn deep_sleep(duration_in_ms: u64) -> ! {
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
fn deep_sleep(_duration_in_ms: u64) -> ! {
//unsafe {
// //if we don't do this here, we might just revert newly flashed firmware
// mark_app_valid();
@ -95,7 +109,6 @@ pub trait BoardInteraction<'a> {
async fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
}
impl dyn BoardInteraction<'_> {
//the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons
async fn _progress(&mut self, counter: u32) {
@ -163,17 +176,19 @@ impl PlantHal {
// Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap())
// }
pub fn create() -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>> {
pub fn create(spawner: Spawner) -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>> {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
esp_alloc::heap_allocator!(size: 64 * 1024);
let timer0 = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(timer0.alarm0);
let systimer = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(systimer.alarm0);
let boot_button = Input::new(
peripherals.GPIO9,
InputConfig::default().with_pull(Pull::None),
);
// let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?;
// boot_button.set_pull(Pull::Floating)?;
//
// let free_pins = FreePeripherals {
// can: peripherals.can,
@ -210,11 +225,44 @@ impl PlantHal {
// gpio30: peripherals.pins.gpio30,
// };
//
let mut storage = esp_storage::FlashStorage::new();
let mut buffer = [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN];
let pt =
esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buffer)?;
// List all partitions - this is just FYI
for i in 0..pt.len() {
println!("{:?}", pt.get_partition(i));
}
// Find the OTA-data partition and show the currently active partition
let ota_part = pt
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
DataPartitionSubType::Ota,
))?
.unwrap();
let mut ota_part = ota_part.as_embedded_storage(&mut storage);
println!("Found ota data");
let mut ota = esp_bootloader_esp_idf::ota::Ota::new(&mut ota_part)?;
let current = ota.current_slot()?;
println!(
"current image state {:?} (only relevant if the bootloader was built with auto-rollback support)",
ota.current_ota_state()
);
println!("current {:?} - next {:?}", current, current.next());
let ota_state = ota.current_ota_state()?;
let mut esp = Esp {
boot_button,
mqtt_client: None,
storage,
slot: current.number(),
next_slot: current.next().number(),
ota_state,
dummy: PhantomData::default(),
wall_clock_offset : 0
// wifi_driver,
wall_clock_offset: 0, // wifi_driver,
// boot_button
};

View File

@ -4,8 +4,10 @@ use alloc::vec::Vec;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::lazy_lock::LazyLock;
use embassy_sync::mutex::Mutex;
use embassy_time::Instant;
use log::info;
use serde::Serialize;
use strum_macros::{EnumIter, IntoStaticStr};
use strum_macros::IntoStaticStr;
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
use unit_enum::UnitEnum;
@ -19,8 +21,9 @@ const BUFFER_SIZE: usize = 220;
static mut BUFFER: ConstGenericRingBuffer<LogEntry, BUFFER_SIZE> =
ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new();
#[allow(static_mut_refs)]
static BUFFER_ACCESS: LazyLock<Mutex<CriticalSectionRawMutex,&mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>> =
LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) });
static BUFFER_ACCESS: LazyLock<
Mutex<CriticalSectionRawMutex, &mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>,
> = LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) });
#[derive(Serialize, Debug, Clone)]
pub struct LogEntry {
@ -69,7 +72,13 @@ pub async fn get_log() -> Vec<LogEntry> {
read_copy
}
pub async fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) {
pub async fn log(
message_key: LogMessage,
number_a: u32,
number_b: u32,
txt_short: &str,
txt_long: &str,
) {
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
@ -77,27 +86,18 @@ pub async fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_shor
limit_length(txt_long, &mut txt_long_stack);
//TODO
let time = 0;
let time = Instant::now().as_secs();
// let time = EspSystemTime {}.now().as_millis() as u64;
//
let ordinal = message_key.ordinal() as u16;
// let template_string: &str = message_key.into();
//
// let mut values: HashMap<&str, &str> = HashMap::new();
// let number_a_str = number_a.to_string();
// let number_b_str = number_b.to_string();
//
// values.insert("number_a", &number_a_str);
// values.insert("number_b", &number_b_str);
// values.insert("txt_short", txt_short);
// values.insert("txt_long", txt_long);
//
// let template = Template::from(template_string);
// let serial_entry = template.fill_in(&values);
//
// log::info!("{serial_entry}");
// //TODO push to mqtt?
let template: &str = message_key.into();
let mut template_string = template.to_string();
template_string = template_string.replace("${number_a}", number_a.to_string().as_str());
template_string = template_string.replace("${number_b}", number_b.to_string().as_str());
template_string = template_string.replace("${txt_long}", txt_long);
template_string = template_string.replace("${txt_short}", txt_short);
info!("LOG: {} : {}", time, template_string);
let entry = LogEntry {
timestamp: time,

View File

@ -10,6 +10,7 @@ esp_bootloader_esp_idf::esp_app_desc!();
use esp_backtrace as _;
use crate::config::PlantConfig;
use crate::tank::WATER_FROZEN_THRESH;
use crate::{
config::BoardVersion::INITIAL,
hal::{PlantHal, HAL, PLANT_COUNT},
@ -20,23 +21,36 @@ use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::{format, vec};
use core::any::Any;
use anyhow::{bail, Context};
use chrono::{DateTime, Datelike, Timelike, Utc};
use chrono_tz::Tz::{self};
use core::sync::atomic::Ordering;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
use embassy_sync::lazy_lock::LazyLock;
use embassy_sync::mutex::Mutex;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock;
use embassy_time::Timer;
use esp_bootloader_esp_idf::ota::OtaImageState;
use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset;
use esp_println::{logger, println};
use hal::battery::BatteryState;
use log::{log, LogMessage};
use plant_state::PlantState;
use portable_atomic::AtomicBool;
use serde::{Deserialize, Serialize};
use crate::tank::{TankError, WATER_FROZEN_THRESH};
#[no_mangle]
extern "C" fn custom_halt() -> ! {
println!("Fatal error occurred. Restarting in 10 seconds...");
for _delay in 0..30 {
ets_delay_us(1_000_000);
}
println!("resetting");
//give serial transmit time to finish
ets_delay_us(500_000);
software_reset()
}
//use tank::*;
mod config;
@ -48,10 +62,8 @@ mod tank;
extern crate alloc;
//mod webserver;
pub static BOARD_ACCESS: LazyLock<Mutex<CriticalSectionRawMutex, HAL>> =
LazyLock::new(|| {
PlantHal::create().unwrap()
});
pub static BOARD_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, HAL>> = OnceLock::new();
pub static STAY_ALIVE: AtomicBool = AtomicBool::new(false);
#[derive(Serialize, Deserialize, Debug, PartialEq)]
@ -140,14 +152,9 @@ async fn safe_main() -> anyhow::Result<()> {
version.git_hash, version.build_time
);
let _esp = BOARD_ACCESS.get().await.lock().await.board_hal.get_esp();
//TODO
//let count = unsafe { esp_ota_get_app_partition_count() };
//log::info!("Partition count is {}", count);
//let mut ota_state: esp_ota_img_states_t = 0;
//let running_partition = unsafe { esp_ota_get_running_partition() };
//let partition_address = unsafe { (*running_partition).address };
//log::info!("Partition address is {}", address);
let partition_address = 0x1337;
// TODO
//let ota_state_string = unsafe {
@ -169,9 +176,9 @@ async fn safe_main() -> anyhow::Result<()> {
//}
//};
//log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
let ota_state_string = "unknown";
let _ota_state_string = "unknown";
println!("faul led");
let mut board = BOARD_ACCESS.get().lock().await;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.general_fault(false).await;
println!("faul led2");
let cur = board
@ -184,7 +191,6 @@ async fn safe_main() -> anyhow::Result<()> {
board.board_hal.general_fault(true);
anyhow::Ok(board.board_hal.get_esp().time())
})?;
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
if cur.year() < 2020 {
to_config = true;
@ -192,10 +198,10 @@ async fn safe_main() -> anyhow::Result<()> {
}
info!("cur is {}", cur);
update_charge_indicator().await;
update_charge_indicator(&mut board).await;
println!("faul led3");
if board.board_hal.get_esp().get_restart_to_conf() {
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await;
for _i in 0..2 {
board.board_hal.general_fault(true).await;
Timer::after_millis(100).await;
@ -221,12 +227,15 @@ async fn safe_main() -> anyhow::Result<()> {
} else {
board.board_hal.general_fault(false).await;
}
} else {
info!("no mode override");
}
if board.board_hal.get_config().hardware.board == INITIAL
&& board.board_hal.get_config().network.ssid.is_none()
{
let _ = board.board_hal.get_esp().wifi_ap();
info!("No wifi configured, starting initial config mode");
board.board_hal.get_esp().wifi_ap().await.unwrap();
drop(board);
let reboot_now = Arc::new(AtomicBool::new(false));
//TODO
@ -254,7 +263,6 @@ async fn safe_main() -> anyhow::Result<()> {
}
}
// let timezone = match &board.board_hal.get_config().timezone {
// Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| {
// info!("Invalid timezone '{}', falling back to UTC", tz_str);
@ -262,25 +270,18 @@ async fn safe_main() -> anyhow::Result<()> {
// }),
// None => UTC, // Fallback to UTC if no timezone is set
// };
let timezone = Tz::UTC;
let _timezone = Tz::UTC;
let timezone_time = cur;//TODO.with_timezone(&timezone);
drop(board);
let timezone_time = cur; //TODO.with_timezone(&timezone);
info!(
"Running logic at utc {} and {} {}",
cur,
"todo timezone.name()",
timezone_time
cur, "todo timezone.name()", timezone_time
);
if let NetworkMode::WIFI { ref ip_address, .. } = network_mode {
publish_firmware_info(
version,
partition_address,
ota_state_string,
ip_address,
&timezone_time.to_rfc3339(),
)
.await;
publish_firmware_info(version, ip_address, &timezone_time.to_rfc3339()).await;
publish_battery_state().await;
let _ = publish_mppt_state().await;
}
@ -299,10 +300,8 @@ async fn safe_main() -> anyhow::Result<()> {
.to_string()
.as_str(),
"",
).await;
//TODO must drop board here?
//drop(board);
)
.await;
if to_config {
//check if client or ap mode and init Wi-Fi
@ -316,7 +315,7 @@ async fn safe_main() -> anyhow::Result<()> {
log(LogMessage::NormalRun, 0, 0, "", "").await;
}
let dry_run = false;
let _dry_run = false;
//
// let tank_state = determine_tank_state(&mut board);
//
@ -353,7 +352,7 @@ async fn safe_main() -> anyhow::Result<()> {
// }
// }
let mut water_frozen = false;
let mut _water_frozen = false;
//TODO
let water_temp = anyhow::Ok(12_f32);
// board
@ -364,14 +363,14 @@ async fn safe_main() -> anyhow::Result<()> {
if let Ok(res) = water_temp {
if res < WATER_FROZEN_THRESH {
water_frozen = true;
_water_frozen = true;
}
}
//publish_tank_state(&tank_state, &water_temp).await;
let plantstate: [PlantState; PLANT_COUNT] = [
board = BOARD_ACCESS.get().await.lock().await;
let _plantstate: [PlantState; PLANT_COUNT] = [
PlantState::read_hardware_state(0, &mut board).await,
PlantState::read_hardware_state(1, &mut board).await,
PlantState::read_hardware_state(2, &mut board).await,
@ -468,7 +467,7 @@ async fn safe_main() -> anyhow::Result<()> {
.await
.unwrap_or(BatteryState::Unknown);
let mut light_state = LightState {
let light_state = LightState {
enabled: board.board_hal.get_config().night_lamp.enabled,
..Default::default()
};
@ -611,7 +610,7 @@ pub async fn do_secure_pump(
let mut error = false;
let mut first_error = true;
let mut pump_time_s = 0;
let board = &mut BOARD_ACCESS.get().lock().await;
let board = &mut BOARD_ACCESS.get().await.lock().await;
if !dry_run {
// board
// .board_hal
@ -733,8 +732,7 @@ pub async fn do_secure_pump(
})
}
async fn update_charge_indicator() {
let board = &mut BOARD_ACCESS.get().lock().await;
async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>) {
//we have mppt controller, ask it for charging current
// let tank_state = determine_tank_state(&mut board);
//
@ -779,7 +777,8 @@ async fn update_charge_indicator() {
else if let Ok(charging) = board
.board_hal
.get_battery_monitor()
.average_current_milli_ampere().await
.average_current_milli_ampere()
.await
{
let _ = board.board_hal.set_charge_indicator(charging > 20);
} else {
@ -830,51 +829,45 @@ async fn update_charge_indicator() {
// }
// }
async fn publish_firmware_info(
version: VersionInfo,
address: u32,
ota_state_string: &str,
ip_address: &String,
timezone_time: &String,
) {
let board = &mut BOARD_ACCESS.get().lock().await;
let _ = board
.board_hal
.get_esp()
async fn publish_firmware_info(version: VersionInfo, ip_address: &String, timezone_time: &String) {
let board = &mut BOARD_ACCESS.get().await.lock().await;
let esp = board.board_hal.get_esp();
let _ = esp
.mqtt_publish("/firmware/address", ip_address.as_bytes())
.await;
let _ = board
.board_hal
.get_esp()
let _ = esp
.mqtt_publish("/firmware/githash", version.git_hash.as_bytes())
.await;
let _ = board
.board_hal
.get_esp()
let _ = esp
.mqtt_publish("/firmware/buildtime", version.build_time.as_bytes())
.await;
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/last_online",
timezone_time.as_bytes(),
);
let _ = board
.board_hal
.get_esp()
.mqtt_publish("/firmware/ota_state", ota_state_string.as_bytes())
let _ = esp.mqtt_publish("/firmware/last_online", timezone_time.as_bytes());
let _ = esp
.mqtt_publish(
"/firmware/ota_state",
state_to_string(esp.ota_state).as_bytes(),
)
.await;
let _ = board.board_hal.get_esp().mqtt_publish(
"/firmware/partition_address",
format!("{:#06x}", address).as_bytes(),
);
let _ = board
.board_hal
.get_esp()
.mqtt_publish("/state", "online".as_bytes())
let slot = esp.slot;
let _ = esp
.mqtt_publish("/firmware/ota_slot", format!("slot{slot}").as_bytes())
.await;
let _ = esp.mqtt_publish("/state", "online".as_bytes()).await;
}
fn state_to_string(state: OtaImageState) -> &'static str {
match state {
OtaImageState::New => "New",
OtaImageState::PendingVerify => "PendingVerify",
OtaImageState::Valid => "Valid",
OtaImageState::Invalid => "Invalid",
OtaImageState::Aborted => "Aborted",
OtaImageState::Undefined => "Undefined",
}
}
async fn try_connect_wifi_sntp_mqtt() -> NetworkMode {
let board = &mut BOARD_ACCESS.get().lock().await;
let board = &mut BOARD_ACCESS.get().await.lock().await;
let nw_conf = &board.board_hal.get_config().network.clone();
match board.board_hal.get_esp().wifi(nw_conf).await {
Ok(ip_info) => {
@ -941,6 +934,7 @@ async fn pump_info(
Ok(state) => {
let _ = BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
@ -957,7 +951,7 @@ async fn pump_info(
}
async fn publish_mppt_state() -> anyhow::Result<()> {
let board_hal = &mut BOARD_ACCESS.get().lock().await.board_hal;
let board_hal = &mut BOARD_ACCESS.get().await.lock().await.board_hal;
let current = board_hal.get_mptt_current().await?;
let voltage = board_hal.get_mptt_voltage().await?;
let solar_state = Solar {
@ -975,8 +969,12 @@ async fn publish_mppt_state() -> anyhow::Result<()> {
}
async fn publish_battery_state() -> () {
let board = &mut BOARD_ACCESS.get().lock().await;
let state = board.board_hal.get_battery_monitor().get_battery_state().await;
let board = &mut BOARD_ACCESS.get().await.lock().await;
let state = board
.board_hal
.get_battery_monitor()
.get_battery_state()
.await;
if let Ok(serialized_battery_state_bytes) =
serde_json::to_string(&state).map(|s| s.into_bytes())
{
@ -992,8 +990,9 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
let mut led_count = 8;
let mut pattern_step = 0;
loop {
update_charge_indicator().await;
let mut board = BOARD_ACCESS.get().lock().await;
let mut board = BOARD_ACCESS.get().await.lock().await;
update_charge_indicator(&mut board).await;
match wait_type {
WaitType::MissingConfig => {
// Keep existing behavior: circular filling pattern
@ -1023,7 +1022,7 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
drop(board);
Timer::after_millis(delay).await;
let mut board = BOARD_ACCESS.get().lock().await;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.general_fault(false);
// Clear all LEDs
@ -1039,7 +1038,13 @@ async fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
if reboot_now.load(Ordering::Relaxed) {
//ensure clean http answer
Timer::after_millis(500).await;
BOARD_ACCESS.get().lock().await.board_hal.deep_sleep(1);
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.deep_sleep(1);
}
}
}
@ -1049,19 +1054,21 @@ async fn main(spawner: Spawner) {
// intialize embassy
logger::init_logger_from_env();
//force init here!
let board = BOARD_ACCESS.get().lock().await;
drop(board);
println!("test");
println!("Hal init");
match BOARD_ACCESS.init(PlantHal::create(spawner).unwrap()) {
Ok(_) => {}
Err(_) => {
panic!("Could not set hal to static")
}
}
println!("Hal init done, starting logic");
info!("Embassy initialized!");
let result = safe_main().await;
match result {
match safe_main().await {
// this should not get triggered, safe_main should not return but go into deep sleep with sensible
// timeout, this is just a fallback
Ok(_) => {
warn!("Main app finished, but should never do, restarting");
let board = &mut BOARD_ACCESS.get().lock().await.board_hal;
let board = &mut BOARD_ACCESS.get().await.lock().await.board_hal;
board.get_esp().set_restart_to_conf(false);
board.deep_sleep(1);

View File

@ -1,6 +1,5 @@
use crate::{config::TankConfig, hal::HAL};
use anyhow::Context;
use crate::alloc::string::{String, ToString};
use crate::config::TankConfig;
use serde::Serialize;
const OPEN_TANK_VOLTAGE: f32 = 3.0;