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
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
189 % switch.switch_every_n_pulses
190 == 0
191 }
192 _ => false,
193 }
194 }
195
196 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 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 #[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 #[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 #[allow(dead_code)]
252 pub fn clear_all_histories(&mut self) {
253 self.signal_histories.clear();
254 }
255
256 #[allow(dead_code)]
258 pub fn clear_signal_history(&mut self, signal: &Signal) {
259 self.signal_histories.remove(&signal.index);
260 }
261
262 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 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 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 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(¤t_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
474impl TipController {
479 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 fn run_inner(&mut self) -> Result<(), Error> {
500 self.pre_loop_initialization()?;
501
502 while self.current_tip_shape != TipShape::Stable {
503 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 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 self.check_shutdown()?;
527
528 self.cycle_count += 1;
530 self.measured_freq_shift = None;
531
532 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 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 self.send_state();
572 }
573 Ok(())
574 }
575
576 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 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 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 self.update_pulse_voltage();
669
670 Ok(())
671 }
672
673 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 !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 let final_freq_shift = self.measure_final_freq_shift()?;
745
746 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 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 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 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 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 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 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 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 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 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 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 let home_position_m = 50e-9;
1203 self.driver
1204 .client_mut()
1205 .z_ctrl_home_props_set(2, home_position_m)?;
1206
1207 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 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 log::debug!("Clearing TCP buffer to get fresh frequency shift data");
1275 self.driver.clear_tcp_buffer();
1276
1277 self.driver
1279 .run(Action::Wait {
1280 duration: self.config.buffer_clear_wait,
1281 })
1282 .go()?;
1283
1284 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}