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
21struct SweepPlan {
27 starting_bias: f32,
29 bias_range: (f32, f32),
31 index: usize,
33 total: usize,
35}
36
37pub struct TipController {
43 driver: ActionDriver,
44 config: TipControllerConfig,
45
46 current_pulse_voltage: f32,
48 current_tip_shape: TipShape,
49 cycles_without_change: u32,
50 cycle_count: u32,
51
52 signal_histories: HashMap<u8, VecDeque<f32>>, max_history_size: usize,
55
56 max_cycles: Option<usize>,
58 max_duration: Option<Duration>,
59 loop_start_time: Option<std::time::Instant>,
60
61 shutdown_requested: Option<Arc<AtomicBool>>,
63
64 base_polarity: PolaritySign,
66 pulse_count_for_random: u32,
67
68 state_sender: Option<Sender<ControllerState>>,
70 current_action: ControllerAction,
71 measured_freq_shift: Option<f32>,
72}
73
74impl TipController {
75 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 pub fn set_state_sender(&mut self, sender: Sender<ControllerState>) {
112 self.state_sender = Some(sender);
113 }
114
115 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 fn send_state(&self) {
136 if let Some(sender) = &self.state_sender {
137 let _ = sender.try_send(self.snapshot());
138 }
139 }
140
141 fn set_action(&mut self, action: ControllerAction) {
143 self.current_action = action;
144 self.send_state();
145 }
146
147 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 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 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 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 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 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 #[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 #[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 #[allow(dead_code)]
247 pub fn clear_all_histories(&mut self) {
248 self.signal_histories.clear();
249 }
250
251 #[allow(dead_code)]
253 pub fn clear_signal_history(&mut self, signal: &Signal) {
254 self.signal_histories.remove(&signal.index);
255 }
256
257 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 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 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 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(¤t_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
460impl TipController {
465 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 fn run_inner(&mut self) -> Result<(), Error> {
486 self.pre_loop_initialization()?;
487
488 while self.current_tip_shape != TipShape::Stable {
489 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 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 self.check_shutdown()?;
511
512 self.cycle_count += 1;
514 self.measured_freq_shift = None;
515
516 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 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 self.send_state();
556 }
557 Ok(())
558 }
559
560 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 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 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 self.update_pulse_voltage();
653
654 Ok(())
655 }
656
657 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 !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 let final_freq_shift = self.measure_final_freq_shift()?;
723
724 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 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 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 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 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 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 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 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 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 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 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 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 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 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 log::debug!("Clearing TCP buffer to get fresh frequency shift data");
1231 self.driver.clear_tcp_buffer();
1232
1233 self.driver
1235 .run(Action::Wait {
1236 duration: self.config.buffer_clear_wait,
1237 })
1238 .go()?;
1239
1240 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}