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