1use log::info;
2use ndarray::Array1;
3
4use crate::actions::{Action, ActionChain, ActionResult, ExpectFromAction};
5use crate::error::NanonisError;
6use crate::nanonis::NanonisClient;
7use crate::types::{
8 DataToGet, MotorGroup, OsciData, Position, PulseMode, ScanDirection, SignalIndex, SignalStats,
9 TriggerConfig, ZControllerHold,
10};
11use crate::utils::{poll_until, poll_with_timeout, PollError};
12use crate::TipShaperConfig;
13use std::collections::HashMap;
14use std::thread;
15use std::time::Duration;
16
17pub struct ActionDriver {
20 client: NanonisClient,
21 stored_values: HashMap<String, ActionResult>,
23}
24
25impl ActionDriver {
26 pub fn new(addr: &str, port: u16) -> Result<Self, NanonisError> {
28 let client = NanonisClient::new(addr, port)?;
29
30 Ok(Self {
31 client,
32 stored_values: HashMap::new(),
33 })
34 }
35
36 pub fn with_nanonis_client(client: NanonisClient) -> Self {
38 Self {
39 client,
40 stored_values: HashMap::new(),
41 }
42 }
43
44 pub fn client(&self) -> &NanonisClient {
46 &self.client
47 }
48
49 pub fn client_mut(&mut self) -> &mut NanonisClient {
51 &mut self.client
52 }
53
54 pub fn execute(&mut self, action: Action) -> Result<ActionResult, NanonisError> {
56 match action {
57 Action::ReadSignal {
59 signal,
60 wait_for_newest,
61 } => {
62 let value = self
63 .client
64 .signals_vals_get(vec![signal.into()], wait_for_newest)?;
65 Ok(ActionResult::Value(value[0] as f64))
66 }
67
68 Action::ReadSignals {
69 signals,
70 wait_for_newest,
71 } => {
72 let indices: Vec<i32> = signals.iter().map(|s| (*s).into()).collect();
73 let values = self.client.signals_vals_get(indices, wait_for_newest)?;
74 Ok(ActionResult::Values(
75 values.into_iter().map(|v| v as f64).collect(),
76 ))
77 }
78
79 Action::ReadSignalNames => {
80 let names = self.client.signal_names_get(false)?;
81 Ok(ActionResult::Text(names))
82 }
83
84 Action::ReadBias => {
86 let bias = self.client.get_bias()?;
87 Ok(ActionResult::Value(bias as f64))
88 }
89
90 Action::SetBias { voltage } => {
91 self.client.set_bias(voltage)?;
92 Ok(ActionResult::Success)
93 }
94
95 Action::ReadOsci {
97 signal,
98 trigger,
99 data_to_get,
100 is_stable,
101 } => {
102 self.client.osci1t_run()?;
103
104 self.client.osci1t_ch_set(signal.0)?;
105
106 if let Some(trigger) = trigger {
107 self.client.osci1t_trig_set(
108 trigger.mode.into(),
109 trigger.slope.into(),
110 trigger.level,
111 trigger.hysteresis,
112 )?;
113 }
114
115 match data_to_get {
116 crate::types::DataToGet::Stable { readings, timeout } => {
117 let osci_data = self.find_stable_oscilloscope_data_with_fallback(
118 data_to_get,
119 readings,
120 timeout,
121 0.01,
122 50e-15,
123 0.8,
124 is_stable,
125 )?;
126 Ok(ActionResult::OsciData(osci_data))
127 }
128 _ => {
129 let data_mode = match data_to_get {
131 DataToGet::Current => 0,
132 DataToGet::NextTrigger => 1,
133 DataToGet::Wait2Triggers => 2,
134 DataToGet::Stable { .. } => 1, };
136 let (t0, dt, size, data) = self.client.osci1t_data_get(data_mode)?;
137 let osci_data = OsciData::new_stable(t0, dt, size, data);
138 Ok(ActionResult::OsciData(osci_data))
139 }
140 }
141 }
142
143 Action::ReadPiezoPosition {
145 wait_for_newest_data,
146 } => {
147 let pos = self.client.folme_xy_pos_get(wait_for_newest_data)?;
148 Ok(ActionResult::Position(pos))
149 }
150
151 Action::SetPiezoPosition {
152 position,
153 wait_until_finished,
154 } => {
155 self.client
156 .folme_xy_pos_set(position, wait_until_finished)?;
157 Ok(ActionResult::Success)
158 }
159
160 Action::MovePiezoRelative { delta } => {
161 let current = self.client.folme_xy_pos_get(true)?;
163 info!("Current position: {current:?}");
164 let new_position = Position {
165 x: current.x + delta.x,
166 y: current.y + delta.y,
167 };
168 self.client.folme_xy_pos_set(new_position, true)?;
169 Ok(ActionResult::Success)
170 }
171
172 Action::MoveMotor { direction, steps } => {
174 self.client.motor_start_move(
175 direction,
176 steps,
177 MotorGroup::Group1,
178 true, )?;
180 Ok(ActionResult::Success)
181 }
182
183 Action::MoveMotorClosedLoop { target, mode } => {
184 self.client.motor_start_closed_loop(
185 mode,
186 target,
187 true, MotorGroup::Group1,
189 )?;
190 Ok(ActionResult::Success)
191 }
192
193 Action::StopMotor => {
194 self.client.motor_stop_move()?;
195 Ok(ActionResult::Success)
196 }
197
198 Action::AutoApproach {
200 wait_until_finished,
201 timeout,
202 } => {
203 log::debug!(
204 "Starting auto-approach (wait: {}, timeout: {:?})",
205 wait_until_finished,
206 timeout
207 );
208
209 match self.client.auto_approach_on_off_get() {
211 Ok(true) => {
212 log::warn!("Auto-approach already running");
213 return Ok(ActionResult::Success); }
215 Ok(false) => {
216 log::debug!("Auto-approach is idle, proceeding to start");
217 }
218 Err(_) => {
219 log::warn!("Auto-approach status unknown, attempting to proceed");
220 }
221 }
222
223 if let Err(e) = self.client.auto_approach_open() {
225 log::error!("Failed to open auto-approach module: {}", e);
226 return Err(NanonisError::InvalidCommand(format!(
227 "Failed to open auto-approach module: {}",
228 e
229 )));
230 }
231
232 std::thread::sleep(std::time::Duration::from_millis(500));
234
235 if let Err(e) = self.client.auto_approach_on_off_set(true) {
237 log::error!("Failed to start auto-approach: {}", e);
238 return Err(NanonisError::InvalidCommand(format!(
239 "Failed to start auto-approach: {}",
240 e
241 )));
242 }
243
244 if !wait_until_finished {
245 log::debug!("Auto-approach started, not waiting for completion");
246 return Ok(ActionResult::Success);
247 }
248
249 log::debug!("Waiting for auto-approach to complete...");
251 let poll_interval = std::time::Duration::from_millis(100);
252
253 match poll_until(
254 || {
255 self.client
257 .auto_approach_on_off_get()
258 .map(|running| !running)
259 },
260 timeout,
261 poll_interval,
262 ) {
263 Ok(()) => {
264 log::debug!("Auto-approach completed successfully");
265 Ok(ActionResult::Success)
266 }
267 Err(PollError::Timeout) => {
268 log::warn!("Auto-approach timed out after {:?}", timeout);
269 let _ = self.client.auto_approach_on_off_set(false);
271 Err(NanonisError::InvalidCommand(
272 "Auto-approach timed out".to_string(),
273 ))
274 }
275 Err(PollError::ConditionError(e)) => {
276 log::error!("Error checking auto-approach status: {}", e);
277 Err(NanonisError::InvalidCommand(format!(
278 "Status check error: {}",
279 e
280 )))
281 }
282 }
283 }
284
285 Action::Withdraw {
286 wait_until_finished,
287 timeout,
288 } => {
289 self.client.z_ctrl_withdraw(wait_until_finished, timeout)?;
290 Ok(ActionResult::Success)
291 }
292
293 Action::SetZSetpoint { setpoint } => {
294 self.client.z_ctrl_setpoint_set(setpoint)?;
295 Ok(ActionResult::Success)
296 }
297
298 Action::ScanControl { action } => {
300 self.client.scan_action(action, ScanDirection::Up)?;
301 Ok(ActionResult::Success)
302 }
303
304 Action::ReadScanStatus => {
305 let is_scanning = self.client.scan_status_get()?;
306 Ok(ActionResult::Status(is_scanning))
307 }
308
309 Action::BiasPulse {
311 wait_until_done,
312 pulse_width,
313 bias_value_v,
314 z_controller_hold,
315 pulse_mode,
316 } => {
317 let hold_enum = match z_controller_hold {
319 0 => ZControllerHold::NoChange,
320 1 => ZControllerHold::Hold,
321 2 => ZControllerHold::Release,
322 _ => ZControllerHold::NoChange, };
324
325 let mode_enum = match pulse_mode {
326 0 => PulseMode::Keep,
327 1 => PulseMode::Relative,
328 2 => PulseMode::Absolute,
329 _ => PulseMode::Keep, };
331
332 self.client.bias_pulse(
333 wait_until_done,
334 pulse_width.as_secs_f32(),
335 bias_value_v,
336 hold_enum.into(),
337 mode_enum.into(),
338 )?;
339
340 Ok(ActionResult::Success)
341 }
342
343 Action::TipShaper {
344 config,
345 wait_until_finished,
346 timeout,
347 } => {
348 self.client.tip_shaper_props_set(config)?;
350
351 self.client.tip_shaper_start(wait_until_finished, timeout)?;
353
354 Ok(ActionResult::Success)
355 }
356
357 Action::PulseRetract {
358 pulse_width,
359 pulse_height_v,
360 } => {
361 let current_bias = self.client_mut().get_bias().unwrap_or(500e-3);
362 let config = TipShaperConfig {
363 switch_off_delay: std::time::Duration::from_millis(10),
364 change_bias: true,
365 bias_v: pulse_height_v,
366 tip_lift_m: 0.0,
367 lift_time_1: pulse_width,
368 bias_lift_v: current_bias,
369 bias_settling_time: std::time::Duration::from_millis(50),
370 lift_height_m: 10e-9,
371 lift_time_2: std::time::Duration::from_millis(100),
372 end_wait_time: std::time::Duration::from_millis(50),
373 restore_feedback: false,
374 };
375
376 self.client_mut().tip_shaper_props_set(config)?;
378 self.client_mut()
379 .tip_shaper_start(true, Duration::from_secs(5))?;
380
381 Ok(ActionResult::Success)
382 }
383
384 Action::Wait { duration } => {
385 thread::sleep(duration);
386 Ok(ActionResult::None)
387 }
388
389 Action::Store { key, action } => {
391 let result = self.execute(*action)?;
392 self.stored_values.insert(key, result.clone());
393 Ok(result) }
395
396 Action::Retrieve { key } => match self.stored_values.get(&key) {
397 Some(value) => Ok(value.clone()), None => Err(NanonisError::InvalidCommand(format!(
399 "No stored value found for key: {}",
400 key
401 ))),
402 },
403 }
404 }
405
406 pub fn execute_expecting<T>(&mut self, action: Action) -> Result<T, NanonisError>
426 where
427 ActionResult: ExpectFromAction<T>,
428 {
429 let result = self.execute(action.clone())?;
430 Ok(result.expect_from_action(&action))
431 }
432
433 fn find_stable_oscilloscope_data(
439 &mut self,
440 _data_to_get: DataToGet,
441 readings: u32,
442 timeout: std::time::Duration,
443 relative_threshold: f64,
444 absolute_threshold: f64,
445 min_window_percent: f64,
446 stability_fn: Option<fn(&[f64]) -> bool>,
447 ) -> Result<Option<OsciData>, NanonisError> {
448 match poll_with_timeout(
449 || {
450 for _attempt in 0..readings {
452 let (t0, dt, size, data) = self.client.osci1t_data_get(2)?; if let Some(stable_osci_data) = self.analyze_stability_window(
455 t0,
456 dt,
457 size,
458 data,
459 relative_threshold,
460 absolute_threshold,
461 min_window_percent,
462 stability_fn,
463 )? {
464 return Ok(Some(stable_osci_data));
465 }
466
467 std::thread::sleep(std::time::Duration::from_millis(100));
469 }
470
471 Ok(None)
473 },
474 timeout,
475 std::time::Duration::from_millis(50), ) {
477 Ok(Some(result)) => Ok(Some(result)),
478 Ok(None) => Ok(None), Err(PollError::ConditionError(e)) => Err(e),
480 Err(PollError::Timeout) => unreachable!(), }
482 }
483
484 fn analyze_stability_window(
486 &self,
487 t0: f64,
488 dt: f64,
489 size: i32,
490 data: Vec<f64>,
491 relative_threshold: f64,
492 absolute_threshold: f64,
493 min_window_percent: f64,
494 stability_fn: Option<fn(&[f64]) -> bool>,
495 ) -> Result<Option<OsciData>, NanonisError> {
496 let min_window = (size as f64 * min_window_percent) as usize;
497 let mut start = 0;
498 let mut end = size as usize;
499
500 while (end - start) > min_window {
501 let window = &data[start..end];
502 let arr = Array1::from_vec(window.to_vec());
503 let mean = arr.mean().expect(
504 "There must be an non-empty array, osci1t_data_get would have returned early.",
505 );
506 let std_dev = arr.std(0.0);
507 let relative_std = std_dev / mean.abs();
508
509 let is_stable = if let Some(stability_fn) = stability_fn {
511 stability_fn(window)
512 } else {
513 let is_relative_stable = relative_std < relative_threshold;
515 let is_absolute_stable = std_dev < absolute_threshold;
516 is_relative_stable || is_absolute_stable
517 };
518
519 if is_stable {
520 let stable_data = window.to_vec();
521 let stability_method = if stability_fn.is_some() {
522 "custom".to_string()
523 } else {
524 let is_relative_stable = relative_std < relative_threshold;
526 let is_absolute_stable = std_dev < absolute_threshold;
527 match (is_relative_stable, is_absolute_stable) {
528 (true, true) => "both".to_string(),
529 (true, false) => "relative".to_string(),
530 (false, true) => "absolute".to_string(),
531 (false, false) => unreachable!(),
532 }
533 };
534
535 let stats = SignalStats {
536 mean,
537 std_dev,
538 relative_std,
539 window_size: stable_data.len(),
540 stability_method,
541 };
542
543 let mut osci_data =
544 OsciData::new_with_stats(t0, dt, stable_data.len() as i32, stable_data, stats);
545 osci_data.is_stable = true; return Ok(Some(osci_data));
547 }
548
549 let shrink = ((end - start) / 10).max(1);
550 start += shrink;
551 end -= shrink;
552 }
553
554 Ok(None)
556 }
557
558 fn find_stable_oscilloscope_data_with_fallback(
565 &mut self,
566 data_to_get: DataToGet,
567 readings: u32,
568 timeout: std::time::Duration,
569 relative_threshold: f64,
570 absolute_threshold: f64,
571 min_window_percent: f64,
572 stability_fn: Option<fn(&[f64]) -> bool>,
573 ) -> Result<OsciData, NanonisError> {
574 if let Some(stable_osci_data) = self.find_stable_oscilloscope_data(
576 data_to_get,
577 readings,
578 timeout,
579 relative_threshold,
580 absolute_threshold,
581 min_window_percent,
582 stability_fn,
583 )? {
584 return Ok(stable_osci_data);
585 }
586
587 let (t0, dt, size, data) = self.client.osci1t_data_get(1)?; let fallback_value = if !data.is_empty() {
592 data.iter().sum::<f64>() / data.len() as f64
593 } else {
594 0.0
595 };
596
597 Ok(OsciData::new_unstable_with_fallback(
598 t0,
599 dt,
600 size,
601 data,
602 fallback_value,
603 ))
604 }
605
606 pub fn execute_chain(
608 &mut self,
609 chain: impl Into<ActionChain>,
610 ) -> Result<Vec<ActionResult>, NanonisError> {
611 let chain = chain.into();
612 let mut results = Vec::with_capacity(chain.len());
613
614 for action in chain.into_iter() {
615 let result = self.execute(action)?;
616 results.push(result);
617 }
618
619 Ok(results)
620 }
621
622 pub fn execute_chain_final(
624 &mut self,
625 chain: impl Into<ActionChain>,
626 ) -> Result<ActionResult, NanonisError> {
627 let results = self.execute_chain(chain)?;
628 Ok(results.into_iter().last().unwrap_or(ActionResult::None))
629 }
630
631 pub fn execute_chain_partial(
633 &mut self,
634 chain: impl Into<ActionChain>,
635 ) -> Result<Vec<ActionResult>, (Vec<ActionResult>, NanonisError)> {
636 let chain = chain.into();
637 let mut results = Vec::new();
638
639 for action in chain.into_iter() {
640 match self.execute(action) {
641 Ok(result) => results.push(result),
642 Err(error) => return Err((results, error)),
643 }
644 }
645
646 Ok(results)
647 }
648
649 pub fn clear_storage(&mut self) {
651 self.stored_values.clear();
652 }
653
654 pub fn stored_keys(&self) -> Vec<&String> {
656 self.stored_values.keys().collect()
657 }
658
659 pub fn read_oscilloscope(
661 &mut self,
662 signal: SignalIndex,
663 trigger: Option<TriggerConfig>,
664 data_to_get: DataToGet,
665 ) -> Result<Option<OsciData>, NanonisError> {
666 match self.execute(Action::ReadOsci {
667 signal,
668 trigger,
669 data_to_get,
670 is_stable: None,
671 })? {
672 ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
673 ActionResult::None => Ok(None),
674 _ => Err(NanonisError::InvalidCommand(
675 "Expected oscilloscope data".into(),
676 )),
677 }
678 }
679
680 pub fn read_oscilloscope_with_stability(
682 &mut self,
683 signal: SignalIndex,
684 trigger: Option<TriggerConfig>,
685 data_to_get: DataToGet,
686 is_stable: fn(&[f64]) -> bool,
687 ) -> Result<Option<OsciData>, NanonisError> {
688 match self.execute(Action::ReadOsci {
689 signal,
690 trigger,
691 data_to_get,
692 is_stable: Some(is_stable),
693 })? {
694 ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
695 ActionResult::None => Ok(None),
696 _ => Err(NanonisError::InvalidCommand(
697 "Expected oscilloscope data".into(),
698 )),
699 }
700 }
701}
702
703pub mod stability {
705 pub fn dual_threshold_stability(window: &[f64]) -> bool {
708 if window.len() < 3 {
709 return false;
710 }
711
712 let mean = window.iter().sum::<f64>() / window.len() as f64;
713 let variance = window.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / window.len() as f64;
714 let std_dev = variance.sqrt();
715 let relative_std = std_dev / mean.abs();
716
717 relative_std < 0.05 || std_dev < 50e-15
719 }
720
721 pub fn trend_analysis_stability(window: &[f64]) -> bool {
724 if window.len() < 5 {
725 return false;
726 }
727
728 let n = window.len() as f64;
730 let x_mean = (n - 1.0) / 2.0; let y_mean = window.iter().sum::<f64>() / n;
732
733 let mut numerator = 0.0;
734 let mut denominator = 0.0;
735
736 for (i, &y) in window.iter().enumerate() {
737 let x = i as f64;
738 numerator += (x - x_mean) * (y - y_mean);
739 denominator += (x - x_mean).powi(2);
740 }
741
742 let slope = if denominator != 0.0 {
743 numerator / denominator
744 } else {
745 0.0
746 };
747
748 let signal_level = y_mean.abs();
750 let noise_level = {
751 let variance = window.iter().map(|y| (y - y_mean).powi(2)).sum::<f64>() / n;
752 variance.sqrt()
753 };
754
755 let snr = if noise_level != 0.0 {
756 signal_level / noise_level
757 } else {
758 f64::INFINITY
759 };
760
761 slope.abs() < 0.001 && snr > 10.0
763 }
764}
765
766#[derive(Debug, Clone)]
768pub struct ExecutionStats {
769 pub total_actions: usize,
770 pub successful_actions: usize,
771 pub failed_actions: usize,
772 pub total_duration: std::time::Duration,
773}
774
775impl ExecutionStats {
776 pub fn success_rate(&self) -> f64 {
777 if self.total_actions == 0 {
778 0.0
779 } else {
780 self.successful_actions as f64 / self.total_actions as f64
781 }
782 }
783}
784
785impl ActionDriver {
787 pub fn execute_chain_with_stats(
789 &mut self,
790 chain: impl Into<ActionChain>,
791 ) -> Result<(Vec<ActionResult>, ExecutionStats), NanonisError> {
792 let chain = chain.into();
793 let start_time = std::time::Instant::now();
794 let mut results = Vec::with_capacity(chain.len());
795 let mut successful = 0;
796 let failed = 0;
797
798 for action in chain.into_iter() {
799 match self.execute(action) {
800 Ok(result) => {
801 results.push(result);
802 successful += 1;
803 }
804 Err(e) => {
805 return Err(e);
809 }
810 }
811 }
812
813 let stats = ExecutionStats {
814 total_actions: results.len(),
815 successful_actions: successful,
816 failed_actions: failed,
817 total_duration: start_time.elapsed(),
818 };
819
820 Ok((results, stats))
821 }
822}
823
824#[cfg(test)]
825mod tests {
826 use std::time::Duration;
827
828 use super::*;
829 #[test]
833 fn test_action_translator_interface() {
834 let driver_result = ActionDriver::new("127.0.0.1", 6501);
838 match driver_result {
839 Ok(mut driver) => {
840 let action = Action::ReadBias;
842 let _result = driver.execute(action);
843
844 let chain = ActionChain::new(vec![
849 Action::ReadBias,
850 Action::Wait {
851 duration: Duration::from_millis(500),
852 },
853 Action::SetBias { voltage: 1.0 },
854 ]);
855
856 let _chain_result = driver.execute_chain(chain);
857 }
858 Err(_) => {
859 println!("Signal discovery failed - this is expected without hardware");
861 }
862 }
863 }
864}