Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ 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 probe_plotter::{make_metric, make_setting};
use probe_plotter::{make_graph, make_metric, make_setting};

#[entry]
fn main() -> ! {
let mut sawtooth = make_metric!(SAWTOOTH: i32 = 42, "(SAWTOOTH / 10) % 100").unwrap();
let mut sine = make_metric!(SINE: i32 = 42, "100 * sin(2 * pi * SINE / 4000)").unwrap();
make_graph!(SAWTOOTH = "(SAWTOOTH / 10) % 100");
make_graph!(SINE = "100 * sin(2 * pi * SINE / 4000)");
make_graph!(SINE_TIMES_SAWTOOTH = "100 * sin(2 * pi * SINE / 4000) * (SAWTOOTH / 10) % 100)");
make_graph!(SETTING = "SETTING");
make_graph!(SETTING_ROUNDTRIP = "SETTING_ROUNDTRIP");

let mut setting_roundtrip =
make_metric!(SETTING_ROUNDTRIP: i8 = 0, "SETTING_ROUNDTRIP").unwrap();
let mut sawtooth = make_metric!(SAWTOOTH: i32 = 42).unwrap();
let mut sine = make_metric!(SINE: i32 = 42).unwrap();
let mut setting_roundtrip = make_metric!(SETTING_ROUNDTRIP: i8 = 0).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();
Expand All @@ -29,6 +33,8 @@ fn main() -> ! {
sine.set(i);

setting_roundtrip.set(setting.get());

cortex_m::asm::delay(100_000);
}
}
}
Expand All @@ -54,7 +60,7 @@ 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
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
```

Expand Down
15 changes: 10 additions & 5 deletions examples/simple/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ use cortex_m_rt::entry;

use defmt_rtt as _;
use panic_halt as _;
use probe_plotter::{make_metric, make_setting};
use probe_plotter::{make_graph, make_metric, make_setting};

#[entry]
fn main() -> ! {
make_graph!(SAWTOOTH = "(SAWTOOTH / 10) % 100");
make_graph!(SINE = "100 * sin(2 * pi * SINE / 4000)");
make_graph!(SINE_TIMES_SAWTOOTH = "100 * sin(2 * pi * SINE / 4000) * (SAWTOOTH / 10) % 100)");
make_graph!(SETTING = "SETTING");
make_graph!(SETTING_ROUNDTRIP = "SETTING_ROUNDTRIP");

defmt::println!("Running...");
let mut sawtooth = make_metric!(SAWTOOTH: i32 = 42, "(SAWTOOTH / 10) % 100").unwrap();
let mut sawtooth = make_metric!(SAWTOOTH: i32 = 42).unwrap();
defmt::println!("foo initialized to: {}", sawtooth.get());
let mut sine = make_metric!(SINE: i32 = 42, "100 * sin(2 * pi * SINE / 4000)").unwrap();
let mut sine = make_metric!(SINE: i32 = 42).unwrap();

let mut setting_roundtrip =
make_metric!(SETTING_ROUNDTRIP: i8 = 0, "SETTING_ROUNDTRIP").unwrap();
let mut setting_roundtrip = make_metric!(SETTING_ROUNDTRIP: i8 = 0).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();
Expand Down
35 changes: 21 additions & 14 deletions macros/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
// Based on defmt

use syn::{
LitStr, RangeLimits, Token,
RangeLimits, Token,
parse::{self, Parse, ParseStream},
spanned::Spanned,
};

//FOO: i32 = 0, "x * 3.0"
//FOO: i32 = 0 // defaults to "x"
//FOO: i32 = 0
pub(crate) struct MetricArgs {
pub(crate) name: syn::Ident,
pub(crate) ty: syn::Ident,
pub(crate) initial_val: syn::Expr,
pub(crate) expression_string: syn::LitStr,
}

impl Parse for MetricArgs {
Expand All @@ -23,20 +21,10 @@ impl Parse for MetricArgs {
let _comma: Token![=] = input.parse()?;
let initial_val = input.parse()?;

let comma: parse::Result<Token![,]> = input.parse();
let expression_string = input.parse();

let expression_string = match (comma, expression_string) {
(Ok(_), Ok(expr)) => expr,
(Ok(_), Err(e)) => return Err(e),
(Err(_), _) => LitStr::new(&name.to_string(), name.span()),
};

Ok(Self {
name,
ty,
initial_val,
expression_string,
})
}
}
Expand Down Expand Up @@ -100,6 +88,25 @@ impl Parse for SettingArgs {
}
}

//FOO = "x * 3.0"
pub(crate) struct GraphArgs {
pub(crate) name: syn::Ident,
pub(crate) expression_string: syn::LitStr,
}

impl Parse for GraphArgs {
fn parse(input: ParseStream) -> parse::Result<Self> {
let name: syn::Ident = input.parse()?;
let _comma: Token![=] = input.parse()?;
let expression_string = input.parse()?;

Ok(Self {
name,
expression_string,
})
}
}

// TODO: Clean up this mess
fn expr_to_float_lit(e: syn::Expr) -> Result<syn::LitFloat, syn::Error> {
let error_msg = "expected float or int literal";
Expand Down
91 changes: 81 additions & 10 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use proc_macro::{Span, TokenStream};
use quote::quote;
use syn::parse_macro_input;

use crate::symbol::{MetricsSymbol, SettingSymbol};
use crate::symbol::{GraphSymbol, MetricsSymbol, SettingSymbol};

mod args;
mod cargo;
Expand All @@ -16,26 +16,21 @@ mod symbol;
/// Create a Metric instance that will be shown in the probe-plotter utility's graph
///
/// ```
/// make_metric!(NAME_AS_SHOWN_IN_GRAPH: DataType = defalt_value, "expression to convert from raw value (x) to the value to plot")
/// make_metric!(NAME_AS_SHOWN_IN_GRAPH: DataType = defalt_value, "expression to convert from raw value (NAME_AS_SHOWN_IN_GRAPH) to the value to plot")
/// ```
///
/// Note that similar to `cortex_m::singleton!`, this should only be called once per metric. The macro will only return Some() the first time, then None.
///
/// ```
/// let mut metric_foo = probe_plotter::make_metric!(FOO: i32 = 0, "x * 3.0").unwrap();
/// let mut metric_foo = probe_plotter::make_metric!(FOO: i32 = 0, "FOO * 3.0").unwrap();
///
/// metric_foo.set(42); // The value 42 will be available for the host after this call. The value will be plotted as x * 3 = 42 * 3 = 126
/// metric_foo.set(42); // The value 42 will be available for the host after this call. The value will be plotted as FOO * 3 = 42 * 3 = 126
/// ```
#[proc_macro]
pub fn make_metric(args: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as args::MetricArgs);

