get log to work, make time accessible

This commit is contained in:
Empire 2025-09-20 11:31:51 +02:00
parent 584d6df2d0
commit c94f5bdb45
6 changed files with 334 additions and 212 deletions

View File

@ -133,16 +133,11 @@ serde = { version = "1.0.219", features = ["derive", "alloc"], default-features
serde_json = { version = "1.0.143", default-features = false, features = ["alloc"] } serde_json = { version = "1.0.143", default-features = false, features = ["alloc"] }
#timezone #timezone
chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] } chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-by-regex"] } chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-by-regex"] }
eeprom24x = "0.7.2" eeprom24x = "0.7.2"
#url = "2.5.3"
crc = "3.2.1" crc = "3.2.1"
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"] }
ringbuffer = "0.15.0"
strum_macros = "0.27.0" strum_macros = "0.27.0"
#esp-ota = { version = "0.2.2", features = ["log"] }
unit-enum = "1.4.1" unit-enum = "1.4.1"
pca9535 = { version = "2.0.0" } pca9535 = { version = "2.0.0" }
ina219 = { version = "0.2.0" } ina219 = { version = "0.2.0" }
@ -157,10 +152,10 @@ edge-dhcp = "0.6.0"
edge-nal = "0.5.0" edge-nal = "0.5.0"
edge-nal-embassy = "0.6.0" edge-nal-embassy = "0.6.0"
static_cell = "2.1.1" static_cell = "2.1.1"
cfg-if = "1.0.3"
edge-http = { version = "0.6.1", features = ["log"] } edge-http = { version = "0.6.1", features = ["log"] }
littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] } littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] }
littlefs2-core = "0.1.1" littlefs2-core = "0.1.1"
bytemuck = { version = "1.23.2", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] }
[patch.crates-io] [patch.crates-io]

View File

