diff --git a/mqtt_display/src/bin/main.rs b/mqtt_display/src/bin/main.rs index 671639c..fcf9850 100644 --- a/mqtt_display/src/bin/main.rs +++ b/mqtt_display/src/bin/main.rs @@ -150,14 +150,23 @@ async fn main(spawner: Spawner) -> ! { match select3( mqtt_rx.receive(), imu_rx.receive(), - Timer::after(Duration::from_secs(30)), + Timer::after(Duration::from_secs(5)), ).await { Either3::First(msg) => { mqtt_msg_count += 1; handle_mqtt_message(msg).await; display::api::set_mqtt_status(true, mqtt_msg_count).await; } - Either3::Second(reading) => { + Either3::Second(mut reading) => { + // Drain any queued IMU messages and keep only the latest + let mut drained = 0; + while let Ok(next) = imu_rx.try_receive() { + reading = next; + drained += 1; + } + if drained > 0 { + log::info!("IMU drained {} stale readings before display", drained); + } imu_reading_count += 1; display::api::show_imu(reading).await; if imu_reading_count % MQTT_PUBLISH_DIVIDER == 0 { @@ -169,7 +178,9 @@ async fn main(spawner: Spawner) -> ! { } } Either3::Third(_) => { - info!("Heartbeat: {} IMU readings", imu_reading_count); + crate::mpu::api::IMU_CHANNEL.clear(); + info!("IMU heartbeat: force-cleared queue, {} readings total", imu_reading_count); + // info!("Heartbeat: {} IMU readings", imu_reading_count); } } } @@ -259,7 +270,6 @@ async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) { #[embassy_executor::task] async fn button_detection_task(mut select_btn: Input<'static>, mut next_btn: Input<'static>) { - use embassy_futures::select::Either; loop { match select( select_btn.wait_for_rising_edge(), diff --git a/mqtt_display/src/bus/mod.rs b/mqtt_display/src/bus/mod.rs index 4a90edf..bdc6d2a 100644 --- a/mqtt_display/src/bus/mod.rs +++ b/mqtt_display/src/bus/mod.rs @@ -1,8 +1,6 @@ // src/bus/mod.rs use core::cell::RefCell; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::mutex::Mutex; use embedded_hal_bus::i2c::RefCellDevice; use esp_hal::i2c::master::I2c; use esp_hal::Async; diff --git a/mqtt_display/src/display/tui.rs b/mqtt_display/src/display/tui.rs index 829bfd4..4b2d17f 100644 --- a/mqtt_display/src/display/tui.rs +++ b/mqtt_display/src/display/tui.rs @@ -136,8 +136,7 @@ impl Component for Screen { } } -// ============ PAGE ORDER ============ - +// PAGE ORDER const PAGE_ORDER: &[&str] = &["menu", "imu", "chat"]; pub fn next_page_id(current: &str) -> &'static str { @@ -150,9 +149,7 @@ pub fn prev_page_id(current: &str) -> &'static str { if idx == 0 { PAGE_ORDER[PAGE_ORDER.len() - 1] } else { PAGE_ORDER[idx - 1] } } -// ============ RENDERING - NO LAYOUT, DIRECT RECTS ============ -// 128x32 display = 21 chars x 4 rows (6x8 font) - +// RENDERING /// Get row rect (0-3) for 128x32 display #[inline] fn row(area: Rect, n: u16) -> Rect { @@ -169,7 +166,7 @@ pub fn render_frame( let area = f.area(); if let Some(ref err) = state.last_error { - f.render_widget(Paragraph::new(format!("ERR:{}", err.as_str())).red().bold(), area); + f.render_widget(Paragraph::new(format!("ERR:{}", err.as_str())).white().bold(), area); return; } @@ -209,10 +206,10 @@ fn render_menu(f: &mut ratatui::Frame, area: Rect, focused: Option<&PageFocus>, fn render_imu(f: &mut ratatui::Frame, area: Rect, focused: Option<&PageFocus>, state: &DisplayState) { if let Some(ref imu) = state.last_imu { // Row 0 Gyro - // f.render_widget( - // Paragraph::new(format!("G:{:+5.0}{:+5.0}{:+5.0}", imu.gyro_dps[0], imu.gyro_dps[1], imu.gyro_dps[2])).cyan(), - // row(area, 0), - // ); + f.render_widget( + Paragraph::new(format!("G:{:+5.0}{:+5.0}{:+5.0}", imu.gyro_dps[0], imu.gyro_dps[1], imu.gyro_dps[2])).cyan(), + row(area, 0), + ); // Row 1 Accel f.render_widget( Paragraph::new(format!("A:{:+.1} {:+.1} {:+.1}", imu.accel_g[0], imu.accel_g[1], imu.accel_g[2])).green(), @@ -228,7 +225,7 @@ fn render_imu(f: &mut ratatui::Frame, area: Rect, focused: Option<&PageFocus>, s } // Row 3 Nav bar - render_nav_bar(f, row(area, 2), focused); + // render_nav_bar(f, row(area, 3), focused); } fn render_chat(f: &mut ratatui::Frame, area: Rect, focused: Option<&PageFocus>, state: &DisplayState) { @@ -245,7 +242,7 @@ fn render_chat(f: &mut ratatui::Frame, area: Rect, focused: Option<&PageFocus>, } // Row 3 Nav bar - render_nav_bar(f, row(area, 3), focused); + render_nav_bar(f, row(area, 2), focused); } /// Nav bar: --[<]-------[>]-- diff --git a/mqtt_display/src/mpu/api.rs b/mqtt_display/src/mpu/api.rs index 857764b..9ade484 100644 --- a/mqtt_display/src/mpu/api.rs +++ b/mqtt_display/src/mpu/api.rs @@ -4,9 +4,9 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::{Channel, Receiver, Sender}; use crate::contracts::ImuReading; -const QUEUE_SIZE: usize = 4; +const QUEUE_SIZE: usize = 16; -pub(crate) static IMU_CHANNEL: Channel = Channel::new(); +pub static IMU_CHANNEL: Channel = Channel::new(); pub fn events() -> Receiver<'static, CriticalSectionRawMutex, ImuReading, QUEUE_SIZE> { IMU_CHANNEL.receiver() diff --git a/mqtt_display/src/mpu/task.rs b/mqtt_display/src/mpu/task.rs index 6a746f8..93ae78e 100644 --- a/mqtt_display/src/mpu/task.rs +++ b/mqtt_display/src/mpu/task.rs @@ -15,7 +15,7 @@ use crate::mpu::api::sender; use crate::mpu::driver::Mpu6050; /// Sampling interval in milliseconds. -/// 50ms = 20Hz, reasonable for display updates. +/// 50ms = 20Hz const SAMPLE_INTERVAL_MS: u64 = 50; /// MPU6050 I2C address (0x68 with AD0 low, 0x69 with AD0 high) @@ -52,7 +52,7 @@ pub async fn mpu_task(i2c: I2cDevice) { Timer::after(Duration::from_secs(2)).await; continue; } - Err(_e) => { + Err(_) => { warn!( "I2C error verifying MPU6050 (attempt {})", init_attempts @@ -68,7 +68,7 @@ pub async fn mpu_task(i2c: I2cDevice) { info!("MPU6050 initialized successfully"); break; } - Err(_e) => { + Err(_) => { error!("MPU6050 init failed (attempt {})", init_attempts); Timer::after(Duration::from_secs(2)).await; continue; @@ -78,11 +78,14 @@ pub async fn mpu_task(i2c: I2cDevice) { // Allow sensor to stabilize after wake-up Timer::after(Duration::from_millis(100)).await; - - info!("MPU task entering sampling loop ({}ms interval)", SAMPLE_INTERVAL_MS); + info!( + "MPU task entering sampling loop ({}ms interval)", + SAMPLE_INTERVAL_MS + ); let tx = sender(); let mut consecutive_errors = 0u32; + let mut sent_count: u32 = 0; loop { let start = Instant::now(); @@ -98,13 +101,20 @@ pub async fn mpu_task(i2c: I2cDevice) { timestamp_ms: start.as_millis(), }; - // Try to send; if queue is full, drop oldest by using try_send - // This ensures we never block the sampling loop - if tx.try_send(reading).is_err() { - // Queue full - that's okay, main will get the next one + sent_count = sent_count.wrapping_add(1); + if tx.try_send(reading).is_ok() { + if sent_count % 20 == 0 { + info!( + "IMU send#{} ax:{:.2} ay:{:.2} az:{:.2} t:{:.1}", + sent_count, accel_g[0], accel_g[1], accel_g[2], temp_c + ); + } + } else if sent_count % 20 == 0 { + info!("IMU drop: channel full ({} sent)", sent_count); } } - Err(_e) => { + + Err(_) => { consecutive_errors += 1; if consecutive_errors == 1 || consecutive_errors % 10 == 0 { warn!("MPU read error (consecutive: {})", consecutive_errors); @@ -122,7 +132,7 @@ pub async fn mpu_task(i2c: I2cDevice) { } } - // Sleep for remainder of interval + // Maintain a steady period let elapsed = start.elapsed(); let target = Duration::from_millis(SAMPLE_INTERVAL_MS); if elapsed < target {