let sym_name = MetricsSymbol::new(
args.ty.to_string(),
args.name.to_string(),
args.expression_string.value(),
)
.mangle();
let sym_name = MetricsSymbol::new(args.ty.to_string(), args.name.to_string()).mangle();

let name = args.name;
let ty = args.ty;
Expand Down Expand Up @@ -116,6 +111,68 @@ pub fn make_setting(args: TokenStream) -> TokenStream {
.into()
}

/// Register a graph for the probe-plotter utility's to plot.
///
/// This has access to all metrics and settings which may be used in the expression
///
/// ```
/// make_graph!(GRAPH_TITLE = "expression to convert from raw values to the value to plot, may refer to any metrics or settings")
/// ```
///
/// ```
/// probe_plotter::make_graph!(FOO_GRAPH = "FOO * 3.0");
///
///
/// let mut metric_foo = probe_plotter::make_metric!(FOO: i32 = 0).unwrap();
///
/// metric_foo.set(42); // The value 42 will be available for the host after this call. The value will be plotted as FOO * 3 = 42 * 3 = 126
/// ```
#[proc_macro]
pub fn make_graph(args: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as args::GraphArgs);

let sym_name = GraphSymbol::new(args.name.to_string(), args.expression_string.value()).mangle();
let section = linker_section(false, &sym_name);
let section_for_macos = linker_section(true, &sym_name);