@ -1,9 +1,9 @@
use crate::config::{NetworkConfig, PlantControllerConfig}; use crate::config::{NetworkConfig, PlantControllerConfig};
use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT}; use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT, TIME_ACCESS};
use crate::log::{log, LogMessage}; use crate::log::{ LogArray, LogMessage, LOG_ACCESS};
use crate::STAY_ALIVE; use crate::STAY_ALIVE;
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use chrono::{DateTime, Utc}; use chrono::{DateTime, FixedOffset, Utc};
use serde::Serialize; use serde::Serialize;
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
@ -13,12 +13,10 @@ use alloc::{format, string::String, vec::Vec};
use core::marker::PhantomData; use core::marker::PhantomData;
use core::net::{IpAddr, Ipv4Addr}; use core::net::{IpAddr, Ipv4Addr};
use core::str::FromStr; use core::str::FromStr;
use embassy_executor::{SendSpawner, Spawner}; use embassy_executor::{Spawner};
use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_sync::rwlock::TryLockError;
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Instant, Timer};
use embedded_storage::nor_flash::ReadNorFlash; use embedded_storage::nor_flash::ReadNorFlash;
use embedded_storage::Storage; use embedded_storage::Storage;
@ -26,25 +24,27 @@ use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot};
use esp_bootloader_esp_idf::partitions::{Error, FlashRegion, PartitionEntry, PartitionTable}; use esp_bootloader_esp_idf::partitions::{Error, FlashRegion, PartitionEntry, PartitionTable};
use esp_hal::gpio::Input; use esp_hal::gpio::Input;
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::Rtc;
use esp_hal::rtc_cntl::sleep::RtcSleepConfig; use esp_hal::rtc_cntl::sleep::RtcSleepConfig;
use esp_println::{print, println}; use esp_hal::system::software_reset;
use esp_println::{println};
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use esp_wifi::wifi::{ use esp_wifi::wifi::{
AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig, AccessPointConfiguration, AccessPointInfo, Configuration, Interfaces, ScanConfig,
ScanTypeConfig, WifiController, WifiDevice, WifiEvent, WifiState, ScanTypeConfig, WifiController, WifiDevice,
}; };
use littlefs2::fs::Filesystem; use littlefs2::fs::Filesystem;
use littlefs2_core::{DynFile, FileType, OpenSeekFrom, Path, PathBuf, SeekFrom}; use littlefs2_core::{DynFile, FileType, PathBuf, SeekFrom};
use log::{info, warn}; use log::{info};
#[link_section = ".rtc.data"] #[esp_hal::ram(rtc_fast, persistent)]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"] #[esp_hal::ram(rtc_fast, persistent)]
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT]; static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"] #[esp_hal::ram(rtc_fast, persistent)]
static mut LOW_VOLTAGE_DETECTED: bool = false; static mut LOW_VOLTAGE_DETECTED: i8 = 0;
#[link_section = ".rtc.data"] #[esp_hal::ram(rtc_fast, persistent)]
static mut RESTART_TO_CONF: bool = false; static mut RESTART_TO_CONF: i8 = 0;
static CONFIG_FILE: &str = "config.json"; static CONFIG_FILE: &str = "config.json";
@ -239,10 +239,6 @@ impl Esp<'_> {
bail!("not implemented") bail!("not implemented")
} }
pub(crate) fn time(&mut self) -> DateTime<Utc> {
let wall_clock = Instant::now().as_millis() + self.wall_clock_offset;
DateTime::from_timestamp_millis(wall_clock as i64).unwrap()
}
pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> { pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
info!("start wifi scan"); info!("start wifi scan");
@ -277,17 +273,17 @@ impl Esp<'_> {
} }
pub(crate) fn set_low_voltage_in_cycle(&mut self) { pub(crate) fn set_low_voltage_in_cycle(&mut self) {
unsafe { unsafe {
LOW_VOLTAGE_DETECTED = true; LOW_VOLTAGE_DETECTED = 1;
} }
} }
pub(crate) fn clear_low_voltage_in_cycle(&mut self) { pub(crate) fn clear_low_voltage_in_cycle(&mut self) {
unsafe { unsafe {
LOW_VOLTAGE_DETECTED = false; LOW_VOLTAGE_DETECTED = 1;
} }
} }
pub(crate) fn low_voltage_in_cycle(&mut self) -> bool { pub(crate) fn low_voltage_in_cycle(&mut self) -> bool {
unsafe { LOW_VOLTAGE_DETECTED } unsafe { LOW_VOLTAGE_DETECTED == 1 }
} }
pub(crate) fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) { pub(crate) fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
unsafe { unsafe {
@ -298,11 +294,15 @@ impl Esp<'_> {
unsafe { CONSECUTIVE_WATERING_PLANT[plant] } unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
} }
pub(crate) fn get_restart_to_conf(&mut self) -> bool { pub(crate) fn get_restart_to_conf(&mut self) -> bool {
unsafe { RESTART_TO_CONF } unsafe { RESTART_TO_CONF == 1}
} }
pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) { pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) {
unsafe { unsafe {
RESTART_TO_CONF = to_conf; if to_conf {
RESTART_TO_CONF = 1;
} else {
RESTART_TO_CONF = 0;
}
} }
} }
@ -374,10 +374,9 @@ impl Esp<'_> {
//unsafe { //unsafe {
// //allow early wakeup by pressing the boot button // //allow early wakeup by pressing the boot button
if duration_in_ms == 0 { if duration_in_ms == 0 {
loop { software_reset();
info!("todo reboot")
}
} else { } else {
loop { loop {
info!("todo deepsleep") info!("todo deepsleep")
} }
@ -547,31 +546,34 @@ impl Esp<'_> {
unsafe { unsafe {
LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT]; LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT];
CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT]; CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT];
LOW_VOLTAGE_DETECTED = false; LOW_VOLTAGE_DETECTED = 0;
crate::log::init().await; if to_config_mode {
RESTART_TO_CONF = to_config_mode; RESTART_TO_CONF = 1
} else {
RESTART_TO_CONF = 0;
}
}; };
} else { } else {
unsafe { unsafe {
if to_config_mode { if to_config_mode {
RESTART_TO_CONF = true; RESTART_TO_CONF = 1;
} }
log( LOG_ACCESS.lock().await.log(
LogMessage::RestartToConfig, LogMessage::RestartToConfig,
RESTART_TO_CONF as u32, RESTART_TO_CONF as u32,
0, 0,
"", "",
"", "",
) ).await
.await; ;
log( LOG_ACCESS.lock().await.log(
LogMessage::LowVoltage, LogMessage::LowVoltage,
LOW_VOLTAGE_DETECTED as u32, LOW_VOLTAGE_DETECTED as u32,
0, 0,
"", "",
"", "",
) ).await
.await; ;
for i in 0..PLANT_COUNT { for i in 0..PLANT_COUNT {
log::info!( log::info!(
"LAST_WATERING_TIMESTAMP[{}] = UTC {}", "LAST_WATERING_TIMESTAMP[{}] = UTC {}",

View File

@ -18,13 +18,15 @@ use crate::{
battery::{BatteryInteraction, NoBatteryMonitor}, battery::{BatteryInteraction, NoBatteryMonitor},
esp::Esp, esp::Esp,
}, },
log::{log, LogMessage}, log::{LogMessage},
}; };
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::format; use alloc::format;
use alloc::sync::Arc; use alloc::sync::Arc;
use core::cell::OnceCell;
use anyhow::{bail, Ok, Result}; use anyhow::{bail, Ok, Result};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, FixedOffset, Utc};
use embassy_executor::Spawner; use embassy_executor::Spawner;
//use battery::BQ34Z100G1; //use battery::BQ34Z100G1;
//use bq34z100::Bq34z100g1Driver; //use bq34z100::Bq34z100g1Driver;
@ -38,16 +40,22 @@ use measurements::{Current, Voltage};
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock;
use esp_alloc as _; use esp_alloc as _;
use esp_backtrace as _; use esp_backtrace as _;
use esp_bootloader_esp_idf::ota::Slot; use esp_bootloader_esp_idf::ota::Slot;
use esp_hal::rng::Rng; use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{Rtc, SocResetReason};
use esp_hal::system::reset_reason;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use esp_wifi::{init, EspWifiController}; use esp_wifi::{init, EspWifiController};
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
use littlefs2::object_safe::DynStorage; use littlefs2::object_safe::DynStorage;
use log::{info, warn}; use log::{info, warn};
use crate::log::{LogArray, LOG_ACCESS};
pub static TIME_ACCESS: OnceLock<Rtc> = OnceLock::new();
//Only support for 8 right now! //Only support for 8 right now!
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
@ -181,6 +189,13 @@ impl PlantHal {
esp_alloc::heap_allocator!(size: 64 * 1024); esp_alloc::heap_allocator!(size: 64 * 1024);
esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000);
let rtc: Rtc = Rtc::new(peripherals.LPWR);
match(TIME_ACCESS.init(rtc)){
Result::Ok(_) => {}
Err(_) => {}
}
let systimer = SystemTimer::new(peripherals.SYSTIMER); let systimer = SystemTimer::new(peripherals.SYSTIMER);
let boot_button = Input::new( let boot_button = Input::new(
@ -335,36 +350,78 @@ impl PlantHal {
//init,reset rtc memory depending on cause //init,reset rtc memory depending on cause
let mut init_rtc_store: bool = false; let mut init_rtc_store: bool = false;
let mut to_config_mode: bool = false; let mut to_config_mode: bool = false;
let reasons = ""; let reasons = match reset_reason() {
// let reasons = ResetReason::get(); None => {
// match reasons { "unknown"
// ResetReason::Software => {} }
// ResetReason::ExternalPin => {} Some(reason) => {
// ResetReason::Watchdog => { match reason {
// init_rtc_store = true; SocResetReason::ChipPowerOn => {
// } "power on"
// ResetReason::Sdio => init_rtc_store = true, }
// ResetReason::Panic => init_rtc_store = true, SocResetReason::CoreSw => {
// ResetReason::InterruptWatchdog => init_rtc_store = true, "software reset"
// ResetReason::PowerOn => init_rtc_store = true, }
// ResetReason::Unknown => init_rtc_store = true, SocResetReason::CoreDeepSleep => {
// ResetReason::Brownout => init_rtc_store = true, "deep sleep"
// ResetReason::TaskWatchdog => init_rtc_store = true, }
// ResetReason::DeepSleep => {} SocResetReason::CoreSDIO => {
// ResetReason::USBPeripheral => { "sdio reset"
// init_rtc_store = true; }
// to_config_mode = true; SocResetReason::CoreMwdt0 => {
// } "Watchdog Main"
// ResetReason::JTAG => init_rtc_store = true, }
// }; SocResetReason::CoreMwdt1 => {
log( "Watchdog 1"
}
SocResetReason::CoreRtcWdt => {
"Watchdog RTC"
}
SocResetReason::Cpu0Mwdt0 => {
"Watchdog MCpu0"
}
SocResetReason::Cpu0Sw => {
"software reset cpu0"
}
SocResetReason::Cpu0RtcWdt => {
init_rtc_store = true;
"Watchdog RTC cpu0"
}
SocResetReason::SysBrownOut => {
"sys brown out"
}
SocResetReason::SysRtcWdt => {
"Watchdog Sys rtc"
}
SocResetReason::Cpu0Mwdt1 => {
"cpu0 mwdt1"
}
SocResetReason::SysSuperWdt => {
"Watchdog Super"
}
SocResetReason::CoreEfuseCrc => {
"core efuse crc"
}
SocResetReason::CoreUsbUart => {
to_config_mode = true;
"core usb uart"
}
SocResetReason::CoreUsbJtag => {
"core usb jtag"
}
SocResetReason::Cpu0JtagCpu => {
"cpu0 jtag cpu"
}
}
}
};
LOG_ACCESS.lock().await.log(
LogMessage::ResetReason, LogMessage::ResetReason,
init_rtc_store as u32, init_rtc_store as u32,
to_config_mode as u32, to_config_mode as u32,
"", "",
&format!("{reasons:?}"), &format!("{reasons:?}"),
) ).await;
.await;
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode) esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode)
.await; .await;
@ -430,32 +487,32 @@ impl PlantHal {
} }
}; };
let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board { let board_hal: Box<dyn BoardInteraction + Send> = //match config.hardware.board {
BoardVersion::INITIAL => { //BoardVersion::INITIAL => {
initial_hal::create_initial_board(free_pins, config, esp)? initial_hal::create_initial_board(free_pins, config, esp)?
} ;
//}
// BoardVersion::V3 => { // BoardVersion::V3 => {
// v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? // v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
// } // }
// BoardVersion::V4 => { //BoardVersion::V4 => {
// v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? // v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)?
// } //}
_ => { //_ => {
todo!() // todo!()
} //}
}; //};
HAL { board_hal } HAL { board_hal }
} }
Err(err) => { Err(err) => {
log( LOG_ACCESS.lock().await.log(
LogMessage::ConfigModeMissingConfig, LogMessage::ConfigModeMissingConfig,
0, 0,
0, 0,
"", "",
&err.to_string(), &err.to_string(),
) ).await;
.await;
HAL { HAL {
board_hal: initial_hal::create_initial_board( board_hal: initial_hal::create_initial_board(
free_pins, free_pins,
@ -469,3 +526,12 @@ impl PlantHal {
Ok(Mutex::new(hal)) Ok(Mutex::new(hal))
} }
} }
pub async fn esp_time() -> DateTime<Utc> {
DateTime::from_timestamp_micros(TIME_ACCESS.get().await.current_time_us() as i64).unwrap()
}
pub async fn esp_set_time(time: DateTime<FixedOffset>) {
TIME_ACCESS.get().await.set_current_time_us(time.timestamp_micros() as u64);
}

View File

@ -1,47 +1,122 @@
use crate::vec; use crate::vec;
use alloc::string::ToString; use alloc::string::ToString;
use alloc::vec::Vec; use alloc::vec::Vec;
use bytemuck::{AnyBitPattern, Contiguous, Pod, Zeroable};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::lazy_lock::LazyLock;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_time::Instant; use embassy_time::Instant;
use esp_println::println; use esp_hal::Persistable;
use log::info; use log::info;
use serde::Serialize; use serde::Serialize;
use strum_macros::IntoStaticStr; use strum_macros::IntoStaticStr;
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
use unit_enum::UnitEnum; use unit_enum::UnitEnum;
use crate::hal::TIME_ACCESS;
#[esp_hal::ram(rtc_fast, persistent)]
static mut LOG_ARRAY: LogArray = LogArray {
buffer: [LogEntryInner { timestamp: 0, message_id: 0, a: 0, b: 0, txt_short: [0;TXT_SHORT_LENGTH], txt_long: [0;TXT_LONG_LENGTH] }; 256],
head: 0,
};
pub static LOG_ACCESS: Mutex<CriticalSectionRawMutex, LogArray> = Mutex::new(unsafe { LOG_ARRAY });
const TXT_SHORT_LENGTH: usize = 8; const TXT_SHORT_LENGTH: usize = 8;
const TXT_LONG_LENGTH: usize = 32; const TXT_LONG_LENGTH: usize = 32;
const BUFFER_SIZE: usize = 220;
#[link_section = ".rtc.data"] #[derive(Debug, Clone, Copy, AnyBitPattern)]
static mut BUFFER: ConstGenericRingBuffer<LogEntry, BUFFER_SIZE> = #[repr(C)]
ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new(); pub struct LogArray{
#[allow(static_mut_refs)] buffer: [LogEntryInner; (u8::MAX_VALUE as usize) +1],
static BUFFER_ACCESS: LazyLock< head: u8
Mutex<CriticalSectionRawMutex, &mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>, }
> = LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) });
#[derive(Serialize, Debug, Clone)] unsafe impl Persistable for LogArray {}
unsafe impl Zeroable for LogEntryInner {}
unsafe impl Pod for LogEntryInner{}
#[derive(Debug, Clone, Copy)]
struct LogEntryInner {
pub timestamp: u64,
pub message_id: u16,
pub a: u32,
pub b: u32,
pub txt_short: [u8; TXT_SHORT_LENGTH],
pub txt_long: [u8; TXT_LONG_LENGTH],
}
#[derive(Serialize)]
pub struct LogEntry { pub struct LogEntry {
pub timestamp: u64, pub timestamp: u64,
pub message_id: u16, pub message_id: u16,
pub a: u32, pub a: u32,
pub b: u32, pub b: u32,
pub txt_short: heapless::String<TXT_SHORT_LENGTH>, pub txt_short: alloc::string::String,
pub txt_long: heapless::String<TXT_LONG_LENGTH>, pub txt_long: alloc::string::String,
} }
pub async fn init() { impl From<LogEntryInner> for LogEntry {
unsafe { fn from(value: LogEntryInner) -> Self {
BUFFER = ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new(); LogEntry{
}; timestamp: value.timestamp,
let mut access = BUFFER_ACCESS.get().lock().await; message_id: value.message_id,
access.drain().for_each(|_| {}); a: value.a,
b: value.b,
txt_short: alloc::string::String::from_utf8_lossy_owned(value.txt_short.to_vec()),
txt_long: alloc::string::String::from_utf8_lossy_owned(value.txt_long.to_vec()),
}
}
}
impl LogArray {
pub fn get(&mut self) -> Vec<LogEntry> {
let mut rv: Vec<LogEntry> = Vec::new();
let mut index = self.head.wrapping_sub(1);
for _ in 0..self.buffer.len() {
let entry = self.buffer[index as usize];
if (entry.message_id as usize) != LogMessage::Empty.ordinal() {
rv.push(entry.into());
}
index = index.wrapping_sub(1);
}
rv
}
pub async fn log(
&mut self,
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();
limit_length(txt_short, &mut txt_short_stack);
limit_length(txt_long, &mut txt_long_stack);
let time = TIME_ACCESS.get().await.current_time_us()/1000;
let ordinal = message_key.ordinal() as u16;
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!("{}", template_string);
let to_modify = &mut self.buffer[self.head as usize];
to_modify.timestamp = time;
to_modify.message_id = ordinal;
to_modify.a = number_a;
to_modify.b = number_b;
to_modify.txt_short.clone_from_slice(&txt_short_stack.as_bytes());
to_modify.txt_long.clone_from_slice(&txt_long_stack.as_bytes());
self.head = self.head.wrapping_add(1);
}
} }
fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<LIMIT>) { fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<LIMIT>) {
@ -60,60 +135,19 @@ fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<L
} }
} }
} }
} while target.len() < LIMIT {
target.push(' ').unwrap();
pub async fn get_log() -> Vec<LogEntry> {
let buffer = BUFFER_ACCESS.get().lock().await;
let mut read_copy = Vec::new();
for entry in buffer.iter() {
let copy = entry.clone();
read_copy.push(copy);
} }
drop(buffer);
read_copy
} }
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();
limit_length(txt_short, &mut txt_short_stack);
limit_length(txt_long, &mut txt_long_stack);
//TODO
let time = Instant::now().as_secs();
// let time = EspSystemTime {}.now().as_millis() as u64;
//
let ordinal = message_key.ordinal() as u16;
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,
message_id: ordinal,
a: number_a,
b: number_b,
txt_short: txt_short_stack,
txt_long: txt_long_stack,
};
let mut buffer = BUFFER_ACCESS.get().lock().await;
buffer.push(entry);
}
#[derive(IntoStaticStr, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)] #[derive(IntoStaticStr, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)]
pub enum LogMessage { pub enum LogMessage {
#[strum(
serialize = ""
)]
Empty,
#[strum( #[strum(
serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}" serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}"
)] )]

