From 61d8072926b7dcd861219df6f1d6dd6639c285f9 Mon Sep 17 00:00:00 2001 From: Lewis Lakerink Date: Mon, 16 Mar 2026 17:28:52 +1100 Subject: [PATCH] Fix UART/BLE status reporting gate and test isolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, process_mesh_operations() gated both BLE notifications and UART status sends on is_add_packet_buf_ready() && !uart_manager.started(). Once the UART daemon connected, started() returned true and ALL status reporting silently stopped — BLE included. The fix separates the two reporting paths: - BLE: gated only on is_add_packet_buf_ready() - UART: gated only on uart_manager.uart_status_reporting_enabled() - Mesh status data collected once and shared between both sinks Supporting changes: - mesh_node_report_status tagged #[cfg_attr(test, mry::mry)] to enable mocking - reset_global_state() in connection tests resets app().uart_manager to prevent mock state leaking into later tests - reset_app_uart_for_test() added to lib.rs as a direct-access helper that resets the global APP's uart_manager without going through the mockable app_mocker(), so it is safe to call from test helpers that run inside a mry::lock(app_mocker) scope - reset_test_state() in packet_processing tests calls reset_app_uart_for_test() to eliminate mock contamination from preceding connection tests - Two new tests: test_process_mesh_operations_ble_and_uart_are_independent and test_process_mesh_operations_ble_fires_without_uart --- rust/src/lib.rs | 11 ++ rust/src/sdk/ble_app/irq/connection.rs | 131 ++++++++++++++---- rust/src/sdk/ble_app/irq/mesh.rs | 1 + .../sdk/ble_app/light_ll/packet_processing.rs | 5 +- 4 files changed, 123 insertions(+), 25 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9781413..9e1e2b8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -62,6 +62,17 @@ pub fn app<'a>() -> &'a mut App { unsafe { __make_static(&mut *app_mocker()) } } +/// Resets the global APP's uart_manager without going through `app_mocker`, so it +/// is safe to call from test helpers that run inside a `mry::lock(app_mocker)` scope. +#[cfg(test)] +#[coverage(off)] +pub fn reset_app_uart_for_test() { + #[allow(static_mut_refs)] + unsafe { + (*APP).uart_manager = crate::uart_manager::UartManager::default(); + } +} + unsafe fn __make_static(t: &mut T) -> &'static mut T { core::mem::transmute(t) } diff --git a/rust/src/sdk/ble_app/irq/connection.rs b/rust/src/sdk/ble_app/irq/connection.rs index 9a31cc3..34154e7 100644 --- a/rust/src/sdk/ble_app/irq/connection.rs +++ b/rust/src/sdk/ble_app/irq/connection.rs @@ -378,7 +378,9 @@ fn process_mesh_operations() { use crate::config::VENDOR_ID; use crate::mesh::MESH_NODE_ST_VAL_LEN; use crate::sdk::ble_app::ble_ll_pair::pair_proc; + use crate::sdk::drivers::uart::{UartData, UART_DATA_LEN}; use crate::sdk::packet_types::{Packet, PacketAttCmd, PacketAttValue, PacketL2capHead}; + use crate::uart_manager::UartMsg; // Process any pending status read operations if SLAVE_READ_STATUS_BUSY.get() != 0 { @@ -397,16 +399,20 @@ fn process_mesh_operations() { // Flush any pending mesh node status updates mesh_node_flush_status(); - // Generate and send mesh status reports if conditions are met: - // - Transmission buffer is available - // - UART manager is not started (to avoid conflicts) - if is_add_packet_buf_ready() && !app().uart_manager.started() { - let mut data = [0u8; 20]; + // Generate and send mesh status reports. + // Data is collected once and dispatched independently to each sink: + // - BLE: sent whenever the TX buffer is ready, regardless of UART state + // - UART: sent whenever UART status reporting is enabled, regardless of BLE state + // The two paths are fully independent of each other. + let mut data = [0u8; 20]; - // Collect mesh node status data (limit based on available space) - let count = super::mesh::mesh_node_report_status(&mut data, 10 / MESH_NODE_ST_VAL_LEN); + // Collect mesh node status data (limit based on available space) + let count = super::mesh::mesh_node_report_status(&mut data, 10 / MESH_NODE_ST_VAL_LEN); - if count != 0 { + if count != 0 { + // BLE path: send status notification to the connected central. + // Only gated on TX buffer availability — not UART state. + if is_add_packet_buf_ready() { // Create BLE ATT packet for mesh status reporting let mut pkt = Packet { att_cmd: PacketAttCmd { @@ -457,24 +463,21 @@ fn process_mesh_operations() { // Copy the collected mesh status data into the packet payload pkt.att_cmd_mut().value.val[3..].copy_from_slice(&data); - // Add the packet to the transmission queue + // Add the packet to the BLE transmission queue rf_link_add_tx_packet(&pkt); + } - // Also send status via UART if UART status reporting is enabled - // This provides debugging/monitoring capability - if app().uart_manager.uart_status_reporting_enabled() { - use crate::sdk::drivers::uart::{UartData, UART_DATA_LEN}; - use crate::uart_manager::UartMsg; - - let mut uart_msg = UartData { - len: UART_DATA_LEN as u32, - data: [0; UART_DATA_LEN], - }; - uart_msg.data[2] = UartMsg::LightStatus as u8; - uart_msg.data[3..3 + count * MESH_NODE_ST_VAL_LEN] - .copy_from_slice(&data[..count * MESH_NODE_ST_VAL_LEN]); - let _ = app().uart_manager.send_message(&uart_msg); - } + // UART path: send status to the connected daemon for monitoring/debugging. + // Only gated on the UART status reporting enable flag — not BLE TX buffer state. + if app().uart_manager.uart_status_reporting_enabled() { + let mut uart_msg = UartData { + len: UART_DATA_LEN as u32, + data: [0; UART_DATA_LEN], + }; + uart_msg.data[2] = UartMsg::LightStatus as u8; + uart_msg.data[3..3 + count * MESH_NODE_ST_VAL_LEN] + .copy_from_slice(&data[..count * MESH_NODE_ST_VAL_LEN]); + let _ = app().uart_manager.send_message(&uart_msg); } } } @@ -644,6 +647,8 @@ mod tests { // Import mock functions from their original modules use crate::common::mock_pair_load_key; use crate::sdk::ble_app::ble_ll_channel_selection::mock_ble_ll_build_available_channel_table; + use crate::sdk::ble_app::irq::mesh::mock_mesh_node_report_status; + use crate::sdk::ble_app::irq::mesh_node_report_status; // needed for mry::lock use crate::sdk::ble_app::light_ll::connection_management::mock_back_to_rxmode_bridge; use crate::sdk::ble_app::light_ll::mesh_management::{ mock_mesh_node_flush_status, mock_mesh_report_status_enable, @@ -661,6 +666,7 @@ mod tests { mock_write_reg_system_tick_irq, }; use crate::sdk::packet_types::{Packet, PacketAttData}; + use crate::uart_manager::UartManager; // Import mock for functions in the same module use super::mock_cleanup_ble_disconnection; @@ -687,6 +693,8 @@ mod tests { let mut chn_map = SLAVE_CHN_MAP.lock(); *chn_map = [0xff, 0xff, 0xff, 0xff, 0x1f]; // Default all channels enabled } + + app().uart_manager = UartManager::default(); } /// Tests that the connection event counter is incremented on every call. @@ -2130,6 +2138,81 @@ mod tests { mock_mesh_node_flush_status().assert_called(1); } + /// Tests that the BLE and UART status reporting paths are fully independent. + /// + /// When the UART daemon is started and status reporting is enabled, UART should + /// fire regardless of BLE TX buffer state. When BLE TX buffer is ready, BLE + /// should fire regardless of UART state. Neither path should block the other. + #[test] + #[mry::lock( + is_add_packet_buf_ready, + mesh_node_flush_status, + mesh_node_report_status, + rf_link_add_tx_packet + )] + fn test_process_mesh_operations_ble_and_uart_are_independent() { + reset_global_state(); + + // UART daemon is started (sender_started = true), and UART status reporting is enabled. + // BLE TX buffer is NOT ready. + // Expected: UART send fires; BLE rf_link_add_tx_packet does NOT fire. + mock_mesh_node_flush_status().returns(()); + mock_is_add_packet_buf_ready().returns(false); + mock_mesh_node_report_status(Any, Any).returns(2); // 2 nodes worth of data + mock_rf_link_add_tx_packet(Any).returns(true); + + { + let mut app = app(); + app.uart_manager + .mock_uart_status_reporting_enabled() + .returns(true); + app.uart_manager.mock_send_message(Any).returns(true); + } + + SLAVE_READ_STATUS_BUSY.set(0); + process_mesh_operations(); + + // BLE should not have been called (TX buffer not ready) + mock_rf_link_add_tx_packet(Any).assert_called(0); + // UART should have been called despite BLE buffer not being ready + app().uart_manager.mock_send_message(Any).assert_called(1); + } + + /// Tests that BLE status is sent when TX buffer is ready, even if UART is not enabled. + #[test] + #[mry::lock( + is_add_packet_buf_ready, + mesh_node_flush_status, + mesh_node_report_status, + rf_link_add_tx_packet + )] + fn test_process_mesh_operations_ble_fires_without_uart() { + reset_global_state(); + + // BLE TX buffer ready; UART status reporting disabled. + // Expected: BLE fires; UART send does NOT fire. + mock_mesh_node_flush_status().returns(()); + mock_is_add_packet_buf_ready().returns(true); + mock_mesh_node_report_status(Any, Any).returns(2); + mock_rf_link_add_tx_packet(Any).returns(true); + + { + let mut app = app(); + app.uart_manager + .mock_uart_status_reporting_enabled() + .returns(false); + app.uart_manager.mock_send_message(Any).returns(true); + } + + SLAVE_READ_STATUS_BUSY.set(0); + process_mesh_operations(); + + // BLE should have fired + mock_rf_link_add_tx_packet(Any).assert_called(1); + // UART should not have fired (reporting disabled) + app().uart_manager.mock_send_message(Any).assert_called(0); + } + /// Tests connection event timing management. /// /// This is the most complex function with critical timing logic. diff --git a/rust/src/sdk/ble_app/irq/mesh.rs b/rust/src/sdk/ble_app/irq/mesh.rs index 1a35a17..740548f 100644 --- a/rust/src/sdk/ble_app/irq/mesh.rs +++ b/rust/src/sdk/ble_app/irq/mesh.rs @@ -28,6 +28,7 @@ use crate::sdk::mcu::clock::CLOCK_SYS_CLOCK_1US; use crate::sdk::mcu::register::*; use crate::state::*; +#[cfg_attr(test, mry::mry)] /// Reports mesh node status information for transmission to the BLE master. /// /// This function collects status information from mesh nodes that have pending updates diff --git a/rust/src/sdk/ble_app/light_ll/packet_processing.rs b/rust/src/sdk/ble_app/light_ll/packet_processing.rs index 4019606..ea2ba68 100644 --- a/rust/src/sdk/ble_app/light_ll/packet_processing.rs +++ b/rust/src/sdk/ble_app/light_ll/packet_processing.rs @@ -1569,7 +1569,7 @@ mod tests { mock_read_reg_dma_tx_rptr, mock_read_reg_dma_tx_wptr, mock_read_reg_rnd_number, mock_read_reg_system_tick, mock_write_reg_dma_tx_fifo, }; - use crate::{app_mocker, mock_app_mocker, App}; + use crate::{app_mocker, mock_app_mocker, reset_app_uart_for_test, App}; /// Helper function to create a test packet with specified values. fn create_test_packet(opcode: u8, sno: [u8; 3], src_adr: u16, dst_adr: u16) -> Packet { @@ -1695,6 +1695,9 @@ mod tests { }, }, }; + + // Reset UartManager to clear mock state from previous tests + reset_app_uart_for_test(); } // ================================================================================