embassy_usb_logger/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5use core::fmt::Write as _;
6use core::future::Future;
7
8use embassy_futures::join::join;
9use embassy_sync::pipe::Pipe;
10use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
11use embassy_usb::driver::Driver;
12use embassy_usb::{Builder, Config};
13use log::{Metadata, Record};
14
15type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
16
17/// A trait that can be implemented and then passed to the
18pub trait ReceiverHandler {
19    /// Data comes in from the serial port with each command and runs this function
20    fn handle_data(&self, data: &[u8]) -> impl Future<Output = ()> + Send;
21
22    /// Create a new instance of the Handler
23    fn new() -> Self;
24}
25
26/// Use this Handler if you don't wish to use any handler
27pub struct DummyHandler;
28
29impl ReceiverHandler for DummyHandler {
30    async fn handle_data(&self, _data: &[u8]) {}
31    fn new() -> Self {
32        Self {}
33    }
34}
35
36/// The logger state containing buffers that must live as long as the USB peripheral.
37pub struct LoggerState<'d> {
38    state: State<'d>,
39    config_descriptor: [u8; 128],
40    bos_descriptor: [u8; 16],
41    msos_descriptor: [u8; 256],
42    control_buf: [u8; 64],
43}
44
45impl<'d> LoggerState<'d> {
46    /// Create a new instance of the logger state.
47    pub fn new() -> Self {
48        Self {
49            state: State::new(),
50            config_descriptor: [0; 128],
51            bos_descriptor: [0; 16],
52            msos_descriptor: [0; 256],
53            control_buf: [0; 64],
54        }
55    }
56}
57
58/// The packet size used in the usb logger, to be used with `create_future_from_class`
59pub const MAX_PACKET_SIZE: u8 = 64;
60
61/// The logger handle, which contains a pipe with configurable size for buffering log messages.
62pub struct UsbLogger<const N: usize, T: ReceiverHandler + Send + Sync> {
63    buffer: Pipe<CS, N>,
64    custom_style: Option<fn(&Record, &mut Writer<'_, N>) -> ()>,
65    recieve_handler: Option<T>,
66}
67
68impl<const N: usize, T: ReceiverHandler + Send + Sync> UsbLogger<N, T> {
69    /// Create a new logger instance.
70    pub const fn new() -> Self {
71        Self {
72            buffer: Pipe::new(),
73            custom_style: None,
74            recieve_handler: None,
75        }
76    }
77
78    /// Create a new logger instance with a custom formatter.
79    pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self {
80        Self {
81            buffer: Pipe::new(),
82            custom_style: Some(custom_style),
83            recieve_handler: None,
84        }
85    }
86
87    /// Add a command handler to the logger
88    pub fn with_handler(&mut self, handler: T) {
89        self.recieve_handler = Some(handler);
90    }
91
92    /// Run the USB logger using the state and USB driver. Never returns.
93    pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> !
94    where
95        D: Driver<'d>,
96        Self: 'd,
97    {
98        let mut config = Config::new(0xc0de, 0xcafe);
99        config.manufacturer = Some("Embassy");
100        config.product = Some("USB-serial logger");
101        config.serial_number = None;
102        config.max_power = 100;
103        config.max_packet_size_0 = MAX_PACKET_SIZE;
104
105        let mut builder = Builder::new(
106            driver,
107            config,
108            &mut state.config_descriptor,
109            &mut state.bos_descriptor,
110            &mut state.msos_descriptor,
111            &mut state.control_buf,
112        );
113
114        // Create classes on the builder.
115        let class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16);
116        let (mut sender, mut receiver) = class.split();
117
118        // Build the builder.
119        let mut device = builder.build();
120        loop {
121            let run_fut = device.run();
122            let class_fut = self.run_logger_class(&mut sender, &mut receiver);
123            join(run_fut, class_fut).await;
124        }
125    }
126
127    async fn run_logger_class<'d, D>(&self, sender: &mut Sender<'d, D>, receiver: &mut Receiver<'d, D>)
128    where
129        D: Driver<'d>,
130    {
131        let log_fut = async {
132            let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
133            sender.wait_connection().await;
134            loop {
135                let len = self.buffer.read(&mut rx[..]).await;
136                let _ = sender.write_packet(&rx[..len]).await;
137                if len as u8 == MAX_PACKET_SIZE {
138                    let _ = sender.write_packet(&[]).await;
139                }
140            }
141        };
142        let reciever_fut = async {
143            let mut reciever_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
144            receiver.wait_connection().await;
145            loop {
146                let n = receiver.read_packet(&mut reciever_buf).await.unwrap();
147                match &self.recieve_handler {
148                    Some(handler) => {
149                        let data = &reciever_buf[..n];
150                        handler.handle_data(data).await;
151                    }
152                    None => (),
153                }
154            }
155        };
156
157        join(log_fut, reciever_fut).await;
158    }
159
160    /// Creates the futures needed for the logger from a given class
161    /// This can be used in cases where the usb device is already in use for another connection
162    pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>)
163    where
164        D: Driver<'d>,
165    {
166        let (mut sender, mut receiver) = class.split();
167        loop {
168            self.run_logger_class(&mut sender, &mut receiver).await;
169        }
170    }
171}
172
173impl<const N: usize, T: ReceiverHandler + Send + Sync> log::Log for UsbLogger<N, T> {
174    fn enabled(&self, _metadata: &Metadata) -> bool {
175        true
176    }
177
178    fn log(&self, record: &Record) {
179        if self.enabled(record.metadata()) {
180            if let Some(custom_style) = self.custom_style {
181                custom_style(record, &mut Writer(&self.buffer));
182            } else {
183                let _ = write!(Writer(&self.buffer), "{}\r\n", record.args());
184            }
185        }
186    }
187
188    fn flush(&self) {}
189}
190
191/// A writer that writes to the USB logger buffer.
192pub struct Writer<'d, const N: usize>(&'d Pipe<CS, N>);
193
194impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
195    fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
196        // The Pipe is implemented in such way that we cannot
197        // write across the wraparound discontinuity.
198        let b = s.as_bytes();
199        if let Ok(n) = self.0.try_write(b) {
200            if n < b.len() {
201                // We wrote some data but not all, attempt again
202                // as the reason might be a wraparound in the
203                // ring buffer, which resolves on second attempt.
204                let _ = self.0.try_write(&b[n..]);
205            }
206        }
207        Ok(())
208    }
209}
210
211/// Initialize and run the USB serial logger, never returns.
212///
213/// Arguments specify the buffer size, log level and the USB driver, respectively. You can optionally add a RecieverHandler.
214///
215/// # Usage
216///
217/// ```
218/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
219/// ```
220///
221/// # Safety
222///
223/// This macro should only be invoked only once since it is setting the global logging state of the application.
224#[macro_export]
225macro_rules! run {
226    ( $x:expr, $l:expr, $p:ident ) => {
227        static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> =
228            ::embassy_usb_logger::UsbLogger::new();
229        unsafe {
230            let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
231        }
232        let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
233    };
234
235    ( $x:expr, $l:expr, $p:ident, $h:ty ) => {
236        unsafe {
237            static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new();
238            LOGGER.with_handler(<$h>::new());
239            let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
240            let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
241        }
242    };
243}
244
245/// Initialize the USB serial logger from a serial class and return the future to run it.
246///
247/// Arguments specify the buffer size, log level and the serial class, respectively. You can optionally add a RecieverHandler.
248///
249/// # Usage
250///
251/// ```
252/// embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, class);
253/// ```
254///
255/// # Safety
256///
257/// This macro should only be invoked only once since it is setting the global logging state of the application.
258#[macro_export]
259macro_rules! with_class {
260    ( $x:expr, $l:expr, $p:ident ) => {{
261        static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> =
262            ::embassy_usb_logger::UsbLogger::new();
263        unsafe {
264            let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
265        }
266        LOGGER.create_future_from_class($p)
267    }};
268
269    ( $x:expr, $l:expr, $p:ident, $h:ty ) => {{
270        unsafe {
271            static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new();
272            LOGGER.with_handler(<$h>::new());
273            let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
274            LOGGER.create_future_from_class($p)
275        }
276    }};
277}
278
279/// Initialize the USB serial logger from a serial class and return the future to run it.
280/// This version of the macro allows for a custom style function to be passed in.
281/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer.
282///
283/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. You can optionally add a RecieverHandler.
284///
285/// # Usage
286///
287/// ```
288/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| {
289///     use core::fmt::Write;
290///     let level = record.level().as_str();
291///     write!(writer, "[{level}] {}\r\n", record.args()).unwrap();
292/// });
293/// ```
294///
295/// # Safety
296///
297/// This macro should only be invoked only once since it is setting the global logging state of the application.
298#[macro_export]
299macro_rules! with_custom_style {
300    ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{
301        static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> =
302            ::embassy_usb_logger::UsbLogger::with_custom_style($s);
303        unsafe {
304            let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
305        }
306        LOGGER.create_future_from_class($p)
307    }};
308
309    ( $x:expr, $l:expr, $p:ident, $s:expr, $h:ty ) => {{
310        unsafe {
311            static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> =
312                ::embassy_usb_logger::UsbLogger::with_custom_style($s);
313            LOGGER.with_handler(<$h>::new());
314            let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
315            LOGGER.create_future_from_class($p)
316        }
317    }};
318}