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
17pub trait ReceiverHandler {
19 fn handle_data(&self, data: &[u8]) -> impl Future<Output = ()> + Send;
21
22 fn new() -> Self;
24}
25
26pub struct DummyHandler;
28
29impl ReceiverHandler for DummyHandler {
30 async fn handle_data(&self, _data: &[u8]) {}
31 fn new() -> Self {
32 Self {}
33 }
34}
35
36pub 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 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
58pub const MAX_PACKET_SIZE: u8 = 64;
60
61pub 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 pub const fn new() -> Self {
71 Self {
72 buffer: Pipe::new(),
73 custom_style: None,
74 recieve_handler: None,
75 }
76 }
77
78 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 pub fn with_handler(&mut self, handler: T) {
89 self.recieve_handler = Some(handler);
90 }
91
92 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 let class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16);
116 let (mut sender, mut receiver) = class.split();
117
118 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 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
191pub 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 let b = s.as_bytes();
199 if let Ok(n) = self.0.try_write(b) {
200 if n < b.len() {
201 let _ = self.0.try_write(&b[n..]);
205 }
206 }
207 Ok(())
208 }
209}
210
211#[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#[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#[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}