From ab09c98084a3e2fd0aa950fbe30437d561bb8e2d Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Wed, 20 May 2026 10:13:16 -0700 Subject: [PATCH] telemetry improvements / cli CSV export --- Cargo.lock | 247 +++++++++++++++++++++++++++++++++++++++++++ cli/Cargo.toml | 6 +- cli/src/main.rs | 64 ++++++++++- firmware/Cargo.toml | 2 + firmware/src/hid.rs | 20 ---- firmware/src/main.rs | 74 +++++++++---- shared/Cargo.toml | 4 + shared/src/lib.rs | 25 +++-- 8 files changed, 396 insertions(+), 46 deletions(-) delete mode 100644 firmware/src/hid.rs diff --git a/Cargo.lock b/Cargo.lock index dc5fd1b..3d715ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "async-hid" version = "0.5.1" @@ -233,17 +283,87 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "cli" version = "0.1.0" dependencies = [ "async-hid", + "clap", + "colog", + "csv", "deku", "futures-lite", + "log", "pollster", "shared", ] +[[package]] +name = "colog" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df62599ba6adc9c6c04a54278c8209125343dc4775f57b9d76c9a4287e58f2bd" +dependencies = [ + "colored", + "env_logger", + "log", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -333,6 +453,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.21.3" @@ -491,6 +632,29 @@ dependencies = [ "num", ] +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -672,6 +836,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -703,6 +873,42 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1020,6 +1226,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "panic-halt" version = "1.0.0" @@ -1108,6 +1320,15 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1169,6 +1390,18 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -1223,6 +1456,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1263,6 +1502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -1299,6 +1539,7 @@ name = "shared" version = "0.0.0" dependencies = [ "deku", + "serde", "usbd-hid", ] @@ -1565,6 +1806,12 @@ dependencies = [ "usb-device", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 02688a6..cb4d852 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -5,7 +5,11 @@ edition = "2024" [dependencies] async-hid = "0.5.1" +clap = { version = "4.6.1", features = ["derive"] } +colog = "1.4.0" +csv = "1.4.0" deku = "0.20.3" futures-lite = "2.6.1" +log = { version = "0.4.29", features = ["std"] } pollster = { version = "0.4.0", features = ["macro"] } -shared = { path = "../shared" } +shared = { path = "../shared", features = ["serde"] } diff --git a/cli/src/main.rs b/cli/src/main.rs index c2ad3b6..ad08958 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,10 +1,67 @@ +use std::io; + use async_hid::{AsyncHidRead, HidBackend, HidResult}; +use clap::{Parser, ValueEnum}; use deku::DekuContainerRead; use futures_lite::StreamExt; use shared::hid::AudioTelemetryReport; +#[derive(Clone, Copy, Debug, ValueEnum)] +enum Format { + Debug, + Csv, +} + +#[derive(Parser, Debug)] +#[command(version, about)] +struct Args { + #[arg(short, long, default_value = "debug")] + format: Format, +} + +trait StateEmitter { + fn from_writer(writer: W) -> Self + where + Self: Sized; + fn emit(&mut self, r: &AudioTelemetryReport); +} + +struct DebugEmitter { + writer: T, +} + +impl StateEmitter for DebugEmitter { + fn from_writer(writer: W) -> Self { + Self { writer } + } + fn emit(&mut self, r: &AudioTelemetryReport) { + writeln!(self.writer, "{r:?}"); + } +} + +struct CsvEmitter { + csv: csv::Writer, +} + +impl StateEmitter for CsvEmitter { + fn from_writer(writer: W) -> Self { + Self { + csv: csv::Writer::from_writer(writer), + } + } + fn emit(&mut self, r: &AudioTelemetryReport) { + if let Err(e) = self.csv.serialize(r) { + eprintln!("Serialization error: {e:?}"); + } else { + self.csv.flush().ok(); + } + } +} + #[pollster::main] async fn main() -> HidResult<()> { + colog::init(); + let args = Args::parse(); let usbhid = HidBackend::default(); let dev = usbhid .enumerate() @@ -13,12 +70,17 @@ async fn main() -> HidResult<()> { .await .expect("GUAC device not found or not accessible (try as root?)"); let mut reader = dev.open_readable().await?; + let mut writer: Box> = match args.format { + Format::Debug => Box::new(DebugEmitter::from_writer(io::stdout())), + Format::Csv => Box::new(CsvEmitter::from_writer(io::stdout())), + }; let mut buf = [0u8; core::mem::size_of::()]; while let Ok(r) = reader.read_input_report(&mut buf).await { + log::debug!("read {}: {:?}", r, &buf[..r]); let buf = &buf[..r]; match AudioTelemetryReport::from_bytes((buf, 0)) { - Ok((_, r)) => println!("{:?}", r), + Ok((_, r)) => writer.emit(&r), Err(e) => eprintln!("Unable to parse report: {:?}", e), } } diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index d8993af..a06f25a 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -8,7 +8,9 @@ default = ["nodac", "hid"] ak4490 = [] cs4398 = [] nodac = [] +wm8904 = [] hid = [ "dep:usbd-hid", "dep:shared" ] +evk = [ "wm8904" ] [dependencies] shared = { path="../shared", optional = true } diff --git a/firmware/src/hid.rs b/firmware/src/hid.rs deleted file mode 100644 index 9586c32..0000000 --- a/firmware/src/hid.rs +++ /dev/null @@ -1,20 +0,0 @@ -use usbd_hid::descriptor::generator_prelude::*; - -#[gen_hid_descriptor( - (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01, ) = { - average_buffer_fill=input; - frame_count=input; - dac_underflow_count=input; - usb_underflow_count=input; - dac_overflow_count=input; - } -)] -#[repr(C)] -// Note these are all actually u32 -pub struct AudioTelemetryReport { - pub average_buffer_fill: i32, - pub frame_count: i32, - pub dac_underflow_count: i32, - pub usb_underflow_count: i32, - pub dac_overflow_count: i32, -} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 75c95e7..7f463e9 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -9,6 +9,7 @@ fn panic() -> ! { use atomic::Atomic; use bytemuck::NoUninit; +use core::error; use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering}; use cortex_m_rt::entry; use defmt; @@ -64,21 +65,19 @@ pub mod dac { } mod dma; -#[cfg(feature = "hid")] -mod hid; mod hw; mod traits; const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels -const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 2000; // run the DMA at 2khz +const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 4000; // run the DMA at 4khz const BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME; const N_SLOTS: usize = 8; const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2; const USB_FRAME_RATE: u32 = 8000; // microframe rate: 8000 for HS, 1000 for FS // In frames -const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 4) / 10; // 40% +const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 5) / 10; // 50% const QUEUE_RUNNING_DOWN: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 2) / 10; // 20% const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms #[cfg(not(feature = "evk"))] @@ -87,7 +86,7 @@ const MCLK_FREQ: u32 = 24576000; const MCLK_FREQ: u32 = 24576000 / 2; const SAMPLE_RATE: u32 = 192000; -const HID_INTERVAL_MS: u8 = 100; +const HID_INTERVAL_MS: u8 = 10; struct CodecPins { reset: Pin>, @@ -100,6 +99,7 @@ struct ClockSelPins { #[derive(Default)] struct PerfCounters { + state: Atomic, received_frames: AtomicUsize, played_frames: AtomicUsize, min_fill: AtomicUsize, @@ -107,6 +107,10 @@ struct PerfCounters { queue_underflows: AtomicUsize, queue_overflows: AtomicUsize, audio_underflows: AtomicUsize, + integrator: AtomicI32, + p: AtomicI32, + i: AtomicI32, + fb: AtomicI32, } impl PerfCounters { @@ -120,14 +124,22 @@ impl PerfCounters { self.queue_underflows.store(0, Ordering::Relaxed); self.queue_overflows.store(0, Ordering::Relaxed); self.audio_underflows.store(0, Ordering::Relaxed); + self.p.store(0, Ordering::Relaxed); + self.i.store(0, Ordering::Relaxed); + // FB loop will have to take care of the fb value } fn build_report(&self) -> AudioTelemetryReport { AudioTelemetryReport { - average_buffer_fill: self.avg_fill.load(Ordering::Relaxed) as i32, + state: self.state.load(Ordering::Relaxed) as u8, + average_buffer_fill: self.avg_fill.load(Ordering::Relaxed) as u16, frame_count: self.played_frames.load(Ordering::Relaxed) as i32, - dac_underflow_count: self.audio_underflows.load(Ordering::Relaxed) as i32, - usb_underflow_count: self.queue_underflows.load(Ordering::Relaxed) as i32, - dac_overflow_count: self.queue_overflows.load(Ordering::Relaxed) as i32, + dac_underflow_count: self.audio_underflows.load(Ordering::Relaxed) as u16, + usb_underflow_count: self.queue_underflows.load(Ordering::Relaxed) as u16, + dac_overflow_count: self.queue_overflows.load(Ordering::Relaxed) as u16, + integrator: self.integrator.load(Ordering::Relaxed), + p: self.p.load(Ordering::Relaxed), + i: self.i.load(Ordering::Relaxed), + fb: self.fb.load(Ordering::Relaxed) as i32, } } } @@ -149,6 +161,7 @@ impl defmt::Format for PerfCounters { } static PERF: PerfCounters = PerfCounters { + state: Atomic::new(AudioState::Stopped), received_frames: AtomicUsize::new(0), // received from USB played_frames: AtomicUsize::new(0), // played audio frames min_fill: AtomicUsize::new(0), // not recording this for now, need to figure out how to make it meaningful, since the queue starts empty @@ -156,6 +169,10 @@ static PERF: PerfCounters = PerfCounters { queue_underflows: AtomicUsize::new(0), // ditto here, since we underflow at startup, but we record this one as it can be trended queue_overflows: AtomicUsize::new(0), audio_underflows: AtomicUsize::new(0), + integrator: AtomicI32::new(0), + p: AtomicI32::new(0), + i: AtomicI32::new(0), + fb: AtomicI32::new(0), }; static DMA_RING: StaticCell> = StaticCell::new(); @@ -255,6 +272,11 @@ enum AudioState { /// AltSetting = 0 -> STOPPED NoData, } +impl Default for AudioState { + fn default() -> Self { + AudioState::Stopped + } +} impl defmt::Format for AudioState { fn format(&self, fmt: defmt::Formatter) { defmt::write!( @@ -328,6 +350,7 @@ impl, I> Audio<'_, D, I> { AudioState::NoData => self.nodata(), } self.state.store(state, Ordering::SeqCst); + PERF.state.store(state, Ordering::Relaxed); } fn init(&mut self) { @@ -568,28 +591,43 @@ impl, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> { PERF.queue_underflows.fetch_add(1, Ordering::Relaxed); return Some(nominal_rate); } + PERF.avg_fill .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| { Some(((v << 6) - v + current_bytes as usize) >> 6) }) .ok(); - // normalize error wrt. frame size etc. - let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / FILL_TARGET_BYTES; + let raw_error = current_bytes - FILL_TARGET_BYTES; + let i_error = if raw_error.abs() <= 4 { 0 } else { raw_error }; // deadband + let current_i = self.fb.integrator.load(Ordering::Relaxed); + let leak = current_i >> 7; + let new_i = current_i + .saturating_sub(leak) + .saturating_add(i_error) + .clamp(-5000, 5000); + self.fb.integrator.store(new_i, Ordering::Relaxed); + PERF.integrator.store(new_i, Ordering::Relaxed); let nominal_v = nominal_rate.to_u32_12_13() as i32; + let max_allowed_deviation = nominal_v / 500; // 0.2% - // 0.2% which is a huge clock error - let max_allowed_deviation = nominal_v / 500; + // 3. SEPARATE GAINS FOR P AND I + // For P: Keep your working math (converting raw error to a permille equivalent scale) + let error_permille = (raw_error * 1000) / FILL_TARGET_BYTES; + let p_term = (-((error_permille as i64) * (nominal_v as i64)) / (10 * 256000)) as i32; + let i_term = (-((new_i as i64) * (nominal_v as i64)) / (256000 * 1000)) as i32; + let i_term = 0; - let p_term = -(error_permille * nominal_v) / 256000; // this works reasonably well to keep the buffer - let i_term = 0; // placeholder + PERF.p.store(p_term, Ordering::Relaxed); + PERF.i.store(i_term, Ordering::Relaxed); let mut v = nominal_v + p_term + i_term; v = v.clamp( nominal_v - max_allowed_deviation, nominal_v + max_allowed_deviation, ); + PERF.fb.store(v, Ordering::Relaxed); Some(UsbIsochronousFeedback::new(v as u32)) } @@ -820,15 +858,15 @@ fn main() -> ! { hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000)); move || { - let active = usb_dev.poll(&mut [&mut uac2, &mut hid]); - if active && hid_update_timer.wait().is_ok() { + usb_dev.poll(&mut [&mut uac2, &mut hid]); + if hid_update_timer.wait().is_ok() { let report = PERF.build_report(); match hid.push_input(&report) { Ok(_) => {} Err(UsbError::WouldBlock) => {} Err(e) => defmt::error!("Failed to send HID report: {:?}", e), } - // lpc55 timer is not Periodic, so restart it + // lpc55 ctimer is not Periodic, so restart it hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000)); } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c6db6f8..44688a6 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -2,6 +2,10 @@ name = "shared" edition = "2024" +[features] +serde = ["dep:serde"] + [dependencies] deku = { version = "0.20.3", default-features = false } +serde = { version = "1.0.228", optional = true, features = ["derive"] } usbd-hid = { version = "0.10.0" } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 8148413..fe09021 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -2,26 +2,39 @@ pub mod hid { use deku::DekuRead; + #[cfg(feature = "serde")] + use serde::{Deserialize, Serialize}; use usbd_hid::descriptor::generator_prelude::*; #[derive(DekuRead)] #[deku(endian = "little")] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[gen_hid_descriptor( (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01, ) = { + state=input; average_buffer_fill=input; frame_count=input; dac_underflow_count=input; usb_underflow_count=input; dac_overflow_count=input; + p=input; + i=input; + fb=input; + integrator=input; } )] - #[repr(C)] - // Note these are all actually u32 + #[repr(C, packed)] + // TODO: Fix that most of these values are actually u32, but usbd_hid macro doesn't work properly on u32 pub struct AudioTelemetryReport { - pub average_buffer_fill: i32, + pub state: u8, + pub average_buffer_fill: u16, pub frame_count: i32, - pub dac_underflow_count: i32, - pub usb_underflow_count: i32, - pub dac_overflow_count: i32, + pub dac_underflow_count: u16, + pub usb_underflow_count: u16, + pub dac_overflow_count: u16, + pub p: i32, + pub i: i32, + pub fb: i32, + pub integrator: i32, } }