Skip to main content

rusty_tip/
types.rs

1use serde::{Deserialize, Serialize};
2
3// Re-export nanonis-rs types from their respective submodules
4pub use nanonis_rs::bias::PulseMode;
5pub use nanonis_rs::motor::{
6    Amplitude, Frequency, MotorAxis, MotorDirection, MotorDisplacement, MotorGroup, MotorMovement,
7    MovementMode, Position3D, StepCount,
8};
9pub use nanonis_rs::oscilloscope::{
10    OsciTriggerMode, OscilloscopeIndex, OversamplingIndex, SampleCount, TimebaseIndex,
11    TriggerConfig, TriggerLevel, TriggerMode, TriggerSlope,
12};
13pub use nanonis_rs::scan::{ScanAction, ScanConfig, ScanDirection, ScanFrame};
14pub use nanonis_rs::signals::SignalFrame;
15pub use nanonis_rs::tcplog::TCPLogStatus;
16pub use nanonis_rs::z_ctrl::ZControllerHold;
17pub use nanonis_rs::Position;
18// DataToGet is extended locally with Stable variant
19
20use std::time::{Duration, Instant};
21
22/// Signal stability statistics computed by rusty-tip.
23///
24/// Previously provided by nanonis-rs; as of 0.4.0 its `OsciData` is a pure
25/// protocol type, so this application-level analysis lives here.
26#[derive(Debug, Clone)]
27pub struct SignalStats {
28    pub mean: f64,
29    pub std_dev: f64,
30    pub relative_std: f64,
31    pub window_size: usize,
32    pub stability_method: String,
33}
34
35/// Oscilloscope reading (t0/dt/size/data from nanonis-rs) plus rusty-tip's
36/// stability analysis. nanonis-rs 0.4.0 made its `OsciData` a pure protocol
37/// type, so the stability fields are tracked here.
38#[derive(Debug, Clone)]
39pub struct OsciData {
40    pub t0: f64,
41    pub dt: f64,
42    pub size: i32,
43    pub data: Vec<f64>,
44    pub signal_stats: Option<SignalStats>,
45    pub is_stable: bool,
46    pub fallback_value: Option<f64>,
47}
48
49impl OsciData {
50    /// Stable reading with no extra statistics attached.
51    pub fn new_stable(t0: f64, dt: f64, size: i32, data: Vec<f64>) -> Self {
52        Self {
53            t0,
54            dt,
55            size,
56            data,
57            signal_stats: None,
58            is_stable: true,
59            fallback_value: None,
60        }
61    }
62
63    /// Stable reading with computed statistics.
64    pub fn new_with_stats(t0: f64, dt: f64, size: i32, data: Vec<f64>, stats: SignalStats) -> Self {
65        Self {
66            t0,
67            dt,
68            size,
69            data,
70            signal_stats: Some(stats),
71            is_stable: true,
72            fallback_value: None,
73        }
74    }
75
76    /// Unstable reading; carries a single fallback value (e.g. the mean).
77    pub fn new_unstable_with_fallback(
78        t0: f64,
79        dt: f64,
80        size: i32,
81        data: Vec<f64>,
82        fallback_value: f64,
83    ) -> Self {
84        Self {
85            t0,
86            dt,
87            size,
88            data,
89            signal_stats: None,
90            is_stable: false,
91            fallback_value: Some(fallback_value),
92        }
93    }
94}
95
96/// Simple tip shape - matches original controller
97#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
98pub enum TipShape {
99    #[default]
100    Blunt,
101    Sharp,
102    Stable,
103}
104
105/// Session metadata for signal tracking
106#[derive(Debug, Clone)]
107pub struct SessionMetadata {
108    pub session_id: String,
109    pub signal_names: Vec<String>,   // All signal names
110    pub active_indices: Vec<usize>,  // Which signals are being monitored
111    pub primary_signal_index: usize, // Index of the primary signal
112    pub session_start: f64,          // Session start timestamp
113}
114
115/// Extended DataToGet with application-specific Stable variant
116#[derive(Debug, Clone, Copy, PartialEq)]
117pub enum DataToGet {
118    Current,
119    NextTrigger,
120    Wait2Triggers,
121    Stable { readings: u32, timeout: Duration },
122}
123
124/// Timestamped version of SignalFrame for efficient buffering
125#[derive(Debug, Clone)]
126pub struct TimestampedSignalFrame {
127    /// The lightweight signal frame
128    pub signal_frame: SignalFrame,
129    /// High-resolution timestamp when frame was received
130    pub timestamp: Instant,
131    /// Time relative to collection start
132    pub relative_time: Duration,
133}
134
135impl TimestampedSignalFrame {
136    /// Create a new timestamped signal frame from lightweight signal frame
137    /// Just adds high-resolution timestamp to existing SignalFrame
138    pub fn new(signal_frame: SignalFrame, start_time: Instant) -> Self {
139        let timestamp = Instant::now();
140        Self {
141            signal_frame,
142            timestamp,
143            relative_time: timestamp.duration_since(start_time),
144        }
145    }
146}
147
148// ==================== Experiment Data with Lightweight Frames ====================
149
150/// Complete experiment result containing action outcome and synchronized signal data
151/// Now uses lightweight SignalFrame structures for better memory efficiency
152#[derive(Debug)]
153pub struct ExperimentData {
154    /// Result of the executed action
155    pub action_result: crate::actions::ActionResult,
156    /// Lightweight signal frames (much more memory efficient)
157    pub signal_frames: Vec<TimestampedSignalFrame>,
158    /// TCP logger configuration for context (stored once, not per frame)
159    pub tcp_config: crate::action_driver::TCPReaderConfig,
160    /// When the action started
161    pub action_start: Instant,
162    /// When the action ended  
163    pub action_end: Instant,
164    /// Total action duration
165    pub total_duration: Duration,
166}
167
168/// Experiment data for action chain execution with timing for each action
169#[derive(Debug)]
170pub struct ChainExperimentData {
171    /// Results of each action in the chain
172    pub action_results: Vec<crate::actions::ActionResult>,
173    /// All signal frames collected during the entire chain execution
174    pub signal_frames: Vec<TimestampedSignalFrame>,
175    /// TCP logger configuration for context
176    pub tcp_config: crate::action_driver::TCPReaderConfig,
177    /// Start and end times for each action in the chain
178    pub action_timings: Vec<(Instant, Instant)>,
179    /// When the entire chain started
180    pub chain_start: Instant,
181    /// When the entire chain ended
182    pub chain_end: Instant,
183    /// Duration of entire chain execution
184    pub total_duration: Duration,
185}
186
187impl ExperimentData {
188    /// Get signal data captured during action execution
189    pub fn data_during_action(&self) -> Vec<&TimestampedSignalFrame> {
190        self.signal_frames
191            .iter()
192            .filter(|frame| {
193                frame.timestamp >= self.action_start && frame.timestamp <= self.action_end
194            })
195            .collect()
196    }
197
198    /// Get signal data before action execution
199    pub fn data_before_action(&self, duration: Duration) -> Vec<&TimestampedSignalFrame> {
200        let cutoff = self.action_start - duration;
201        self.signal_frames
202            .iter()
203            .filter(|frame| frame.timestamp >= cutoff && frame.timestamp < self.action_start)
204            .collect()
205    }
206
207    /// Get signal data after action execution
208    pub fn data_after_action(&self, duration: Duration) -> Vec<&TimestampedSignalFrame> {
209        let cutoff = self.action_end + duration;
210        self.signal_frames
211            .iter()
212            .filter(|frame| frame.timestamp > self.action_end && frame.timestamp <= cutoff)
213            .collect()
214    }
215}
216
217impl ChainExperimentData {
218    /// Get signal data captured during a specific action in the chain
219    ///
220    /// # Arguments
221    /// * `action_index` - Index of the action in the chain (0-based)
222    ///
223    /// # Returns
224    /// Vector of signal frames collected during the specified action
225    pub fn data_during_action(&self, action_index: usize) -> Vec<&TimestampedSignalFrame> {
226        if let Some((start, end)) = self.action_timings.get(action_index) {
227            self.signal_frames
228                .iter()
229                .filter(|frame| frame.timestamp >= *start && frame.timestamp <= *end)
230                .collect()
231        } else {
232            Vec::new()
233        }
234    }
235
236    /// Get signal data captured between two actions in the chain
237    ///
238    /// # Arguments
239    /// * `action1_index` - Index of first action (end time)
240    /// * `action2_index` - Index of second action (start time)
241    ///
242    /// # Returns
243    /// Vector of signal frames collected between the two specified actions
244    pub fn data_between_actions(
245        &self,
246        action1_index: usize,
247        action2_index: usize,
248    ) -> Vec<&TimestampedSignalFrame> {
249        if let (Some((_, end1)), Some((start2, _))) = (
250            self.action_timings.get(action1_index),
251            self.action_timings.get(action2_index),
252        ) {
253            self.signal_frames
254                .iter()
255                .filter(|frame| frame.timestamp > *end1 && frame.timestamp < *start2)
256                .collect()
257        } else {
258            Vec::new()
259        }
260    }
261
262    /// Get signal data before the chain execution
263    ///
264    /// # Arguments
265    /// * `duration` - How far back from chain start to collect data
266    ///
267    /// # Returns
268    /// Vector of signal frames from before the chain started
269    pub fn data_before_chain(&self, duration: Duration) -> Vec<&TimestampedSignalFrame> {
270        let cutoff = self.chain_start - duration;
271        self.signal_frames
272            .iter()
273            .filter(|frame| frame.timestamp >= cutoff && frame.timestamp < self.chain_start)
274            .collect()
275    }
276
277    /// Get signal data after the chain execution
278    ///
279    /// # Arguments
280    /// * `duration` - How far forward from chain end to collect data
281    ///
282    /// # Returns
283    /// Vector of signal frames from after the chain ended
284    pub fn data_after_chain(&self, duration: Duration) -> Vec<&TimestampedSignalFrame> {
285        let cutoff = self.chain_end + duration;
286        self.signal_frames
287            .iter()
288            .filter(|frame| frame.timestamp > self.chain_end && frame.timestamp <= cutoff)
289            .collect()
290    }
291
292    /// Get all signal data for the entire chain execution
293    ///
294    /// # Returns
295    /// Vector of signal frames from chain start to chain end
296    pub fn data_for_entire_chain(&self) -> Vec<&TimestampedSignalFrame> {
297        self.signal_frames
298            .iter()
299            .filter(|frame| {
300                frame.timestamp >= self.chain_start && frame.timestamp <= self.chain_end
301            })
302            .collect()
303    }
304
305    /// Get timing information for a specific action
306    ///
307    /// # Arguments
308    /// * `action_index` - Index of the action in the chain
309    ///
310    /// # Returns
311    /// Optional tuple of (start_time, end_time, duration)
312    pub fn action_timing(&self, action_index: usize) -> Option<(Instant, Instant, Duration)> {
313        self.action_timings
314            .get(action_index)
315            .map(|(start, end)| (*start, *end, end.duration_since(*start)))
316    }
317
318    /// Get summary statistics for the chain execution
319    ///
320    /// # Returns
321    /// Tuple of (total_actions, successful_actions, total_frames, chain_duration)
322    pub fn chain_summary(&self) -> (usize, usize, usize, Duration) {
323        let total_actions = self.action_results.len();
324        let successful_actions = self
325            .action_results
326            .iter()
327            .filter(|result| matches!(result, crate::actions::ActionResult::Success))
328            .count();
329        let total_frames = self.signal_frames.len();
330
331        (
332            total_actions,
333            successful_actions,
334            total_frames,
335            self.total_duration,
336        )
337    }
338}
339
340/// Result of an auto-approach operation
341#[derive(Debug, Clone, PartialEq, Eq)]
342pub enum AutoApproachResult {
343    /// Auto-approach completed successfully
344    Success,
345    /// Auto-approach timed out before completion
346    Timeout,
347    /// Auto-approach failed (e.g., hardware error, abnormal termination)
348    Failed(String),
349    /// Auto-approach was already running when attempted to start
350    AlreadyRunning,
351    /// Auto-approach was cancelled/stopped externally
352    Cancelled,
353}
354
355impl AutoApproachResult {
356    /// Check if the result represents a successful operation
357    pub fn is_success(&self) -> bool {
358        matches!(self, AutoApproachResult::Success)
359    }
360
361    /// Check if the result represents a failure
362    pub fn is_failure(&self) -> bool {
363        matches!(
364            self,
365            AutoApproachResult::Failed(_) | AutoApproachResult::Timeout
366        )
367    }
368
369    /// Get error description if this is a failure
370    pub fn error_message(&self) -> Option<&str> {
371        match self {
372            AutoApproachResult::Failed(msg) => Some(msg),
373            AutoApproachResult::Timeout => Some("Auto-approach timed out"),
374            AutoApproachResult::AlreadyRunning => Some("Auto-approach already running"),
375            AutoApproachResult::Cancelled => Some("Auto-approach was cancelled"),
376            AutoApproachResult::Success => None,
377        }
378    }
379}
380
381/// Status information for auto-approach operations
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum AutoApproachStatus {
384    /// Auto-approach is not running
385    Idle,
386    /// Auto-approach is currently running
387    Running,
388    /// Auto-approach status is unknown (e.g., communication error)
389    Unknown,
390}