mso50xx 0.1.0

SCPI/LXI based driver for Rigol MSO50xx series mixed signal oscilloscopes
Documentation
use plotters::prelude::*;
use rustfft::{
    num_complex::{Complex, ComplexFloat},
    FftPlanner,
};

const PLOT_LINE_COLOR: RGBColor = RGBColor(0, 0, 0);

pub struct ChartOptions {
    /// X increment
    pub x_inc: f32,
}

impl Default for ChartOptions {
    fn default() -> Self {
        Self { x_inc: 1.0 }
    }
}

pub fn draw_chart(file: &str, data: Vec<(f32, f32)>, opts: ChartOptions) -> anyhow::Result<()> {
    // Setup container
    let root = SVGBackend::new(file, (1024, 768)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut y_min = -1.0;
    let mut y_max = 1.0;
    for (_x, y) in data.iter() {
        if *y < y_min {
            y_min = *y;
        }
        if *y > y_max {
            y_max = *y;
        }
    }

    // Setup chart
    let mut chart = ChartBuilder::on(&root)
        .x_label_area_size(28)
        .y_label_area_size(28)
        .margin(20)
        .build_cartesian_2d(0.0..data.len() as f32, y_min..y_max)?;

    // Draw labels
    chart
        .configure_mesh()
        .y_labels(10)
        .y_label_formatter(&|y: &f32| format!("{:.2}", y))
        .x_labels(10)
        .x_label_formatter(&|x: &f32| format!("{:.2}", x * opts.x_inc))
        .draw()
        .expect("failed to draw chart mesh");

    // Draw data
    chart.draw_series(LineSeries::new(
        data.iter().map(|(x, y)| (*x, *y)),
        PLOT_LINE_COLOR,
    ))?;

    root.present()?;

    Ok(())
}

pub fn do_fft(data: &[f32]) -> Vec<f32> {
    let mut planner = FftPlanner::<f32>::new();

    let fft = planner.plan_fft_forward(data.len());

    let mut buffer: Vec<_> = data.iter().map(|re| Complex { re: *re, im: 0.0 }).collect();

    fft.process(&mut buffer);

    // Half buffer for real-only fft
    let res = buffer
        .drain(..buffer.len() / 2)
        .map(|c| c.abs() * 2.0 / data.len() as f32)
        .collect();

    res
}