View File

@ -1,6 +1,7 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![feature(never_type)] #![feature(never_type)]
#![feature(string_from_utf8_lossy_owned)]
#![deny( #![deny(
clippy::mem_forget, clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
@ -35,9 +36,11 @@ use esp_hal::rom::ets_delay_us;
use esp_hal::system::software_reset; use esp_hal::system::software_reset;
use esp_println::{logger, println}; use esp_println::{logger, println};
use hal::battery::BatteryState; use hal::battery::BatteryState;
use log::{log, LogMessage}; use log::{ LogMessage};
use plant_state::PlantState; use plant_state::PlantState;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::hal::esp_time;
use crate::log::LOG_ACCESS;
#[no_mangle] #[no_mangle]
extern "C" fn custom_halt() -> ! { extern "C" fn custom_halt() -> ! {
@ -180,6 +183,7 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
let _ota_state_string = "unknown"; let _ota_state_string = "unknown";
board.board_hal.general_fault(false).await; board.board_hal.general_fault(false).await;
let time = esp_time().await;
let cur = board let cur = board
.board_hal .board_hal
.get_rtc_module() .get_rtc_module()
@ -188,19 +192,19 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
.or_else(|err| { .or_else(|err| {
info!("rtc module error: {:?}", err); info!("rtc module error: {:?}", err);
board.board_hal.general_fault(true); board.board_hal.general_fault(true);
anyhow::Ok(board.board_hal.get_esp().time()) anyhow::Ok(time)
})?; })?;
//check if we know the time current > 2020 (plausibility checks, this code is newer than 2020) //check if we know the time current > 2020 (plausibility checks, this code is newer than 2020)
if cur.year() < 2020 { if cur.year() < 2020 {
to_config = true; to_config = true;
log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "").await; LOG_ACCESS.lock().await.log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "").await;
} }
info!("cur is {}", cur); info!("cur is {}", cur);
update_charge_indicator(&mut board).await; update_charge_indicator(&mut board).await;
println!("faul led3"); println!("faul led3");
if board.board_hal.get_esp().get_restart_to_conf() { if board.board_hal.get_esp().get_restart_to_conf() {
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await; LOG_ACCESS.lock().await.log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "").await;
for _i in 0..2 { for _i in 0..2 {
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
Timer::after_millis(100).await; Timer::after_millis(100).await;
@ -212,7 +216,7 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
board.board_hal.get_esp().set_restart_to_conf(false); board.board_hal.get_esp().set_restart_to_conf(false);
} else if board.board_hal.get_esp().mode_override_pressed() { } else if board.board_hal.get_esp().mode_override_pressed() {
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "").await; LOG_ACCESS.lock().await.log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "").await;
for _i in 0..5 { for _i in 0..5 {
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
Timer::after_millis(100).await; Timer::after_millis(100).await;
@ -293,7 +297,7 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
let _ = publish_mppt_state(&mut board).await; let _ = publish_mppt_state(&mut board).await;
} }
log( LOG_ACCESS.lock().await.log(
LogMessage::StartupInfo, LogMessage::StartupInfo,
matches!(network_mode, NetworkMode::WIFI { .. }) as u32, matches!(network_mode, NetworkMode::WIFI { .. }) as u32,
matches!( matches!(
@ -307,8 +311,8 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
.to_string() .to_string()
.as_str(), .as_str(),
"", "",
) ).await
.await; ;
if to_config { if to_config {
//check if client or ap mode and init Wi-Fi //check if client or ap mode and init Wi-Fi
@ -319,7 +323,7 @@ async fn safe_main(spawner: Spawner) -> anyhow::Result<()> {
let board = BOARD_ACCESS.get().await.lock().await; let board = BOARD_ACCESS.get().await.lock().await;
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await; wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
} else { } else {
log(LogMessage::NormalRun, 0, 0, "", "").await; LOG_ACCESS.lock().await.log(LogMessage::NormalRun, 0, 0, "", "").await;
} }
let _dry_run = false; let _dry_run = false;
@ -658,14 +662,14 @@ pub async fn do_secure_pump(
let high_current = current_ma > plant_config.max_pump_current_ma; let high_current = current_ma > plant_config.max_pump_current_ma;
if high_current { if high_current {
if first_error { if first_error {
log( LOG_ACCESS.lock().await.log(
LogMessage::PumpOverCurrent, LogMessage::PumpOverCurrent,
plant_id as u32 + 1, plant_id as u32 + 1,
current_ma as u32, current_ma as u32,
plant_config.max_pump_current_ma.to_string().as_str(), plant_config.max_pump_current_ma.to_string().as_str(),
step.to_string().as_str(), step.to_string().as_str(),
) ).await
.await; ;
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?; board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error { if !plant_config.ignore_current_error {
@ -678,14 +682,14 @@ pub async fn do_secure_pump(
let low_current = current_ma < plant_config.min_pump_current_ma; let low_current = current_ma < plant_config.min_pump_current_ma;
if low_current { if low_current {
if first_error { if first_error {
log( LOG_ACCESS.lock().await.log(
LogMessage::PumpOpenLoopCurrent, LogMessage::PumpOpenLoopCurrent,
plant_id as u32 + 1, plant_id as u32 + 1,
current_ma as u32, current_ma as u32,
plant_config.min_pump_current_ma.to_string().as_str(), plant_config.min_pump_current_ma.to_string().as_str(),
step.to_string().as_str(), step.to_string().as_str(),
) ).await
.await; ;
board.board_hal.general_fault(true).await; board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?; board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error { if !plant_config.ignore_current_error {
@ -699,14 +703,14 @@ pub async fn do_secure_pump(
Err(err) => { Err(err) => {
if !plant_config.ignore_current_error { if !plant_config.ignore_current_error {
info!("Error getting pump current: {}", err); info!("Error getting pump current: {}", err);
log( LOG_ACCESS.lock().await.log(
LogMessage::PumpMissingSensorCurrent, LogMessage::PumpMissingSensorCurrent,
plant_id as u32, plant_id as u32,
0, 0,
"", "",
"", "",
) ).await
.await; ;
error = true; error = true;
break; break;
} else { } else {
@ -1058,7 +1062,7 @@ async fn wait_infinity(
.lock() .lock()
.await .await
.board_hal .board_hal
.deep_sleep(1); .deep_sleep(0).await;
} }
} }
} }

View File

@ -3,29 +3,30 @@
use crate::config::PlantControllerConfig; use crate::config::PlantControllerConfig;
use crate::{get_version, log::LogMessage, BOARD_ACCESS}; use crate::{get_version, log::LogMessage, BOARD_ACCESS};
use alloc::borrow::ToOwned; use alloc::borrow::ToOwned;
use alloc::fmt::format;
use alloc::format; use alloc::format;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::sync::Arc; use alloc::sync::Arc;
use alloc::vec::Vec; use alloc::vec::Vec;
use anyhow::bail; use anyhow::{bail};
use core::fmt::{Debug, Display}; use core::fmt::{Debug, Display};
use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::result::Result::Ok; use core::result::Result::Ok;
use core::str::from_utf8; use core::str::from_utf8;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use chrono::DateTime;
use edge_http::io::server::{Connection, Handler, Server}; use edge_http::io::server::{Connection, Handler, Server};
use edge_http::io::Error; use edge_http::io::Error;
use edge_http::Method; use edge_http::Method;
use edge_nal::{TcpBind, TcpSplit}; use edge_nal::{TcpBind};
use edge_nal_embassy::{Tcp, TcpBuffers}; use edge_nal_embassy::{Tcp, TcpBuffers};
use embassy_net::Stack; use embassy_net::Stack;
use embassy_time::Instant; use embassy_time::Instant;
use embedded_io_async::{Read, Write}; use embedded_io_async::{Read, Write};
use esp_println::println; use esp_println::println;
use littlefs2_core::Path;
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::hal::{esp_set_time, esp_time};
use crate::log::{LOG_ACCESS};
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct SSIDList { struct SSIDList {
@ -72,25 +73,6 @@ pub struct NightLampCommand {
active: bool, active: bool,
} }
// //
// fn write_time(
// request: &mut Request<&mut EspHttpConnection>,
// ) -> Result<Option<std::string::String>, anyhow::Error> {
// let actual_data = read_up_to_bytes_from_request(request, None)?;
// let time: SetTime = serde_json::from_slice(&actual_data)?;
// let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?;
// let mut board = BOARD_ACCESS.lock().expect("board access");
//
// let now = timeval {
// tv_sec: parsed.to_utc().timestamp(),
// tv_usec: 0,
// };
// unsafe { settimeofday(&now, core::ptr::null_mut()) };
// board
// .board_hal
// .get_rtc_module()
// .set_rtc_time(&parsed.to_utc())?;
// anyhow::Ok(None)
// }
// //
// //
@ -430,12 +412,10 @@ impl Handler for HttpHandler {
conn.write_all(include_bytes!("bundle.js")).await?; conn.write_all(include_bytes!("bundle.js")).await?;
Some(200) Some(200)
} }
"/reboot" => { "/log" => {
let mut board = BOARD_ACCESS.get().await.lock().await; let buf = get_log(conn).await;
board.board_hal.get_esp().set_restart_to_conf(true);
self.reboot_now.store(true, Ordering::Relaxed);
Some(200) Some(200)
} },
&_ => { &_ => {
let json = match path { let json = match path {
"/version" => Some(get_version_web(conn).await), "/version" => Some(get_version_web(conn).await),
@ -445,7 +425,6 @@ impl Handler for HttpHandler {
"/get_config" => Some(get_config(conn).await), "/get_config" => Some(get_config(conn).await),
"/files" => Some(list_files(conn).await), "/files" => Some(list_files(conn).await),
"/log_localization" => Some(get_log_localization_config(conn).await), "/log_localization" => Some(get_log_localization_config(conn).await),
"/log" => Some(get_log(conn).await),
"/wifiscan" => Some(wifi_scan(conn).await), "/wifiscan" => Some(wifi_scan(conn).await),
_ => None, _ => None,
}; };
@ -459,6 +438,13 @@ impl Handler for HttpHandler {
let json = match path { let json = match path {
"/wifiscan" => Some(wifi_scan(conn).await), "/wifiscan" => Some(wifi_scan(conn).await),
"/set_config" => Some(set_config(conn).await), "/set_config" => Some(set_config(conn).await),
"/time" => Some(write_time(conn).await),
"/reboot" => {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().set_restart_to_conf(true);
self.reboot_now.store(true, Ordering::Relaxed);
Some(Ok(None))
}
_ => None, _ => None,
}; };
match json { match json {
@ -568,6 +554,20 @@ impl Handler for HttpHandler {
// }) // })
// .unwrap(); // .unwrap();
async fn write_time<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error>
where
T: Read + Write
{
let actual_data = read_up_to_bytes_from_request(request, None).await?;
let time: SetTime = serde_json::from_slice(&actual_data)?;
let parsed = DateTime::parse_from_rfc3339(time.time).unwrap();
esp_set_time(parsed).await;
anyhow::Ok(None)
}
async fn set_config<T, const N: usize>( async fn set_config<T, const N: usize>(
request: &mut Connection<'_, T, N>, request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> ) -> Result<Option<String>, anyhow::Error>
@ -633,10 +633,30 @@ async fn wifi_scan<T, const N: usize>(
} }
async fn get_log<T, const N: usize>( async fn get_log<T, const N: usize>(
_request: &mut Connection<'_, T, N>, conn: &mut Connection<'_, T, N>,
) -> Result<Option<String>, anyhow::Error> { ) -> anyhow::Result<()>
let output = crate::log::get_log().await; where
anyhow::Ok(Some(serde_json::to_string(&output)?)) T: Read + Write,{
let log = LOG_ACCESS.lock().await.get();
conn.initiate_response(
200,
Some("OK"),
&[("Content-Type", "text/javascript")],
)
.await
.unwrap();
conn.write_all("[".as_bytes()).await.unwrap();
let mut append = false;
for entry in log {
if append {
conn.write_all(",".as_bytes()).await.unwrap();
}
append = true;
let json = serde_json::to_string(&entry)?;
conn.write_all(json.as_bytes()).await.unwrap();
}
conn.write_all("]".as_bytes()).await.unwrap();
Ok(())
} }
async fn get_log_localization_config<T, const N: usize>( async fn get_log_localization_config<T, const N: usize>(
@ -698,16 +718,17 @@ async fn get_time<T, const N: usize>(
) -> Result<Option<String>, anyhow::Error> { ) -> Result<Option<String>, anyhow::Error> {
let mut board = BOARD_ACCESS.get().await.lock().await; let mut board = BOARD_ACCESS.get().await.lock().await;
//TODO do not fail if rtc module is missing //TODO do not fail if rtc module is missing
let native = board.board_hal.get_esp().time().to_rfc3339(); let native = esp_time().await.to_rfc3339();
let rtc = board let rtc = "todo";
.board_hal // board
.get_rtc_module() // .board_hal
.get_rtc_time() // .get_rtc_module()
.await? // .get_rtc_time()
.to_rfc3339(); // .await?
// .to_rfc3339();
let data = LoadData { let data = LoadData {
rtc: rtc.as_str(), rtc,
native: native.as_str(), native: native.as_str(),
}; };
let json = serde_json::to_string(&data)?; let json = serde_json::to_string(&data)?;