rusty_tip/
tip_prep.rs

1use crate::action_driver::ActionDriver;
2use crate::actions::{Action, ActionChain};
3use crate::error::NanonisError;
4use crate::job::Job;
5use crate::types::{DataToGet, MotorDirection, SignalIndex};
6use crate::utils::{poll_with_timeout, PollError};
7use crate::{stability, Logger};
8use log::{debug, info, warn};
9use serde::Serialize;
10use std::collections::{HashMap, VecDeque};
11use std::time::Duration;
12
13/// Simple tip state - matches original controller
14#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
15pub enum TipState {
16    Bad,
17    Good,
18    Stable,
19}
20
21/// Loop types based on tip state - simple and direct
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum LoopType {
24    BadLoop,
25    GoodLoop,
26    StableLoop,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
30pub struct LogLine {
31    cycle: u32,
32    freq_shift: f32,
33    tip_state: TipState,
34    pulse_voltage: f32,
35    freq_shift_change: Option<f32>,
36    z_change: Option<f32>,
37}
38
39/// Enhanced tip controller with pulse voltage stepping
40pub struct TipController {
41    driver: ActionDriver,
42    signal_index: SignalIndex,
43
44    // Pulse stepping parameters
45    pulse_voltage: f32,
46    pulse_voltage_step: f32,
47    change_threshold: Box<dyn Fn(f32) -> f32 + Send + Sync>,
48    cycles_before_step: u32,
49    min_pulse_voltage: f32,
50    max_pulse_voltage: f32,
51
52    // Step tracking
53    cycles_without_change: u32,
54
55    // Signal bounds and thresholds
56    bound: (f32, f32),
57
58    // State tracking
59    good_count: u32,
60    stable_threshold: u32,
61    cycle_count: u32,
62
63    // Multi-signal history for bias adjustment and analysis
64    signal_histories: HashMap<SignalIndex, VecDeque<f32>>,
65    max_history_size: usize,
66
67    // Json Logger
68    logger: Option<Logger<LogLine>>,
69}
70
71impl TipController {
72    /// Create new tip controller with basic signal bounds
73    pub fn new(
74        driver: ActionDriver,
75        signal_index: SignalIndex,
76        pulse_voltage: f32,
77        bound: (f32, f32),
78    ) -> Self {
79        Self {
80            driver,
81            signal_index,
82            pulse_voltage,
83            pulse_voltage_step: 0.1,
84            change_threshold: Box::new(|_| 0.1),
85            cycles_before_step: 3,
86            min_pulse_voltage: pulse_voltage,
87            max_pulse_voltage: 5.0,
88            cycles_without_change: 0,
89            bound,
90            good_count: 0,
91            stable_threshold: 3,
92            cycle_count: 0,
93            signal_histories: HashMap::new(),
94            max_history_size: 10,
95            logger: None,
96        }
97    }
98
99    /// Set pulse stepping parameters with closure-based threshold
100    pub fn set_pulse_stepping(
101        &mut self,
102        pulse_step: f32,
103        change_threshold: Box<dyn Fn(f32) -> f32 + Send + Sync>,
104        cycles_before_step: u32,
105        max_pulse: f32,
106    ) -> &mut Self {
107        self.pulse_voltage_step = pulse_step.abs(); // Ensure positive
108        self.change_threshold = change_threshold;
109        self.cycles_before_step = cycles_before_step.max(1); // At least 1
110        self.max_pulse_voltage = max_pulse.abs();
111        self
112    }
113
114    /// Set pulse stepping parameters with fixed threshold (convenience method)
115    pub fn set_pulse_stepping_fixed(
116        &mut self,
117        pulse_step: f32,
118        change_threshold: f32,
119        cycles_before_step: u32,
120        max_pulse: f32,
121    ) -> &mut Self {
122        let threshold = change_threshold.abs();
123        self.set_pulse_stepping(
124            pulse_step,
125            Box::new(move |_| threshold),
126            cycles_before_step,
127            max_pulse,
128        )
129    }
130
131    /// Provide Json File logger for inspecting behavior
132    pub fn with_logger(&mut self, logger: Logger<LogLine>) -> &mut Self {
133        self.logger = Some(logger);
134        self
135    }
136
137    /// Flush the logger (useful for signal handlers)
138    pub fn flush_logger(&mut self) -> Result<(), NanonisError> {
139        if let Some(ref mut logger) = self.logger {
140            logger.flush()?;
141        }
142        Ok(())
143    }
144
145    /// Set stability threshold (how many good readings needed for stable)
146    pub fn set_stability_threshold(&mut self, threshold: u32) -> &mut Self {
147        self.stable_threshold = threshold.max(1); // At least 1
148        self
149    }
150
151    /// Get current pulse voltage
152    pub fn current_pulse_voltage(&self) -> f32 {
153        self.pulse_voltage
154    }
155
156    /// Get signal history (most recent first) for the frequency shift signal
157    pub fn signal_history(&self) -> Option<&VecDeque<f32>> {
158        self.get_signal_history(self.signal_index)
159    }
160
161    /// Calculate average of recent signals for the frequency shift signal
162    pub fn average_signal(&self) -> Option<f32> {
163        self.average_signal_for(self.signal_index)
164    }
165
166    /// Calculate average of recent signals for a specific signal
167    pub fn average_signal_for(&self, signal_index: SignalIndex) -> Option<f32> {
168        if let Some(history) = self.signal_histories.get(&signal_index) {
169            if history.is_empty() {
170                None
171            } else {
172                Some(history.iter().sum::<f32>() / history.len() as f32)
173            }
174        } else {
175            None
176        }
177    }
178
179    /// Track a signal value in history
180    pub fn track_signal(&mut self, signal_index: SignalIndex, value: f32) {
181        let history = self.signal_histories.entry(signal_index).or_default();
182
183        // Add new value to front
184        history.push_front(value);
185
186        // Maintain size limit
187        while history.len() > self.max_history_size {
188            history.pop_back();
189        }
190    }
191
192    /// Get signal change (latest - previous) for a specific signal
193    pub fn get_signal_change(&self, signal_index: SignalIndex) -> Option<f32> {
194        if let Some(history) = self.signal_histories.get(&signal_index) {
195            if history.len() >= 2 {
196                Some(history[0] - history[1]) // Latest - Previous
197            } else {
198                None
199            }
200        } else {
201            None
202        }
203    }
204
205    /// Get signal history for a specific signal (most recent first)
206    pub fn get_signal_history(&self, signal_index: SignalIndex) -> Option<&VecDeque<f32>> {
207        self.signal_histories.get(&signal_index)
208    }
209
210    pub fn get_last_signal(&self, signal_index: SignalIndex) -> Option<f32> {
211        match self.get_signal_history(signal_index) {
212            Some(history) => history.iter().last().copied(),
213            None => None,
214        }
215    }
216
217    /// Clear all signal histories (useful for logger integration)
218    pub fn clear_all_histories(&mut self) {
219        self.signal_histories.clear();
220    }
221
222    /// Clear history for a specific signal
223    pub fn clear_signal_history(&mut self, signal_index: SignalIndex) {
224        self.signal_histories.remove(&signal_index);
225    }
226
227    /// Check if current signal represents a significant change from recent stable period
228    fn has_significant_change(&self, signal_index: SignalIndex) -> (bool, f32) {
229        if let Some(history) = self.signal_histories.get(&signal_index) {
230            if history.len() < 2 {
231                // First signal - consider it a significant change to initialize properly
232                (true, 0.0)
233            } else {
234                // Compare only against signals from the current stable period
235                // cycles_without_change tells us how many recent signals were stable
236                let stable_period_size =
237                    (self.cycles_without_change as usize).min(history.len() - 1);
238
239                if stable_period_size == 0 {
240                    // No stable period yet, compare against last signal
241                    let signal = history[0];
242                    let last_signal = history[1];
243                    info!(
244                        "Last signal: {} | Current threshold: {}",
245                        last_signal,
246                        (self.change_threshold)(signal)
247                    );
248                    let has_change =
249                        (signal - last_signal).abs() >= (self.change_threshold)(signal);
250
251                    (has_change, (signal - last_signal))
252                } else {
253                    // Compare against mean of current stable period (skip current signal at index 0)
254                    let signal = history[0];
255                    let stable_signals: Vec<f32> = history
256                        .iter()
257                        .skip(1)
258                        .take(stable_period_size)
259                        .cloned()
260                        .collect();
261                    let stable_mean =
262                        stable_signals.iter().sum::<f32>() / stable_signals.len() as f32;
263
264                    info!(
265                        "Stable mean: {} | Current threshold: {}",
266                        stable_mean,
267                        (self.change_threshold)(signal)
268                    );
269                    let has_change =
270                        (signal - stable_mean).abs() >= (self.change_threshold)(signal);
271                    (has_change, (signal - stable_mean))
272                }
273            }
274        } else {
275            // No history yet - consider it a significant change
276            (true, 0.0)
277        }
278    }
279
280    /// Step up the pulse voltage if possible
281    fn step_pulse_voltage(&mut self) -> bool {
282        let new_pulse = (self.pulse_voltage + self.pulse_voltage_step).min(self.max_pulse_voltage);
283        if new_pulse > self.pulse_voltage {
284            info!(
285                "Stepping pulse voltage: {:.3}V -> {:.3}V",
286                self.pulse_voltage, new_pulse
287            );
288            self.pulse_voltage = new_pulse;
289            self.cycles_without_change = 0; // Reset counter after stepping
290            true
291        } else {
292            debug!(
293                "Pulse voltage already at maximum: {:.3}V",
294                self.max_pulse_voltage
295            );
296            self.cycles_without_change = 0; // Reset counter even if at max
297            false
298        }
299    }
300
301    /// Update signal history and step pulse voltage if needed
302    fn update_pulse_voltage(&mut self) {
303        let (is_significant, change) = self.has_significant_change(self.signal_index);
304
305        // 2. Check for significant change and respond accordingly
306        if is_significant && change >= 0.0 {
307            self.cycles_without_change = 0;
308            self.pulse_voltage = self.min_pulse_voltage;
309        } else if is_significant {
310            warn!("Positive change significant change!");
311            self.cycles_without_change += 1;
312
313            // Check if we need to step the pulse voltage
314            if self.cycles_without_change >= self.cycles_before_step {
315                self.step_pulse_voltage();
316            }
317        } else {
318            self.cycles_without_change += 1;
319
320            // Check if we need to step the pulse voltage
321            if self.cycles_without_change >= self.cycles_before_step {
322                self.step_pulse_voltage();
323            }
324        }
325    }
326}
327
328impl TipController {
329    /// Main control loop - with pulse voltage stepping
330    pub fn run_loop(&mut self, timeout: Duration) -> Result<TipState, NanonisError> {
331        self.pre_loop_initialization()?;
332
333        let z_signal_index = SignalIndex(30);
334
335        match poll_with_timeout(
336            || {
337                // Execute one control cycle
338                self.cycle_count += 1;
339
340                let ampl_setpoint = self.driver.client_mut().pll_amp_ctrl_setpnt_get(1)?;
341                let ampl_current = self.driver.client_mut().signal_val_get(75, true)?;
342
343                let (freq_shift, tip_state) = if (ampl_setpoint - 5e-12..ampl_setpoint + 5e-12).contains(&ampl_current) {
344                    info!("Amplitude reached the target range");
345                    self.read_and_track_freq_shift()?;
346
347                    let freq_shift = self
348                        .get_last_signal(self.signal_index)
349                        .expect("Should have failed earlier");
350
351                    self.update_pulse_voltage();
352
353                    let tip_state = self.classify(freq_shift);
354                    (freq_shift, tip_state)
355                } else {
356                    info!("Amplitude did not reach the target range");
357                    let freq_shift = -76.3; // add client call
358                    let tip_state = TipState::Bad;
359
360                    self.track_signal(self.signal_index, freq_shift);
361                    self.update_pulse_voltage();
362                    (freq_shift, tip_state)
363                };
364
365                info!("Cycle {}: State = {:?}", self.cycle_count, tip_state);
366
367                self.read_and_track_z_pos(z_signal_index)?;
368
369                info!(
370                    "Cycle {}: Freq Shift = {:.6?}, Pulse = {:.3}V, Cycles w/o change = {}/{}",
371                    self.cycle_count,
372                    freq_shift,
373                    self.pulse_voltage,
374                    self.cycles_without_change,
375                    self.cycles_before_step
376                );
377
378                // Execute based on state
379                match tip_state {
380                    TipState::Bad => {
381                        self.bad_loop(self.cycle_count)?; // Execute full recovery sequence
382                    }
383                    TipState::Good => {
384                        self.good_loop(self.cycle_count)?; // Monitor and count
385                    }
386                    TipState::Stable => {
387                        info!(
388                            "STABLE achieved after {} cycles! Final pulse voltage: {:.3}V",
389                            self.cycle_count, self.pulse_voltage
390                        );
391                        return Ok(Some(TipState::Stable));
392                    }
393                }
394
395                // Add information about this cycle to the logger buffer
396                if self.logger.is_some() {
397                    // Calculate changes before borrowing logger mutably
398                    let freq_shift_change = self.get_signal_change(self.signal_index);
399                    let z_change = self.get_signal_change(z_signal_index);
400
401                    if let Some(ref mut logger) = self.logger {
402                        logger.add(LogLine {
403                            cycle: self.cycle_count,
404                            freq_shift,
405                            tip_state,
406                            pulse_voltage: self.pulse_voltage,
407                            freq_shift_change,
408                            z_change,
409                        })?
410                    }
411                }
412
413                // Continue polling (return None to continue the loop)
414                Ok(None)
415            },
416            timeout,
417            Duration::from_millis(0), // No sleep between cycles in control loop
418        ) {
419            Ok(Some(tip_state)) => Ok(tip_state),
420            Ok(None) => {
421                debug!("Tip control loop reached timeout");
422                Err(NanonisError::InvalidCommand("Loop timeout".to_string()))
423            }
424            Err(PollError::ConditionError(e)) => Err(e),
425            Err(PollError::Timeout) => unreachable!(), // poll_with_timeout returns Ok(None) on timeout
426        }
427    }
428
429    /// Bad loop - execute recovery sequence with stable signal monitoring
430    /// Sequence: capture_stable_before → pulse → capture_stable_after → withdraw → move → approach → check
431    fn bad_loop(&mut self, cycle: u32) -> Result<(), NanonisError> {
432        info!(
433            "Cycle {}: Executing bad signal recovery sequence with stability detection",
434            cycle
435        );
436
437        let z_signal_index = SignalIndex(30); // Z (m) signal index
438
439        // Reset good count
440        self.good_count = 0;
441
442        // Execute bias pulse
443        info!(
444            "Cycle {}: Executing bias pulse at {:.3}V",
445            cycle, self.pulse_voltage
446        );
447        self.driver.execute(Action::BiasPulse {
448            wait_until_done: true,
449            pulse_width: Duration::from_millis(50),
450            bias_value_v: self.pulse_voltage,
451            z_controller_hold: 1,
452            pulse_mode: 2,
453        })?;
454
455        self.read_and_track_z_pos(z_signal_index)?;
456
457        // Continue with rest of recovery sequence
458        info!("Cycle {}: Continuing with withdraw and movement...", cycle);
459        self.driver.execute_chain(ActionChain::new(vec![
460            Action::Withdraw {
461                wait_until_finished: true,
462                timeout: Duration::from_secs(5),
463            },
464            Action::MoveMotor {
465                direction: MotorDirection::ZMinus,
466                steps: 3,
467            },
468            Action::MoveMotor {
469                direction: MotorDirection::XPlus,
470                steps: 2,
471            },
472            Action::MoveMotor {
473                direction: MotorDirection::YPlus,
474                steps: 2,
475            },
476            Action::AutoApproach {
477                wait_until_finished: true,
478                timeout: Duration::from_secs(5),
479            },
480            Action::Wait {
481                duration: Duration::from_secs(1),
482            },
483        ]))?;
484
485        info!(
486            "Cycle {}: Recovery sequence completed - checking tip state... \n",
487            cycle
488        );
489
490        Ok(())
491    }
492
493    /// Good loop - monitoring, increment good count
494    fn good_loop(&mut self, cycle: u32) -> Result<(), NanonisError> {
495        self.good_count += 1;
496        debug!("Cycle {}: Good signal (count: {})", cycle, self.good_count);
497        // Just wait and continue monitoring
498        Ok(())
499    }
500
501    /// Simple classification based on bounds
502    fn classify(&mut self, signal: f32) -> TipState {
503        if signal < self.bound.0 || signal > self.bound.1 {
504            TipState::Bad
505        } else if self.good_count >= self.stable_threshold {
506            TipState::Stable
507        } else {
508            TipState::Good
509        }
510    }
511
512    fn pre_loop_initialization(&mut self) -> Result<(), NanonisError> {
513        self.driver.client_mut().set_bias(-500e-3)?;
514
515        self.driver.client_mut().z_ctrl_setpoint_set(100e-12)?;
516
517        self.driver.execute(Action::AutoApproach {
518            wait_until_finished: true,
519            timeout: Duration::from_secs(1),
520        })?;
521
522        Ok(())
523    }
524
525    fn read_and_track_freq_shift(&mut self) -> Result<(), NanonisError> {
526        let freq_shift;
527
528        if let Some(freq_shift_frame) = self.driver.read_oscilloscope_with_stability(
529            self.signal_index,
530            None,
531            DataToGet::Stable {
532                readings: 5,
533                timeout: Duration::from_secs(10),
534            },
535            stability::dual_threshold_stability,
536        )? {
537            crate::plot_values(
538                &freq_shift_frame.data,
539                Some("Frequency Shift Oscilloscope Frame"),
540                None,
541                None,
542            )
543            .unwrap();
544            freq_shift =
545                freq_shift_frame.data.iter().sum::<f64>() as f32 / freq_shift_frame.size as f32;
546        } else {
547            warn!("Using single value read fallback for frequency shift");
548            let result = self.driver.execute(Action::ReadSignal {
549                signal: self.signal_index,
550                wait_for_newest: true,
551            })?;
552            freq_shift = result
553                .as_f64()
554                .expect("Must be able to Read from Interface") as f32;
555        }
556
557        self.track_signal(self.signal_index, freq_shift);
558
559        Ok(())
560    }
561    fn read_and_track_z_pos(&mut self, z_signal_index: SignalIndex) -> Result<(), NanonisError> {
562        let z_pos;
563
564        if let Some(z_pos_frame) = self.driver.read_oscilloscope_with_stability(
565            z_signal_index,
566            None,
567            DataToGet::Stable {
568                readings: 5,
569                timeout: Duration::from_secs(10),
570            },
571            stability::dual_threshold_stability,
572        )? {
573            // crate::plot_values(
574            //     &z_pos_frame.data,
575            //     Some("Z-Position Oscilloscope Frame"),
576            //     None,
577            //     None,
578            // )
579            // .unwrap();
580
581            z_pos = z_pos_frame.data.iter().sum::<f64>() as f32 / z_pos_frame.size as f32;
582        } else {
583            warn!("Using read single signal fallback for z position");
584            let result = self.driver.execute(Action::ReadSignal {
585                signal: self.signal_index,
586                wait_for_newest: true,
587            })?;
588            z_pos = result
589                .as_f64()
590                .expect("Must be able to Read from Interface") as f32;
591        }
592
593        self.track_signal(z_signal_index, z_pos);
594
595        Ok(())
596    }
597}
598
599// Implement Job trait for TipController
600impl Job for TipController {
601    type Output = TipState;
602
603    fn run(&mut self, timeout: Duration) -> Result<Self::Output, NanonisError> {
604        self.run_loop(timeout)
605    }
606}