Skip to main content

veloxity_core/
log.rs

1use core::cell::RefCell;
2use core::fmt;
3
4pub mod drain;
5
6// WARNING: Critical Section forces interrupts to wait while processing
7use critical_section::Mutex;
8
9use crate::comm::messages::enums::Severity;
10
11// --- Configuration ---
12const LOG_QUEUE_SIZE: usize = 16;
13const MAX_LOG_LEN: usize = 50; // Matches MAVLink STATUSTEXT length
14
15// --- Data Structures ---
16
17/// A minimal fixed-capacity string buffer (Replaces heapless::String)
18#[derive(Clone, Copy)]
19pub struct LogString {
20    buffer: [u8; MAX_LOG_LEN],
21    len: usize,
22}
23
24impl LogString {
25    pub const fn new() -> Self {
26        Self {
27            buffer: [0; MAX_LOG_LEN],
28            len: 0,
29        }
30    }
31
32    pub fn as_str(&self) -> &str {
33        // SAFETY: We only write valid UTF-8 via fmt::Write
34        unsafe { core::str::from_utf8_unchecked(&self.buffer[..self.len]) }
35    }
36}
37
38impl fmt::Write for LogString {
39    fn write_str(&mut self, s: &str) -> fmt::Result {
40        let bytes = s.as_bytes();
41        let remaining = MAX_LOG_LEN - self.len;
42        let copy_len = bytes.len().min(remaining);
43
44        // Copy data
45        self.buffer[self.len..self.len + copy_len].copy_from_slice(&bytes[..copy_len]);
46        self.len += copy_len;
47
48        if bytes.len() > remaining {
49            return Err(fmt::Error); // Indicate truncation
50        }
51        Ok(())
52    }
53}
54
55/// A combined entry holding severity and text
56#[derive(Clone, Copy)]
57pub struct LogEntry {
58    pub severity: Severity,
59    pub message: LogString,
60}
61
62impl LogEntry {
63    pub const fn empty() -> Self {
64        Self {
65            // Keep this aligned with the core communication message severity surface.
66            severity: Severity::Info,
67            message: LogString {
68                buffer: [0; MAX_LOG_LEN],
69                len: 0,
70            },
71        }
72    }
73}
74
75/// A minimal Ring Buffer (Replaces heapless::Deque)
76struct LogQueue {
77    storage: [LogEntry; LOG_QUEUE_SIZE],
78    head: usize, // Write index
79    tail: usize, // Read index
80    full: bool,
81}
82
83impl LogQueue {
84    const fn new() -> Self {
85        Self {
86            storage: [LogEntry::empty(); LOG_QUEUE_SIZE],
87            head: 0,
88            tail: 0,
89            full: false,
90        }
91    }
92
93    fn push(&mut self, entry: LogEntry) {
94        self.storage[self.head] = entry;
95        self.head = (self.head + 1) % LOG_QUEUE_SIZE;
96
97        if self.full {
98            // If full, head bumped into tail, so move tail (overwrite oldest)
99            self.tail = (self.tail + 1) % LOG_QUEUE_SIZE;
100        }
101
102        self.full = self.head == self.tail;
103    }
104
105    fn pop(&mut self) -> Option<LogEntry> {
106        if !self.full && self.head == self.tail {
107            return None; // Empty
108        }
109
110        let entry = self.storage[self.tail];
111        self.tail = (self.tail + 1) % LOG_QUEUE_SIZE;
112        self.full = false;
113        Some(entry)
114    }
115}
116
117// --- Global State ---
118
119static LOG_QUEUE: Mutex<RefCell<LogQueue>> = Mutex::new(RefCell::new(LogQueue::new()));
120
121// --- Public API ---
122
123pub struct Logger;
124
125impl Logger {
126    pub fn log(severity: Severity, args: fmt::Arguments) {
127        critical_section::with(|cs| {
128            let mut queue = LOG_QUEUE.borrow_ref_mut(cs);
129
130            let mut entry = LogEntry::empty();
131            entry.severity = severity;
132
133            // Write the formatted string into our buffer
134            // We ignore errors (truncation) to ensure we always log something
135            let _ = fmt::Write::write_fmt(&mut entry.message, args);
136
137            queue.push(entry);
138        });
139    }
140
141    // Keep variants aligned with the core communication message severity surface.
142    pub fn info(args: fmt::Arguments) {
143        Self::log(Severity::Info, args);
144    }
145    pub fn warn(args: fmt::Arguments) {
146        Self::log(Severity::Warning, args);
147    }
148    pub fn error(args: fmt::Arguments) {
149        Self::log(Severity::Error, args);
150    }
151    pub fn debug(args: fmt::Arguments) {
152        Self::log(Severity::Debug, args);
153    }
154
155    /// Called by Main Loop to drain queue
156    pub fn pop() -> Option<LogEntry> {
157        critical_section::with(|cs| LOG_QUEUE.borrow_ref_mut(cs).pop())
158    }
159}
160
161// --- Macros ---
162// These allow you to use log_info!("val: {}", x) anywhere in your code.
163// The Macros can generally be used either by placing
164//  `use crate::log_<info, warn, error, or debug>;`
165// at the top of the crate, then using `log_<info, warn, error, or debug>!("{}", var);`,
166// or by using `crate::log_<info, warn, error, or debug>!("{}", var);` directly.
167
168#[macro_export]
169macro_rules! log_info {
170    ($($arg:tt)*) => { $crate::log::Logger::info(format_args!($($arg)*)) };
171}
172#[macro_export]
173macro_rules! log_warn {
174    ($($arg:tt)*) => { $crate::log::Logger::warn(format_args!($($arg)*)) };
175}
176#[macro_export]
177macro_rules! log_error {
178    ($($arg:tt)*) => { $crate::log::Logger::error(format_args!($($arg)*)) };
179}
180// Add debug macro if you want it exposed
181#[macro_export]
182macro_rules! log_debug {
183    ($($arg:tt)*) => { $crate::log::Logger::debug(format_args!($($arg)*))};
184}