From f5a1a797b99b24aea2ee12c0d49d5ddd7a592bc0 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 4 Aug 2025 17:15:11 +0200 Subject: [PATCH 1/3] Logs --- probe-plotter-tools/Cargo.lock | 233 ++++++++++++++++-- probe-plotter-tools/Cargo.toml | 5 +- .../src/bin/{viewer.rs => custom-viewer.rs} | 75 +++--- .../src/{main.rs => bin/minimal.rs} | 2 + probe-plotter-tools/src/lib.rs | 212 +++++++++++++++- 5 files changed, 460 insertions(+), 67 deletions(-) rename probe-plotter-tools/src/bin/{viewer.rs => custom-viewer.rs} (62%) rename probe-plotter-tools/src/{main.rs => bin/minimal.rs} (93%) diff --git a/probe-plotter-tools/Cargo.lock b/probe-plotter-tools/Cargo.lock index ee28a5f..6e780e4 100644 --- a/probe-plotter-tools/Cargo.lock +++ b/probe-plotter-tools/Cargo.lock @@ -124,7 +124,7 @@ version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli", + "gimli 0.31.1", ] [[package]] @@ -163,6 +163,17 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alterable_logger" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a2c81a0e8d57d88d11554612d5e0afe5f942cecbcc239b10a394fd7ce404b" +dependencies = [ + "arc-swap", + "log", + "once_cell", +] + [[package]] name = "android-activity" version = "0.6.0" @@ -281,6 +292,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "array-init" version = "2.1.0" @@ -621,9 +638,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -1204,6 +1221,23 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "cbor-edn" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b43829b1f353168aa8593c2e4ef6e71a81d6749fe09edfc33849f890c69278" +dependencies = [ + "chrono", + "data-encoding", + "data-encoding-macro", + "encoding_rs", + "hex", + "hexfloat2", + "num-bigint", + "num-traits", + "peg 0.8.5", +] + [[package]] name = "cc" version = "1.2.31" @@ -1724,6 +1758,32 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "data-encoding-macro" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +dependencies = [ + "data-encoding", + "syn 2.0.104", +] + [[package]] name = "data-url" version = "0.3.1" @@ -2261,6 +2321,41 @@ dependencies = [ "defmt-macros", ] +[[package]] +name = "defmt-decoder" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b563d700380314b45fa0ddfbf2ea998a80c64b7d88ab42b0b1fba774c4da66" +dependencies = [ + "alterable_logger", + "anyhow", + "byteorder", + "cbor-edn", + "colored", + "defmt-json-schema", + "defmt-parser", + "dissimilar", + "gimli 0.29.0", + "log", + "nom", + "object 0.35.0", + "regex", + "ryu", + "serde", + "serde_json", + "time", +] + +[[package]] +name = "defmt-json-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b04d228e57a61cf385d86bc8980bb41b47c6fc0eace90592668df97b2dad6a" +dependencies = [ + "log", + "serde", +] + [[package]] name = "defmt-macros" version = "1.0.1" @@ -2375,6 +2470,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + [[package]] name = "dlib" version = "0.5.2" @@ -2706,6 +2807,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -2915,9 +3025,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2934,6 +3044,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.3.0" @@ -2951,9 +3067,9 @@ dependencies = [ [[package]] name = "ffmpeg-sidecar" -version = "2.0.6" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3389f35638a429d3f2b1bbe18a548d6b65cce18f4c32b93d965bf4368fa42d" +checksum = "0f35f3bfdf862abfb6999b3f9079c5ec5c55dfa1010e907ed055e4fbdd64c335" dependencies = [ "anyhow", ] @@ -3143,9 +3259,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -3242,6 +3358,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.31.1" @@ -3582,6 +3708,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hexfloat2" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befe65164a090041cdf6e0d21a0ec3198d856fbfe2b76e324a073e790bb49f8c" + [[package]] name = "hidapi" version = "2.6.3" @@ -4180,8 +4312,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lexers" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82de9bb5d9e8ff5e13532a45583ea972e220b8a6efef755daad1f285333a8afa" +source = "git+https://github.com/usbalbin/tox?branch=add-ln#835b0b2598deb80583faee261798a8c4768bbc6a" [[package]] name = "lexical-core" @@ -4346,6 +4477,9 @@ name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "serde", +] [[package]] name = "log-once" @@ -4524,6 +4658,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -4713,6 +4853,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "6.1.1" @@ -5170,6 +5320,15 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "object" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +dependencies = [ + "memchr", +] + [[package]] name = "object" version = "0.36.7" @@ -5368,8 +5527,18 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" dependencies = [ - "peg-macros", - "peg-runtime", + "peg-macros 0.6.3", + "peg-runtime 0.6.3", +] + +[[package]] +name = "peg" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" +dependencies = [ + "peg-macros 0.8.5", + "peg-runtime 0.8.5", ] [[package]] @@ -5378,7 +5547,18 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" dependencies = [ - "peg-runtime", + "peg-runtime 0.6.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" +dependencies = [ + "peg-runtime 0.8.5", "proc-macro2", "quote", ] @@ -5389,6 +5569,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" +[[package]] +name = "peg-runtime" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -5536,7 +5722,7 @@ checksum = "dbadf9cb4a79d516de4c64806fe64ffbd8161d1ac685d000be789fb628b88963" dependencies = [ "byteorder", "linked-hash-map", - "peg", + "peg 0.6.3", "skeptic", ] @@ -5567,9 +5753,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", @@ -5639,6 +5825,8 @@ name = "probe-plotter-tools" version = "0.1.0" dependencies = [ "defmt", + "defmt-decoder", + "defmt-parser", "goblin", "mimalloc", "object 0.37.2", @@ -8306,8 +8494,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "shunting" version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6bc210d95d59fbf54a2849a956866f9ec11f8e2ca3595a6d3f94e8d5b4cd74f" +source = "git+https://github.com/usbalbin/tox?branch=add-ln#835b0b2598deb80583faee261798a8c4768bbc6a" dependencies = [ "lexers", "libm", @@ -8317,9 +8504,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -8919,9 +9106,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", diff --git a/probe-plotter-tools/Cargo.toml b/probe-plotter-tools/Cargo.toml index 5679a25..34c6150 100644 --- a/probe-plotter-tools/Cargo.toml +++ b/probe-plotter-tools/Cargo.toml @@ -18,4 +18,7 @@ serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.141" # mimalloc is a much faster allocator: mimalloc = "0.1.43" -shunting = "0.1.2" \ No newline at end of file +#shunting = "0.1.2" +shunting = { git = "https://github.com/usbalbin/tox", branch = "add-ln" } +defmt-decoder = "1.0.0" +defmt-parser = "1.0.0" diff --git a/probe-plotter-tools/src/bin/viewer.rs b/probe-plotter-tools/src/bin/custom-viewer.rs similarity index 62% rename from probe-plotter-tools/src/bin/viewer.rs rename to probe-plotter-tools/src/bin/custom-viewer.rs index caf31bf..5e1dac2 100644 --- a/probe-plotter-tools/src/bin/viewer.rs +++ b/probe-plotter-tools/src/bin/custom-viewer.rs @@ -1,27 +1,42 @@ // A custom rerun viewer capable of showing and editing settings -use std::{sync::mpsc, thread, time::Duration}; - -use probe_plotter_tools::{gui::MyApp, metric::Status, parse_elf_file, setting::Setting}; -use rerun::external::{eframe, re_crash_handler, re_grpc_server, re_log, re_viewer, tokio}; -use shunting::MathContext; +use probe_plotter_tools::{gui::MyApp, parse, probe_background_thread, setting::Setting}; +use rerun::external::{eframe, re_crash_handler, re_grpc_server, re_viewer, tokio}; +use std::{env, io::Read, sync::mpsc, thread, time::Duration}; #[tokio::main] async fn main() -> Result<(), Box> { - let elf_path = std::env::args() + let help = "Usage: \nprobe-plotter /path/to/elf chip update_rate"; + + let elf_path = env::args() .nth(1) - .expect("Usage: \nprobe-plotter /path/to/elf chip"); + .expect("Usage: \nprobe-plotter /path/to/elf chip update_rate"); - let target = std::env::args() + let target = env::args() .nth(2) .unwrap_or_else(|| "stm32g474retx".to_owned()); - let (mut metrics, mut settings) = parse_elf_file(&elf_path); + let update_rate = env::args() + .nth(2) + .map(|s| { + Duration::from_millis( + s.parse() + .unwrap_or_else(|_| panic!("Invalid update_rate\n\n{help}")), + ) + }) + .unwrap_or_else(|| Duration::from_millis(10)); + + let mut elf_bytes = Vec::new(); + std::fs::File::open(elf_path) + .unwrap() + .read_to_end(&mut elf_bytes) + .unwrap(); + + let (metrics, settings) = parse(&elf_bytes); let main_thread_token = rerun::MainThreadToken::i_promise_i_am_on_the_main_thread(); // Direct calls using the `log` crate to stderr. Control with `RUST_LOG=debug` etc. - re_log::setup_logging(); // Install handlers for panics and crashes that prints to stderr and send // them to Rerun analytics (if the `analytics` feature is on in `Cargo.toml`). @@ -49,37 +64,15 @@ async fn main() -> Result<(), Box> { // probe-thread thread::spawn(move || { - let mut session = probe_rs::Session::auto_attach(target, Default::default()).unwrap(); - let mut core = session.core(0).unwrap(); - - let rec = rerun::RecordingStreamBuilder::new("probe-plotter") - .spawn() - .unwrap(); - - // Load initial values from device - for setting in &mut settings { - setting.read(&mut core).unwrap(); - } - - // Send initial settings back to main thread - initial_settings_sender.send(settings).unwrap(); - - let mut math_ctx = MathContext::new(); - loop { - for mut setting in settings_update_receiver.try_iter() { - setting.write(setting.value, &mut core).unwrap(); - } - - for m in &mut metrics { - m.read(&mut core, &mut math_ctx).unwrap(); - let (x, s) = m.compute(&mut math_ctx); - if let Status::New = s { - rec.log(m.name.clone(), &rerun::Scalars::single(x)).unwrap(); - } else { - std::thread::sleep(Duration::from_millis(1)); - } - } - } + probe_background_thread( + update_rate, + &target, + &elf_bytes, + settings, + metrics, + settings_update_receiver, + initial_settings_sender, + ) }); // Receive initial settings from to probe-thread thread diff --git a/probe-plotter-tools/src/main.rs b/probe-plotter-tools/src/bin/minimal.rs similarity index 93% rename from probe-plotter-tools/src/main.rs rename to probe-plotter-tools/src/bin/minimal.rs index 851a117..32e1784 100644 --- a/probe-plotter-tools/src/main.rs +++ b/probe-plotter-tools/src/bin/minimal.rs @@ -1,3 +1,5 @@ +// Connect to the regular rerun viewer. This does not support Settings, only Metrics + use probe_plotter_tools::{metric::Status, parse_elf_file}; use shunting::MathContext; use std::time::Duration; diff --git a/probe-plotter-tools/src/lib.rs b/probe-plotter-tools/src/lib.rs index dad1840..de44de0 100644 --- a/probe-plotter-tools/src/lib.rs +++ b/probe-plotter-tools/src/lib.rs @@ -3,10 +3,13 @@ pub mod metric; pub mod setting; pub mod symbol; -use std::io::Read; +use std::{io::Read, sync::mpsc, time::Duration}; +use defmt_decoder::DecodeError; +use defmt_parser::Level; use object::{Object, ObjectSymbol}; -use probe_rs::{Core, MemoryInterface}; +use probe_rs::{Core, MemoryInterface, rtt::Rtt}; +use rerun::TextLogLevel; use serde::Deserialize; use shunting::{MathContext, ShuntingParser}; @@ -95,6 +98,7 @@ pub fn parse(elf_bytes: &[u8]) -> (Vec, Vec) { (metrics, settings) } +/// Parse elf file into a set of Metrics and Settings pub fn parse_elf_file(elf_path: &str) -> (Vec, Vec) { let mut buffer = Vec::new(); std::fs::File::open(elf_path) @@ -106,3 +110,207 @@ pub fn parse_elf_file(elf_path: &str) -> (Vec, Vec) { (metrics, settings) } + +/// A background task which communicates with the probe +/// +/// This handles +/// * defmt logging +/// * reading metrics +/// * reading initial values for settings +/// * writing updated settings +pub fn probe_background_thread( + update_rate: Duration, + target: &str, + elf_bytes: &[u8], + mut settings: Vec, + mut metrics: Vec, + settings_update_receiver: mpsc::Receiver, + initial_settings_sender: mpsc::Sender>, +) { + let mut session = probe_rs::Session::auto_attach(target, Default::default()).unwrap(); + let mut core = session.core(0).unwrap(); + let mut rtt = Rtt::attach(&mut core).unwrap(); + let table = defmt_decoder::Table::parse(elf_bytes).unwrap().unwrap(); + + // TODO: Get this to work + // This produces ascii escape codes which egui/rerun does not seem to understand + + /*let show_timestamps = true; + let show_location = true; + let log_format = None; + let has_timestamp = table.has_timestamp(); + + // Format options: + // 1. Oneline format with optional location + // 2. Custom format for the channel + // 3. Default with optional location + let format = match log_format { + None | Some("oneline") => FormatterFormat::OneLine { + with_location: show_location, + }, + Some("full") => FormatterFormat::Default { + with_location: show_location, + }, + Some(format) => FormatterFormat::Custom(format), + }; + + let formatter = Formatter::new(FormatterConfig { + format, + is_timestamp_available: has_timestamp && show_timestamps, + });*/ + + let rec = rerun::RecordingStreamBuilder::new("probe-plotter") + .spawn() + .unwrap(); + + let locs = match table.get_locations(elf_bytes) { + Ok(locs) if locs.is_empty() => { + rec.log( + "log", + &rerun::TextLog::new("Insufficient DWARF info; compile your program with `debug = 2` to enable location info.").with_level(TextLogLevel::WARN)).unwrap(); + None + } + Ok(locs) if table.indices().all(|idx| locs.contains_key(&(idx as u64))) => Some(locs), + Ok(_) => { + rec.log( + "log", + &rerun::TextLog::new( + "Location info is incomplete; it will be omitted from the output.", + ) + .with_level(TextLogLevel::WARN), + ) + .unwrap(); + None + } + Err(e) => { + rec.log( + "log", + &rerun::TextLog::new(format!( + "Failed to parse location data: {e:?}; it will be omitted from the output." + )) + .with_level(TextLogLevel::WARN), + ) + .unwrap(); + + None + } + }; + + let mut decoders: Vec<_> = rtt + .up_channels + .iter() + .map(|_| table.new_stream_decoder()) + .collect(); + + // Load initial values from device + for setting in &mut settings { + setting.read(&mut core).unwrap(); + } + + // Send initial settings back to main thread + initial_settings_sender.send(settings).unwrap(); + + let mut math_ctx = MathContext::new(); + loop { + for mut setting in settings_update_receiver.try_iter() { + setting.write(setting.value, &mut core).unwrap(); + } + + receive_defmt_messages(&mut rtt, &mut core, &mut decoders); + log_defmt_messages(&rec, &locs, &mut decoders); + + for m in &mut metrics { + m.read(&mut core, &mut math_ctx).unwrap(); + let (x, _s) = m.compute(&mut math_ctx); + rec.log(m.name.clone(), &rerun::Scalars::single(x)).unwrap(); + } + std::thread::sleep(update_rate); + } +} + +/// Receive defmt messages frome probe +pub fn receive_defmt_messages<'a>( + rtt: &mut Rtt, + core: &mut Core, + decoders: &mut Vec>, +) { + loop { + let mut has_data = false; + let mut buf = [0; 256]; + for (ch, decoder) in rtt.up_channels.iter_mut().zip(&mut *decoders) { + let read_count = ch.read(core, &mut buf).unwrap(); + + if read_count > 0 { + dbg!(read_count); + decoder.received(&buf[..read_count]); + has_data = true; + } + } + if !has_data { + break; + } + } +} + +/// Log defmt messages to rerun +pub fn log_defmt_messages<'a>( + rec: &rerun::RecordingStream, + locs: &'a Option>, + decoders: &mut Vec>, +) { + loop { + let mut has_decoded = false; + for decoder in &mut *decoders { + let frame = match decoder.decode() { + Ok(f) => f, + Err(DecodeError::UnexpectedEof) => continue, + Err(defmt_decoder::DecodeError::Malformed) => { + rec.log( + "log", + &rerun::TextLog::new("DecodeError::Malformed") + .with_level(TextLogLevel::WARN), + ) + .unwrap(); + continue; + } + }; + has_decoded = true; + + let level = match frame.level() { + Some(Level::Trace) => TextLogLevel::TRACE, + Some(Level::Debug) => TextLogLevel::DEBUG, + Some(Level::Info) => TextLogLevel::INFO, + Some(Level::Warn) => TextLogLevel::WARN, + Some(Level::Error) => TextLogLevel::ERROR, + None => TextLogLevel::INFO, + }; + + let loc = locs.as_ref().and_then(|locs| locs.get(&frame.index())); + let (file, line, module) = if let Some(loc) = loc { + ( + loc.file.display().to_string(), + loc.line.to_string(), + loc.module.as_str(), + ) + } else { + ( + format!( + "└─ ", + frame.index() + ), + "?".to_string(), + "?", + ) + }; + + //let msg = formatter.format_frame(frame, Some(&file), line, module); + let msg = format!("{module} :: {} {file}:{line}", frame.display(false)); + + rec.log("log", &rerun::TextLog::new(msg).with_level(level)) + .unwrap(); + } + if !has_decoded { + break; + } + } +} From 541aa94ebee973e7c48fd9a7895faa6873da295a Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 10 Aug 2025 13:19:40 +0200 Subject: [PATCH 2/3] Add setting for channel mode --- probe-plotter-tools/src/bin/custom-viewer.rs | 18 +++++++++++++----- probe-plotter-tools/src/lib.rs | 13 ++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/probe-plotter-tools/src/bin/custom-viewer.rs b/probe-plotter-tools/src/bin/custom-viewer.rs index 5e1dac2..e1be368 100644 --- a/probe-plotter-tools/src/bin/custom-viewer.rs +++ b/probe-plotter-tools/src/bin/custom-viewer.rs @@ -6,18 +6,16 @@ use std::{env, io::Read, sync::mpsc, thread, time::Duration}; #[tokio::main] async fn main() -> Result<(), Box> { - let help = "Usage: \nprobe-plotter /path/to/elf chip update_rate"; + let help = "Usage: \nprobe-plotter /path/to/elf [chip] [update_rate_ms=10] [channel_mode=no change]"; - let elf_path = env::args() - .nth(1) - .expect("Usage: \nprobe-plotter /path/to/elf chip update_rate"); + let elf_path = env::args().nth(1).expect(help); let target = env::args() .nth(2) .unwrap_or_else(|| "stm32g474retx".to_owned()); let update_rate = env::args() - .nth(2) + .nth(3) .map(|s| { Duration::from_millis( s.parse() @@ -26,6 +24,15 @@ async fn main() -> Result<(), Box> { }) .unwrap_or_else(|| Duration::from_millis(10)); + let channel_mode = env::args() + .nth(4) + .map(|s| match s.as_str() { + "NoBlockSkip" => probe_rs::rtt::ChannelMode::NoBlockSkip, + "NoBlockTrim" => probe_rs::rtt::ChannelMode::NoBlockTrim, + "BlockIfFull" => probe_rs::rtt::ChannelMode::BlockIfFull, + _ => panic!("Invalid channel_mode. Select one of\n* NoBlockSkip\n* NoBlockTrim\n* BlockIfFull\n\n{help}"), + }); + let mut elf_bytes = Vec::new(); std::fs::File::open(elf_path) .unwrap() @@ -66,6 +73,7 @@ async fn main() -> Result<(), Box> { thread::spawn(move || { probe_background_thread( update_rate, + channel_mode, &target, &elf_bytes, settings, diff --git a/probe-plotter-tools/src/lib.rs b/probe-plotter-tools/src/lib.rs index de44de0..c223d42 100644 --- a/probe-plotter-tools/src/lib.rs +++ b/probe-plotter-tools/src/lib.rs @@ -8,7 +8,10 @@ use std::{io::Read, sync::mpsc, time::Duration}; use defmt_decoder::DecodeError; use defmt_parser::Level; use object::{Object, ObjectSymbol}; -use probe_rs::{Core, MemoryInterface, rtt::Rtt}; +use probe_rs::{ + Core, MemoryInterface, + rtt::{ChannelMode, Rtt}, +}; use rerun::TextLogLevel; use serde::Deserialize; use shunting::{MathContext, ShuntingParser}; @@ -118,8 +121,10 @@ pub fn parse_elf_file(elf_path: &str) -> (Vec, Vec) { /// * reading metrics /// * reading initial values for settings /// * writing updated settings +#[allow(clippy::too_many_arguments)] pub fn probe_background_thread( update_rate: Duration, + channel_mode: Option, target: &str, elf_bytes: &[u8], mut settings: Vec, @@ -196,6 +201,12 @@ pub fn probe_background_thread( } }; + if let Some(channel_mode) = channel_mode { + for ch in &mut rtt.up_channels { + ch.set_mode(&mut core, channel_mode).unwrap(); + } + } + let mut decoders: Vec<_> = rtt .up_channels .iter() From 2e14b0b1f414510bbffaa4e8775430f74927dd6c Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 10 Aug 2025 13:31:33 +0200 Subject: [PATCH 3/3] Update example --- README.md | 14 +++++++++----- examples/simple/src/main.rs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b5036aa..6d6b110 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,23 @@ A set of tools to plot values from the target to graph in rerun with minimal per #![no_main] use cortex_m_rt::entry; + +use defmt_rtt as _; +use panic_halt as _; use probe_plotter::{make_metric, make_setting}; #[entry] fn main() -> ! { + defmt::println!("Running..."); let mut sawtooth = make_metric!(SAWTOOTH: i32 = 42, "(SAWTOOTH / 10) % 100").unwrap(); + defmt::println!("sawtooth initialized to: {}", sawtooth.get()); let mut sine = make_metric!(SINE: i32 = 42, "100 * sin(2 * pi * SINE / 4000)").unwrap(); let mut setting_roundtrip = make_metric!(SETTING_ROUNDTRIP: i8 = 0, "SETTING_ROUNDTRIP").unwrap(); // Allow values -1..=7, step by 2, so {-1, 1, 3, 5, 7} - let mut setting = make_setting!(SETTING: i8 = 42, -1..=7, 2).unwrap(); + let mut setting = make_setting!(SETTING: i8 = 5, -1..=7, 2).unwrap(); loop { for i in 0..i32::MAX { @@ -32,7 +37,6 @@ fn main() -> ! { } } } - ``` The formulas seen in the `make_metric` macro invocation are computed by the host and will thus have zero impact on the targets performance. The `set` method on the metrics object is simply a volatile store which is quite cheap. The host will then read that value using the debug probe at regular intervals and update the graph on any changes. @@ -54,8 +58,8 @@ cd examples/simple cargo run # Let it flash and then cancel (Ctrl+C) to let the target continue running in the background while giving up access to the probe cd ../probe-plotter-tools -cargo run ../examples/simple/target/thumbv7em-none-eabihf/debug/simple stm32g474retx -# Rerun will open with a graph showing all created metrics objects +cargo run --bin custom-viewer ../examples/simple/target/thumbv7em-none-eabihf/debug/simple stm32g474retx +# Rerun will open with a graph showing all created metrics objects and a panel with settings at the right hand side ``` -image +image diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index a79b1e2..48401de 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -11,14 +11,14 @@ use probe_plotter::{make_metric, make_setting}; fn main() -> ! { defmt::println!("Running..."); let mut sawtooth = make_metric!(SAWTOOTH: i32 = 42, "(SAWTOOTH / 10) % 100").unwrap(); - defmt::println!("foo initialized to: {}", sawtooth.get()); + defmt::println!("sawtooth initialized to: {}", sawtooth.get()); let mut sine = make_metric!(SINE: i32 = 42, "100 * sin(2 * pi * SINE / 4000)").unwrap(); let mut setting_roundtrip = make_metric!(SETTING_ROUNDTRIP: i8 = 0, "SETTING_ROUNDTRIP").unwrap(); // Allow values -1..=7, step by 2, so {-1, 1, 3, 5, 7} - let mut setting = make_setting!(SETTING: i8 = 42, -1..=7, 2).unwrap(); + let mut setting = make_setting!(SETTING: i8 = 5, -1..=7, 2).unwrap(); loop { for i in 0..i32::MAX {