let name = args.name;
quote!(
#[cfg_attr(target_os = "macos", unsafe(link_section = #section_for_macos))]
#[cfg_attr(not(target_os = "macos"), unsafe(link_section = #section))]
#[unsafe(export_name = #sym_name)]
static mut #name: u8 = 42;

#[allow(unsafe_code)]
unsafe {
// TODO: Find better way to ensure the compiler considers this static used
// the #[used] attribute does not seem enough
let mut x = &raw const #name as usize;
core::arch::asm!("mov {0}, {0}", inout(reg) x);
}
)
.into()
}
/*
quote!(
#[cfg_attr(target_os = "macos", unsafe(link_section = #section_for_macos))]
#[cfg_attr(not(target_os = "macos"), unsafe(link_section = #section))]
#[used]
#[unsafe(export_name = #sym_name)]
static #name: u8 = 0;
)
.into()


let name = args.name;
quote!(
#[used]
#[unsafe(export_name = #sym_name)]
static mut #name: (i8, bool) =
(0, false);
)
.into()
*/

pub(crate) fn crate_local_disambiguator() -> u64 {
// We want a deterministic, but unique-per-macro-invocation identifier. For that we
// hash the call site `Span`'s debug representation, which contains a counter that
Expand All @@ -128,3 +185,17 @@ fn hash(string: &str) -> u64 {
string.hash(&mut hasher);
hasher.finish()
}

/// work around restrictions on length and allowed characters imposed by macos linker
/// returns (note the comma character for macos):
/// under macos: ".defmt," + 16 character hex digest of symbol's hash
/// otherwise: ".defmt." + prefix + symbol
pub(crate) fn linker_section(for_macos: bool, symbol: &str) -> String {
let mut sub_section = format!(".{symbol}");

if for_macos {
sub_section = format!(",{:x}", hash(&sub_section));
}

format!(".defmt{sub_section}")
}
51 changes: 44 additions & 7 deletions macros/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,28 @@ pub struct MetricsSymbol {
/// Variable name
name: String,

/// Expression used to calculate the value to plot
expression_string: String,

/// Crate name obtained via CARGO_CRATE_NAME (added since a Cargo package can contain many crates).
crate_name: String,
}

impl MetricsSymbol {
pub fn new(ty: String, name: String, expr: String) -> Self {
pub fn new(ty: String, name: String) -> Self {
Self {
// `CARGO_PKG_NAME` is set to the invoking package's name.
package: cargo::package_name(),
disambiguator: super::crate_local_disambiguator(),
ty,
name,
expression_string: expr,
crate_name: cargo::crate_name(),
}
}

pub fn mangle(&self) -> String {
format!(
r#"{{"type":"Metric","package":"{}","ty":"{}","name":"{}","expr":"{}","disambiguator":"{}","crate_name":"{}"}}"#,
r#"{{"type":"Metric","package":"{}","ty":"{}","name":"{}","disambiguator":"{}","crate_name":"{}"}}"#,
json_escape(&self.package),
json_escape(&self.ty),
json_escape(&self.name),
json_escape(&self.expression_string),
self.disambiguator,
json_escape(&self.crate_name),
)
Expand Down Expand Up @@ -104,6 +99,48 @@ impl SettingSymbol {
}
}

pub struct GraphSymbol {
/// Name of the Cargo package in which the symbol is being instantiated. Used for avoiding
/// symbol name collisions.
package: String,

/// Unique identifier that disambiguates otherwise equivalent invocations in the same crate.
disambiguator: u64,

/// Variable name
name: String,

/// Expression used to calculate the value to plot
expression_string: String,

/// Crate name obtained via CARGO_CRATE_NAME (added since a Cargo package can contain many crates).
crate_name: String,
}

impl GraphSymbol {
pub fn new(name: String, expr: String) -> Self {
Self {
// `CARGO_PKG_NAME` is set to the invoking package's name.
package: cargo::package_name(),
disambiguator: super::crate_local_disambiguator(),
name,
expression_string: expr,
crate_name: cargo::crate_name(),
}
}

pub fn mangle(&self) -> String {
format!(
r#"{{"type":"Graph","package":"{}","name":"{}","expr":"{}","disambiguator":"{}","crate_name":"{}"}}"#,
json_escape(&self.package),
json_escape(&self.name),
json_escape(&self.expression_string),
self.disambiguator,
json_escape(&self.crate_name),
)
}
}

fn json_escape(string: &str) -> String {
use std::fmt::Write;

Expand Down
Loading
Loading