Skip to main content

rusty_tip/
tip_controller.rs

1use crossbeam_channel::Sender;
2use log::info;
3use nanonis_rs::signals::SignalIndex;
4use std::collections::{HashMap, VecDeque};
5use std::path::Path;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::Arc;
8use std::time::Duration;
9
10use crate::action_driver::ActionDriver;
11use crate::actions::{Action, TipCheckMethod, TipState};
12use crate::controller_types::{
13    BiasSweepPolarity, ControllerAction, ControllerState, PolaritySign, PulseMethod,
14    TipControllerConfig,
15};
16use crate::error::{Error, RunOutcome};
17use crate::types::{MotorDisplacement, TipShape};
18use crate::ScanConfig;
19use crate::Signal;
20
21// ============================================================================
22// SWEEP PLAN (internal)
23// ============================================================================
24
25/// Plan for a single bias stability sweep
26struct SweepPlan {
27    /// Bias voltage to set before approach (extreme value, far from zero)
28    starting_bias: f32,
29    /// Sweep range: (from, to) -- always sweeps from extreme toward zero
30    bias_range: (f32, f32),
31    /// 1-based index of this sweep
32    index: usize,
33    /// Total number of sweeps
34    total: usize,
35}
36
37// ============================================================================
38// TIP CONTROLLER
39// ============================================================================
40
41/// Enhanced tip controller with pulse voltage stepping
42pub struct TipController {
43    driver: ActionDriver,
44    config: TipControllerConfig,
45
46    // State tracking
47    current_pulse_voltage: f32,
48    current_tip_shape: TipShape,
49    cycles_without_change: u32,
50    cycle_count: u32,
51
52    // Multi-signal history for bias adjustment and analysis
53    signal_histories: HashMap<u8, VecDeque<f32>>, // Key is signal.index
54    max_history_size: usize,
55
56    // Loop termination safeguards
57    max_cycles: Option<usize>,
58    max_duration: Option<Duration>,
59    loop_start_time: Option<std::time::Instant>,
60
61    // Graceful shutdown support
62    shutdown_requested: Option<Arc<AtomicBool>>,
63
64    // Polarity tracking
65    base_polarity: PolaritySign,
66    pulse_count_for_random: u32,
67
68    // State reporting for GUI
69    state_sender: Option<Sender<ControllerState>>,
70    current_action: ControllerAction,
71    measured_freq_shift: Option<f32>,
72}
73
74impl TipController {
75    /// Create new tip controller with basic signal bounds
76    pub fn new(driver: ActionDriver, config: TipControllerConfig) -> Self {
77        let initial_voltage = match &config.pulse_method {
78            PulseMethod::Fixed { voltage, .. } => *voltage,
79            PulseMethod::Stepping { voltage_bounds, .. } => voltage_bounds.0,
80            PulseMethod::Linear { voltage_bounds, .. } => voltage_bounds.0,
81        };
82        let base_polarity = match &config.pulse_method {
83            PulseMethod::Fixed { polarity, .. } => *polarity,
84            PulseMethod::Stepping { polarity, .. } => *polarity,
85            PulseMethod::Linear { polarity, .. } => *polarity,
86        };
87        let max_cycles = config.max_cycles;
88        let max_duration = config.max_duration;
89        Self {
90            driver,
91            config,
92            current_pulse_voltage: initial_voltage,
93            current_tip_shape: TipShape::Blunt,
94            cycles_without_change: 0,
95            cycle_count: 0,
96            signal_histories: HashMap::new(),
97            max_history_size: 100,
98            max_cycles,
99            max_duration,
100            loop_start_time: None,
101            shutdown_requested: None,
102            base_polarity,
103            pulse_count_for_random: 0,
104            state_sender: None,
105            current_action: ControllerAction::Idle,
106            measured_freq_shift: None,
107        }
108    }
109
110    /// Set a channel to send state updates to (for GUI)
111    pub fn set_state_sender(&mut self, sender: Sender<ControllerState>) {
112        self.state_sender = Some(sender);
113    }
114
115    /// Get current controller state snapshot
116    pub fn snapshot(&self) -> ControllerState {
117        let elapsed_secs = self
118            .loop_start_time
119            .map(|t| t.elapsed().as_secs_f64())
120            .unwrap_or(0.0);
121
122        let freq_shift = self.measured_freq_shift;
123
124        ControllerState {
125            tip_shape: self.current_tip_shape,
126            cycle_count: self.cycle_count,
127            pulse_voltage: self.current_pulse_voltage,
128            freq_shift,
129            elapsed_secs,
130            current_action: self.current_action.clone(),
131        }
132    }
133
134    /// Send current state to the GUI (if connected)
135    fn send_state(&self) {
136        if let Some(sender) = &self.state_sender {
137            let _ = sender.try_send(self.snapshot());
138        }
139    }
140
141    /// Update current action and send state
142    fn set_action(&mut self, action: ControllerAction) {
143        self.current_action = action;
144        self.send_state();
145    }
146
147    /// Set shutdown flag for graceful termination
148    pub fn set_shutdown_flag(&mut self, flag: Arc<AtomicBool>) {
149        self.shutdown_requested = Some(flag.clone());
150        self.driver.set_shutdown_flag(flag);
151    }
152
153    /// Check if shutdown has been requested
154    fn is_shutdown_requested(&self) -> bool {
155        self.shutdown_requested
156            .as_ref()
157            .map(|f| f.load(Ordering::SeqCst))
158            .unwrap_or(false)
159    }
160
161    /// Return Err(Error::Shutdown) if shutdown was requested
162    fn check_shutdown(&self) -> Result<(), Error> {
163        if self.is_shutdown_requested() {
164            info!("Shutdown requested at cycle {}", self.cycle_count);
165            Err(Error::Shutdown)
166        } else {
167            Ok(())
168        }
169    }
170
171    /// Check if this pulse should use opposite polarity
172    fn should_use_opposite_polarity(&self) -> bool {
173        match &self.config.pulse_method {
174            PulseMethod::Stepping {
175                random_polarity_switch: Some(switch),
176                ..
177            }
178            | PulseMethod::Fixed {
179                random_polarity_switch: Some(switch),
180                ..
181            }
182            | PulseMethod::Linear {
183                random_polarity_switch: Some(switch),
184                ..
185            } => {
186                switch.enabled
187                    && self.pulse_count_for_random > 0
188                    && self.pulse_count_for_random % switch.switch_every_n_pulses == 0
189            }
190            _ => false,
191        }
192    }
193
194    /// Get the signed pulse voltage based on current polarity
195    fn get_signed_pulse_voltage(&self) -> f32 {
196        let polarity = if self.should_use_opposite_polarity() {
197            self.base_polarity.opposite()
198        } else {
199            self.base_polarity
200        };
201
202        match polarity {
203            PolaritySign::Positive => self.current_pulse_voltage,
204            PolaritySign::Negative => -self.current_pulse_voltage,
205        }
206    }
207
208    /// Track a signal value in history
209    pub fn track_signal(&mut self, signal: &Signal, value: f32) {
210        let history = self.signal_histories.entry(signal.index).or_default();
211        history.push_front(value);
212        while history.len() > self.max_history_size {
213            history.pop_back();
214        }
215    }
216
217    /// Get signal change (latest - previous) for a specific signal
218    #[allow(dead_code)]
219    pub fn get_signal_change(&self, signal: &Signal) -> Option<f32> {
220        if let Some(history) = self.signal_histories.get(&signal.index) {
221            if history.len() >= 2 {
222                Some(history[0] - history[1])
223            } else {
224                None
225            }
226        } else {
227            None
228        }
229    }
230
231    /// Get signal history for a specific signal (most recent first)
232    #[allow(dead_code)]
233    pub fn get_signal_history(&self, signal: &Signal) -> Option<&VecDeque<f32>> {
234        self.signal_histories.get(&signal.index)
235    }
236
237    #[allow(dead_code)]
238    pub fn get_last_signal(&self, signal: &Signal) -> Option<f32> {
239        match self.get_signal_history(signal) {
240            Some(history) => history.front().copied(),
241            None => None,
242        }
243    }
244
245    /// Clear all signal histories
246    #[allow(dead_code)]
247    pub fn clear_all_histories(&mut self) {
248        self.signal_histories.clear();
249    }
250
251    /// Clear history for a specific signal
252    #[allow(dead_code)]
253    pub fn clear_signal_history(&mut self, signal: &Signal) {
254        self.signal_histories.remove(&signal.index);
255    }
256
257    /// Execute a single pulse at maximum voltage to aggressively reshape the tip
258    /// Used when stability check fails to force tip into a different state
259    fn execute_max_pulse(&mut self) -> Result<(), Error> {
260        self.pulse_count_for_random += 1;
261
262        let max_voltage = self.config.pulse_method.max_voltage();
263        let using_opposite = self.should_use_opposite_polarity();
264        let signed_voltage = if using_opposite {
265            match self.base_polarity.opposite() {
266                PolaritySign::Positive => max_voltage,
267                PolaritySign::Negative => -max_voltage,
268            }
269        } else {
270            match self.base_polarity {
271                PolaritySign::Positive => max_voltage,
272                PolaritySign::Negative => -max_voltage,
273            }
274        };
275
276        let current_polarity = if using_opposite {
277            self.base_polarity.opposite()
278        } else {
279            self.base_polarity
280        };
281
282        info!(
283            "Executing MAX pulse #{} due to stability failure: {:.3}V ({:?}{})",
284            self.pulse_count_for_random,
285            signed_voltage,
286            current_polarity,
287            if using_opposite { " - SWITCHED" } else { "" }
288        );
289
290        self.driver
291            .run(Action::BiasPulse {
292                wait_until_done: true,
293                pulse_width: self.config.pulse_width,
294                bias_value_v: signed_voltage,
295                z_controller_hold: 0,
296                pulse_mode: 0,
297            })
298            .go()?;
299
300        Ok(())
301    }
302
303    /// Check if current signal represents a significant change from recent stable period
304    fn has_significant_change(&self, signal: &Signal) -> (bool, f32) {
305        let threshold_value = match &self.config.pulse_method {
306            PulseMethod::Stepping {
307                threshold_value, ..
308            } => *threshold_value,
309            PulseMethod::Fixed { .. } => return (false, 0.0),
310            PulseMethod::Linear { .. } => return (false, 0.0),
311        };
312
313        if let Some(history) = self.signal_histories.get(&signal.index) {
314            if history.len() < 2 {
315                (true, 0.0)
316            } else {
317                let stable_period_size =
318                    (self.cycles_without_change as usize).min(history.len() - 1);
319
320                if stable_period_size == 0 {
321                    let current_signal = history[0];
322                    let last_signal = history[1];
323
324                    log::debug!(
325                        "Last signal: {:.3e} | Current threshold: {:.3e}",
326                        last_signal,
327                        threshold_value
328                    );
329
330                    let change = current_signal - last_signal;
331                    let has_change = change.abs() >= threshold_value;
332                    (has_change, change)
333                } else {
334                    let current_signal = history[0];
335                    let stable_signals: Vec<f32> = history
336                        .iter()
337                        .skip(1)
338                        .take(stable_period_size)
339                        .cloned()
340                        .collect();
341                    let stable_mean =
342                        stable_signals.iter().sum::<f32>() / stable_signals.len() as f32;
343
344                    log::debug!(
345                        "Current: {:.3e} | Stable mean: {:.3e} | Threshold: {:.3e}",
346                        current_signal,
347                        stable_mean,
348                        threshold_value
349                    );
350
351                    let change = current_signal - stable_mean;
352                    let has_change = change.abs() >= threshold_value;
353                    (has_change, change)
354                }
355            }
356        } else {
357            (true, 0.0)
358        }
359    }
360
361    /// Step up the pulse voltage if possible
362    fn step_pulse_voltage(&mut self) -> bool {
363        let (voltage_bounds, voltage_steps) = match &self.config.pulse_method {
364            PulseMethod::Stepping {
365                voltage_bounds,
366                voltage_steps,
367                ..
368            } => (*voltage_bounds, *voltage_steps),
369            PulseMethod::Fixed { .. } => return false,
370            PulseMethod::Linear { .. } => return false,
371        };
372
373        let step_size = (voltage_bounds.1 - voltage_bounds.0) / voltage_steps as f32;
374        let new_pulse = (self.current_pulse_voltage + step_size).min(voltage_bounds.1);
375
376        if new_pulse > self.current_pulse_voltage {
377            info!(
378                "Stepping pulse voltage: {:.3}V -> {:.3}V",
379                self.current_pulse_voltage, new_pulse
380            );
381            self.current_pulse_voltage = new_pulse;
382            self.cycles_without_change = 0;
383            true
384        } else {
385            log::debug!("Pulse voltage already at maximum: {:.3}V", voltage_bounds.1);
386            self.cycles_without_change = 0;
387            false
388        }
389    }
390
391    /// Update signal history and step pulse voltage if needed
392    fn update_pulse_voltage(&mut self) {
393        match &self.config.pulse_method {
394            PulseMethod::Stepping {
395                voltage_bounds,
396                cycles_before_step,
397                ..
398            } => {
399                let (is_significant, change) =
400                    self.has_significant_change(&self.config.freq_shift_signal);
401                if is_significant && change >= 0.0 {
402                    self.cycles_without_change = 0;
403                    self.current_pulse_voltage = voltage_bounds.0;
404                    log::debug!(
405                        "Positive significant change detected, resetting pulse voltage to minimum: {:.3}V",
406                        self.current_pulse_voltage
407                    );
408                } else if is_significant {
409                    log::warn!("Negative significant change detected!");
410                    self.cycles_without_change += 1;
411                    if self.cycles_without_change >= *cycles_before_step as u32 {
412                        self.step_pulse_voltage();
413                    }
414                } else {
415                    self.cycles_without_change += 1;
416                    if self.cycles_without_change >= *cycles_before_step as u32 {
417                        self.step_pulse_voltage();
418                    }
419                }
420            }
421            PulseMethod::Fixed { .. } => {}
422            PulseMethod::Linear {
423                voltage_bounds,
424                linear_clamp,
425                ..
426            } => {
427                let current_freq_shift;
428                let mut pulse_voltage = self.current_pulse_voltage;
429
430                if let Some(freq_shift_history) = self
431                    .signal_histories
432                    .get(&self.config.freq_shift_signal.index)
433                {
434                    current_freq_shift = freq_shift_history[0];
435
436                    if !(linear_clamp.0..linear_clamp.1).contains(&current_freq_shift) {
437                        log::info!(
438                            "Linear pulse: freq_shift {:.2} Hz outside range [{:.2}, {:.2}] Hz -> using max voltage {:.2}V",
439                            current_freq_shift, linear_clamp.0, linear_clamp.1, voltage_bounds.1
440                        );
441                        pulse_voltage = voltage_bounds.1;
442                    } else {
443                        let slope = (voltage_bounds.1 - voltage_bounds.0)
444                            / (linear_clamp.1 - linear_clamp.0);
445                        let d = voltage_bounds.0 - slope * linear_clamp.0;
446                        pulse_voltage = slope * current_freq_shift + d;
447                        log::info!(
448                            "Linear pulse: freq_shift {:.2} Hz in range [{:.2}, {:.2}] Hz -> calculated voltage {:.2}V",
449                            current_freq_shift, linear_clamp.0, linear_clamp.1, pulse_voltage
450                        );
451                    }
452                }
453
454                self.current_pulse_voltage = pulse_voltage;
455            }
456        }
457    }
458}
459
460// ============================================================================
461// MAIN CONTROL LOOP
462// ============================================================================
463
464impl TipController {
465    /// Main control loop - returns RunOutcome on success, Error on failure.
466    /// Shutdown is treated as a successful outcome (StoppedByUser), not an error.
467    pub fn run(&mut self) -> Result<RunOutcome, Error> {
468        self.loop_start_time = Some(std::time::Instant::now());
469        self.set_action(ControllerAction::Initializing);
470
471        match self.run_inner() {
472            Ok(()) => {
473                self.set_action(ControllerAction::Completed);
474                Ok(RunOutcome::Completed)
475            }
476            Err(Error::Shutdown) => {
477                self.set_action(ControllerAction::Stopped);
478                Ok(RunOutcome::StoppedByUser)
479            }
480            Err(e) => Err(e),
481        }
482    }
483
484    /// Inner loop that uses Error::Shutdown for shutdown signaling
485    fn run_inner(&mut self) -> Result<(), Error> {
486        self.pre_loop_initialization()?;
487
488        while self.current_tip_shape != TipShape::Stable {
489            // Check cycle limit
490            if let Some(max) = self.max_cycles {
491                if self.cycle_count >= max as u32 {
492                    self.set_action(ControllerAction::Error("Max cycles exceeded".to_string()));
493                    return Err(Error::CycleLimit(max as u32));
494                }
495            }
496
497            // Check wall-clock timeout
498            if let Some(max_dur) = self.max_duration {
499                if let Some(start_time) = self.loop_start_time {
500                    if start_time.elapsed() > max_dur {
501                        self.set_action(ControllerAction::Error(
502                            "Max duration exceeded".to_string(),
503                        ));
504                        return Err(Error::TimedOut(max_dur));
505                    }
506                }
507            }
508
509            // Check shutdown flag
510            self.check_shutdown()?;
511
512            // Execute one control cycle
513            self.cycle_count += 1;
514            self.measured_freq_shift = None;
515
516            // Periodic status report
517            if self.cycle_count % self.config.status_interval as u32 == 0 {
518                if let Some(start_time) = self.loop_start_time {
519                    let elapsed = start_time.elapsed();
520                    info!(
521                        "Status: cycle={}, state={:?}, pulse_v={:.2}V, elapsed={:.1}s",
522                        self.cycle_count,
523                        self.current_tip_shape,
524                        self.current_pulse_voltage,
525                        elapsed.as_secs_f32()
526                    );
527                }
528            }
529
530            // Execute based on state
531            match self.current_tip_shape {
532                TipShape::Blunt => {
533                    info!(
534                        "Cycle {}: running bad loop ==============",
535                        self.cycle_count
536                    );
537                    self.set_action(ControllerAction::Pulsing);
538                    self.bad_loop()?;
539                }
540                TipShape::Sharp => {
541                    info!(
542                        "Cycle {}: running good loop ==============",
543                        self.cycle_count
544                    );
545                    self.set_action(ControllerAction::StabilityCheck);
546                    self.good_loop()?;
547                }
548                TipShape::Stable => {
549                    info!("STABLE achieved after {} cycles!", self.cycle_count);
550                    break;
551                }
552            }
553
554            // Send state after the cycle so measured_freq_shift reflects any measurement taken
555            self.send_state();
556        }
557        Ok(())
558    }
559
560    /// Bad loop - execute recovery sequence with stable signal monitoring
561    fn bad_loop(&mut self) -> Result<(), Error> {
562        self.pulse_count_for_random += 1;
563
564        let using_opposite = self.should_use_opposite_polarity();
565        let current_polarity = if using_opposite {
566            self.base_polarity.opposite()
567        } else {
568            self.base_polarity
569        };
570        let signed_voltage = self.get_signed_pulse_voltage();
571
572        info!(
573            "Executing pulse #{}: {:.3}V ({} method, {:?}{})",
574            self.pulse_count_for_random,
575            signed_voltage,
576            self.config.pulse_method.method_name(),
577            current_polarity,
578            if using_opposite { " - SWITCHED" } else { "" }
579        );
580
581        self.driver
582            .run(Action::BiasPulse {
583                wait_until_done: true,
584                pulse_width: self.config.pulse_width,
585                bias_value_v: signed_voltage,
586                z_controller_hold: 0,
587                pulse_mode: 0,
588            })
589            .go()?;
590
591        log::debug!(
592            "Waiting {:?} for signal to settle after pulse...",
593            self.config.post_pulse_settle
594        );
595        self.driver
596            .run(Action::Wait {
597                duration: self.config.post_pulse_settle,
598            })
599            .go()?;
600
601        log::debug!("Repositioning...");
602
603        self.set_action(ControllerAction::Repositioning);
604        self.driver
605            .run(Action::SafeReposition {
606                x_steps: self.config.reposition_steps.0,
607                y_steps: self.config.reposition_steps.1,
608            })
609            .go()?;
610
611        // Wait for signal to stabilize after reposition/approach
612        log::debug!(
613            "Waiting {:?} for signal to stabilize after reposition...",
614            self.config.post_reposition_settle
615        );
616        self.driver
617            .run(Action::Wait {
618                duration: self.config.post_reposition_settle,
619            })
620            .go()?;
621
622        self.set_action(ControllerAction::MeasuringSignal);
623        let tip_state: TipState = self
624            .driver
625            .run(Action::CheckTipState {
626                method: TipCheckMethod::SignalBounds {
627                    signal: self.config.freq_shift_signal.clone(),
628                    bounds: self.config.sharp_tip_bounds,
629                },
630            })
631            .expecting()?;
632
633        self.current_tip_shape = tip_state.shape;
634
635        // Track the frequency shift signal if available
636        if let Some(freq_shift_value) = tip_state
637            .measured_signals
638            .get(&SignalIndex::new(self.config.freq_shift_signal.index))
639            .copied()
640        {
641            let signal = self.config.freq_shift_signal.clone();
642            self.track_signal(&signal, freq_shift_value);
643            self.measured_freq_shift = Some(freq_shift_value);
644        } else {
645            log::warn!(
646                "CheckTipState did not return frequency shift signal (index: {})",
647                self.config.freq_shift_signal.index
648            );
649        }
650
651        // Update pulse voltage based on signal changes (stepping logic)
652        self.update_pulse_voltage();
653
654        Ok(())
655    }
656
657    /// Good loop - monitoring, increment good count
658    fn good_loop(&mut self) -> Result<(), Error> {
659        self.set_action(ControllerAction::StabilityCheck);
660
661        let (confident_tip_shape, baseline_freq_shift) = self.pre_good_loop_check()?;
662
663        if matches!(confident_tip_shape, TipShape::Blunt) {
664            info!("Tip Shape was wrongly measured as good");
665            self.current_tip_shape = TipShape::Blunt;
666            self.send_state();
667            return Ok(());
668        }
669
670        // If stability checking is disabled, mark tip as stable immediately
671        if !self.config.check_stability {
672            info!("Stability checking disabled - marking tip as stable");
673            self.current_tip_shape = TipShape::Stable;
674            self.send_state();
675            return Ok(());
676        }
677
678        let baseline_freq_shift = match baseline_freq_shift {
679            Some(v) => v,
680            None => {
681                log::error!("No baseline freq_shift available for stability check");
682                self.current_tip_shape = TipShape::Blunt;
683                self.send_state();
684                return Ok(());
685            }
686        };
687
688        info!(
689            "Baseline freq_shift for stability comparison: {:.3} Hz",
690            baseline_freq_shift
691        );
692
693        let sweep_plans = self.build_sweep_plans();
694
695        info!(
696            "Starting stability check with polarity mode: {:?}, {} sweep(s)",
697            self.config.stability_config.polarity_mode,
698            sweep_plans.len()
699        );
700
701        let original_scan_config = self.save_and_set_scan_speed()?;
702
703        let mut shutdown_requested = false;
704        for plan in &sweep_plans {
705            if self.is_shutdown_requested() {
706                log::info!("Shutdown requested before stability sweep {}", plan.index);
707                shutdown_requested = true;
708                break;
709            }
710
711            self.prepare_for_sweep(plan.starting_bias)?;
712            let _ = self.execute_stability_sweep(plan)?;
713        }
714
715        self.restore_scan_speed(original_scan_config);
716
717        if shutdown_requested {
718            return Err(Error::Shutdown);
719        }
720
721        // After all sweeps: withdraw, restore initial bias, approach, read freq_shift
722        let final_freq_shift = self.measure_final_freq_shift()?;
723
724        // Compare baseline vs final freq_shift
725        let signal_change = (final_freq_shift - baseline_freq_shift).abs();
726        let is_stable = signal_change <= self.config.allowed_change_for_stable;
727
728        info!(
729            "Stability comparison: baseline={:.3} Hz, final={:.3} Hz, change={:.3} Hz, threshold={:.3} Hz, stable={}",
730            baseline_freq_shift, final_freq_shift, signal_change, self.config.allowed_change_for_stable, is_stable
731        );
732
733        self.handle_stability_outcome(is_stable, sweep_plans.len())?;
734
735        Ok(())
736    }
737
738    /// After all stability sweeps, withdraw, restore initial bias, approach, and read freq_shift.
739    fn measure_final_freq_shift(&mut self) -> Result<f32, Error> {
740        info!("Measuring final freq_shift after all sweeps");
741
742        self.set_action(ControllerAction::Withdrawing);
743        self.driver
744            .run(Action::Withdraw {
745                wait_until_finished: true,
746                timeout: Duration::from_secs(5),
747            })
748            .go()?;
749
750        self.driver
751            .run(Action::Wait {
752                duration: Duration::from_millis(200),
753            })
754            .go()?;
755
756        self.driver
757            .run(Action::SetBias {
758                voltage: self.config.initial_bias_v,
759            })
760            .go()?;
761        info!(
762            "Bias restored to initial value: {:.3} V",
763            self.config.initial_bias_v
764        );
765
766        self.set_action(ControllerAction::CenteringFreqShift);
767        self.driver
768            .run(Action::AutoApproach {
769                wait_until_finished: true,
770                timeout: Duration::from_secs(600),
771                center_freq_shift: true,
772            })
773            .go()?;
774
775        info!(
776            "Waiting {:?} for signal to stabilize after approach...",
777            self.config.post_approach_settle
778        );
779        self.driver
780            .run(Action::Wait {
781                duration: self.config.post_approach_settle,
782            })
783            .go()?;
784
785        self.set_action(ControllerAction::MeasuringSignal);
786        let tip_state: TipState = self
787            .driver
788            .run(Action::CheckTipState {
789                method: TipCheckMethod::SignalBounds {
790                    signal: self.config.freq_shift_signal.clone(),
791                    bounds: self.config.sharp_tip_bounds,
792                },
793            })
794            .expecting()?;
795
796        let final_freq_shift = tip_state
797            .measured_signals
798            .get(&SignalIndex::new(self.config.freq_shift_signal.index))
799            .copied()
800            .ok_or_else(|| {
801                Error::Nanonis(crate::NanonisError::Protocol(
802                    "Failed to read final freq_shift after stability sweeps".to_string(),
803                ))
804            })?;
805
806        let signal = self.config.freq_shift_signal.clone();
807        self.track_signal(&signal, final_freq_shift);
808        self.measured_freq_shift = Some(final_freq_shift);
809
810        info!("Final freq_shift: {:.3} Hz", final_freq_shift);
811        Ok(final_freq_shift)
812    }
813
814    /// Build sweep plans based on polarity mode.
815    fn build_sweep_plans(&self) -> Vec<SweepPlan> {
816        let stability_config = &self.config.stability_config;
817        let polarity_mode = stability_config.polarity_mode;
818        let bias_range = stability_config.bias_range;
819
820        match polarity_mode {
821            BiasSweepPolarity::Positive => {
822                vec![SweepPlan {
823                    starting_bias: bias_range.1,
824                    bias_range: (bias_range.1, bias_range.0),
825                    index: 1,
826                    total: 1,
827                }]
828            }
829            BiasSweepPolarity::Negative => {
830                vec![SweepPlan {
831                    starting_bias: -bias_range.1,
832                    bias_range: (-bias_range.1, -bias_range.0),
833                    index: 1,
834                    total: 1,
835                }]
836            }
837            BiasSweepPolarity::Both => {
838                vec![
839                    SweepPlan {
840                        starting_bias: bias_range.1,
841                        bias_range: (bias_range.1, bias_range.0),
842                        index: 1,
843                        total: 2,
844                    },
845                    SweepPlan {
846                        starting_bias: -bias_range.1,
847                        bias_range: (-bias_range.1, -bias_range.0),
848                        index: 2,
849                        total: 2,
850                    },
851                ]
852            }
853        }
854    }
855
856    /// Prepare the tip for a stability sweep
857    fn prepare_for_sweep(&mut self, starting_bias: f32) -> Result<(), Error> {
858        info!(
859            "Preparing for sweep: withdrawing and repositioning, starting bias = {:.3}V",
860            starting_bias
861        );
862
863        self.set_action(ControllerAction::Withdrawing);
864        self.driver
865            .run(Action::Withdraw {
866                wait_until_finished: true,
867                timeout: Duration::from_secs(5),
868            })
869            .go()?;
870
871        self.set_action(ControllerAction::Repositioning);
872        self.driver
873            .run(Action::MoveMotor3D {
874                displacement: MotorDisplacement::new(
875                    self.config.reposition_steps.0,
876                    self.config.reposition_steps.1,
877                    -3,
878                ),
879                blocking: true,
880            })
881            .go()?;
882
883        self.driver
884            .run(Action::Wait {
885                duration: Duration::from_millis(200),
886            })
887            .go()?;
888
889        self.driver
890            .run(Action::SetBias {
891                voltage: starting_bias,
892            })
893            .go()?;
894        info!("Bias set to {:.3}V before approach", starting_bias);
895
896        self.set_action(ControllerAction::CenteringFreqShift);
897        self.driver
898            .run(Action::AutoApproach {
899                wait_until_finished: true,
900                timeout: Duration::from_secs(600),
901                center_freq_shift: true,
902            })
903            .go()?;
904
905        info!(
906            "Waiting {:?} for signal to stabilize after approach...",
907            self.config.post_approach_settle
908        );
909        self.driver
910            .run(Action::Wait {
911                duration: self.config.post_approach_settle,
912            })
913            .go()?;
914
915        Ok(())
916    }
917
918    /// Execute a single stability sweep and return whether the tip was stable.
919    fn execute_stability_sweep(&mut self, plan: &SweepPlan) -> Result<bool, Error> {
920        let stability_config = self.config.stability_config.clone();
921        let step_duration = Duration::from_millis(stability_config.step_period_ms);
922        let max_duration = Duration::from_secs(stability_config.max_duration_secs);
923        let bias_steps = stability_config.bias_steps;
924
925        self.set_action(ControllerAction::StabilitySweep {
926            sweep: plan.index as u32,
927            total: plan.total as u32,
928        });
929        info!(
930            "Stability sweep {}/{}: {:.2}V to {:.2}V",
931            plan.index, plan.total, plan.bias_range.0, plan.bias_range.1
932        );
933
934        let stability_result: crate::actions::StabilityResult = self
935            .driver
936            .run(Action::CheckTipStability {
937                method: crate::actions::TipStabilityMethod::BiasSweepResponse {
938                    signal: self.config.freq_shift_signal.clone(),
939                    bias_range: plan.bias_range,
940                    bias_steps,
941                    step_duration,
942                    allowed_signal_change: self.config.allowed_change_for_stable,
943                },
944                max_duration,
945            })
946            .expecting()?;
947
948        if let Some(signal_values) = stability_result
949            .measured_values
950            .get(&self.config.freq_shift_signal)
951        {
952            if let Some(&last_value) = signal_values.last() {
953                let signal = self.config.freq_shift_signal.clone();
954                self.track_signal(&signal, last_value);
955            }
956        }
957
958        if !stability_result.is_stable {
959            info!(
960                "Tip unstable during sweep {}/{} ({:.2}V to {:.2}V)",
961                plan.index, plan.total, plan.bias_range.0, plan.bias_range.1
962            );
963        }
964
965        Ok(stability_result.is_stable)
966    }
967
968    /// Save the current scan speed and set the stability-check speed if configured.
969    fn save_and_set_scan_speed(&mut self) -> Result<Option<ScanConfig>, Error> {
970        let target_speed = self.config.stability_config.scan_speed_m_s;
971
972        if let Some(target_speed) = target_speed {
973            match self.driver.client_mut().scan_speed_get() {
974                Ok(config) => {
975                    info!(
976                        "Saving original scan speed: {:.2e} m/s (forward), {:.2e} m/s (backward)",
977                        config.forward_linear_speed_m_s, config.backward_linear_speed_m_s
978                    );
979                    let mut new_config = config;
980                    new_config.forward_linear_speed_m_s = target_speed;
981                    new_config.backward_linear_speed_m_s = target_speed;
982                    new_config.keep_parameter_constant = 1;
983                    if let Err(e) = self.driver.client_mut().scan_config_set(new_config) {
984                        log::warn!("Failed to set scan speed for stability check: {}", e);
985                    } else {
986                        info!(
987                            "Set scan speed to {:.2e} m/s for stability check",
988                            target_speed
989                        );
990                    }
991                    Ok(Some(config))
992                }
993                Err(e) => {
994                    log::warn!("Failed to get current scan speed: {}", e);
995                    Ok(None)
996                }
997            }
998        } else {
999            Ok(None)
1000        }
1001    }
1002
1003    /// Restore the original scan speed if it was saved.
1004    fn restore_scan_speed(&mut self, original: Option<ScanConfig>) {
1005        if let Some(config) = original {
1006            if let Err(e) = self.driver.client_mut().scan_config_set(config) {
1007                log::warn!("Failed to restore original scan speed: {}", e);
1008            } else {
1009                info!(
1010                    "Restored original scan speed: {:.2e} m/s (forward)",
1011                    config.forward_linear_speed_m_s
1012                );
1013            }
1014        }
1015    }
1016
1017    /// Handle the outcome of stability sweeps.
1018    fn handle_stability_outcome(
1019        &mut self,
1020        overall_stable: bool,
1021        sweep_count: usize,
1022    ) -> Result<(), Error> {
1023        if overall_stable {
1024            info!("Tip is stable after {} sweep(s)", sweep_count);
1025            self.current_tip_shape = TipShape::Stable;
1026        } else {
1027            info!("Stability check failed - executing max voltage pulse to reshape tip");
1028
1029            self.execute_max_pulse()?;
1030
1031            self.set_action(ControllerAction::Repositioning);
1032            self.driver
1033                .run(Action::SafeReposition {
1034                    x_steps: self.config.reposition_steps.0,
1035                    y_steps: self.config.reposition_steps.1,
1036                })
1037                .go()?;
1038
1039            info!("Restarting tip preparation from beginning after stability failure");
1040            self.current_tip_shape = TipShape::Blunt;
1041        }
1042
1043        Ok(())
1044    }
1045
1046    /// Check reliability of tip state and return baseline freq_shift for stability comparison.
1047    fn pre_good_loop_check(&mut self) -> Result<(TipShape, Option<f32>), Error> {
1048        log::info!("Checking reliability of tip state result");
1049
1050        let mut last_freq_shift: Option<f32> = None;
1051
1052        for i in 0..3 {
1053            self.check_shutdown().map_err(|_| {
1054                log::info!(
1055                    "Shutdown requested during pre_good_loop_check at iteration {}/3",
1056                    i + 1
1057                );
1058                Error::Shutdown
1059            })?;
1060
1061            self.set_action(ControllerAction::Repositioning);
1062            self.driver
1063                .run(Action::SafeReposition {
1064                    x_steps: self.config.reposition_steps.0,
1065                    y_steps: self.config.reposition_steps.1,
1066                })
1067                .go()?;
1068
1069            self.check_shutdown().map_err(|_| {
1070                log::info!("Shutdown requested after reposition in pre_good_loop_check");
1071                Error::Shutdown
1072            })?;
1073
1074            self.set_action(ControllerAction::MeasuringSignal);
1075            let tip_state: TipState = self
1076                .driver
1077                .run(Action::CheckTipState {
1078                    method: TipCheckMethod::SignalBounds {
1079                        signal: self.config.freq_shift_signal.clone(),
1080                        bounds: self.config.sharp_tip_bounds,
1081                    },
1082                })
1083                .expecting()?;
1084
1085            if let Some(freq_shift_value) = tip_state
1086                .measured_signals
1087                .get(&SignalIndex::new(self.config.freq_shift_signal.index))
1088                .copied()
1089            {
1090                last_freq_shift = Some(freq_shift_value);
1091                let signal = self.config.freq_shift_signal.clone();
1092                self.track_signal(&signal, freq_shift_value);
1093            }
1094
1095            if matches!(tip_state.shape, TipShape::Blunt) {
1096                self.measured_freq_shift = last_freq_shift;
1097                return Ok((TipShape::Blunt, None));
1098            }
1099        }
1100
1101        log::info!(
1102            "Baseline freq_shift for stability check: {:?}",
1103            last_freq_shift
1104        );
1105        Ok((TipShape::Sharp, last_freq_shift))
1106    }
1107
1108    fn pre_loop_initialization(&mut self) -> Result<(), Error> {
1109        log::info!("Running pre loop initialization");
1110
1111        // Load layout file if specified
1112        if let Some(layout_path) = self.config.layout_file.clone() {
1113            self.set_action(ControllerAction::LoadingLayout);
1114            let abs_path = Path::new(&layout_path).canonicalize().map_err(|e| {
1115                Error::Nanonis(crate::NanonisError::Protocol(format!(
1116                    "Layout file not found: {} ({})",
1117                    layout_path, e
1118                )))
1119            })?;
1120            let abs_path_str = abs_path.to_string_lossy();
1121            info!("Loading layout from: {}", abs_path_str);
1122            self.driver
1123                .client_mut()
1124                .util_layout_load(&abs_path_str, false)?;
1125            info!("Layout loaded successfully");
1126        }
1127
1128        // Load settings file if specified
1129        if let Some(settings_path) = self.config.settings_file.clone() {
1130            self.set_action(ControllerAction::LoadingSettings);
1131            let abs_path = Path::new(&settings_path).canonicalize().map_err(|e| {
1132                Error::Nanonis(crate::NanonisError::Protocol(format!(
1133                    "Settings file not found: {} ({})",
1134                    settings_path, e
1135                )))
1136            })?;
1137            let abs_path_str = abs_path.to_string_lossy();
1138            info!("Loading settings from: {}", abs_path_str);
1139            self.driver
1140                .client_mut()
1141                .util_settings_load(&abs_path_str, false)?;
1142            info!("Settings loaded successfully");
1143        }
1144
1145        self.set_action(ControllerAction::SettingBias);
1146        self.driver
1147            .run(Action::SetBias {
1148                voltage: self.config.initial_bias_v,
1149            })
1150            .go()?;
1151
1152        self.set_action(ControllerAction::SettingSetpoint);
1153        self.driver
1154            .run(Action::SetZSetpoint {
1155                setpoint: self.config.initial_z_setpoint_a,
1156            })
1157            .go()?;
1158
1159        // Set homing config TODO: move parameter to config
1160        let home_position_m = 50e-9;
1161        self.driver
1162            .client_mut()
1163            .z_ctrl_home_props_set(nanonis_rs::z_ctrl::ZHomeMode::Relative, home_position_m)?;
1164
1165        // Set correct safe tip config
1166        let safe_tip_threshold = self.config.safe_tip_threshold;
1167        self.driver
1168            .client_mut()
1169            .safe_tip_props_set(false, true, safe_tip_threshold)?;
1170
1171        // Update some random User Output to update TCP Channel List
1172        // Should be fixed in next Nanonis Software Update
1173        let output_to_toggle = 3;
1174        let current_mode = self
1175            .driver
1176            .client_mut()
1177            .user_out_mode_get(output_to_toggle)?;
1178
1179        match current_mode {
1180            nanonis_rs::user_out::OutputMode::UserOutput => {
1181                self.driver.client_mut().user_out_mode_set(
1182                    output_to_toggle,
1183                    nanonis_rs::user_out::OutputMode::Monitor,
1184                )?;
1185                self.driver
1186                    .client_mut()
1187                    .user_out_mode_set(output_to_toggle, current_mode)?;
1188            }
1189            nanonis_rs::user_out::OutputMode::CalcSignal => {
1190                self.driver.client_mut().user_out_mode_set(
1191                    output_to_toggle,
1192                    nanonis_rs::user_out::OutputMode::UserOutput,
1193                )?;
1194                self.driver
1195                    .client_mut()
1196                    .user_out_mode_set(output_to_toggle, current_mode)?;
1197            }
1198            nanonis_rs::user_out::OutputMode::Monitor => {
1199                self.driver.client_mut().user_out_mode_set(
1200                    output_to_toggle,
1201                    nanonis_rs::user_out::OutputMode::CalcSignal,
1202                )?;
1203                self.driver
1204                    .client_mut()
1205                    .user_out_mode_set(output_to_toggle, current_mode)?;
1206            }
1207            nanonis_rs::user_out::OutputMode::Override => {
1208                self.driver.client_mut().user_out_mode_set(
1209                    output_to_toggle,
1210                    nanonis_rs::user_out::OutputMode::Monitor,
1211                )?;
1212                self.driver
1213                    .client_mut()
1214                    .user_out_mode_set(output_to_toggle, current_mode)?;
1215            }
1216        }
1217
1218        info!("Executing Initial Approach");
1219        self.set_action(ControllerAction::Approaching);
1220
1221        self.driver
1222            .run(Action::AutoApproach {
1223                wait_until_finished: true,
1224                timeout: Duration::from_secs(600),
1225                center_freq_shift: true,
1226            })
1227            .go()?;
1228
1229        // Clear TCP buffer to discard any stale data from before approach
1230        log::debug!("Clearing TCP buffer to get fresh frequency shift data");
1231        self.driver.clear_tcp_buffer();
1232
1233        // Wait briefly for fresh data to accumulate in buffer
1234        self.driver
1235            .run(Action::Wait {
1236                duration: self.config.buffer_clear_wait,
1237            })
1238            .go()?;
1239
1240        // Wait for signal to stabilize after approach
1241        info!(
1242            "Waiting {:?} for signal to stabilize after approach...",
1243            self.config.post_approach_settle
1244        );
1245        self.driver
1246            .run(Action::Wait {
1247                duration: self.config.post_approach_settle,
1248            })
1249            .go()?;
1250
1251        let initial_tip_state: TipState = self
1252            .driver
1253            .run(Action::CheckTipState {
1254                method: TipCheckMethod::SignalBounds {
1255                    signal: self.config.freq_shift_signal.clone(),
1256                    bounds: self.config.sharp_tip_bounds,
1257                },
1258            })
1259            .expecting()?;
1260
1261        info!("Current tip shape: {:?}", initial_tip_state.shape);
1262        self.current_tip_shape = initial_tip_state.shape;
1263
1264        Ok(())
1265    }
1266}