diff --git a/Software/MainBoard/rust/.idea/vcs.xml b/Software/MainBoard/rust/.idea/vcs.xml
index 298f634..c2365ab 100644
--- a/Software/MainBoard/rust/.idea/vcs.xml
+++ b/Software/MainBoard/rust/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/Software/MainBoard/rust/src/hal/water.rs b/Software/MainBoard/rust/src/hal/water.rs
index 44036c7..5f3b457 100644
--- a/Software/MainBoard/rust/src/hal/water.rs
+++ b/Software/MainBoard/rust/src/hal/water.rs
@@ -88,15 +88,33 @@ impl<'a> TankSensor<'a> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let mut delay = Delay::new();
- self.one_wire_bus.reset(&mut delay)?;
+
+ let presence = self.one_wire_bus.reset(&mut delay)?;
+ println!("OneWire: reset presence pulse = {}", presence);
+ if !presence {
+ println!("OneWire: no device responded to reset — check pull-up resistor and wiring");
+ }
+
let mut search = DeviceSearch::new();
let mut water_temp_sensor: Option = None;
+ let mut devices_found = 0u8;
while let Some(device) = self.one_wire_bus.search_next(&mut search, &mut delay)? {
+ devices_found += 1;
+ println!(
+ "OneWire: found device #{} family=0x{:02X} addr={:02X?}",
+ devices_found, device.address[0], device.address
+ );
if device.address[0] == ds18b20::FAMILY_CODE {
water_temp_sensor = Some(device);
break;
+ } else {
+ println!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE);
}
}
+ if devices_found == 0 {
+ println!("OneWire: search found zero devices on the bus");
+ }
+
match water_temp_sensor {
Some(device) => {
println!("Found one wire device: {:?}", device);
diff --git a/Software/MainBoard/rust/src/log/interceptor.rs b/Software/MainBoard/rust/src/log/interceptor.rs
index 62611df..adeb034 100644
--- a/Software/MainBoard/rust/src/log/interceptor.rs
+++ b/Software/MainBoard/rust/src/log/interceptor.rs
@@ -2,42 +2,84 @@ use alloc::string::String;
use alloc::vec::Vec;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex as BlockingMutex;
-use embassy_sync::mutex::Mutex;
-use log::{error, LevelFilter, Log, Metadata, Record};
+use log::{LevelFilter, Log, Metadata, Record};
+
+const MAX_LIVE_LOG_ENTRIES: usize = 64;
+
+struct LiveLogBuffer {
+ entries: Vec<(u64, String)>,
+ next_seq: u64,
+}
+
+impl LiveLogBuffer {
+ const fn new() -> Self {
+ Self {
+ entries: Vec::new(),
+ next_seq: 0,
+ }
+ }
+
+ fn push(&mut self, text: String) {
+ if self.entries.len() >= MAX_LIVE_LOG_ENTRIES {
+ self.entries.remove(0);
+ }
+ self.entries.push((self.next_seq, text));
+ self.next_seq += 1;
+ }
+
+ fn get_after(&self, after: Option) -> (Vec<(u64, String)>, bool, u64) {
+ let next_seq = self.next_seq;
+ match after {
+ None => (self.entries.clone(), false, next_seq),
+ Some(after_seq) => {
+ let result: Vec<_> = self.entries
+ .iter()
+ .filter(|(seq, _)| *seq > after_seq)
+ .cloned()
+ .collect();
+
+ // Dropped if there are entries that should exist (seq > after_seq) but
+ // the oldest retained entry has a higher seq than after_seq + 1.
+ let dropped = if next_seq > after_seq.saturating_add(1) {
+ if let Some((oldest_seq, _)) = self.entries.first() {
+ *oldest_seq > after_seq.saturating_add(1)
+ } else {
+ // Buffer empty but entries were written — all dropped
+ true
+ }
+ } else {
+ false
+ };
+
+ (result, dropped, next_seq)
+ }
+ }
+ }
+}
pub struct InterceptorLogger {
- // Async mutex for start/stop capture from async context
- async_capture: Mutex,
- // Blocking mutex for the actual data to be used in sync log()
- sync_capture: BlockingMutex>>>,
+ live_log: BlockingMutex>,
}
impl InterceptorLogger {
pub const fn new() -> Self {
Self {
- async_capture: Mutex::new(()),
- sync_capture: BlockingMutex::new(core::cell::RefCell::new(None)),
+ live_log: BlockingMutex::new(core::cell::RefCell::new(LiveLogBuffer::new())),
}
}
- pub async fn start_capture(&self) {
- let _guard = self.async_capture.lock().await;
- self.sync_capture.lock(|capture| {
- *capture.borrow_mut() = Some(Vec::new());
- });
- }
-
- pub async fn stop_capture(&self) -> Option> {
- let _guard = self.async_capture.lock().await;
- self.sync_capture
- .lock(|capture| capture.borrow_mut().take())
+ /// Returns (entries_after, dropped, next_seq).
+ /// Pass `after = None` to retrieve the entire current buffer.
+ /// Pass `after = Some(seq)` to retrieve only entries with seq > that value.
+ pub fn get_live_logs(&self, after: Option) -> (Vec<(u64, String)>, bool, u64) {
+ self.live_log.lock(|buf| buf.borrow().get_after(after))
}
pub fn init(&'static self) {
match log::set_logger(self).map(|()| log::set_max_level(LevelFilter::Info)) {
Ok(()) => {}
- Err(e) => {
- error!("Logger already set: {}", e);
+ Err(_e) => {
+ esp_println::println!("ERROR: Logger already set");
}
}
}
@@ -50,16 +92,14 @@ impl Log for InterceptorLogger {
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
- let message = alloc::format!("{}", record.args());
+ let message = alloc::format!("{}: {}", record.level(), record.args());
- // Print to serial using esp_println
- esp_println::println!("{}: {}", record.level(), message);
+ // Print to serial
+ esp_println::println!("{}", message);
- // Capture if active
- self.sync_capture.lock(|capture| {
- if let Some(ref mut buffer) = *capture.borrow_mut() {
- buffer.push(alloc::format!("{}: {}", record.level(), message));
- }
+ // Store in live log ring buffer
+ self.live_log.lock(|buf| {
+ buf.borrow_mut().push(message);
});
}
}
diff --git a/Software/MainBoard/rust/src/webserver/get_log.rs b/Software/MainBoard/rust/src/webserver/get_log.rs
index 0f403a0..1a4e504 100644
--- a/Software/MainBoard/rust/src/webserver/get_log.rs
+++ b/Software/MainBoard/rust/src/webserver/get_log.rs
@@ -1,7 +1,10 @@
use crate::fat_error::FatResult;
use crate::log::LOG_ACCESS;
+use alloc::string::String;
+use alloc::vec::Vec;
use edge_http::io::server::Connection;
use edge_nal::io::{Read, Write};
+use serde::Serialize;
pub(crate) async fn get_log(
conn: &mut Connection<'_, T, N>,
@@ -34,3 +37,29 @@ where
conn.write_all("]".as_bytes()).await?;
Ok(Some(200))
}
+
+#[derive(Serialize)]
+struct LiveLogEntry {
+ seq: u64,
+ text: String,
+}
+
+#[derive(Serialize)]
+struct LiveLogResponse {
+ entries: Vec,
+ dropped: bool,
+ next_seq: u64,
+}
+
+pub(crate) async fn get_live_log(after: Option) -> FatResult