Merge pull request #3486 from BjornTheProgrammer/main

Add ReceiverHandler to logger
This commit is contained in:
Ulf Lilleengen 2024-11-01 09:28:43 +00:00 committed by GitHub
commit 763de8a37e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 143 additions and 13 deletions

View File

@ -3,6 +3,7 @@
#![warn(missing_docs)] #![warn(missing_docs)]
use core::fmt::Write as _; use core::fmt::Write as _;
use core::future::Future;
use embassy_futures::join::join; use embassy_futures::join::join;
use embassy_sync::pipe::Pipe; use embassy_sync::pipe::Pipe;
@ -13,6 +14,25 @@ use log::{Metadata, Record};
type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
/// A trait that can be implemented and then passed to the
pub trait ReceiverHandler {
/// Data comes in from the serial port with each command and runs this function
fn handle_data(&self, data: &[u8]) -> impl Future<Output = ()> + Send;
/// Create a new instance of the Handler
fn new() -> Self;
}
/// Use this Handler if you don't wish to use any handler
pub struct DummyHandler;
impl ReceiverHandler for DummyHandler {
async fn handle_data(&self, _data: &[u8]) {}
fn new() -> Self {
Self {}
}
}
/// The logger state containing buffers that must live as long as the USB peripheral. /// The logger state containing buffers that must live as long as the USB peripheral.
pub struct LoggerState<'d> { pub struct LoggerState<'d> {
state: State<'d>, state: State<'d>,
@ -39,17 +59,19 @@ impl<'d> LoggerState<'d> {
pub const MAX_PACKET_SIZE: u8 = 64; pub const MAX_PACKET_SIZE: u8 = 64;
/// The logger handle, which contains a pipe with configurable size for buffering log messages. /// The logger handle, which contains a pipe with configurable size for buffering log messages.
pub struct UsbLogger<const N: usize> { pub struct UsbLogger<const N: usize, T: ReceiverHandler + Send + Sync> {
buffer: Pipe<CS, N>, buffer: Pipe<CS, N>,
custom_style: Option<fn(&Record, &mut Writer<'_, N>) -> ()>, custom_style: Option<fn(&Record, &mut Writer<'_, N>) -> ()>,
recieve_handler: Option<T>,
} }
impl<const N: usize> UsbLogger<N> { impl<const N: usize, T: ReceiverHandler + Send + Sync> UsbLogger<N, T> {
/// Create a new logger instance. /// Create a new logger instance.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
buffer: Pipe::new(), buffer: Pipe::new(),
custom_style: None, custom_style: None,
recieve_handler: None,
} }
} }
@ -58,9 +80,15 @@ impl<const N: usize> UsbLogger<N> {
Self { Self {
buffer: Pipe::new(), buffer: Pipe::new(),
custom_style: Some(custom_style), custom_style: Some(custom_style),
recieve_handler: None,
} }
} }
/// Add a command handler to the logger
pub fn with_handler(&mut self, handler: T) {
self.recieve_handler = Some(handler);
}
/// Run the USB logger using the state and USB driver. Never returns. /// Run the USB logger using the state and USB driver. Never returns.
pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> ! pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> !
where where
@ -118,15 +146,22 @@ impl<const N: usize> UsbLogger<N> {
} }
} }
}; };
let discard_fut = async { let reciever_fut = async {
let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; let mut reciever_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
receiver.wait_connection().await; receiver.wait_connection().await;
loop { loop {
let _ = receiver.read_packet(&mut discard_buf).await; let n = receiver.read_packet(&mut reciever_buf).await.unwrap();
match &self.recieve_handler {
Some(handler) => {
let data = &reciever_buf[..n];
handler.handle_data(data).await;
}
None => (),
}
} }
}; };
join(log_fut, discard_fut).await; join(log_fut, reciever_fut).await;
} }
/// Creates the futures needed for the logger from a given class /// Creates the futures needed for the logger from a given class
@ -142,7 +177,7 @@ impl<const N: usize> UsbLogger<N> {
} }
} }
impl<const N: usize> log::Log for UsbLogger<N> { impl<const N: usize, T: ReceiverHandler + Send + Sync> log::Log for UsbLogger<N, T> {
fn enabled(&self, _metadata: &Metadata) -> bool { fn enabled(&self, _metadata: &Metadata) -> bool {
true true
} }
@ -182,7 +217,7 @@ impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
/// Initialize and run the USB serial logger, never returns. /// Initialize and run the USB serial logger, never returns.
/// ///
/// Arguments specify the buffer size, log level and the USB driver, respectively. /// Arguments specify the buffer size, log level and the USB driver, respectively. You can optionally add a RecieverHandler.
/// ///
/// # Usage /// # Usage
/// ///
@ -196,17 +231,27 @@ impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
#[macro_export] #[macro_export]
macro_rules! run { macro_rules! run {
( $x:expr, $l:expr, $p:ident ) => { ( $x:expr, $l:expr, $p:ident ) => {
static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> =
::embassy_usb_logger::UsbLogger::new();
unsafe { unsafe {
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
} }
let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
}; };
( $x:expr, $l:expr, $p:ident, $h:ty ) => {
unsafe {
static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new();
LOGGER.with_handler(<$h>::new());
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
}
};
} }
/// Initialize the USB serial logger from a serial class and return the future to run it. /// Initialize the USB serial logger from a serial class and return the future to run it.
/// ///
/// Arguments specify the buffer size, log level and the serial class, respectively. /// Arguments specify the buffer size, log level and the serial class, respectively. You can optionally add a RecieverHandler.
/// ///
/// # Usage /// # Usage
/// ///
@ -220,19 +265,29 @@ macro_rules! run {
#[macro_export] #[macro_export]
macro_rules! with_class { macro_rules! with_class {
( $x:expr, $l:expr, $p:ident ) => {{ ( $x:expr, $l:expr, $p:ident ) => {{
static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> =
::embassy_usb_logger::UsbLogger::new();
unsafe { unsafe {
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
} }
LOGGER.create_future_from_class($p) LOGGER.create_future_from_class($p)
}}; }};
( $x:expr, $l:expr, $p:ident, $h:ty ) => {{
unsafe {
static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new();
LOGGER.with_handler(<$h>::new());
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
LOGGER.create_future_from_class($p)
}
}};
} }
/// Initialize the USB serial logger from a serial class and return the future to run it. /// Initialize the USB serial logger from a serial class and return the future to run it.
/// This version of the macro allows for a custom style function to be passed in. /// This version of the macro allows for a custom style function to be passed in.
/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer. /// The custom style function will be called for each log record and is responsible for writing the log message to the buffer.
/// ///
/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. /// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. You can optionally add a RecieverHandler.
/// ///
/// # Usage /// # Usage
/// ///
@ -250,10 +305,21 @@ macro_rules! with_class {
#[macro_export] #[macro_export]
macro_rules! with_custom_style { macro_rules! with_custom_style {
( $x:expr, $l:expr, $p:ident, $s:expr ) => {{ ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{
static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::with_custom_style($s); static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> =
::embassy_usb_logger::UsbLogger::with_custom_style($s);
unsafe { unsafe {
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
} }
LOGGER.create_future_from_class($p) LOGGER.create_future_from_class($p)
}}; }};
( $x:expr, $l:expr, $p:ident, $s:expr, $h:ty ) => {{
unsafe {
static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> =
::embassy_usb_logger::UsbLogger::with_custom_style($s);
LOGGER.with_handler(<$h>::new());
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
LOGGER.create_future_from_class($p)
}
}};
} }

View File

@ -0,0 +1,64 @@
//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
//!
//! This creates the possibility to send log::info/warn/error/debug! to USB serial port.
#![no_std]
#![no_main]
use core::str;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::USB;
use embassy_rp::rom_data::reset_to_usb_boot;
use embassy_rp::usb::{Driver, InterruptHandler};
use embassy_time::Timer;
use embassy_usb_logger::ReceiverHandler;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
});
struct Handler;
impl ReceiverHandler for Handler {
async fn handle_data(&self, data: &[u8]) {
if let Ok(data) = str::from_utf8(data) {
let data = data.trim();
// If you are using elf2uf2-term with the '-t' flag, then when closing the serial monitor,
// this will automatically put the pico into boot mode
if data == "q" || data == "elf2uf2-term" {
reset_to_usb_boot(0, 0); // Restart the chip
} else if data.eq_ignore_ascii_case("hello") {
log::info!("World!");
} else {
log::info!("Recieved: {:?}", data);
}
}
}
fn new() -> Self {
Self
}
}
#[embassy_executor::task]
async fn logger_task(driver: Driver<'static, USB>) {
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver, Handler);
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let driver = Driver::new(p.USB, Irqs);
spawner.spawn(logger_task(driver)).unwrap();
let mut counter = 0;
loop {
counter += 1;
log::info!("Tick {}", counter);
Timer::after_secs(1).await;
}
}