use crate::flea_connector::{FleaConnector, FleaConnectorError};
use crate::serial_terminal::{BusyFleaTerminal, ConnectionLostError, IdleFleaTerminal};
use crate::trigger_config::{DigitalTrigger, StringifiedTriggerConfig, Trigger};
use polars::prelude::*;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProbeType {
X1,
X10,
}
impl ProbeType {
pub fn to_multiplier(&self) -> i32 {
match self {
ProbeType::X1 => 1,
ProbeType::X10 => 10,
}
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum Waveform {
Sine,
Square,
Triangle,
Ekg,
}
impl Waveform {
pub fn as_str(&self) -> &'static str {
match self {
Waveform::Sine => "sine",
Waveform::Square => "square",
Waveform::Triangle => "triangle",
Waveform::Ekg => "ekg",
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CaptureConfigError {
#[error("Time frame too large (max 3.49 seconds)")]
TimeFrameTooLarge,
#[error("Time frame too small (min 111 microseconds)")]
TimeFrameTooSmall,
#[error("Delay too large (max 1 second)")]
DelayTooLarge,
#[error("Voltage out of range")]
VoltageOutOfRange,
}
#[derive(Debug, thiserror::Error)]
pub enum CalibrationError {
#[error("No zero calibration available for this probe")]
NoZeroCalibrarion,
#[error("No calibrarion available for this probe")]
NoCalibrationPresent,
#[error("Signal to unstable")]
UnstableSignal,
#[error("Failure to get calibrartion data")]
CalibrationDataError(#[from] PolarsError),
}
pub struct ReadingFleaScope {
_ver: String,
hostname: String,
serial: BusyFleaTerminal,
effective_msps: f64,
}
impl ReadingFleaScope {
pub fn wait(self) -> (IdleFleaScope, Result<(f64, String), ConnectionLostError>) {
let (data, serial) = self.serial.wait();
let scope = IdleFleaScope {
serial,
_ver: self._ver,
hostname: self.hostname,
};
(scope, data.map(|data| (self.effective_msps, data)))
}
pub fn is_done(&mut self) -> bool {
self.serial.is_ready().unwrap_or(true)
}
pub fn cancel(&mut self) {
self.serial.cancel();
}
}
pub struct IdleFleaScope {
serial: IdleFleaTerminal,
_ver: String,
hostname: String,
}
impl IdleFleaScope {
const MSPS: u32 = 18; const MCU_MHZ: f64 = 120.0; const INTERLEAVE: u32 = 5; const TOTAL_SAMPLES: u32 = 2000;
pub fn connect(
name: Option<&str>,
port: Option<&str>,
read_calibrations: bool,
) -> Result<(Self, FleaProbe, FleaProbe), FleaConnectorError> {
let serial = FleaConnector::connect(name, port, true)?;
let mut x1 = FleaProbe::new(ProbeType::X1);
let mut x10 = FleaProbe::new(ProbeType::X10);
let mut scope = Self::new(serial);
if read_calibrations {
x1.read_calibration_from_flash(&mut scope.serial);
x10.read_calibration_from_flash(&mut scope.serial);
}
Ok((scope, x1, x10))
}
pub fn new(mut serial: IdleFleaTerminal) -> Self {
log::debug!("Turning off echo");
serial.exec_sync("echo off", None);
let ver = serial.exec_sync("ver", None);
log::debug!("FleaScope version: {}", ver);
let hostname = serial.exec_sync("hostname", None);
log::debug!("FleaScope hostname: {}", hostname);
Self {
serial,
_ver: ver,
hostname,
}
}
pub fn set_waveform(&mut self, waveform: Waveform, hz: i32) {
self.serial
.exec_sync(&format!("wave {} {}", waveform.as_str(), hz), None);
}
fn number1_to_prescaler(number1: u32) -> Result<u32, CaptureConfigError> {
let ps = if number1 > 1000 { 16 } else { 1 };
let t =
((Self::MCU_MHZ * (number1 * Self::INTERLEAVE) as f64 / ps as f64 / Self::MSPS as f64)
+ 0.5) as u32;
if t == 0 {
return Err(CaptureConfigError::TimeFrameTooSmall);
}
if t > 65535 {
return Err(CaptureConfigError::TimeFrameTooLarge);
}
Ok(ps * t)
}
fn prescaler_to_effective_msps(prescaler: u32) -> f64 {
Self::MCU_MHZ * Self::INTERLEAVE as f64 / prescaler as f64
}
fn prepare_read_command(
time_frame: Duration,
trigger_fields: StringifiedTriggerConfig,
delay: Option<Duration>,
) -> Result<(f64, String), CaptureConfigError> {
let delay = delay.unwrap_or(Duration::from_millis(0));
if time_frame.as_secs_f64() > 3.49 {
return Err(CaptureConfigError::TimeFrameTooLarge);
}
if time_frame.as_secs() == 0 && time_frame.as_micros() < 111 {
return Err(CaptureConfigError::TimeFrameTooSmall);
}
if delay.as_secs_f64() > 1.0 {
return Err(CaptureConfigError::DelayTooLarge);
}
let number1 = Self::MSPS * (time_frame.as_micros() as u32) / Self::TOTAL_SAMPLES;
if number1 == 0 {
return Err(CaptureConfigError::TimeFrameTooSmall);
}
let prescaler = Self::number1_to_prescaler(number1)?;
let effective_msps = Self::prescaler_to_effective_msps(prescaler);
let delay_samples = (delay.as_micros() as f64 * effective_msps) as u32;
if delay_samples > 1_000_000 {
return Err(CaptureConfigError::DelayTooLarge);
}
Ok((
effective_msps,
format!(
"scope {} {} {}",
number1,
trigger_fields.into_string(),
delay_samples
),
))
}
pub fn read_async(
self,
time_frame: Duration,
trigger_fields: StringifiedTriggerConfig,
delay: Option<Duration>,
) -> Result<ReadingFleaScope, (IdleFleaScope, CaptureConfigError)> {
match Self::prepare_read_command(time_frame, trigger_fields, delay) {
Ok((effective_msps, command)) => {
let data = self.serial.exec_async(&command);
Ok(ReadingFleaScope {
_ver: self._ver,
hostname: self.hostname,
serial: data,
effective_msps,
})
}
Err(e) => Err((self, e)),
}
}
pub fn read_sync(
&mut self,
time_frame: Duration,
trigger_fields: StringifiedTriggerConfig,
delay: Option<Duration>,
) -> Result<(f64, String), CaptureConfigError> {
let (effective_msps, command) =
Self::prepare_read_command(time_frame, trigger_fields, delay)?;
let data = self.serial.exec_sync(&command, None);
Ok((effective_msps, data))
}
pub fn parse_csv(csv_data: &str, effective_msps: f64) -> Result<LazyFrame, PolarsError> {
let df = CsvReadOptions::default()
.with_has_header(false)
.into_reader_with_file_handle(std::io::Cursor::new(csv_data.as_bytes()))
.finish()?
.lazy()
.select([
col("column_1").alias("bnc").cast(DataType::Float64),
col("column_2").alias("bitmap"),
])
.with_row_index("row_index", Some(0))
.with_columns([
(col("row_index").cast(DataType::Float64)
* lit(1.0 / (effective_msps * 1_000_000.0)))
.alias("time"),
])
.select([col("time"), col("bnc"), col("bitmap")]);
Ok(df)
}
pub fn extract_bits(df: &DataFrame) -> Result<DataFrame, PolarsError> {
let bitmap_column = df.column("bitmap")?;
let bitmap_strings = bitmap_column.str()?;
let mut bit_columns: Vec<Vec<bool>> = vec![Vec::new(); 10];
for bitmap_opt in bitmap_strings.into_iter() {
if let Some(bitmap_str) = bitmap_opt {
let bitmap_str = bitmap_str.trim_start_matches("0x");
if let Ok(bitmap_val) = u32::from_str_radix(bitmap_str, 16) {
for (bit, column) in bit_columns.iter_mut().enumerate().take(10) {
column.push((bitmap_val >> bit) & 1 == 1);
}
} else {
for column in bit_columns.iter_mut().take(10) {
column.push(false);
}
}
} else {
for column in bit_columns.iter_mut().take(10) {
column.push(false);
}
}
}
let mut df_data = vec![df.column("time")?.clone(), df.column("bnc")?.clone()];
for (bit, values) in bit_columns.into_iter().enumerate() {
df_data.push(Series::new(format!("bit_{}", bit).into(), values).into());
}
let result = DataFrame::new(df_data)?;
Ok(result)
}
pub fn set_hostname(&mut self, hostname: &str) {
self.serial
.exec_sync(&format!("hostname {}", hostname), None);
self.hostname = hostname.to_string();
}
pub fn teardown(mut self) {
let _ = self.serial.exec_sync("echo on", None);
let _ = self.serial.exec_sync("prompt on", None);
}
}
#[derive(Debug)]
pub struct FleaProbe {
multiplier: ProbeType,
cal_zero: Option<f64>, cal_3v3: Option<f64>, }
impl Clone for FleaProbe {
fn clone(&self) -> Self {
Self {
multiplier: self.multiplier,
cal_zero: self.cal_zero,
cal_3v3: self.cal_3v3,
}
}
}
impl FleaProbe {
pub fn new(multiplier: ProbeType) -> Self {
Self {
multiplier,
cal_zero: None,
cal_3v3: None,
}
}
pub fn trigger_to_string(
&self,
trigger: Trigger,
) -> Result<StringifiedTriggerConfig, CaptureConfigError> {
match trigger {
Trigger::Analog(at) => Ok(at.into_trigger_fields(|v| self.voltage_to_raw(v))?),
Trigger::Digital(dt) => Ok(dt.into_trigger_fields()),
}
}
pub fn read_calibration_from_flash(&mut self, serial: &mut IdleFleaTerminal) {
let dim_result = serial.exec_sync(
&format!(
"dim cal_zero_x{} as flash, cal_3v3_x{} as flash",
self.multiplier.to_multiplier(),
self.multiplier.to_multiplier()
),
None,
);
let expected_response = format!(
"var 'cal_zero_x{}' already declared at this scope\r\nvar 'cal_3v3_x{}' already declared at this scope",
self.multiplier.to_multiplier(), self.multiplier.to_multiplier()
);
if dim_result == expected_response {
log::debug!("Variables for calibration already declared. Reading values.");
}
let cal_zero_raw: i32 = serial
.exec_sync(
&format!("print cal_zero_x{}", self.multiplier.to_multiplier()),
None,
)
.trim()
.parse()
.expect("Failed to parse cal_zero_x value");
let cal_3v3_raw: i32 = serial
.exec_sync(
&format!("print cal_3v3_x{}", self.multiplier.to_multiplier()),
None,
)
.trim()
.parse()
.expect("Failed to parse cal_3v3_x value");
self.cal_zero = Some((cal_zero_raw - 1000) as f64 + 2048.0);
self.cal_3v3 = Some((cal_3v3_raw - 1000) as f64 / self.multiplier.to_multiplier() as f64);
log::debug!(
"Probe x{} calibration: cal_zero={:?}, cal_3v3={:?}",
self.multiplier.to_multiplier(),
self.cal_zero,
self.cal_3v3
);
}
pub fn set_calibration(&mut self, offset_0: f64, offset_3v3: f64) {
self.cal_zero = Some(offset_0);
self.cal_3v3 = Some(offset_3v3);
}
pub fn write_calibration_to_flash(
&self,
scope: &mut IdleFleaScope,
) -> Result<(), CalibrationError> {
let cal_zero = self
.cal_zero
.ok_or(CalibrationError::NoCalibrationPresent)?;
let cal_3v3 = self.cal_3v3.ok_or(CalibrationError::NoCalibrationPresent)?;
let zero_value = (cal_zero - 2048.0 + 1000.0 + 0.5) as i32;
let v3v3_value = (cal_3v3 * self.multiplier.to_multiplier() as f64 + 1000.0 + 0.5) as i32;
scope.serial.exec_sync(
&format!(
"cal_zero_x{} = {}",
self.multiplier.to_multiplier(),
zero_value
),
None,
);
scope.serial.exec_sync(
&format!(
"cal_3v3_x{} = {}",
self.multiplier.to_multiplier(),
v3v3_value
),
None,
);
Ok(())
}
pub fn apply_calibration(&self, df: LazyFrame) -> LazyFrame {
df.select([col("time"), self.raw_to_voltage(col("bnc")), col("bitmap")])
}
pub fn read_stable_value_for_calibration(
&self,
scope: &mut IdleFleaScope,
) -> Result<f64, CalibrationError> {
let trigger_fields = DigitalTrigger::start_capturing_when()
.is_matching()
.into_trigger_fields();
let (emsps, data) = scope
.read_sync(Duration::from_millis(20), trigger_fields, None)
.expect("This should not fail, as we are reading a stable value for calibration");
let df = IdleFleaScope::parse_csv(&data, emsps)?;
let relevant_data = df.select([col("bnc")]).collect()?;
let bnc_series = relevant_data.column("bnc")?;
let bnc_values: Vec<f64> = bnc_series.f64()?.into_no_null_iter().collect();
let min_val = bnc_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_val = bnc_values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
if max_val - min_val > 14.0 {
return Err(CalibrationError::UnstableSignal);
}
let mean = bnc_values.iter().sum::<f64>() / bnc_values.len() as f64;
Ok(mean)
}
pub fn raw_to_voltage(&self, raw_value: Expr) -> Expr {
let cal_zero = self.cal_zero.expect("Calibration for 0V is not set");
let cal_3v3 = self.cal_3v3.expect("Calibration for 3.3V is not set");
(raw_value - cal_zero.into()) / cal_3v3.into() * 3.3.into()
}
pub fn voltage_to_raw(&self, voltage: f64) -> f64 {
let cal_zero = self.cal_zero.expect("Calibration for 0V is not set");
let cal_3v3 = self.cal_3v3.expect("Calibration for 3.3V is not set");
(voltage / 3.3 * cal_3v3) + cal_zero
}
pub fn calibrate_0(&mut self, scope: &mut IdleFleaScope) -> Result<f64, CalibrationError> {
let raw_value_3v3 = if let (Some(_), Some(_)) = (self.cal_zero, self.cal_3v3) {
Some(self.voltage_to_raw(3.3))
} else {
None
};
self.cal_zero = Some(self.read_stable_value_for_calibration(scope)?);
if let Some(raw_3v3) = raw_value_3v3 {
self.cal_3v3 = Some(raw_3v3 - self.cal_zero.unwrap());
}
Ok(self.cal_zero.unwrap())
}
pub fn calibrate_3v3(&mut self, scope: &mut IdleFleaScope) -> Result<f64, CalibrationError> {
let cal_zero = self.cal_zero.ok_or(CalibrationError::NoZeroCalibrarion)?;
let raw_3v3 = self.read_stable_value_for_calibration(scope)?;
self.cal_3v3 = Some(raw_3v3 - cal_zero);
Ok(self.cal_3v3.unwrap())
}
pub fn calibration(&self) -> (Option<f64>, Option<f64>) {
(self.cal_zero, self.cal_3v3)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_waveform_as_str() {
assert_eq!(Waveform::Sine.as_str(), "sine");
assert_eq!(Waveform::Square.as_str(), "square");
assert_eq!(Waveform::Triangle.as_str(), "triangle");
assert_eq!(Waveform::Ekg.as_str(), "ekg");
}
#[test]
fn test_number1_to_prescaler() {
assert!(IdleFleaScope::number1_to_prescaler(100).is_ok());
assert!(IdleFleaScope::number1_to_prescaler(0).is_err());
}
}