1 desktop: add LSV point density config, remove auto/manual toggle

This commit is contained in:
jess 2026-04-02 23:09:31 -07:00
parent a21b014d89
commit 090fcfa2f5
2 changed files with 91 additions and 42 deletions

View File

@ -17,6 +17,25 @@ use crate::protocol::{
use crate::storage::{self, Session, Storage}; use crate::storage::{self, Session, Storage};
use crate::udp::UdpEvent; use crate::udp::UdpEvent;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LsvDensityMode {
PtsPerMv,
PtsPerSec,
}
impl LsvDensityMode {
pub const ALL: &[Self] = &[Self::PtsPerMv, Self::PtsPerSec];
}
impl std::fmt::Display for LsvDensityMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PtsPerMv => f.write_str("pts/mV"),
Self::PtsPerSec => f.write_str("pts/s"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tab { pub enum Tab {
Eis, Eis,
@ -71,6 +90,8 @@ pub enum Message {
LsvStopVChanged(String), LsvStopVChanged(String),
LsvScanRateChanged(String), LsvScanRateChanged(String),
LsvRtiaSelected(LpRtia), LsvRtiaSelected(LpRtia),
LsvDensityModeSelected(LsvDensityMode),
LsvDensityChanged(String),
StartLsv, StartLsv,
/* Amperometry */ /* Amperometry */
AmpVholdChanged(String), AmpVholdChanged(String),
@ -133,13 +154,22 @@ pub enum Message {
BrowseBack, BrowseBack,
ExportSession(i64), ExportSession(i64),
ImportSession, ImportSession,
/* LSV analysis */
LsvToggleManual,
/* Misc */ /* Misc */
Reconnect, Reconnect,
UdpAddrChanged(String), UdpAddrChanged(String),
} }
fn lsv_calc_points(v_start: f32, v_stop: f32, scan_rate: f32, density: f32, mode: LsvDensityMode) -> u16 {
let range = (v_stop - v_start).abs();
let raw = match mode {
LsvDensityMode::PtsPerMv => range * density,
LsvDensityMode::PtsPerSec => {
if scan_rate.abs() < 0.001 { 2.0 } else { (range / scan_rate.abs()) * density }
}
};
(raw as u16).clamp(2, 500)
}
pub struct App { pub struct App {
tab: Tab, tab: Tab,
status: String, status: String,
@ -181,8 +211,9 @@ pub struct App {
lsv_stop_v: String, lsv_stop_v: String,
lsv_scan_rate: String, lsv_scan_rate: String,
lsv_rtia: LpRtia, lsv_rtia: LpRtia,
lsv_density_mode: LsvDensityMode,
lsv_density: String,
lsv_peaks: Vec<crate::lsv_analysis::LsvPeak>, lsv_peaks: Vec<crate::lsv_analysis::LsvPeak>,
lsv_manual_peaks: bool,
lsv_data: text_editor::Content, lsv_data: text_editor::Content,
/* Amp */ /* Amp */
@ -426,8 +457,9 @@ impl App {
lsv_stop_v: "500".into(), lsv_stop_v: "500".into(),
lsv_scan_rate: "50".into(), lsv_scan_rate: "50".into(),
lsv_rtia: LpRtia::R10K, lsv_rtia: LpRtia::R10K,
lsv_density_mode: LsvDensityMode::PtsPerMv,
lsv_density: "1".into(),
lsv_peaks: Vec::new(), lsv_peaks: Vec::new(),
lsv_manual_peaks: false,
lsv_data: text_editor::Content::with_text(&fmt_lsv(&[])), lsv_data: text_editor::Content::with_text(&fmt_lsv(&[])),
amp_points: Vec::new(), amp_points: Vec::new(),
@ -659,9 +691,7 @@ impl App {
if let Some(sid) = self.current_session { if let Some(sid) = self.current_session {
self.save_lsv(sid); self.save_lsv(sid);
} }
if !self.lsv_manual_peaks {
self.lsv_peaks = crate::lsv_analysis::detect_peaks(&self.lsv_points); self.lsv_peaks = crate::lsv_analysis::detect_peaks(&self.lsv_points);
}
let mut st = format!("LSV complete: {} points", self.lsv_points.len()); let mut st = format!("LSV complete: {} points", self.lsv_points.len());
if let (Some(s), Some(o)) = (self.ph_slope, self.ph_offset) { if let (Some(s), Some(o)) = (self.ph_slope, self.ph_offset) {
if let Some(peak) = crate::lsv_analysis::detect_qhq_peak(&self.lsv_points) { if let Some(peak) = crate::lsv_analysis::detect_qhq_peak(&self.lsv_points) {
@ -850,20 +880,22 @@ impl App {
Message::LsvStopVChanged(s) => self.lsv_stop_v = s, Message::LsvStopVChanged(s) => self.lsv_stop_v = s,
Message::LsvScanRateChanged(s) => self.lsv_scan_rate = s, Message::LsvScanRateChanged(s) => self.lsv_scan_rate = s,
Message::LsvRtiaSelected(r) => self.lsv_rtia = r, Message::LsvRtiaSelected(r) => self.lsv_rtia = r,
Message::LsvDensityModeSelected(m) => {
self.lsv_density_mode = m;
self.lsv_density = match m {
LsvDensityMode::PtsPerMv => "1".into(),
LsvDensityMode::PtsPerSec => "100".into(),
};
}
Message::LsvDensityChanged(s) => self.lsv_density = s,
Message::StartLsv => { Message::StartLsv => {
let vs = self.lsv_start_v.parse::<f32>().unwrap_or(0.0); let vs = self.lsv_start_v.parse::<f32>().unwrap_or(0.0);
let ve = self.lsv_stop_v.parse::<f32>().unwrap_or(500.0); let ve = self.lsv_stop_v.parse::<f32>().unwrap_or(500.0);
let sr = self.lsv_scan_rate.parse::<f32>().unwrap_or(50.0); let sr = self.lsv_scan_rate.parse::<f32>().unwrap_or(50.0);
let density = self.lsv_density.parse::<f32>().unwrap_or(1.0);
let n = lsv_calc_points(vs, ve, sr, density, self.lsv_density_mode);
self.send_cmd(&protocol::build_sysex_get_temp()); self.send_cmd(&protocol::build_sysex_get_temp());
self.send_cmd(&protocol::build_sysex_start_lsv(vs, ve, sr, self.lsv_rtia)); self.send_cmd(&protocol::build_sysex_start_lsv(vs, ve, sr, self.lsv_rtia, n));
}
Message::LsvToggleManual => {
self.lsv_manual_peaks = !self.lsv_manual_peaks;
if self.lsv_manual_peaks {
self.lsv_peaks.clear();
} else {
self.lsv_peaks = crate::lsv_analysis::detect_peaks(&self.lsv_points);
}
} }
/* Amp */ /* Amp */
Message::AmpVholdChanged(s) => self.amp_v_hold = s, Message::AmpVholdChanged(s) => self.amp_v_hold = s,
@ -1540,7 +1572,13 @@ impl App {
.align_y(iced::Alignment::End) .align_y(iced::Alignment::End)
.into(), .into(),
Tab::Lsv => row![ Tab::Lsv => {
let vs = self.lsv_start_v.parse::<f32>().unwrap_or(0.0);
let ve = self.lsv_stop_v.parse::<f32>().unwrap_or(500.0);
let sr = self.lsv_scan_rate.parse::<f32>().unwrap_or(50.0);
let d = self.lsv_density.parse::<f32>().unwrap_or(1.0);
let n = lsv_calc_points(vs, ve, sr, d, self.lsv_density_mode);
row![
column![ column![
text("Start mV").size(12), text("Start mV").size(12),
text_input("0", &self.lsv_start_v).on_input(Message::LsvStartVChanged).width(80), text_input("0", &self.lsv_start_v).on_input(Message::LsvStartVChanged).width(80),
@ -1557,14 +1595,24 @@ impl App {
text("RTIA").size(12), text("RTIA").size(12),
pick_list(LpRtia::ALL, Some(self.lsv_rtia), Message::LsvRtiaSelected).width(Length::Shrink), pick_list(LpRtia::ALL, Some(self.lsv_rtia), Message::LsvRtiaSelected).width(Length::Shrink),
].spacing(2), ].spacing(2),
column![
text("Density").size(12),
pick_list(LsvDensityMode::ALL, Some(self.lsv_density_mode), Message::LsvDensityModeSelected).width(Length::Shrink),
].spacing(2),
column![
text("Value").size(12),
text_input("1", &self.lsv_density).on_input(Message::LsvDensityChanged).width(60),
].spacing(2),
column![
text("Points").size(12),
text(format!("{}", n)).size(13),
].spacing(2),
button(text("Start LSV").size(13)) button(text("Start LSV").size(13))
.style(style_action()) .style(style_action())
.padding([6, 16]) .padding([6, 16])
.on_press(Message::StartLsv), .on_press(Message::StartLsv),
button(text(if self.lsv_manual_peaks { "Manual" } else { "Auto" }).size(13))
.padding([6, 12])
.on_press(Message::LsvToggleManual),
] ]
}
.spacing(10) .spacing(10)
.align_y(iced::Alignment::End) .align_y(iced::Alignment::End)
.into(), .into(),

View File

@ -465,12 +465,13 @@ pub fn build_sysex_get_config() -> Vec<u8> {
vec![0xF0, SYSEX_MFR, CMD_GET_CONFIG, 0xF7] vec![0xF0, SYSEX_MFR, CMD_GET_CONFIG, 0xF7]
} }
pub fn build_sysex_start_lsv(v_start: f32, v_stop: f32, scan_rate: f32, lp_rtia: LpRtia) -> Vec<u8> { pub fn build_sysex_start_lsv(v_start: f32, v_stop: f32, scan_rate: f32, lp_rtia: LpRtia, num_points: u16) -> Vec<u8> {
let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_LSV]; let mut sx = vec![0xF0, SYSEX_MFR, CMD_START_LSV];
sx.extend_from_slice(&encode_float(v_start)); sx.extend_from_slice(&encode_float(v_start));
sx.extend_from_slice(&encode_float(v_stop)); sx.extend_from_slice(&encode_float(v_stop));
sx.extend_from_slice(&encode_float(scan_rate)); sx.extend_from_slice(&encode_float(scan_rate));
sx.push(lp_rtia.as_byte()); sx.push(lp_rtia.as_byte());
sx.extend_from_slice(&encode_u16(num_points));
sx.push(0xF7); sx.push(0xF7);
sx sx
} }