#[cfg(feature = "async-print")]
use embedded_io_async::Write;
use esp_hal::peripherals::Peripherals;
use heapless::String;
use portable_atomic::{AtomicBool, AtomicU8, Ordering};
use postcard::experimental::max_size::MaxSize;
#[cfg(all(feature = "defmt", not(feature = "async-print")))]
use defmt_rtt as _;
#[allow(dead_code)]
const CSI_LOG_CHANNEL_CAPACITY: usize = 32;
#[allow(dead_code)]
const TEXT_LOG_CHANNEL_CAPACITY: usize = 64;
#[allow(dead_code)]
const DEFMT_LOG_CHANNEL_CAPACITY: usize = 64;
const fn parse_u32(s: &str) -> u32 {
let bytes = s.as_bytes();
let mut result: u32 = 0;
let mut i = 0;
while i < bytes.len() {
result = result * 10 + (bytes[i] - b'0') as u32;
i += 1;
}
result
}
const UART_LOG_BAUDRATE: u32 = parse_u32(env!("UART_LOG_BAUDRATE"));
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print"
))]
mod csi_interface {
use crate::csi::CSIDataPacket;
use crate::logging::logging::CSI_LOG_CHANNEL_CAPACITY;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
#[cfg(feature = "statistics")]
use portable_atomic::AtomicU32;
pub static CSI_CHANNEL: Channel<
CriticalSectionRawMutex,
CSIDataPacket,
CSI_LOG_CHANNEL_CAPACITY,
> = Channel::new();
#[cfg(feature = "statistics")]
pub static LOG_DROPPED_PACKETS: AtomicU32 = AtomicU32::new(0);
}
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print"
))]
pub use csi_interface::CSI_CHANNEL;
#[cfg(all(feature = "statistics", feature = "async-print"))]
pub use csi_interface::LOG_DROPPED_PACKETS;
static LOG_MODE: AtomicU8 = AtomicU8::new(LogMode::Text as u8);
static ROLE: AtomicU8 = AtomicU8::new(Role::Sta as u8);
static ESP_CSI_TOOL_HEADER_PRINTED: AtomicBool = AtomicBool::new(false);
static CSI_TOOL_EMIT_CAP: portable_atomic::AtomicU16 = portable_atomic::AtomicU16::new(0);
pub fn set_csi_tool_emit_cap(cap: u16) {
CSI_TOOL_EMIT_CAP.store(cap, Ordering::Relaxed);
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Role {
Sta = 0,
Ap = 1,
Passive = 2,
}
impl Role {
fn as_str(self) -> &'static str {
match self {
Role::Sta => "STA",
Role::Ap => "AP",
Role::Passive => "PASSIVE",
}
}
}
impl From<u8> for Role {
fn from(value: u8) -> Self {
match value {
1 => Role::Ap,
2 => Role::Passive,
_ => Role::Sta,
}
}
}
pub fn set_role(role: Role) {
ROLE.store(role as u8, Ordering::Relaxed);
}
pub fn get_log_packet_drops() -> u32 {
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print",
feature = "statistics"
))]
{
LOG_DROPPED_PACKETS.load(Ordering::Relaxed)
}
#[cfg(not(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print",
feature = "statistics"
)))]
{
0
}
}
#[cfg(all(feature = "println", feature = "async-print"))]
mod log_impl {
use crate::logging::logging::TEXT_LOG_CHANNEL_CAPACITY;
use core::fmt::Write;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
use heapless::String;
pub static LOG_CHANNEL: Channel<
CriticalSectionRawMutex,
String<256>,
TEXT_LOG_CHANNEL_CAPACITY,
> = Channel::new();
struct EspLogger;
impl log::Log for EspLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let mut text: String<256> = String::new();
if write!(&mut text, "{}\r\n", record.args()).is_ok() {
let _ = LOG_CHANNEL.try_send(text);
}
}
}
fn flush(&self) {}
}
pub fn init_logger(level: log::LevelFilter) {
static LOGGER: EspLogger = EspLogger;
unsafe { log::set_logger_racy(&LOGGER) }.unwrap();
unsafe { log::set_max_level_racy(level) };
}
}
#[cfg(all(feature = "defmt", feature = "async-print"))]
mod defmt_impl {
use super::*;
use crate::logging::logging::DEFMT_LOG_CHANNEL_CAPACITY;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
pub static DEFMT_CHANNEL: Channel<
CriticalSectionRawMutex,
[u8; 256],
DEFMT_LOG_CHANNEL_CAPACITY,
> = Channel::new();
#[defmt::global_logger]
struct AsyncDefmtBackend;
unsafe impl defmt::Logger for AsyncDefmtBackend {
fn acquire() {}
unsafe fn release() {}
unsafe fn flush() {}
unsafe fn write(bytes: &[u8]) {
#[cfg(any(feature = "uart", feature = "jtag-serial", feature = "auto"))]
{
let _ = DEFMT_CHANNEL.try_send(bytes.try_into().unwrap());
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LogMode {
Text,
Serialized,
ArrayList,
EspCsiTool,
}
impl From<u8> for LogMode {
fn from(value: u8) -> Self {
match value {
0 => LogMode::Text,
1 => LogMode::Serialized,
2 => LogMode::ArrayList,
3 => LogMode::EspCsiTool,
_ => LogMode::Text, }
}
}
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print"
))]
mod logging_impl {
use embedded_io_async::{ErrorType, Write};
use esp_hal::peripherals::Peripherals;
#[cfg(all(any(feature = "jtag-serial", feature = "auto"), not(any(feature = "esp32", feature = "esp32c2"))))]
use esp_hal::usb_serial_jtag::UsbSerialJtag;
use esp_hal::{
uart::{Config, Uart},
Async,
};
use crate::logging::logging::UART_LOG_BAUDRATE;
pub enum Backend {
#[cfg(any(feature = "uart", feature = "auto"))]
Uart(Uart<'static, Async>),
#[cfg(all(any(feature = "jtag-serial", feature = "auto"), not(any(feature = "esp32", feature = "esp32c2"))))]
Jtag(UsbSerialJtag<'static, Async>),
}
impl ErrorType for Backend {
type Error = embedded_io_async::ErrorKind;
}
impl embedded_io_async::Write for Backend {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
#[cfg(any(feature = "uart", feature = "jtag-serial", feature = "auto"))]
match self {
#[cfg(any(feature = "uart", feature = "auto"))]
Self::Uart(driver) => driver
.write_all(buf)
.await
.map(|_| buf.len())
.map_err(|_| embedded_io_async::ErrorKind::Other),
#[cfg(all(any(feature = "jtag-serial", feature = "auto"), not(any(feature = "esp32", feature = "esp32c2"))))]
Self::Jtag(driver) => driver
.write_all(buf)
.await
.map(|_| buf.len())
.map_err(|_| embedded_io_async::ErrorKind::Other),
}
#[cfg(feature = "no-print")]
Err(embedded_io_async::ErrorKind::Other)
}
async fn flush(&mut self) -> Result<(), Self::Error> {
#[cfg(any(feature = "uart", feature = "jtag-serial", feature = "auto"))]
match self {
#[cfg(any(feature = "uart", feature = "auto"))]
Self::Uart(driver) => driver
.flush_async()
.await
.map_err(|_| embedded_io_async::ErrorKind::Other),
#[cfg(all(any(feature = "jtag-serial", feature = "auto"), not(any(feature = "esp32", feature = "esp32c2"))))]
Self::Jtag(driver) => driver
.flush()
.await
.map_err(|_| embedded_io_async::ErrorKind::Other),
}
#[cfg(feature = "no-print")]
Err(embedded_io_async::ErrorKind::Other)
}
}
pub struct LogOutput {
inner: Backend,
}
impl LogOutput {
#[cfg(any(feature = "uart", feature = "auto"))]
pub fn new_uart(periphs: Peripherals) -> Self {
let raw_driver =
Uart::new(periphs.UART0, Config::default().with_baudrate(UART_LOG_BAUDRATE))
.unwrap()
.into_async();
Self {
inner: Backend::Uart(raw_driver),
}
}
#[cfg(all(any(feature = "jtag-serial", feature = "auto"), not(any(feature = "esp32", feature = "esp32c2"))))]
pub fn new_jtag(periphs: Peripherals) -> Self {
let raw_driver = UsbSerialJtag::new(periphs.USB_DEVICE).into_async();
Self {
inner: Backend::Jtag(raw_driver),
}
}
}
impl ErrorType for LogOutput {
type Error = embedded_io_async::ErrorKind;
}
impl Write for LogOutput {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.inner.write(buf).await
}
async fn flush(&mut self) -> Result<(), Self::Error> {
self.inner.flush().await
}
}
}
#[macro_export]
macro_rules! log_ln {
($($arg:tt)*) => {{
#[cfg(
all(any(feature = "uart", feature = "jtag-serial", feature = "auto"), feature = "async-print")
)]
{
#[cfg(feature = "println")]
{
log::info!($($arg)*);
}
#[cfg(feature = "defmt")]
{
defmt::info!($($arg)*);
}
#[cfg(not(any(
feature = "println",
feature = "defmt"
)))]
{
}
}
#[cfg(all(not(feature = "async-print"), any(feature = "uart", feature = "jtag-serial", feature = "auto")))]
{
#[cfg(feature = "println")]
{
esp_println::println!($($arg)*);
}
#[cfg(feature = "defmt")]
{
defmt::println!($($arg)*);
}
#[cfg(not(any(
feature = "println",
feature = "defmt"
)))]
{
}
}
}};
}
#[allow(unused_variables)]
pub fn print_raw_bytes(bytes: &[u8]) {
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print"
))]
{
use core::fmt::Write;
let mut printer = esp_println::Printer;
for chunk in bytes.chunks(64) {
for &b in chunk {
let _ = printer.write_char(b as char);
}
}
}
}
#[macro_export]
macro_rules! log_raw {
($data:expr) => {{
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
not(feature = "async-print"),
feature = "println"
))]
{
use core::fmt::Write as _FmtWrite;
let mut _printer = esp_println::Printer;
for &_b in AsRef::<[u8]>::as_ref(&$data) {
let _ = _printer.write_char(_b as char);
}
}
}};
}
use crate::csi::CSIDataPacket;
pub fn log_csi(packet: CSIDataPacket) {
#[cfg(feature = "async-print")]
{
#[cfg(any(feature = "uart", feature = "jtag-serial", feature = "auto"))]
{
match CSI_CHANNEL.try_send(packet) {
Ok(_) => {}
Err(_) => {
#[cfg(feature = "statistics")]
LOG_DROPPED_PACKETS.fetch_add(1, Ordering::Relaxed);
}
}
}
#[cfg(not(any(feature = "uart", feature = "jtag-serial", feature = "auto")))]
{}
}
#[cfg(not(feature = "async-print"))]
{
#[cfg(any(feature = "uart", feature = "jtag-serial", feature = "auto"))]
{
use core::sync::atomic::Ordering;
match LogMode::from(LOG_MODE.load(Ordering::Relaxed)) {
LogMode::Text => {
write_text_packet(packet);
}
LogMode::Serialized => {
write_serialized_packet(packet);
}
LogMode::ArrayList => {
write_text_array_packet(packet);
}
LogMode::EspCsiTool => {
write_csi_tool_packet(packet);
}
}
}
#[cfg(not(any(feature = "uart", feature = "jtag-serial", feature = "auto")))]
{}
}
}
#[cfg(all(
feature = "async-print",
any(feature = "uart", feature = "jtag-serial", feature = "auto")
))]
use crate::logging::logging::logging_impl::LogOutput;
pub fn init_logger(spawner: embassy_executor::Spawner, log_mode: LogMode) {
LOG_MODE.store(log_mode as u8, Ordering::Relaxed);
ESP_CSI_TOOL_HEADER_PRINTED.store(false, Ordering::Relaxed);
#[cfg(not(feature = "async-print"))]
crate::set_csi_logging_enabled(true);
#[cfg(feature = "async-print")]
{
#[cfg(feature = "println")]
{
log_impl::init_logger(log::LevelFilter::Info);
}
#[cfg(feature = "auto")]
{
#[cfg(not(any(feature = "esp32", feature = "esp32c2")))]
{
let periphs = unsafe { Peripherals::steal() };
#[cfg(feature = "esp32c3")]
const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32;
#[cfg(feature = "esp32c6")]
const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
#[cfg(feature = "esp32s3")]
const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32;
const SOF_INT_MASK: u32 = 0b10;
let res = unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 };
if res == true {
let driver = LogOutput::new_jtag(periphs);
spawner.spawn(logger_backend(driver)).unwrap();
} else {
let driver = LogOutput::new_uart(periphs);
spawner.spawn(logger_backend(driver)).unwrap();
}
}
#[cfg(feature = "esp32")]
{
let periphs = unsafe { Peripherals::steal() };
let driver = LogOutput::new_uart(periphs);
spawner.spawn(logger_backend(driver)).unwrap();
}
}
#[cfg(all(feature = "jtag-serial", not(any(feature = "esp32", feature = "esp32c2"))))]
{
let periphs = unsafe { Peripherals::steal() };
let driver = LogOutput::new_jtag(periphs);
spawner.spawn(logger_backend(driver)).unwrap();
}
#[cfg(feature = "uart")]
{
let periphs = unsafe { Peripherals::steal() };
let driver = LogOutput::new_uart(periphs);
spawner.spawn(logger_backend(driver)).unwrap();
}
#[cfg(not(any(feature = "uart", feature = "jtag-serial", feature = "auto")))]
{}
}
#[cfg(not(feature = "async-print"))]
{
#[cfg(any(feature = "uart", feature = "jtag-serial", feature = "auto"))]
{
use core::sync::atomic::Ordering;
LOG_MODE.store(log_mode as u8, Ordering::Relaxed);
}
#[cfg(any(feature = "uart", feature = "auto"))]
{
let periphs = unsafe { Peripherals::steal() };
if let Ok(uart) = esp_hal::uart::Uart::new(
periphs.UART0,
esp_hal::uart::Config::default().with_baudrate(UART_LOG_BAUDRATE),
) {
core::mem::forget(uart);
}
}
let _ = spawner;
}
}
pub fn set_log_mode(log_mode: LogMode) {
LOG_MODE.store(log_mode as u8, Ordering::Relaxed);
ESP_CSI_TOOL_HEADER_PRINTED.store(false, Ordering::Relaxed);
}
#[cfg(feature = "async-print")]
async fn write_serialized_packet(packet: CSIDataPacket, driver: &mut LogOutput) -> Result<(), ()> {
const PACKET_MAX_SIZE: usize = CSIDataPacket::POSTCARD_MAX_SIZE;
const PACKET_BUF_SIZE: usize = PACKET_MAX_SIZE + (PACKET_MAX_SIZE / 254) + 1;
let mut buf = [0u8; PACKET_BUF_SIZE];
match postcard::to_slice_cobs(&packet, &mut buf) {
Ok(cobs_slice) => match driver.write(cobs_slice).await {
Ok(_) => Ok(()),
Err(_) => Err(()),
},
Err(_) => Err(()),
}
}
#[cfg(not(feature = "async-print"))]
fn write_serialized_packet(packet: CSIDataPacket) {
const PACKET_MAX_SIZE: usize = CSIDataPacket::POSTCARD_MAX_SIZE;
const PACKET_BUF_SIZE: usize = PACKET_MAX_SIZE + (PACKET_MAX_SIZE / 254) + 1;
let mut buf = [0u8; PACKET_BUF_SIZE];
match postcard::to_slice_cobs(&packet, &mut buf) {
Ok(cobs_slice) => {
log_raw!(cobs_slice);
}
Err(_) => {}
}
}
#[cfg(feature = "async-print")]
async fn write_text_array_packet(packet: CSIDataPacket, driver: &mut LogOutput) -> Result<(), ()> {
use core::fmt::Write as FmtWrite;
let mut buf = String::<64>::new();
macro_rules! write_field {
($arg:expr) => {
buf.clear();
if write!(&mut buf, "{},", $arg).is_ok() {
driver.write(buf.as_bytes()).await.map_err(|_| ())?;
}
};
}
macro_rules! write_first_field {
($arg:expr) => {
buf.clear();
if write!(&mut buf, "[{},", $arg).is_ok() {
driver.write(buf.as_bytes()).await.map_err(|_| ())?;
}
};
}
#[allow(unused_macros)]
macro_rules! write_last_field {
($arg:expr) => {
buf.clear();
if write!(&mut buf, "{}]\r\n", $arg).is_ok() {
driver.write(buf.as_bytes()).await.map_err(|_| ())?;
}
};
}
write_first_field!(packet.sequence_number);
write_field!(packet.rssi);
write_field!(packet.rate);
write_field!(packet.noise_floor);
write_field!(packet.channel);
write_field!(packet.timestamp);
write_field!(packet.sig_len);
write_field!(packet.rx_state);
#[cfg(not(feature = "esp32c6"))]
{
write_field!(packet.secondary_channel);
write_field!(packet.sgi);
write_field!(packet.antenna);
write_field!(packet.ampdu_cnt);
write_field!(packet.sig_mode);
write_field!(packet.mcs);
write_field!(packet.bandwidth);
write_field!(packet.smoothing);
write_field!(packet.not_sounding);
write_field!(packet.aggregation);
write_field!(packet.stbc);
write_field!(packet.fec_coding);
}
#[cfg(feature = "esp32c6")]
{
write_field!(packet.dump_len);
write_field!(packet.he_sigb_len);
write_field!(packet.cur_single_mpdu);
write_field!(packet.cur_bb_format);
write_field!(packet.rx_channel_estimate_info_vld);
write_field!(packet.rx_channel_estimate_len);
write_field!(packet.second);
write_field!(packet.channel);
write_field!(packet.is_group);
write_field!(packet.rxend_state);
write_field!(packet.rxmatch3);
write_field!(packet.rxmatch2);
write_field!(packet.rxmatch1);
write_field!(packet.rxmatch0);
}
write_field!(packet.sig_len);
write_field!(packet.csi_data_len);
driver.write(b"[").await.map_err(|_| ())?;
let data_len = packet.csi_data.len();
for (i, val) in packet.csi_data.iter().enumerate() {
buf.clear();
if i + 1 < data_len {
let _ = write!(&mut buf, "{},", val);
} else {
let _ = write!(&mut buf, "{}", val);
}
driver.write(buf.as_bytes()).await.map_err(|_| ())?;
}
driver.write(b"]]
").await.map_err(|_| ())?;
Ok(())
}
#[cfg(not(feature = "async-print"))]
fn write_text_array_packet(packet: CSIDataPacket) {
use core::fmt::Write as FmtWrite;
let mut buf = String::<64>::new();
macro_rules! write_field {
($arg:expr) => {
buf.clear();
if write!(&mut buf, "{},", $arg).is_ok() {
log_raw!(buf.as_str());
}
};
}
macro_rules! write_first_field {
($arg:expr) => {
buf.clear();
if write!(&mut buf, "[{},", $arg).is_ok() {
log_raw!(buf.as_str());
}
};
}
#[allow(unused_macros)]
macro_rules! write_last_field {
($arg:expr) => {
buf.clear();
if write!(&mut buf, "{}]\r\n", $arg).is_ok() {
log_raw!(buf.as_str());
}
};
}
write_first_field!(packet.sequence_number);
write_field!(packet.rssi);
write_field!(packet.rate);
write_field!(packet.noise_floor);
write_field!(packet.channel);
write_field!(packet.timestamp);
write_field!(packet.sig_len);
write_field!(packet.rx_state);
#[cfg(not(feature = "esp32c6"))]
{
write_field!(packet.secondary_channel);
write_field!(packet.sgi);
write_field!(packet.antenna);
write_field!(packet.ampdu_cnt);
write_field!(packet.sig_mode);
write_field!(packet.mcs);
write_field!(packet.bandwidth);
write_field!(packet.smoothing);
write_field!(packet.not_sounding);
write_field!(packet.aggregation);
write_field!(packet.stbc);
write_field!(packet.fec_coding);
}
#[cfg(feature = "esp32c6")]
{
write_field!(packet.dump_len);
write_field!(packet.he_sigb_len);
write_field!(packet.cur_single_mpdu);
write_field!(packet.cur_bb_format);
write_field!(packet.rx_channel_estimate_info_vld);
write_field!(packet.rx_channel_estimate_len);
write_field!(packet.second);
write_field!(packet.channel);
write_field!(packet.is_group);
write_field!(packet.rxend_state);
write_field!(packet.rxmatch3);
write_field!(packet.rxmatch2);
write_field!(packet.rxmatch1);
write_field!(packet.rxmatch0);
}
write_field!(packet.sig_len);
write_field!(packet.csi_data_len);
log_raw!("[");
let data_len = packet.csi_data.len();
for (i, val) in packet.csi_data.iter().enumerate() {
buf.clear();
if i + 1 < data_len {
let _ = write!(&mut buf, "{},", val);
} else {
let _ = write!(&mut buf, "{}", val);
}
log_raw!(buf.as_str());
}
log_raw!("]]
");
}
const ESP_CSI_TOOL_HEADER: &str = "type,role,mac,rssi,rate,sig_mode,mcs,bandwidth,smoothing,not_sounding,aggregation,stbc,fec_coding,sgi,noise_floor,ampdu_cnt,channel,secondary_channel,local_timestamp,ant,sig_len,rx_state,real_time_set,real_timestamp,len,CSI_DATA\n";
#[inline(always)]
fn write_i8_space(buf: &mut [u8], offset: &mut usize, val: i8) {
let mut o = *offset;
let mut n: i16 = val as i16;
if n < 0 {
buf[o] = b'-';
o += 1;
n = -n;
}
let n = n as u16;
if n >= 100 {
buf[o] = b'0' + (n / 100) as u8;
buf[o + 1] = b'0' + ((n / 10) % 10) as u8;
buf[o + 2] = b'0' + (n % 10) as u8;
o += 3;
} else if n >= 10 {
buf[o] = b'0' + (n / 10) as u8;
buf[o + 1] = b'0' + (n % 10) as u8;
o += 2;
} else {
buf[o] = b'0' + n as u8;
o += 1;
}
buf[o] = b' ';
*offset = o + 1;
}
#[inline(always)]
fn write_u32(buf: &mut [u8], pos: &mut usize, mut n: u32) {
if n == 0 {
buf[*pos] = b'0';
*pos += 1;
return;
}
let mut tmp = [0u8; 10];
let mut i = 0;
while n > 0 {
tmp[i] = b'0' + (n % 10) as u8;
n /= 10;
i += 1;
}
let mut p = *pos;
while i > 0 {
i -= 1;
buf[p] = tmp[i];
p += 1;
}
*pos = p;
}
#[inline(always)]
fn write_i32(buf: &mut [u8], pos: &mut usize, n: i32) {
if n < 0 {
buf[*pos] = b'-';
*pos += 1;
write_u32(buf, pos, (-(n as i64)) as u32);
} else {
write_u32(buf, pos, n as u32);
}
}
#[inline(always)]
fn write_u32_pad6(buf: &mut [u8], pos: &mut usize, mut n: u32) {
let mut tmp = [b'0'; 6];
let mut i = 0;
while n > 0 && i < 6 {
tmp[i] = b'0' + (n % 10) as u8;
n /= 10;
i += 1;
}
let mut p = *pos;
for j in (0..6).rev() {
buf[p] = tmp[j];
p += 1;
}
*pos = p;
}
#[inline(always)]
fn write_hex_u8(buf: &mut [u8], pos: &mut usize, n: u8) {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let p = *pos;
buf[p] = HEX[(n >> 4) as usize];
buf[p + 1] = HEX[(n & 0x0f) as usize];
*pos = p + 2;
}
#[inline(always)]
fn write_slice(buf: &mut [u8], pos: &mut usize, s: &[u8]) {
let p = *pos;
buf[p..p + s.len()].copy_from_slice(s);
*pos = p + s.len();
}
fn format_csi_tool_into(packet: &CSIDataPacket, buf: &mut [u8]) -> usize {
let role = Role::from(ROLE.load(Ordering::Relaxed)).as_str();
let real_time_set: u8 = if packet.date_time.is_some() { 1 } else { 0 };
let real_secs = packet.timestamp / 1_000_000;
let real_usecs = packet.timestamp % 1_000_000;
let mut p = 0usize;
write_slice(buf, &mut p, b"CSI_DATA,");
write_slice(buf, &mut p, role.as_bytes());
buf[p] = b',';
p += 1;
write_hex_u8(buf, &mut p, packet.mac[0]);
buf[p] = b':';
p += 1;
write_hex_u8(buf, &mut p, packet.mac[1]);
buf[p] = b':';
p += 1;
write_hex_u8(buf, &mut p, packet.mac[2]);
buf[p] = b':';
p += 1;
write_hex_u8(buf, &mut p, packet.mac[3]);
buf[p] = b':';
p += 1;
write_hex_u8(buf, &mut p, packet.mac[4]);
buf[p] = b':';
p += 1;
write_hex_u8(buf, &mut p, packet.mac[5]);
buf[p] = b',';
p += 1;
write_i32(buf, &mut p, packet.rssi);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.rate);
buf[p] = b',';
p += 1;
#[cfg(not(feature = "esp32c6"))]
{
write_u32(buf, &mut p, packet.sig_mode);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.mcs);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.bandwidth);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.smoothing);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.not_sounding);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.aggregation);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.stbc);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.fec_coding);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.sgi);
buf[p] = b',';
p += 1;
write_i32(buf, &mut p, packet.noise_floor);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.ampdu_cnt);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.channel);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.secondary_channel);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.timestamp);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.antenna);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.sig_len);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.rx_state);
buf[p] = b',';
p += 1;
}
#[cfg(feature = "esp32c6")]
{
write_slice(buf, &mut p, b"0,0,0,0,0,0,0,0,0,");
write_i32(buf, &mut p, packet.noise_floor);
buf[p] = b',';
p += 1;
write_slice(buf, &mut p, b"0,");
write_u32(buf, &mut p, packet.channel);
buf[p] = b',';
p += 1;
write_slice(buf, &mut p, b"0,");
write_u32(buf, &mut p, packet.timestamp);
buf[p] = b',';
p += 1;
write_slice(buf, &mut p, b"0,");
write_u32(buf, &mut p, packet.sig_len);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, packet.rx_state);
buf[p] = b',';
p += 1;
}
write_u32(buf, &mut p, real_time_set as u32);
buf[p] = b',';
p += 1;
write_u32(buf, &mut p, real_secs);
buf[p] = b'.';
p += 1;
write_u32_pad6(buf, &mut p, real_usecs);
buf[p] = b',';
p += 1;
let cap = CSI_TOOL_EMIT_CAP.load(Ordering::Relaxed) as usize;
let actual_len = packet.csi_data.len();
let emit_len = if cap == 0 { actual_len } else { actual_len.min(cap) };
write_u32(buf, &mut p, emit_len as u32);
buf[p] = b',';
p += 1;
buf[p] = b'[';
p += 1;
let body_cap = buf.len().saturating_sub(2); for &val in packet.csi_data.iter().take(emit_len) {
if p + 5 > body_cap {
break;
}
write_i8_space(buf, &mut p, val);
}
buf[p] = b']';
buf[p + 1] = b'\n';
p + 2
}
#[cfg(feature = "async-print")]
async fn write_csi_tool_packet(packet: CSIDataPacket, driver: &mut LogOutput) -> Result<(), ()> {
if !ESP_CSI_TOOL_HEADER_PRINTED.swap(true, Ordering::Relaxed) {
driver
.write(ESP_CSI_TOOL_HEADER.as_bytes())
.await
.map_err(|_| ())?;
}
static mut SCRATCH: [u8; 3328] = [0u8; 3328];
let scratch = unsafe { &mut *core::ptr::addr_of_mut!(SCRATCH) };
let n = format_csi_tool_into(&packet, scratch);
driver.write(&scratch[..n]).await.map_err(|_| ())?;
Ok(())
}
#[cfg(all(
not(feature = "async-print"),
feature = "esp32",
any(feature = "uart", feature = "auto")
))]
#[inline]
fn uart0_write_bytes_fast(bytes: &[u8]) {
const UART0_FIFO: *mut u32 = 0x3FF40000 as *mut u32;
const UART0_STATUS: *const u32 = 0x3FF4001C as *const u32;
const FIFO_SIZE: usize = 128;
let mut i = 0usize;
while i < bytes.len() {
let used = unsafe { (UART0_STATUS.read_volatile() >> 16) & 0xFF } as usize;
if used >= FIFO_SIZE {
continue;
}
let free = FIFO_SIZE - used;
let chunk = core::cmp::min(free, bytes.len() - i);
let end = i + chunk;
while i < end {
unsafe { UART0_FIFO.write_volatile(bytes[i] as u32) };
i += 1;
}
}
}
#[cfg(all(
not(feature = "async-print"),
feature = "println",
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
not(feature = "esp32")
))]
#[allow(dead_code)]
fn uart0_write_bytes_fast(bytes: &[u8]) {
esp_println::Printer::write_bytes(bytes);
}
#[cfg(not(feature = "async-print"))]
pub static SYNC_FORMAT_US: portable_atomic::AtomicU64 = portable_atomic::AtomicU64::new(0);
#[cfg(not(feature = "async-print"))]
pub static SYNC_WRITE_US: portable_atomic::AtomicU64 = portable_atomic::AtomicU64::new(0);
#[cfg(not(feature = "async-print"))]
pub static SYNC_PKT_COUNT: portable_atomic::AtomicU64 = portable_atomic::AtomicU64::new(0);
#[cfg(not(feature = "async-print"))]
fn write_csi_tool_packet(packet: CSIDataPacket) {
static mut SCRATCH: [u8; 3328] = [0u8; 3328];
let scratch = unsafe { &mut *core::ptr::addr_of_mut!(SCRATCH) };
if !ESP_CSI_TOOL_HEADER_PRINTED.swap(true, Ordering::Relaxed) {
#[cfg(all(feature = "esp32", any(feature = "uart", feature = "auto")))]
uart0_write_bytes_fast(ESP_CSI_TOOL_HEADER.as_bytes());
#[cfg(not(all(feature = "esp32", any(feature = "uart", feature = "auto"))))]
log_raw!(ESP_CSI_TOOL_HEADER);
}
let t0 = embassy_time::Instant::now();
let n = format_csi_tool_into(&packet, scratch);
let t1 = embassy_time::Instant::now();
#[cfg(all(feature = "esp32", any(feature = "uart", feature = "auto")))]
uart0_write_bytes_fast(&scratch[..n]);
#[cfg(not(all(feature = "esp32", any(feature = "uart", feature = "auto"))))]
log_raw!(&scratch[..n]);
let t2 = embassy_time::Instant::now();
SYNC_FORMAT_US.fetch_add(t1.duration_since(t0).as_micros() as u64, Ordering::Relaxed);
SYNC_WRITE_US.fetch_add(t2.duration_since(t1).as_micros() as u64, Ordering::Relaxed);
SYNC_PKT_COUNT.fetch_add(1, Ordering::Relaxed);
}
#[cfg(feature = "async-print")]
async fn write_text_packet(packet: CSIDataPacket, driver: &mut LogOutput) -> Result<(), ()> {
use core::fmt::Write as FmtWrite;
let mut buf = String::<128>::new();
macro_rules! send_line {
($($arg:tt)*) => {
buf.clear();
if write!(&mut buf, $($arg)*).is_ok() {
driver.write(buf.as_bytes()).await.map_err(|_| ())?;
}
};
}
let res = async {
if let Some(dt) = &packet.date_time {
send_line!(
"Recieved at {:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}\r\n",
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.millisecond
);
}
send_line!(
"mac: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}\r\n",
packet.mac[0],
packet.mac[1],
packet.mac[2],
packet.mac[3],
packet.mac[4],
packet.mac[5]
);
send_line!("sequence number: {}\r\n", packet.sequence_number);
send_line!("rssi: {}\r\n", packet.rssi);
send_line!("rate: {}\r\n", packet.rate);
send_line!("noise floor: {}\r\n", packet.noise_floor);
send_line!("channel: {}\r\n", packet.channel);
send_line!("timestamp: {}\r\n", packet.timestamp);
send_line!("sig len: {}\r\n", packet.sig_len);
send_line!("rx state: {}\r\n", packet.rx_state);
#[cfg(not(feature = "esp32c6"))]
{
send_line!("secondary channel: {}\r\n", packet.secondary_channel);
send_line!("sgi: {}\r\n", packet.sgi);
send_line!("ant: {}\r\n", packet.antenna);
send_line!("ampdu cnt: {}\r\n", packet.ampdu_cnt);
send_line!("sig_mode: {}\r\n", packet.sig_mode);
send_line!("mcs: {}\r\n", packet.mcs);
send_line!("cwb: {}\r\n", packet.bandwidth);
send_line!("smoothing: {}\r\n", packet.smoothing);
send_line!("not sounding: {}\r\n", packet.not_sounding);
send_line!("aggregation: {}\r\n", packet.aggregation);
send_line!("stbc: {}\r\n", packet.stbc);
send_line!("fec coding: {}\r\n", packet.fec_coding);
}
#[cfg(feature = "esp32c6")]
{
send_line!("dump len: {}\r\n", packet.dump_len);
send_line!("he sigb len: {}\r\n", packet.he_sigb_len);
send_line!("cur single mpdu: {}\r\n", packet.cur_single_mpdu);
send_line!("cur bb format: {}\r\n", packet.cur_bb_format);
send_line!(
"rx channel estimate info vld: {}\r\n",
packet.rx_channel_estimate_info_vld
);
send_line!(
"rx channel estimate len: {}\r\n",
packet.rx_channel_estimate_len
);
send_line!("time seconds: {}\r\n", packet.second);
send_line!("channel: {}\r\n", packet.channel);
send_line!("is group: {}\r\n", packet.is_group);
send_line!("rxend state: {}\r\n", packet.rxend_state);
send_line!("rxmatch3: {}\r\n", packet.rxmatch3);
send_line!("rxmatch2: {}\r\n", packet.rxmatch2);
send_line!("rxmatch1: {}\r\n", packet.rxmatch1);
send_line!("rxmatch0: {}\r\n", packet.rxmatch0);
}
send_line!("sig_len: {}\r\n", packet.sig_len);
send_line!("data length: {}\r\n", packet.csi_data_len);
Ok::<(), ()>(())
}
.await;
if res.is_err() {
return Err(());
}
if driver.write(b"csi raw data: [").await.is_err() {
return Err(());
}
let mut chunk_buf = [0u8; 128];
let mut offset = 0;
for (i, val) in packet.csi_data.iter().enumerate() {
let mut wrapper = String::<16>::new();
if i == packet.csi_data.len() - 1 {
write!(wrapper, "{}", val).ok();
} else {
write!(wrapper, "{}, ", val).ok();
}
let bytes = wrapper.as_bytes();
if offset + bytes.len() > chunk_buf.len() {
if driver.write(&chunk_buf[..offset]).await.is_err() {
return Err(());
}
offset = 0;
}
chunk_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
if offset > 0 {
if driver.write(&chunk_buf[..offset]).await.is_err() {
return Err(());
}
}
if driver.write(b"]\r\n").await.is_err() {
return Err(());
}
Ok(())
}
#[cfg(not(feature = "async-print"))]
fn write_text_packet(packet: CSIDataPacket) {
if let Some(dt) = &packet.date_time {
log_ln!(
"Recieved at {:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.millisecond
);
}
log_ln!(
"mac: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
packet.mac[0],
packet.mac[1],
packet.mac[2],
packet.mac[3],
packet.mac[4],
packet.mac[5]
);
log_ln!("sequence number: {}", packet.sequence_number);
log_ln!("rssi: {}", packet.rssi);
log_ln!("rate: {}", packet.rate);
log_ln!("noise floor: {}", packet.noise_floor);
log_ln!("channel: {}", packet.channel);
log_ln!("timestamp: {}", packet.timestamp);
log_ln!("sig len: {}", packet.sig_len);
log_ln!("rx state: {}", packet.rx_state);
#[cfg(not(feature = "esp32c6"))]
{
log_ln!("secondary channel: {}", packet.secondary_channel);
log_ln!("sgi: {}", packet.sgi);
log_ln!("ant: {}", packet.antenna);
log_ln!("ampdu cnt: {}", packet.ampdu_cnt);
log_ln!("sig_mode: {}", packet.sig_mode);
log_ln!("mcs: {}", packet.mcs);
log_ln!("cwb: {}", packet.bandwidth);
log_ln!("smoothing: {}", packet.smoothing);
log_ln!("not sounding: {}", packet.not_sounding);
log_ln!("aggregation: {}", packet.aggregation);
log_ln!("stbc: {}", packet.stbc);
log_ln!("fec coding: {}", packet.fec_coding);
}
#[cfg(feature = "esp32c6")]
{
log_ln!("dump len: {}", packet.dump_len);
log_ln!("he sigb len: {}", packet.he_sigb_len);
log_ln!("cur single mpdu: {}", packet.cur_single_mpdu);
log_ln!("cur bb format: {}", packet.cur_bb_format);
log_ln!(
"rx channel estimate info vld: {}",
packet.rx_channel_estimate_info_vld
);
log_ln!(
"rx channel estimate len: {}",
packet.rx_channel_estimate_len
);
log_ln!("time seconds: {}", packet.second);
log_ln!("channel: {}", packet.channel);
log_ln!("is group: {}", packet.is_group);
log_ln!("rxend state: {}", packet.rxend_state);
log_ln!("rxmatch3: {}", packet.rxmatch3);
log_ln!("rxmatch2: {}", packet.rxmatch2);
log_ln!("rxmatch1: {}", packet.rxmatch1);
log_ln!("rxmatch0: {}", packet.rxmatch0);
}
log_ln!("sig_len: {}", packet.sig_len);
log_ln!("data length: {}", packet.csi_data_len);
log_ln!("csi raw data: [{:X?}]", packet.csi_data);
}
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print"
))]
#[embassy_executor::task]
pub async fn logger_backend(mut driver: LogOutput) {
use embassy_futures::select::{select, Either};
use embedded_io_async::Write;
loop {
let csi_future = CSI_CHANNEL.receive();
#[cfg(all(feature = "println", not(feature = "defmt")))]
let log_future = log_impl::LOG_CHANNEL.receive();
#[cfg(feature = "defmt")]
let log_future = defmt_impl::DEFMT_CHANNEL.receive();
#[cfg(not(any(feature = "println", feature = "defmt")))]
let log_future = embassy_futures::pending::<usize>();
match select(csi_future, log_future).await {
Either::First(packet) => {
let _ = match LOG_MODE.load(Ordering::Relaxed).into() {
LogMode::Serialized => write_serialized_packet(packet, &mut driver).await,
LogMode::ArrayList => write_text_array_packet(packet, &mut driver).await,
LogMode::Text => write_text_packet(packet, &mut driver).await,
LogMode::EspCsiTool => write_csi_tool_packet(packet, &mut driver).await,
};
#[cfg(all(feature = "println", not(feature = "defmt")))]
while let Ok(message) = log_impl::LOG_CHANNEL.try_receive() {
let _ = driver.write_all(message.as_bytes()).await;
}
#[cfg(feature = "defmt")]
while let Ok(message) = defmt_impl::DEFMT_CHANNEL.try_receive() {
let _ = driver.write_all(&message).await;
}
}
Either::Second(message) => {
#[cfg(all(feature = "println", not(feature = "defmt")))]
let _ = driver.write_all(message.as_bytes()).await;
#[cfg(feature = "defmt")]
let _ = driver.write_all(&message).await;
#[cfg(all(feature = "println", not(feature = "defmt")))]
if log_impl::LOG_CHANNEL.is_empty() {
let _ = driver.flush().await;
}
#[cfg(feature = "defmt")]
if defmt_impl::DEFMT_CHANNEL.is_empty() {
let _ = driver.flush().await;
}
}
}
}
}
pub fn reset_global_log_drops() {
#[cfg(all(
any(feature = "uart", feature = "jtag-serial", feature = "auto"),
feature = "async-print"
))]
{
#[cfg(feature = "statistics")]
LOG_DROPPED_PACKETS.store(0, Ordering::Relaxed);
}
}