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