startup and ota state detection working, now wifi_ap next
This commit is contained in:
parent
4160202cdc
commit
be3c4a5095
@ -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]
|
||||
|
@ -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()
|
||||
|
@ -1,9 +1,6 @@
|
||||
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]
|
||||
|
@ -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");
|
||||
//
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
207
rust/src/main.rs
207
rust/src/main.rs
@ -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;
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user