1use std::{
2 collections::HashMap,
3 sync::{
4 atomic::{AtomicBool, Ordering},
5 Arc,
6 },
7 thread,
8 time::{Duration, Instant},
9};
10
11use log::{debug, info, warn};
12use nanonis_rs::signals::SignalIndex;
13use ndarray::Array1;
14
15use crate::{
16 actions::{
17 Action, ActionChain, ActionLogEntry, ActionLogResult, ActionResult, ExpectFromAction,
18 },
19 buffered_tcp_reader::BufferedTCPReader,
20 controller_types::TipStateConfig,
21 signal_registry::SignalRegistry,
22 types::{DataToGet, OsciData, SignalStats, TriggerConfig},
23 utils::{poll_until, poll_with_timeout, PollError},
24 MotorGroup, NanonisClient, NanonisError, Position, PulseMode, ScanAction, ScanDirection,
25 Signal, TipShaperConfig, ZControllerHold,
26};
27
28#[derive(Debug, Clone)]
30pub struct TCPReaderConfig {
31 pub stream_port: u16,
33 pub channels: Vec<i32>,
35 pub oversampling: i32,
37 pub auto_start: bool,
39 pub buffer_size: Option<usize>,
42}
43
44impl Default for TCPReaderConfig {
45 fn default() -> Self {
46 Self {
47 stream_port: 6590,
48 channels: (0..=23).collect(),
49 oversampling: 20,
50 auto_start: true,
51 buffer_size: Some(10_000),
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
58pub enum ActionRequest {
59 Single(Action),
61 Chain(Vec<Action>),
63}
64
65impl From<Action> for ActionRequest {
66 fn from(action: Action) -> Self {
67 ActionRequest::Single(action)
68 }
69}
70
71impl From<Vec<Action>> for ActionRequest {
72 fn from(actions: Vec<Action>) -> Self {
73 ActionRequest::Chain(actions)
74 }
75}
76
77impl From<ActionChain> for ActionRequest {
78 fn from(chain: ActionChain) -> Self {
79 ActionRequest::Chain(chain.into_iter().collect())
80 }
81}
82
83impl ActionRequest {
84 pub fn is_single(&self) -> bool {
85 matches!(self, ActionRequest::Single(_))
86 }
87
88 pub fn is_chain(&self) -> bool {
89 matches!(self, ActionRequest::Chain(_))
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct ExecutionConfig {
96 pub data_collection: Option<(Duration, Duration)>,
98 pub chain_behavior: ChainBehavior,
100 pub logging_behavior: LoggingBehavior,
102 pub performance_mode: PerformanceMode,
104}
105
106#[derive(Debug, Clone)]
107pub enum ChainBehavior {
108 Complete,
110 FinalOnly,
112 Partial,
114}
115
116#[derive(Debug, Clone)]
117pub enum LoggingBehavior {
118 Normal,
120 Deferred,
122 Disabled,
124}
125
126#[derive(Debug, Clone)]
127pub enum PerformanceMode {
128 Normal,
130 Fast,
132}
133
134impl Default for ExecutionConfig {
135 fn default() -> Self {
136 Self {
137 data_collection: None,
138 chain_behavior: ChainBehavior::Complete,
139 logging_behavior: LoggingBehavior::Normal,
140 performance_mode: PerformanceMode::Normal,
141 }
142 }
143}
144
145impl ExecutionConfig {
146 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn with_data_collection(mut self, pre_duration: Duration, post_duration: Duration) -> Self {
153 self.data_collection = Some((pre_duration, post_duration));
154 self
155 }
156
157 pub fn final_only(mut self) -> Self {
159 self.chain_behavior = ChainBehavior::FinalOnly;
160 self
161 }
162
163 pub fn partial(mut self) -> Self {
165 self.chain_behavior = ChainBehavior::Partial;
166 self
167 }
168
169 pub fn deferred_logging(mut self) -> Self {
171 self.logging_behavior = LoggingBehavior::Deferred;
172 self
173 }
174
175 pub fn no_logging(mut self) -> Self {
177 self.logging_behavior = LoggingBehavior::Disabled;
178 self
179 }
180
181 pub fn fast_mode(mut self) -> Self {
183 self.performance_mode = PerformanceMode::Fast;
184 self
185 }
186}
187
188#[derive(Debug)]
190pub enum ExecutionResult {
191 Single(ActionResult),
193 Chain(Vec<ActionResult>),
195 ExperimentData(crate::types::ExperimentData),
197 ChainExperimentData(crate::types::ChainExperimentData),
199 Partial(Vec<ActionResult>, NanonisError),
201}
202
203impl ExecutionResult {
204 pub fn into_single(self) -> Result<ActionResult, NanonisError> {
206 match self {
207 ExecutionResult::Single(result) => Ok(result),
208 ExecutionResult::Chain(mut results) if results.len() == 1 => Ok(results.pop().unwrap()),
209 _ => Err(NanonisError::Protocol("Expected single result".to_string())),
210 }
211 }
212
213 pub fn into_chain(self) -> Result<Vec<ActionResult>, NanonisError> {
215 match self {
216 ExecutionResult::Chain(results) => Ok(results),
217 ExecutionResult::Single(result) => Ok(vec![result]),
218 ExecutionResult::Partial(results, _) => Ok(results),
219 _ => Err(NanonisError::Protocol("Expected chain results".to_string())),
220 }
221 }
222
223 pub fn into_experiment_data(self) -> Result<crate::types::ExperimentData, NanonisError> {
225 match self {
226 ExecutionResult::ExperimentData(data) => Ok(data),
227 _ => Err(NanonisError::Protocol(
228 "Expected experiment data".to_string(),
229 )),
230 }
231 }
232
233 pub fn into_chain_experiment_data(
235 self,
236 ) -> Result<crate::types::ChainExperimentData, NanonisError> {
237 match self {
238 ExecutionResult::ChainExperimentData(data) => Ok(data),
239 _ => Err(NanonisError::Protocol(
240 "Expected chain experiment data".to_string(),
241 )),
242 }
243 }
244
245 pub fn expecting<T>(self) -> Result<T, NanonisError>
247 where
248 Self: ExpectFromExecution<T>,
249 {
250 self.expect_from_execution()
251 }
252}
253
254pub trait ExpectFromExecution<T> {
256 fn expect_from_execution(self) -> Result<T, NanonisError>;
257}
258
259pub struct ExecutionBuilder<'a> {
261 driver: &'a mut ActionDriver,
262 request: ActionRequest,
263 config: ExecutionConfig,
264}
265
266impl<'a> ExecutionBuilder<'a> {
267 fn new(driver: &'a mut ActionDriver, request: ActionRequest) -> Self {
268 Self {
269 driver,
270 request,
271 config: ExecutionConfig::default(),
272 }
273 }
274
275 pub fn with_data_collection(mut self, pre_duration: Duration, post_duration: Duration) -> Self {
277 self.config = self
278 .config
279 .with_data_collection(pre_duration, post_duration);
280 self
281 }
282
283 pub fn final_only(mut self) -> Self {
285 self.config = self.config.final_only();
286 self
287 }
288
289 pub fn partial(mut self) -> Self {
291 self.config = self.config.partial();
292 self
293 }
294
295 pub fn deferred_logging(mut self) -> Self {
297 self.config = self.config.deferred_logging();
298 self
299 }
300
301 pub fn no_logging(mut self) -> Self {
303 self.config = self.config.no_logging();
304 self
305 }
306
307 pub fn fast_mode(mut self) -> Self {
309 self.config = self.config.fast_mode();
310 self
311 }
312
313 pub fn expecting<T>(self) -> Result<T, NanonisError>
315 where
316 ExecutionResult: ExpectFromExecution<T>,
317 {
318 let result = self.driver.run_with_config(self.request, self.config)?;
319 result.expecting()
320 }
321
322 pub fn execute(self) -> Result<ExecutionResult, NanonisError> {
324 self.driver.run_with_config(self.request, self.config)
325 }
326}
327
328impl<'a> ExecutionBuilder<'a> {
329 pub fn go(self) -> Result<ActionResult, NanonisError> {
331 match self.request {
332 ActionRequest::Single(_) => {
333 let result = self.driver.run_with_config(self.request, self.config)?;
334 result.into_single()
335 }
336 ActionRequest::Chain(_) => Err(NanonisError::Protocol(
337 "Use .execute() for chains, .go() is only for single actions".to_string(),
338 )),
339 }
340 }
341}
342
343#[derive(Debug, Clone)]
345pub struct ActionDriverBuilder {
346 addr: String,
347 port: u16,
348 connection_timeout: Option<Duration>,
349 initial_storage: HashMap<String, ActionResult>,
350 tcp_reader_config: Option<TCPReaderConfig>,
351 action_logger_config: Option<(std::path::PathBuf, usize, bool)>, custom_tcp_mapping: Option<Vec<(u8, u8)>>, shutdown_flag: Option<Arc<AtomicBool>>, tip_state_config: TipStateConfig,
355}
356
357impl ActionDriverBuilder {
358 pub fn new(addr: &str, port: u16) -> Self {
360 Self {
361 addr: addr.to_string(),
362 port,
363 connection_timeout: None,
364 initial_storage: HashMap::new(),
365 tcp_reader_config: None,
366 action_logger_config: None,
367 custom_tcp_mapping: None,
368 shutdown_flag: None,
369 tip_state_config: TipStateConfig::default(),
370 }
371 }
372
373 pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
375 self.connection_timeout = Some(timeout);
376 self
377 }
378
379 pub fn with_initial_storage(mut self, storage: HashMap<String, ActionResult>) -> Self {
381 self.initial_storage = storage;
382 self
383 }
384
385 pub fn with_stored_value(mut self, key: String, value: ActionResult) -> Self {
387 self.initial_storage.insert(key, value);
388 self
389 }
390
391 pub fn with_tcp_reader(mut self, config: TCPReaderConfig) -> Self {
411 if config.buffer_size.is_none() {
412 log::warn!("TCPLoggerConfig buffer_size is None - buffering disabled");
413 }
414 self.tcp_reader_config = Some(config);
415 self
416 }
417
418 pub fn with_action_logging(
443 mut self,
444 file_path: impl Into<std::path::PathBuf>,
445 buffer_size: usize,
446 final_format_json: bool,
447 ) -> Self {
448 self.action_logger_config = Some((file_path.into(), buffer_size, final_format_json));
449 self
450 }
451
452 pub fn with_custom_tcp_mapping(mut self, mapping: &[(u8, u8)]) -> Self {
473 self.custom_tcp_mapping = Some(mapping.to_vec());
474 self
475 }
476
477 pub fn with_shutdown_flag(mut self, flag: Arc<AtomicBool>) -> Self {
482 self.shutdown_flag = Some(flag);
483 self
484 }
485
486 pub fn with_tip_state_config(mut self, config: TipStateConfig) -> Self {
488 self.tip_state_config = config;
489 self
490 }
491
492 pub fn build(self) -> Result<ActionDriver, NanonisError> {
494 let mut client = {
495 let mut builder = NanonisClient::builder().address(&self.addr).port(self.port);
496
497 if let Some(timeout) = self.connection_timeout {
498 builder = builder.connect_timeout(timeout);
499 }
500
501 builder.build()?
502 };
503
504 let tcp_reader = if let Some(ref config) = self.tcp_reader_config {
505 if let Some(buffer_size) = config.buffer_size {
506 client.tcplog_chs_set(config.channels.clone())?;
508 client.tcplog_oversampl_set(config.oversampling)?;
509
510 let reader = crate::buffered_tcp_reader::BufferedTCPReader::new(
512 "127.0.0.1",
513 config.stream_port,
514 buffer_size,
515 config.channels.len() as u32,
516 config.oversampling as f32,
517 )?;
518 log::debug!(
519 "TCP stream connected, buffer capacity: {} frames",
520 buffer_size
521 );
522
523 if config.auto_start {
525 log::debug!("Stopping TCP logger to ensure clean state");
527 let _ = client.tcplog_stop(); std::thread::sleep(std::time::Duration::from_millis(200)); client.tcplog_start()?;
532 log::debug!("TCP logger started, data collection active");
533 }
534
535 Some(reader)
536 } else {
537 None
538 }
539 } else {
540 None
541 };
542
543 let action_logger =
545 if let Some((file_path, buffer_size, final_format_json)) = self.action_logger_config {
546 Some(crate::logger::Logger::new(
547 file_path,
548 buffer_size,
549 final_format_json,
550 ))
551 } else {
552 None
553 };
554
555 let signal_names = client.signal_names_get()?;
557 let signal_registry = if let Some(ref custom_map) = self.custom_tcp_mapping {
558 log::debug!(
559 "Using custom TCP channel mapping with {} entries",
560 custom_map.len()
561 );
562 SignalRegistry::builder()
563 .with_standard_map()
564 .add_tcp_map(custom_map)
565 .from_signal_names(&signal_names)
566 .create_aliases()
567 .build()
568 } else {
569 SignalRegistry::with_hardcoded_tcp_mapping(&signal_names)
570 };
571
572 Ok(ActionDriver {
573 client,
574 stored_values: self.initial_storage,
575 tcp_reader_config: self.tcp_reader_config,
576 tcp_reader,
577 action_logger,
578 action_logging_enabled: true, signal_registry,
580 recent_stable_signals: std::collections::VecDeque::new(),
581 shutdown_flag: self.shutdown_flag,
582 tip_state_config: self.tip_state_config,
583 })
584 }
585}
586
587pub struct ActionDriver {
590 client: NanonisClient,
592 stored_values: HashMap<String, ActionResult>,
594 tcp_reader_config: Option<TCPReaderConfig>,
596 tcp_reader: Option<crate::buffered_tcp_reader::BufferedTCPReader>,
598 action_logger: Option<crate::logger::Logger<crate::actions::ActionLogEntry>>,
600 action_logging_enabled: bool,
602 signal_registry: SignalRegistry,
604 recent_stable_signals:
606 std::collections::VecDeque<(crate::actions::StableSignal, std::time::Instant)>,
607 shutdown_flag: Option<Arc<AtomicBool>>,
609 tip_state_config: TipStateConfig,
611}
612
613impl ActionDriver {
614 pub fn builder(addr: &str, port: u16) -> ActionDriverBuilder {
616 ActionDriverBuilder::new(addr, port)
617 }
618
619 pub fn new(addr: &str, port: u16) -> Result<Self, NanonisError> {
621 Self::builder(addr, port).build()
622 }
623
624 pub fn with_nanonis_client(mut client: NanonisClient) -> Self {
626 let signal_names = client.signal_names_get().unwrap_or_default();
628 let signal_registry = SignalRegistry::with_hardcoded_tcp_mapping(&signal_names);
629
630 Self {
631 client,
632 stored_values: HashMap::new(),
633 tcp_reader_config: None,
634 tcp_reader: None,
635 action_logger: None,
636 action_logging_enabled: false,
637 signal_registry,
638 recent_stable_signals: std::collections::VecDeque::new(),
639 shutdown_flag: None,
640 tip_state_config: TipStateConfig::default(),
641 }
642 }
643
644 pub fn client(&self) -> &NanonisClient {
646 &self.client
647 }
648
649 pub fn client_mut(&mut self) -> &mut NanonisClient {
651 &mut self.client
652 }
653
654 pub fn set_shutdown_flag(&mut self, flag: Arc<AtomicBool>) {
656 self.shutdown_flag = Some(flag);
657 }
658
659 fn is_shutdown_requested(&self) -> bool {
661 self.shutdown_flag
662 .as_ref()
663 .map(|f| f.load(Ordering::SeqCst))
664 .unwrap_or(false)
665 }
666
667 pub fn auto_approach(
672 &mut self,
673 wait_until_finished: bool,
674 timeout: Duration,
675 ) -> Result<(), NanonisError> {
676 match self.client.auto_approach_on_off_get() {
678 Ok(true) => {
679 log::warn!("Auto-approach already running");
680 return Ok(());
681 }
682 Ok(false) => {
683 log::debug!("Auto-approach is idle, proceeding to start");
684 }
685 Err(_) => {
686 log::warn!("Auto-approach status unknown, attempting to proceed");
687 }
688 }
689
690 match self.client.auto_approach_open() {
692 Ok(_) => log::debug!("Opened the auto-approach module"),
693 Err(_) => {
694 log::debug!("Failed to open auto-approach module, already open")
695 }
696 }
697
698 std::thread::sleep(std::time::Duration::from_millis(500));
700
701 if let Err(e) = self.client.auto_approach_on_off_set(true) {
703 log::error!("Failed to start auto-approach: {}", e);
704 return Err(NanonisError::Protocol(format!(
705 "Failed to start auto-approach: {}",
706 e
707 )));
708 }
709
710 if !wait_until_finished {
711 log::debug!("Auto-approach started, not waiting for completion");
712 return Ok(());
713 }
714
715 log::debug!("Waiting for auto-approach to complete...");
717 let poll_interval = std::time::Duration::from_millis(100);
718
719 match poll_until(
720 || {
721 self.client
722 .auto_approach_on_off_get()
723 .map(|running| !running)
724 },
725 timeout,
726 poll_interval,
727 ) {
728 Ok(()) => {
729 log::debug!("Auto-approach completed successfully");
730 Ok(())
731 }
732 Err(PollError::Timeout) => {
733 log::warn!("Auto-approach timed out after {:?}", timeout);
734 let _ = self.client.auto_approach_on_off_set(false);
735 Err(NanonisError::Protocol(
736 "Auto-approach timed out".to_string(),
737 ))
738 }
739 Err(PollError::ConditionError(e)) => {
740 log::error!("Error checking auto-approach status: {}", e);
741 Err(NanonisError::Protocol(format!("Status check error: {}", e)))
742 }
743 }
744 }
745
746 pub fn center_freq_shift(&mut self) -> Result<(), NanonisError> {
748 let modulator_index = 1;
749 log::debug!("Centering frequency shift");
750 self.client.pll_freq_shift_auto_center(modulator_index)
751 }
752
753 pub fn tcp_reader_config(&self) -> Option<&TCPReaderConfig> {
755 self.tcp_reader_config.as_ref()
756 }
757
758 pub fn has_tcp_reader(&self) -> bool {
760 self.tcp_reader.is_some()
761 }
762
763 pub fn tcp_reader_mut(&mut self) -> Option<&mut BufferedTCPReader> {
764 self.tcp_reader.as_mut()
765 }
766
767 pub fn clear_tcp_buffer(&self) {
772 if let Some(ref tcp_reader) = self.tcp_reader {
773 tcp_reader.clear_buffer();
774 debug!("TCP reader buffer cleared");
775 } else {
776 warn!("No TCP reader available to clear");
777 }
778 }
779
780 pub fn signal_registry(&self) -> &SignalRegistry {
782 &self.signal_registry
783 }
784
785 fn calculate_samples_for_duration(&self, target_duration: Duration) -> Option<usize> {
799 if let Some(ref config) = self.tcp_reader_config {
800 let base_rate = 2000.0; let effective_rate = base_rate / config.oversampling as f64;
805 let samples = (effective_rate * target_duration.as_secs_f64()).ceil() as usize;
806 log::debug!(
807 "Calculated {} samples for {:.0}ms (base: {}Hz, oversampling: {}, effective: {:.1}Hz)",
808 samples,
809 target_duration.as_millis(),
810 base_rate,
811 config.oversampling,
812 effective_rate
813 );
814 Some(samples.max(50)) } else {
816 None
817 }
818 }
819
820 pub fn run<R>(&mut self, request: R) -> ExecutionBuilder<'_>
841 where
842 R: Into<ActionRequest>,
843 {
844 ExecutionBuilder::new(self, request.into())
845 }
846
847 pub fn run_with_config(
849 &mut self,
850 request: ActionRequest,
851 config: ExecutionConfig,
852 ) -> Result<ExecutionResult, NanonisError> {
853 match (&request, &config.data_collection) {
854 (ActionRequest::Single(action), Some((pre_duration, post_duration))) => {
856 let experiment_data = self.execute_with_data_collection(
857 action.clone(),
858 *pre_duration,
859 *post_duration,
860 )?;
861 Ok(ExecutionResult::ExperimentData(experiment_data))
862 }
863
864 (ActionRequest::Chain(actions), Some((pre_duration, post_duration))) => {
866 let chain_experiment_data = self.execute_chain_with_data_collection(
867 actions.clone(),
868 *pre_duration,
869 *post_duration,
870 )?;
871 Ok(ExecutionResult::ChainExperimentData(chain_experiment_data))
872 }
873
874 (ActionRequest::Single(action), None) => {
876 let result = match config.logging_behavior {
877 LoggingBehavior::Disabled => {
878 let previous_state = self.set_action_logging_enabled(false);
879 let result = self.execute(action.clone());
880 self.set_action_logging_enabled(previous_state);
881 result
882 }
883 _ => self.execute(action.clone()),
884 }?;
885 Ok(ExecutionResult::Single(result))
886 }
887
888 (ActionRequest::Chain(actions), None) => {
890 let results = match (&config.chain_behavior, &config.logging_behavior) {
891 (ChainBehavior::Complete, LoggingBehavior::Normal) => {
892 self.execute_chain(actions.clone())?
893 }
894 (ChainBehavior::Complete, LoggingBehavior::Deferred) => {
895 self.execute_chain_deferred(actions.clone())?
896 }
897 (ChainBehavior::Complete, LoggingBehavior::Disabled) => {
898 let previous_state = self.set_action_logging_enabled(false);
899 let result = self.execute_chain(actions.clone());
900 self.set_action_logging_enabled(previous_state);
901 result?
902 }
903 (ChainBehavior::FinalOnly, _) => {
904 let results = match config.logging_behavior {
905 LoggingBehavior::Deferred => {
906 self.execute_chain_deferred(actions.clone())?
907 }
908 LoggingBehavior::Disabled => {
909 let previous_state = self.set_action_logging_enabled(false);
910 let result = self.execute_chain(actions.clone());
911 self.set_action_logging_enabled(previous_state);
912 result?
913 }
914 _ => self.execute_chain(actions.clone())?,
915 };
916 vec![results.into_iter().last().unwrap_or(ActionResult::None)]
917 }
918 (ChainBehavior::Partial, _) => {
919 match self.execute_chain_partial(actions.clone()) {
920 Ok(results) => results,
921 Err((partial_results, error)) => {
922 return Ok(ExecutionResult::Partial(partial_results, error));
923 }
924 }
925 }
926 };
927
928 Ok(ExecutionResult::Chain(results))
929 }
930 }
931 }
932
933 pub fn get_recent_tcp_data(
947 &self,
948 duration: Duration,
949 ) -> Vec<crate::types::TimestampedSignalFrame> {
950 self.tcp_reader
951 .as_ref()
952 .map(|reader| reader.get_recent_data(duration))
953 .unwrap_or_default()
954 }
955
956 pub fn execute_with_data_collection(
972 &mut self,
973 action: Action,
974 pre_duration: Duration,
975 post_duration: Duration,
976 ) -> Result<crate::types::ExperimentData, NanonisError> {
977 if self.tcp_reader.is_none() {
978 return Err(NanonisError::Protocol(
979 "TCP buffering not active".to_string(),
980 ));
981 }
982
983 let action_start = Instant::now();
984 let action_result = self.execute(action.clone())?;
985 let action_end = Instant::now();
986
987 std::thread::sleep(post_duration);
988
989 let window_start = action_start - pre_duration;
990 let window_end = action_end + post_duration;
991
992 let signal_frames = self
993 .tcp_reader
994 .as_ref()
995 .unwrap()
996 .get_data_between(window_start, window_end);
997 let tcp_config = self.tcp_reader_config.as_ref().unwrap().clone();
998
999 let experiment_data = crate::types::ExperimentData {
1000 action_result,
1001 signal_frames,
1002 tcp_config,
1003 action_start,
1004 action_end,
1005 total_duration: action_end.duration_since(action_start),
1006 };
1007
1008 if self.action_logging_enabled && self.action_logger.is_some() {
1010 let log_entry = ActionLogEntry {
1011 action: format!("Data Collection: {}", action.description()),
1012 result: ActionLogResult::from_experiment_data(&experiment_data),
1013 start_time: chrono::Utc::now(),
1014 duration_ms: experiment_data.total_duration.as_millis() as u64,
1015 metadata: Some(
1016 [
1017 ("type".to_string(), "experiment_data_collection".to_string()),
1018 (
1019 "pre_duration_ms".to_string(),
1020 pre_duration.as_millis().to_string(),
1021 ),
1022 (
1023 "post_duration_ms".to_string(),
1024 post_duration.as_millis().to_string(),
1025 ),
1026 (
1027 "signal_frame_count".to_string(),
1028 experiment_data.signal_frames.len().to_string(),
1029 ),
1030 ]
1031 .into_iter()
1032 .collect(),
1033 ),
1034 };
1035
1036 if let Err(log_error) = self.action_logger.as_mut().unwrap().add(log_entry) {
1037 log::warn!("Failed to log experiment data: {}", log_error);
1038 }
1039 }
1040
1041 Ok(experiment_data)
1042 }
1043
1044 pub fn pulse_with_data_collection(
1055 &mut self,
1056 pulse_voltage: f32,
1057 pulse_duration: Duration,
1058 pre_duration: Duration,
1059 post_duration: Duration,
1060 ) -> Result<crate::types::ExperimentData, NanonisError> {
1061 self.execute_with_data_collection(
1062 Action::BiasPulse {
1063 wait_until_done: true,
1064 bias_value_v: pulse_voltage,
1065 pulse_width: pulse_duration,
1066 z_controller_hold: crate::types::ZControllerHold::Hold as u16,
1067 pulse_mode: crate::types::PulseMode::Absolute as u16,
1068 },
1069 pre_duration,
1070 post_duration,
1071 )
1072 }
1073
1074 pub fn tcp_buffer_stats(&self) -> Option<(usize, usize, Duration)> {
1082 self.tcp_reader.as_ref().map(|reader| reader.buffer_stats())
1083 }
1084
1085 pub fn stop_tcp_buffering(
1094 &mut self,
1095 ) -> Result<Vec<crate::types::TimestampedSignalFrame>, NanonisError> {
1096 if let Some(mut reader) = self.tcp_reader.take() {
1097 let final_data = reader.get_all_data();
1098 reader.stop()?;
1099 log::info!(
1100 "Manually stopped TCP buffering, collected {} frames",
1101 final_data.len()
1102 );
1103 Ok(final_data)
1104 } else {
1105 Ok(Vec::new())
1106 }
1107 }
1108
1109 pub fn execute_chain_with_data_collection(
1125 &mut self,
1126 actions: Vec<Action>,
1127 pre_duration: Duration,
1128 post_duration: Duration,
1129 ) -> Result<crate::types::ChainExperimentData, NanonisError> {
1130 if self.tcp_reader.is_none() {
1131 return Err(NanonisError::Protocol(
1132 "TCP buffering not active".to_string(),
1133 ));
1134 }
1135
1136 let chain_start = Instant::now();
1137 let mut action_results = Vec::with_capacity(actions.len());
1138 let mut action_timings = Vec::with_capacity(actions.len());
1139
1140 for action in actions {
1142 let action_start = Instant::now();
1143 let action_result = self.execute(action)?;
1144 let action_end = Instant::now();
1145
1146 action_results.push(action_result);
1147 action_timings.push((action_start, action_end));
1148 }
1149
1150 let chain_end = Instant::now();
1151
1152 std::thread::sleep(post_duration);
1154
1155 let window_start = chain_start - pre_duration;
1157 let window_end = chain_end + post_duration;
1158
1159 let signal_frames = self
1160 .tcp_reader
1161 .as_ref()
1162 .unwrap()
1163 .get_data_between(window_start, window_end);
1164 let tcp_config = self.tcp_reader_config.as_ref().unwrap().clone();
1165
1166 let chain_experiment_data = crate::types::ChainExperimentData {
1167 action_results,
1168 signal_frames,
1169 tcp_config,
1170 action_timings,
1171 chain_start,
1172 chain_end,
1173 total_duration: chain_end.duration_since(chain_start),
1174 };
1175
1176 if self.action_logging_enabled && self.action_logger.is_some() {
1178 let log_entry = ActionLogEntry {
1179 action: format!(
1180 "Chain Data Collection: {} actions",
1181 chain_experiment_data.action_results.len()
1182 ),
1183 result: ActionLogResult::from_chain_experiment_data(&chain_experiment_data),
1184 start_time: chrono::Utc::now(),
1185 duration_ms: chain_experiment_data.total_duration.as_millis() as u64,
1186 metadata: Some(
1187 [
1188 (
1189 "type".to_string(),
1190 "chain_experiment_data_collection".to_string(),
1191 ),
1192 (
1193 "pre_duration_ms".to_string(),
1194 pre_duration.as_millis().to_string(),
1195 ),
1196 (
1197 "post_duration_ms".to_string(),
1198 post_duration.as_millis().to_string(),
1199 ),
1200 (
1201 "action_count".to_string(),
1202 chain_experiment_data.action_results.len().to_string(),
1203 ),
1204 (
1205 "signal_frame_count".to_string(),
1206 chain_experiment_data.signal_frames.len().to_string(),
1207 ),
1208 ]
1209 .into_iter()
1210 .collect(),
1211 ),
1212 };
1213
1214 if let Err(log_error) = self.action_logger.as_mut().unwrap().add(log_entry) {
1215 log::warn!("Failed to log chain experiment data: {}", log_error);
1216 }
1217 }
1218
1219 Ok(chain_experiment_data)
1220 }
1221
1222 pub fn start_tcp_logger(&mut self) -> Result<(), NanonisError> {
1224 self.client.tcplog_start()
1225 }
1226
1227 pub fn stop_tcp_logger(&mut self) -> Result<(), NanonisError> {
1229 self.client.tcplog_stop()
1230 }
1231
1232 pub fn set_tcp_logger_channels(&mut self, channels: Vec<i32>) -> Result<(), NanonisError> {
1234 self.client.tcplog_chs_set(channels)
1235 }
1236
1237 pub fn set_tcp_logger_oversampling(&mut self, oversampling: i32) -> Result<(), NanonisError> {
1239 self.client.tcplog_oversampl_set(oversampling)
1240 }
1241
1242 pub fn get_tcp_logger_status(&mut self) -> Result<crate::types::TCPLogStatus, NanonisError> {
1244 self.client.tcplog_status_get()
1245 }
1246
1247 pub fn execute(&mut self, action: Action) -> Result<ActionResult, NanonisError> {
1249 let start_time = chrono::Utc::now();
1250 let start_instant = std::time::Instant::now();
1251
1252 let result = self.execute_internal(action.clone());
1253
1254 let duration = start_instant.elapsed();
1255
1256 if self.action_logging_enabled && self.action_logger.is_some() {
1258 let log_entry = match &result {
1259 Ok(action_result) => {
1260 ActionLogEntry::new(&action, action_result, start_time, duration)
1261 }
1262 Err(error) => ActionLogEntry::new_error(&action, error, start_time, duration),
1263 };
1264
1265 if let Err(log_error) = self.action_logger.as_mut().unwrap().add(log_entry) {
1266 log::warn!("Failed to log action: {}", log_error);
1267 }
1268 }
1269
1270 result
1271 }
1272
1273 pub fn execute_with_options(
1296 &mut self,
1297 action: Action,
1298 data_collection: bool,
1299 pre_duration: Duration,
1300 post_duration: Duration,
1301 ) -> Result<ActionResult, NanonisError> {
1302 if data_collection && self.tcp_reader.is_some() {
1303 let _experiment_data =
1305 self.execute_with_data_collection(action, pre_duration, post_duration)?;
1306 Ok(ActionResult::Success) } else {
1309 self.execute(action)
1311 }
1312 }
1313
1314 pub fn execute_chain_with_options(
1325 &mut self,
1326 chain: impl Into<ActionChain>,
1327 data_collection: bool,
1328 pre_duration: Duration,
1329 post_duration: Duration,
1330 ) -> Result<Vec<ActionResult>, NanonisError> {
1331 if data_collection && self.tcp_reader.is_some() {
1332 let chain_experiment_data = self.execute_chain_with_data_collection(
1334 chain.into().into_iter().collect(),
1335 pre_duration,
1336 post_duration,
1337 )?;
1338 Ok(chain_experiment_data.action_results)
1340 } else {
1341 self.execute_chain(chain)
1343 }
1344 }
1345
1346 fn execute_internal(&mut self, action: Action) -> Result<ActionResult, NanonisError> {
1348 match action {
1349 Action::ReadSignal {
1351 signal,
1352 wait_for_newest,
1353 } => {
1354 let value = self.client.signals_vals_get(
1355 vec![SignalIndex::new(signal.index).into()],
1356 wait_for_newest,
1357 )?;
1358 Ok(ActionResult::Value(value[0] as f64))
1359 }
1360
1361 Action::ReadSignals {
1362 signals,
1363 wait_for_newest,
1364 } => {
1365 let indices: Vec<i32> = signals
1366 .iter()
1367 .map(|s| SignalIndex::new(s.index).into())
1368 .collect();
1369 let values = self.client.signals_vals_get(indices, wait_for_newest)?;
1370 Ok(ActionResult::Values(
1371 values.into_iter().map(|v| v as f64).collect(),
1372 ))
1373 }
1374
1375 Action::ReadSignalNames => {
1376 let names = self.client.signal_names_get()?;
1377 Ok(ActionResult::Text(names))
1378 }
1379
1380 Action::ReadBias => {
1382 let bias = self.client.bias_get()?;
1383 Ok(ActionResult::Value(bias as f64))
1384 }
1385
1386 Action::SetBias { voltage } => {
1387 self.client.bias_set(voltage)?;
1388 Ok(ActionResult::Success)
1389 }
1390
1391 Action::ReadOsci {
1393 signal,
1394 trigger,
1395 data_to_get,
1396 is_stable,
1397 } => {
1398 self.client.osci1t_run()?;
1399
1400 self.client.osci1t_ch_set(signal.index as i32)?;
1401
1402 if let Some(trigger) = trigger {
1403 self.client.osci1t_trig_set(
1404 trigger.mode.into(),
1405 trigger.slope.into(),
1406 trigger.level,
1407 trigger.hysteresis,
1408 )?;
1409 }
1410
1411 match data_to_get {
1412 crate::types::DataToGet::Stable { readings, timeout } => {
1413 let osci_data = self.find_stable_oscilloscope_data_with_fallback(
1414 data_to_get,
1415 readings,
1416 timeout,
1417 0.01,
1418 50e-15,
1419 0.8,
1420 is_stable,
1421 )?;
1422 Ok(ActionResult::OsciData(osci_data))
1423 }
1424 _ => {
1425 let data_mode = match data_to_get {
1427 DataToGet::Current => 0,
1428 DataToGet::NextTrigger => 1,
1429 DataToGet::Wait2Triggers => 2,
1430 DataToGet::Stable { .. } => 1, };
1432 let (t0, dt, size, data) = self.client.osci1t_data_get(data_mode)?;
1433 let osci_data = OsciData::new_stable(t0, dt, size, data);
1434 Ok(ActionResult::OsciData(osci_data))
1435 }
1436 }
1437 }
1438
1439 Action::ReadPiezoPosition {
1441 wait_for_newest_data,
1442 } => {
1443 let pos = self.client.folme_xy_pos_get(wait_for_newest_data)?;
1444 Ok(ActionResult::Position(pos))
1445 }
1446
1447 Action::SetPiezoPosition {
1448 position,
1449 wait_until_finished,
1450 } => {
1451 self.client
1452 .folme_xy_pos_set(position, wait_until_finished)?;
1453 Ok(ActionResult::Success)
1454 }
1455
1456 Action::MovePiezoRelative { delta } => {
1457 let current = self.client.folme_xy_pos_get(true)?;
1459 info!("Current position: {current:?}");
1460 let new_position = Position {
1461 x: current.x + delta.x,
1462 y: current.y + delta.y,
1463 };
1464 self.client.folme_xy_pos_set(new_position, true)?;
1465 Ok(ActionResult::Success)
1466 }
1467
1468 Action::MoveMotorAxis {
1470 direction,
1471 steps,
1472 blocking,
1473 } => {
1474 self.client
1475 .motor_start_move(direction, steps, MotorGroup::Group1, blocking)?;
1476 Ok(ActionResult::Success)
1477 }
1478
1479 Action::MoveMotor3D {
1480 displacement,
1481 blocking,
1482 } => {
1483 let movements = displacement_to_motor_movements(&displacement);
1485
1486 for (direction, steps) in movements {
1488 self.client
1489 .motor_start_move(direction, steps, MotorGroup::Group1, blocking)?;
1490 }
1491 Ok(ActionResult::Success)
1492 }
1493
1494 Action::MoveMotorClosedLoop { target, mode } => {
1495 self.client.motor_start_closed_loop(
1496 mode,
1497 target,
1498 true, MotorGroup::Group1,
1500 )?;
1501 Ok(ActionResult::Success)
1502 }
1503
1504 Action::StopMotor => {
1505 self.client.motor_stop_move()?;
1506 Ok(ActionResult::Success)
1507 }
1508
1509 Action::AutoApproach {
1511 wait_until_finished,
1512 timeout,
1513 center_freq_shift,
1514 } => {
1515 log::debug!(
1516 "Starting auto-approach (wait: {}, timeout: {:?}, center_freq: {})",
1517 wait_until_finished,
1518 timeout,
1519 center_freq_shift
1520 );
1521
1522 if center_freq_shift {
1524 self.auto_approach(true, timeout)?;
1526
1527 std::thread::sleep(Duration::from_millis(200));
1529
1530 if let Ok(safetip_state) = self.client_mut().safe_tip_on_off_get() {
1532 if !safetip_state {
1533 self.client_mut().safe_tip_on_off_set(true)?;
1534 }
1535 } else {
1536 log::warn!("Failed to read safe tip state, setting true");
1537 self.client_mut().safe_tip_on_off_set(true)?;
1538 }
1539
1540 self.check_safetip_status("after enabling safe tip")?;
1541
1542 self.client_mut().z_ctrl_home()?;
1544
1545 self.check_safetip_status("after z_ctrl_home")?;
1546
1547 std::thread::sleep(Duration::from_millis(500));
1549
1550 self.check_safetip_status("after 500ms settle")?;
1551
1552 if let Err(e) = self.center_freq_shift() {
1554 log::warn!("Failed to center frequency shift: {}", e);
1555 }
1557
1558 self.check_safetip_status("after center_freq_shift")?;
1559
1560 self.auto_approach(wait_until_finished, timeout)?;
1562
1563 self.check_safetip_status("after final auto_approach")?;
1564
1565 if let Ok(safetip_state) = self.client_mut().safe_tip_on_off_get() {
1567 if safetip_state {
1568 self.client_mut().safe_tip_on_off_set(false)?;
1569 }
1570 } else {
1571 log::warn!("Failed to read safe tip state, setting false");
1572 self.client_mut().safe_tip_on_off_set(false)?;
1573 }
1574 } else {
1575 self.auto_approach(wait_until_finished, timeout)?;
1576 }
1577
1578 Ok(ActionResult::Success)
1579 }
1580
1581 Action::Withdraw {
1582 wait_until_finished,
1583 timeout,
1584 } => {
1585 self.client.z_ctrl_withdraw(wait_until_finished, timeout)?;
1586 Ok(ActionResult::Success)
1587 }
1588
1589 Action::SafeReposition { x_steps, y_steps } => {
1590 let displacement = crate::types::MotorDisplacement::new(x_steps, y_steps, -3);
1592 let withdraw_timeout = Duration::from_secs(5);
1593 let approach_timeout = Duration::from_secs(10);
1594 let stabilization_wait = Duration::from_millis(500);
1595
1596 self.client.z_ctrl_withdraw(true, withdraw_timeout)?;
1599
1600 let movements = displacement_to_motor_movements(&displacement);
1602 for (direction, steps) in movements {
1603 self.client
1604 .motor_start_move(direction, steps, MotorGroup::Group1, true)?;
1605 }
1606
1607 thread::sleep(Duration::from_millis(500));
1608
1609 self.run(Action::AutoApproach {
1611 wait_until_finished: true,
1612 timeout: approach_timeout,
1613 center_freq_shift: true,
1614 })
1615 .go()?;
1616
1617 thread::sleep(stabilization_wait);
1619
1620 Ok(ActionResult::Success)
1621 }
1622
1623 Action::SetZSetpoint { setpoint } => {
1624 self.client.z_ctrl_setpoint_set(setpoint)?;
1625 Ok(ActionResult::Success)
1626 }
1627
1628 Action::ScanControl { action } => {
1630 self.client.scan_action(action, ScanDirection::Up)?;
1631 Ok(ActionResult::Success)
1632 }
1633
1634 Action::ReadScanStatus => {
1635 let is_scanning = self.client.scan_status_get()?;
1636 Ok(ActionResult::Status(is_scanning))
1637 }
1638
1639 Action::BiasPulse {
1641 wait_until_done,
1642 pulse_width,
1643 bias_value_v,
1644 z_controller_hold,
1645 pulse_mode,
1646 } => {
1647 let hold_enum = match z_controller_hold {
1649 0 => ZControllerHold::NoChange,
1650 1 => ZControllerHold::Hold,
1651 2 => ZControllerHold::Release,
1652 _ => ZControllerHold::NoChange, };
1654
1655 let mode_enum = match pulse_mode {
1656 0 => PulseMode::Keep,
1657 1 => PulseMode::Relative,
1658 2 => PulseMode::Absolute,
1659 _ => PulseMode::Keep, };
1661
1662 self.client.bias_pulse(
1663 wait_until_done,
1664 pulse_width.as_secs_f32(),
1665 bias_value_v,
1666 hold_enum.into(),
1667 mode_enum.into(),
1668 )?;
1669
1670 Ok(ActionResult::Success)
1671 }
1672
1673 Action::TipShaper {
1674 config,
1675 wait_until_finished,
1676 timeout,
1677 } => {
1678 self.client.tip_shaper_props_set(config)?;
1680
1681 self.client.tip_shaper_start(wait_until_finished, timeout)?;
1683
1684 Ok(ActionResult::Success)
1685 }
1686
1687 Action::PulseRetract {
1688 pulse_width,
1689 pulse_height_v,
1690 } => {
1691 let current_bias = self.client_mut().bias_get().unwrap_or(500e-3);
1692
1693 let config = TipShaperConfig {
1694 switch_off_delay: std::time::Duration::from_millis(10),
1695 change_bias: true,
1696 bias_v: pulse_height_v,
1697 tip_lift_m: 0.0,
1698 lift_time_1: pulse_width,
1699 bias_lift_v: current_bias,
1700 bias_settling_time: std::time::Duration::from_millis(50),
1701 lift_height_m: 100e-9,
1702 lift_time_2: std::time::Duration::from_millis(100),
1703 end_wait_time: std::time::Duration::from_millis(50),
1704 restore_feedback: false,
1705 };
1706
1707 self.client_mut().tip_shaper_props_set(config)?;
1709 self.client_mut()
1710 .tip_shaper_start(true, Duration::from_secs(5))?;
1711
1712 Ok(ActionResult::Success)
1713 }
1714
1715 Action::Wait { duration } => {
1716 thread::sleep(duration);
1717 Ok(ActionResult::None)
1718 }
1719
1720 Action::Store { key, action } => {
1722 let result = self.execute(*action)?;
1723 self.stored_values.insert(key, result.clone());
1724 Ok(result) }
1726
1727 Action::Retrieve { key } => match self.stored_values.get(&key) {
1728 Some(value) => Ok(value.clone()), None => Err(NanonisError::Protocol(format!(
1730 "No stored value found for key: {}",
1731 key
1732 ))),
1733 },
1734
1735 Action::StartTCPLogger => {
1737 self.start_tcp_logger()?;
1738 Ok(ActionResult::Success)
1739 }
1740
1741 Action::StopTCPLogger => {
1742 self.stop_tcp_logger()?;
1743 Ok(ActionResult::Success)
1744 }
1745
1746 Action::GetTCPLoggerStatus => {
1747 use crate::actions::TCPReaderStatus;
1748 let status = self.get_tcp_logger_status()?;
1749 let config = self.tcp_reader_config();
1750
1751 Ok(ActionResult::TCPReaderStatus(TCPReaderStatus {
1752 status,
1753 channels: config.map(|c| c.channels.clone()).unwrap_or_default(),
1754 oversampling: config.map(|c| c.oversampling).unwrap_or(0),
1755 }))
1756 }
1757
1758 Action::ConfigureTCPLogger {
1759 channels,
1760 oversampling,
1761 } => {
1762 self.set_tcp_logger_channels(channels)?;
1763 self.set_tcp_logger_oversampling(oversampling)?;
1764 Ok(ActionResult::Success)
1765 }
1766
1767 Action::CheckTipState { method } => {
1768 use std::collections::HashMap;
1769
1770 use crate::{
1771 actions::{TipCheckMethod, TipState},
1772 types::TipShape,
1773 };
1774
1775 let (tip_shape, measured_signals, mut metadata) = match method {
1776 TipCheckMethod::SignalBounds { signal, bounds } => {
1777 if let Some(ref tcp_reader) = self.tcp_reader {
1779 let (frame_count, _max_capacity, time_span) = tcp_reader.buffer_stats();
1780 log::debug!("CheckTipState: TCP reader available with {} frames, timespan: {}ms",
1781 frame_count, time_span.as_millis());
1782 } else {
1783 log::warn!(
1784 "CheckTipState: No TCP reader available for signal {}",
1785 signal.index
1786 );
1787 }
1788
1789 log::debug!(
1791 "CheckTipState: Calling ReadStableSignal for signal {}",
1792 signal.index
1793 );
1794
1795 let data_points = self
1797 .calculate_samples_for_duration(
1798 self.tip_state_config.data_collection_duration,
1799 )
1800 .unwrap_or(100); let stable_result = self
1803 .run(Action::ReadStableSignal {
1804 signal: signal.clone(),
1805 data_points: Some(data_points),
1806 use_new_data: true, stability_method: crate::actions::SignalStabilityMethod::Combined {
1808 max_std_dev: self.tip_state_config.max_std_dev,
1809 max_slope: self.tip_state_config.max_slope,
1810 },
1811 timeout: self.tip_state_config.read_timeout,
1812 retry_count: Some(self.tip_state_config.read_retry_count),
1813 })
1814 .execute();
1815
1816 let (value, raw_data, read_method) = match stable_result {
1817 Ok(exec_result) => match exec_result {
1818 ExecutionResult::Single(ActionResult::StableSignal(
1819 stable_signal,
1820 )) => {
1821 log::debug!("CheckTipState: ReadStableSignal succeeded with {} data points", stable_signal.raw_data.len());
1823 (
1824 stable_signal.stable_value,
1825 stable_signal.raw_data,
1826 "stable_signal",
1827 )
1828 }
1829 ExecutionResult::Single(ActionResult::Values(values)) => {
1830 log::warn!("CheckTipState: ReadStableSignal failed but returned {} raw values, using mean as fallback", values.len());
1833 let raw_data: Vec<f32> =
1834 values.iter().map(|&v| v as f32).collect();
1835 let mean_value = if raw_data.is_empty() {
1836 f32::NAN
1837 } else {
1838 raw_data.iter().sum::<f32>() / raw_data.len() as f32
1839 };
1840 (mean_value, raw_data, "fallback_mean")
1841 }
1842 _ => {
1843 log::warn!("CheckTipState: ReadStableSignal returned unexpected result type, falling back to single read");
1845 let single_value =
1846 self.client.signal_val_get(signal.index, true)?;
1847 (single_value, vec![single_value], "single_read_fallback")
1848 }
1849 },
1850 Err(e) => {
1851 log::warn!("CheckTipState: ReadStableSignal failed with error: {}, falling back to single read", e);
1853 let single_value =
1854 self.client.signal_val_get(signal.index, true)?;
1855 (single_value, vec![single_value], "single_read_fallback")
1856 }
1857 };
1858
1859 let mut measured = HashMap::new();
1860 measured.insert(SignalIndex::new(signal.index), value);
1861
1862 let shape = if value >= bounds.0 && value <= bounds.1 {
1863 TipShape::Sharp
1864 } else {
1865 TipShape::Blunt
1866 };
1867
1868 let bounds_center = (bounds.0 + bounds.1) / 2.0;
1870 let bounds_width = (bounds.1 - bounds.0).abs();
1871 let distance_from_center = (value - bounds_center).abs();
1872 let relative_distance = if bounds_width > 0.0 {
1873 distance_from_center / (bounds_width / 2.0)
1874 } else {
1875 0.0
1876 };
1877 let mut metadata = HashMap::new();
1878 metadata.insert("method".to_string(), "signal_bounds".to_string());
1879 metadata.insert("signal_index".to_string(), signal.index.to_string());
1880 metadata.insert("measured_value".to_string(), format!("{:.6e}", value));
1881 metadata.insert("bounds_lower".to_string(), format!("{:.6e}", bounds.0));
1882 metadata.insert("bounds_upper".to_string(), format!("{:.6e}", bounds.1));
1883 metadata.insert(
1884 "bounds_center".to_string(),
1885 format!("{:.6e}", bounds_center),
1886 );
1887 metadata
1888 .insert("bounds_width".to_string(), format!("{:.6e}", bounds_width));
1889 metadata.insert(
1890 "distance_from_center".to_string(),
1891 format!("{:.6e}", distance_from_center),
1892 );
1893 metadata.insert(
1894 "relative_distance".to_string(),
1895 format!("{:.3}", relative_distance),
1896 );
1897 metadata.insert(
1898 "within_bounds".to_string(),
1899 (shape == TipShape::Sharp).to_string(),
1900 );
1901 metadata.insert("read_method".to_string(), read_method.to_string());
1902 metadata.insert("dataset_size".to_string(), raw_data.len().to_string());
1903
1904 let raw_data_summary = if raw_data.len() <= 10 {
1906 raw_data
1907 .iter()
1908 .map(|x| format!("{:.3e}", x))
1909 .collect::<Vec<_>>()
1910 .join(",")
1911 } else {
1912 let first_5: String = raw_data
1913 .iter()
1914 .take(5)
1915 .map(|x| format!("{:.3e}", x))
1916 .collect::<Vec<_>>()
1917 .join(",");
1918 let last_5: String = raw_data
1919 .iter()
1920 .rev()
1921 .take(5)
1922 .rev()
1923 .map(|x| format!("{:.3e}", x))
1924 .collect::<Vec<_>>()
1925 .join(",");
1926 format!("{},...,{}", first_5, last_5)
1927 };
1928 metadata.insert(
1929 "raw_dataset_summary".to_string(),
1930 format!("[{}]", raw_data_summary),
1931 );
1932
1933 if shape == TipShape::Blunt {
1934 let margin_violation = if value < bounds.0 {
1935 bounds.0 - value
1936 } else {
1937 value - bounds.1
1938 };
1939 metadata.insert(
1940 "margin_violation".to_string(),
1941 format!("{:.6e}", margin_violation),
1942 );
1943 metadata.insert(
1944 "violation_direction".to_string(),
1945 if value < bounds.0 {
1946 "below_lower_bound".to_string()
1947 } else {
1948 "above_upper_bound".to_string()
1949 },
1950 );
1951 }
1952
1953 (shape, measured, metadata)
1954 }
1955
1956 TipCheckMethod::MultiSignalBounds { ref signals } => {
1957 let mut measured = HashMap::new();
1958 let mut violations = Vec::new();
1959 let mut all_good = true;
1960 let mut all_datasets = Vec::new();
1961 let mut read_methods = Vec::new();
1962
1963 let data_points = self
1965 .calculate_samples_for_duration(
1966 self.tip_state_config.data_collection_duration,
1967 )
1968 .unwrap_or(100); for (signal, bounds) in signals.iter() {
1972 let stable_result = self
1973 .run(Action::ReadStableSignal {
1974 signal: signal.clone(),
1975 data_points: Some(data_points),
1976 use_new_data: true, stability_method:
1978 crate::actions::SignalStabilityMethod::Combined {
1979 max_std_dev: self.tip_state_config.max_std_dev,
1980 max_slope: self.tip_state_config.max_slope,
1981 },
1982 timeout: self.tip_state_config.read_timeout,
1983 retry_count: Some(self.tip_state_config.read_retry_count),
1984 })
1985 .execute();
1986
1987 let (value, raw_data, read_method) = match stable_result {
1988 Ok(exec_result) => match exec_result {
1989 ExecutionResult::Single(ActionResult::StableSignal(
1990 stable_signal,
1991 )) => (
1992 stable_signal.stable_value,
1993 stable_signal.raw_data,
1994 "stable_signal",
1995 ),
1996 ExecutionResult::Single(ActionResult::Values(values)) => {
1997 let raw_data: Vec<f32> =
1998 values.iter().map(|&v| v as f32).collect();
1999 let mean_value = if raw_data.is_empty() {
2000 f32::NAN
2001 } else {
2002 raw_data.iter().sum::<f32>() / raw_data.len() as f32
2003 };
2004 (mean_value, raw_data, "fallback_mean")
2005 }
2006 _ => {
2007 let single_value =
2008 self.client.signal_val_get(signal.index, true)?;
2009 (single_value, vec![single_value], "single_read_fallback")
2010 }
2011 },
2012 Err(_) => {
2013 let single_value =
2014 self.client.signal_val_get(signal.index, true)?;
2015 (single_value, vec![single_value], "single_read_fallback")
2016 }
2017 };
2018
2019 measured.insert(SignalIndex::new(signal.index), value);
2020 all_datasets.push(raw_data);
2021 read_methods.push(read_method);
2022
2023 let in_bounds = value >= bounds.0 && value <= bounds.1;
2024 if !in_bounds {
2025 violations.push((signal.clone(), value, *bounds));
2026 all_good = false;
2027 }
2028 }
2029
2030 let shape = if all_good {
2031 TipShape::Sharp
2032 } else {
2033 TipShape::Blunt
2034 };
2035
2036 let mut metadata = HashMap::new();
2038 metadata.insert("method".to_string(), "multi_signal_bounds".to_string());
2039 metadata.insert("signal_count".to_string(), signals.len().to_string());
2040 metadata.insert(
2041 "signals_in_bounds".to_string(),
2042 (signals.len() - violations.len()).to_string(),
2043 );
2044 metadata
2045 .insert("violation_count".to_string(), violations.len().to_string());
2046 metadata.insert("overall_pass".to_string(), all_good.to_string());
2047
2048 for (i, ((signal, bounds), dataset)) in
2050 signals.iter().zip(all_datasets.iter()).enumerate()
2051 {
2052 let prefix = format!("signal_{}", i);
2053 let value = measured[&SignalIndex::new(signal.index)];
2054
2055 metadata.insert(format!("{}_index", prefix), signal.index.to_string());
2056 metadata.insert(format!("{}_value", prefix), format!("{:.6e}", value));
2057 metadata.insert(
2058 format!("{}_bounds", prefix),
2059 format!("[{:.3e}, {:.3e}]", bounds.0, bounds.1),
2060 );
2061 metadata.insert(
2062 format!("{}_in_bounds", prefix),
2063 (value >= bounds.0 && value <= bounds.1).to_string(),
2064 );
2065 metadata.insert(
2066 format!("{}_read_method", prefix),
2067 read_methods[i].to_string(),
2068 );
2069 metadata.insert(
2070 format!("{}_dataset_size", prefix),
2071 dataset.len().to_string(),
2072 );
2073
2074 let dataset_summary = if dataset.len() <= 10 {
2076 dataset
2077 .iter()
2078 .map(|x| format!("{:.3e}", x))
2079 .collect::<Vec<_>>()
2080 .join(",")
2081 } else {
2082 let first_3: String = dataset
2083 .iter()
2084 .take(3)
2085 .map(|x| format!("{:.3e}", x))
2086 .collect::<Vec<_>>()
2087 .join(",");
2088 let last_3: String = dataset
2089 .iter()
2090 .rev()
2091 .take(3)
2092 .rev()
2093 .map(|x| format!("{:.3e}", x))
2094 .collect::<Vec<_>>()
2095 .join(",");
2096 format!("{},...,{}", first_3, last_3)
2097 };
2098 metadata.insert(
2099 format!("{}_dataset_summary", prefix),
2100 format!("[{}]", dataset_summary),
2101 );
2102 }
2103
2104 (shape, measured, metadata)
2105 }
2106 };
2107
2108 if let Some(ref tcp_reader) = self.tcp_reader {
2110 let (frame_count, _max_capacity, time_span) = tcp_reader.buffer_stats();
2111 metadata.insert("tcp_buffer_frames".to_string(), frame_count.to_string());
2112 metadata.insert(
2113 "tcp_buffer_utilization".to_string(),
2114 format!("{:.2}", tcp_reader.buffer_utilization()),
2115 );
2116 metadata.insert(
2117 "tcp_data_timespan_ms".to_string(),
2118 time_span.as_millis().to_string(),
2119 );
2120 metadata.insert(
2121 "tcp_uptime_ms".to_string(),
2122 tcp_reader.uptime().as_millis().to_string(),
2123 );
2124
2125 for signal_idx in measured_signals.keys() {
2127 if tcp_reader.frame_count() >= 20 {
2128 let recent_frames = tcp_reader.get_recent_frames(50); let signal_values: Vec<f32> = recent_frames
2133 .iter()
2134 .filter_map(|frame| {
2135 let idx = signal_idx.get() as usize;
2137 if idx < frame.signal_frame.data.len() {
2138 Some(frame.signal_frame.data[idx])
2139 } else {
2140 None
2141 }
2142 })
2143 .collect();
2144
2145 if signal_values.len() >= 10 {
2146 let mean =
2148 signal_values.iter().sum::<f32>() / signal_values.len() as f32;
2149 let variance = signal_values
2150 .iter()
2151 .map(|x| (x - mean).powi(2))
2152 .sum::<f32>()
2153 / signal_values.len() as f32;
2154 let std_dev = variance.sqrt();
2155 let relative_std = if mean.abs() > 1e-15 {
2156 (std_dev / mean.abs()) * 100.0
2157 } else {
2158 0.0
2159 };
2160
2161 let x_values: Vec<f32> =
2163 (0..signal_values.len()).map(|i| i as f32).collect();
2164 let x_mean = x_values.iter().sum::<f32>() / x_values.len() as f32;
2165 let y_mean = mean;
2166
2167 let numerator: f32 = x_values
2168 .iter()
2169 .zip(signal_values.iter())
2170 .map(|(x, y)| (x - x_mean) * (y - y_mean))
2171 .sum();
2172 let denominator: f32 =
2173 x_values.iter().map(|x| (x - x_mean).powi(2)).sum();
2174
2175 let trend_slope = if denominator.abs() > 1e-15 {
2176 numerator / denominator
2177 } else {
2178 0.0
2179 };
2180
2181 let signal_prefix = format!("tcp_signal_{}", signal_idx.get());
2182 metadata.insert(
2183 format!("{}_recent_samples", signal_prefix),
2184 signal_values.len().to_string(),
2185 );
2186 metadata.insert(
2187 format!("{}_recent_mean", signal_prefix),
2188 format!("{:.6e}", mean),
2189 );
2190 metadata.insert(
2191 format!("{}_recent_std", signal_prefix),
2192 format!("{:.6e}", std_dev),
2193 );
2194 metadata.insert(
2195 format!("{}_recent_relative_std_pct", signal_prefix),
2196 format!("{:.3}", relative_std),
2197 );
2198 metadata.insert(
2199 format!("{}_trend_slope", signal_prefix),
2200 format!("{:.6e}", trend_slope),
2201 );
2202 metadata.insert(
2203 format!("{}_current_vs_recent_mean", signal_prefix),
2204 format!("{:.6e}", measured_signals[signal_idx] - mean),
2205 );
2206
2207 let is_stable_signal =
2209 relative_std < 5.0 && trend_slope.abs() < (std_dev * 0.1);
2210 metadata.insert(
2211 format!("{}_appears_stable", signal_prefix),
2212 is_stable_signal.to_string(),
2213 );
2214
2215 let min_recent =
2217 signal_values.iter().cloned().fold(f32::INFINITY, f32::min);
2218 let max_recent = signal_values
2219 .iter()
2220 .cloned()
2221 .fold(f32::NEG_INFINITY, f32::max);
2222 let current_in_recent_range = measured_signals[signal_idx]
2223 >= min_recent
2224 && measured_signals[signal_idx] <= max_recent;
2225 metadata.insert(
2226 format!("{}_current_in_recent_range", signal_prefix),
2227 current_in_recent_range.to_string(),
2228 );
2229 metadata.insert(
2230 format!("{}_recent_range", signal_prefix),
2231 format!("[{:.6e}, {:.6e}]", min_recent, max_recent),
2232 );
2233 }
2234 }
2235 }
2236 }
2237
2238 let now = std::time::Instant::now();
2240 let recent_signals: Vec<_> = self
2241 .recent_stable_signals
2242 .iter()
2243 .filter(|(_, timestamp)| {
2244 now.duration_since(*timestamp) < std::time::Duration::from_secs(300)
2245 }) .collect();
2247
2248 if !recent_signals.is_empty() {
2249 metadata.insert(
2250 "recent_stable_signals_count".to_string(),
2251 recent_signals.len().to_string(),
2252 );
2253
2254 if let Some((most_recent_signal, timestamp)) = recent_signals.last() {
2256 let age_ms = now.duration_since(*timestamp).as_millis();
2257 metadata.insert(
2258 "most_recent_stable_signal_age_ms".to_string(),
2259 age_ms.to_string(),
2260 );
2261 metadata.insert(
2262 "most_recent_stable_value".to_string(),
2263 format!("{:.6e}", most_recent_signal.stable_value),
2264 );
2265 metadata.insert(
2266 "most_recent_data_points".to_string(),
2267 most_recent_signal.data_points_used.to_string(),
2268 );
2269 metadata.insert(
2270 "most_recent_analysis_duration_ms".to_string(),
2271 most_recent_signal.analysis_duration.as_millis().to_string(),
2272 );
2273
2274 let raw_data = &most_recent_signal.raw_data;
2276 let raw_data_summary = if raw_data.len() <= 10 {
2277 raw_data
2279 .iter()
2280 .map(|x| format!("{:.3e}", x))
2281 .collect::<Vec<_>>()
2282 .join(",")
2283 } else {
2284 let first_5: String = raw_data
2286 .iter()
2287 .take(5)
2288 .map(|x| format!("{:.3e}", x))
2289 .collect::<Vec<_>>()
2290 .join(",");
2291 let last_5: String = raw_data
2292 .iter()
2293 .rev()
2294 .take(5)
2295 .rev()
2296 .map(|x| format!("{:.3e}", x))
2297 .collect::<Vec<_>>()
2298 .join(",");
2299 format!("{},...,{}", first_5, last_5)
2300 };
2301 metadata.insert(
2302 "most_recent_raw_data_summary".to_string(),
2303 format!("[{}]", raw_data_summary),
2304 );
2305 metadata.insert(
2306 "most_recent_raw_data_full_count".to_string(),
2307 raw_data.len().to_string(),
2308 );
2309
2310 for (metric_name, metric_value) in &most_recent_signal.stability_metrics {
2312 metadata.insert(
2313 format!("most_recent_metric_{}", metric_name),
2314 format!("{:.6e}", metric_value),
2315 );
2316 }
2317 }
2318 }
2319
2320 metadata.insert(
2322 "execution_timestamp".to_string(),
2323 chrono::Utc::now().to_rfc3339(),
2324 );
2325
2326 let signal_values_str = measured_signals
2328 .iter()
2329 .map(|(signal_idx, value)| format!("signal_{}={:.3}", signal_idx.get(), value))
2330 .collect::<Vec<_>>()
2331 .join(", ");
2332
2333 log::info!(
2334 "CheckTipState: shape={:?}, signals=[{}]",
2335 tip_shape,
2336 signal_values_str
2337 );
2338
2339 log::debug!(
2340 "CheckTipState detail: read_method={}, dataset_size={}, recent_stable_count={}",
2341 metadata
2342 .get("read_method")
2343 .map(|s| s.as_str())
2344 .unwrap_or("unknown"),
2345 metadata
2346 .get("dataset_size")
2347 .map(|s| s.as_str())
2348 .unwrap_or("unknown"),
2349 recent_signals.len()
2350 );
2351
2352 Ok(ActionResult::TipState(TipState {
2353 shape: tip_shape,
2354 measured_signals,
2355 metadata,
2356 }))
2357 }
2358
2359 Action::CheckTipStability {
2360 method,
2361 max_duration: _,
2362 } => {
2363 use std::collections::HashMap;
2364
2365 use crate::actions::{StabilityResult, TipStabilityMethod};
2366
2367 let start_time = std::time::Instant::now();
2368 let mut metrics = HashMap::new();
2369 let mut recommendations = Vec::new();
2370
2371 let (is_stable, measured_values) = match method {
2372 TipStabilityMethod::ExtendedMonitoring {
2373 signal: _,
2374 duration: _,
2375 sampling_interval: _,
2376 stability_threshold: _,
2377 } => {
2378 todo!("ExtendedMonitoring not yet implemented");
2379 }
2380
2381 TipStabilityMethod::BiasSweepResponse {
2382 ref signal,
2383 bias_range,
2384 bias_steps,
2385 step_duration,
2386 allowed_signal_change,
2387 } => {
2388 log::info!(
2389 "Performing simple bias sweep stability test: {:.2}V to {:.2}V",
2390 bias_range.0,
2391 bias_range.1
2392 );
2393
2394 let tcp_channel = signal.tcp_channel.ok_or_else(|| {
2396 NanonisError::Protocol(format!(
2397 "Signal {} (Nanonis index) has no TCP channel mapping",
2398 signal.index
2399 ))
2400 })?;
2401
2402 log::info!("Reading current scan properties...");
2404 let original_props = self.client.scan_props_get()?;
2405 log::info!(
2406 "Original scan props: continuous={}, bouncy={}",
2407 original_props.continuous_scan,
2408 original_props.bouncy_scan
2409 );
2410
2411 log::info!("Configuring scan: continuous=true, bouncy=true");
2413 let scan_props = nanonis_rs::scan::ScanPropsBuilder::new()
2414 .continuous_scan(true)
2415 .bouncy_scan(true);
2416 self.client.scan_props_set(scan_props)?;
2417 log::info!("Scan properties configured");
2418
2419 let initial_bias = self.client.bias_get()?;
2421 log::info!(
2422 "Initial bias: {:.3} V (will restore after sweep)",
2423 initial_bias
2424 );
2425
2426 let baseline_value = {
2428 let tcp_reader = self.tcp_reader_mut().ok_or_else(|| {
2429 NanonisError::Protocol("TCP reader not available".to_string())
2430 })?;
2431
2432 let recent_frames = tcp_reader.get_recent_frames(1);
2433 if recent_frames.is_empty() {
2434 return Err(NanonisError::Protocol(
2435 "No frames available from TCP reader".to_string(),
2436 ));
2437 }
2438
2439 recent_frames[0].signal_frame.data[tcp_channel as usize]
2440 };
2441
2442 log::info!(
2443 "Baseline signal: {:.3}, threshold: {:.3}",
2444 baseline_value,
2445 allowed_signal_change
2446 );
2447
2448 self.client
2450 .scan_action(ScanAction::Start, ScanDirection::Down)?;
2451 log::info!("Scan started");
2452
2453 let mut scan_started = false;
2455 for _ in 0..50 {
2456 if self.is_shutdown_requested() {
2458 log::info!("Shutdown requested while waiting for scan to start");
2459 let _ =
2460 self.client.scan_action(ScanAction::Stop, ScanDirection::Up);
2461 let _ = self.client.bias_set(initial_bias);
2462 return Err(NanonisError::Protocol(
2463 "Shutdown requested".to_string(),
2464 ));
2465 }
2466 std::thread::sleep(Duration::from_millis(100));
2467 let is_scanning = self.client.scan_status_get()?;
2468 if is_scanning {
2469 scan_started = true;
2470 log::info!("Scan started successfully");
2471 break;
2472 }
2473 }
2474
2475 if !scan_started {
2476 return Err(NanonisError::Protocol(
2477 "Scan failed to start within 5 seconds".to_string(),
2478 ));
2479 }
2480
2481 let bias_step_size = (bias_range.1 - bias_range.0) / (bias_steps as f32);
2483 let mut current_bias = bias_range.0;
2484
2485 for step_num in 0..bias_steps {
2486 if self.is_shutdown_requested() {
2488 log::info!(
2489 "Shutdown requested during bias sweep at step {}/{}",
2490 step_num + 1,
2491 bias_steps
2492 );
2493 let _ =
2494 self.client.scan_action(ScanAction::Stop, ScanDirection::Up);
2495 let _ = self.client.bias_set(initial_bias);
2496 return Err(NanonisError::Protocol(
2497 "Shutdown requested".to_string(),
2498 ));
2499 }
2500 self.client.bias_set(current_bias)?;
2501 log::debug!(
2502 "Step {}/{}: bias={:.2}V",
2503 step_num + 1,
2504 bias_steps,
2505 current_bias
2506 );
2507 let sleep_chunks = (step_duration.as_millis() / 10).max(1) as u32;
2509 let chunk_duration = step_duration / sleep_chunks;
2510 for _ in 0..sleep_chunks {
2511 if self.is_shutdown_requested() {
2512 log::info!("Shutdown requested during bias sweep step sleep");
2513 let _ = self
2514 .client
2515 .scan_action(ScanAction::Stop, ScanDirection::Up);
2516 let _ = self.client.bias_set(initial_bias);
2517 return Err(NanonisError::Protocol(
2518 "Shutdown requested".to_string(),
2519 ));
2520 }
2521 std::thread::sleep(chunk_duration);
2522 }
2523 current_bias += bias_step_size;
2524 }
2525
2526 log::info!("Bias sweep completed");
2527
2528 let final_value = {
2530 let tcp_reader = self.tcp_reader_mut().ok_or_else(|| {
2531 NanonisError::Protocol("TCP reader not available".to_string())
2532 })?;
2533
2534 let recent_frames = tcp_reader.get_recent_frames(1);
2535 if recent_frames.is_empty() {
2536 return Err(NanonisError::Protocol(
2537 "No frames available from TCP reader".to_string(),
2538 ));
2539 }
2540
2541 recent_frames[0].signal_frame.data[tcp_channel as usize]
2542 };
2543
2544 let _ = self.client.scan_action(ScanAction::Stop, ScanDirection::Up);
2546
2547 if let Err(e) = self.client.z_ctrl_withdraw(true, Duration::from_secs(5)) {
2549 log::error!("Failed to withdraw before restoring bias: {}", e);
2550 }
2551
2552 std::thread::sleep(Duration::from_millis(200));
2554
2555 if let Err(e) = self.client.bias_set(initial_bias) {
2556 log::error!("Failed to restore initial bias: {}", e);
2557 } else {
2558 log::info!("Bias restored to {:.3} V", initial_bias);
2559 }
2560
2561 let signal_change = (final_value - baseline_value).abs();
2563 let is_stable = signal_change <= allowed_signal_change;
2564
2565 log::info!(
2566 "Bias sweep result: baseline={:.3}, final={:.3}, change={:.3}, threshold={:.3}, stable={}",
2567 baseline_value,
2568 final_value,
2569 signal_change,
2570 allowed_signal_change,
2571 is_stable
2572 );
2573
2574 metrics.insert("baseline_value".to_string(), baseline_value);
2576 metrics.insert("final_value".to_string(), final_value);
2577 metrics.insert("signal_change".to_string(), signal_change);
2578 metrics.insert("threshold".to_string(), allowed_signal_change);
2579
2580 if is_stable {
2582 recommendations.push(format!(
2583 "Tip is stable - signal change {:.3} within threshold {:.3}",
2584 signal_change, allowed_signal_change
2585 ));
2586 } else {
2587 recommendations.push(format!(
2588 "Tip is blunt - signal change {:.3} exceeded threshold {:.3}. Tip shaping recommended.",
2589 signal_change, allowed_signal_change
2590 ));
2591 }
2592
2593 let mut measured_values = HashMap::new();
2595 measured_values.insert(signal.clone(), vec![baseline_value, final_value]);
2596
2597 (is_stable, measured_values)
2598 }
2599 };
2600
2601 let analysis_duration = start_time.elapsed();
2602 let result = StabilityResult {
2603 is_stable,
2604 method_used: format!("{:?}", method.clone()),
2605 measured_values,
2606 analysis_duration,
2607 metrics,
2608 potential_damage_detected: !is_stable
2609 && matches!(method, TipStabilityMethod::BiasSweepResponse { .. }),
2610 recommendations,
2611 };
2612
2613 Ok(ActionResult::StabilityResult(result))
2614 }
2615
2616 Action::ReadStableSignal {
2617 signal,
2618 data_points,
2619 use_new_data,
2620 stability_method,
2621 timeout,
2622 retry_count,
2623 } => {
2624 use std::time::Instant;
2625
2626 let start_time = Instant::now();
2627 let data_points = data_points.unwrap_or(50);
2628 let max_retries = retry_count.unwrap_or(0);
2629
2630 let tcp_config = self.tcp_reader_config.as_ref().ok_or_else(|| {
2632 NanonisError::Protocol("TCP logger not configured".to_string())
2633 })?;
2634
2635 log::debug!(
2637 "ReadStableSignal: Looking up signal {} in signal registry",
2638 signal.index
2639 );
2640
2641 let registry_signal =
2643 self.signal_registry
2644 .get_by_index(signal.index)
2645 .ok_or_else(|| {
2646 NanonisError::Protocol(format!(
2647 "Signal {} not found in registry",
2648 signal.index
2649 ))
2650 })?;
2651
2652 let tcp_channel = registry_signal.tcp_channel.ok_or_else(|| {
2653 log::error!(
2654 "ReadStableSignal: Signal {} (Nanonis index) has no TCP channel mapping",
2655 signal.index
2656 );
2657 NanonisError::Protocol(format!(
2658 "Signal {} (Nanonis index) has no TCP channel mapping",
2659 signal.index
2660 ))
2661 })?;
2662
2663 log::debug!(
2664 "ReadStableSignal: Signal {} mapped to TCP channel {}",
2665 signal.index,
2666 tcp_channel
2667 );
2668
2669 log::debug!(
2671 "ReadStableSignal: Signal {} (Nanonis) maps to TCP channel {}",
2672 signal.index,
2673 tcp_channel
2674 );
2675 log::debug!(
2676 "ReadStableSignal: Available TCP channels: {:?}",
2677 tcp_config.channels
2678 );
2679 let signal_channel_idx = tcp_config
2680 .channels
2681 .iter()
2682 .position(|&ch| ch == tcp_channel as i32)
2683 .ok_or_else(|| {
2684 log::error!("ReadStableSignal: TCP channel {} for signal {} (Nanonis) not found in TCP logger configuration. Available channels: {:?}",
2685 tcp_channel, signal.index, tcp_config.channels);
2686 NanonisError::Protocol(format!(
2687 "TCP channel {} for signal {} (Nanonis) not found in TCP logger configuration. Available: {:?}",
2688 tcp_channel, signal.index, tcp_config.channels
2689 ))
2690 })?;
2691
2692 log::debug!(
2693 "ReadStableSignal: Signal {} (Nanonis) -> TCP channel {} -> Array position {}",
2694 signal.index,
2695 tcp_channel,
2696 signal_channel_idx
2697 );
2698 log::debug!(
2699 "ReadStableSignal: Full TCP channel list: {:?}",
2700 tcp_config.channels
2701 );
2702
2703 let mut attempt = 0;
2705
2706 loop {
2707 match self.attempt_stable_signal_read(
2708 signal_channel_idx,
2709 data_points,
2710 use_new_data,
2711 timeout,
2712 &stability_method,
2713 ) {
2714 Ok((signal_data, is_stable, metrics)) => {
2715 let analysis_duration = start_time.elapsed();
2716
2717 if is_stable {
2718 let stable_value =
2720 signal_data.iter().sum::<f32>() / signal_data.len() as f32;
2721
2722 use crate::actions::StableSignal;
2723 log::info!(
2724 "Stable signal acquired on attempt {} (retries: {})",
2725 attempt + 1,
2726 attempt
2727 );
2728
2729 let stable_signal = StableSignal {
2730 stable_value,
2731 data_points_used: signal_data.len(),
2732 analysis_duration,
2733 stability_metrics: metrics,
2734 raw_data: if is_stable {
2737 vec![stable_value]
2738 } else {
2739 signal_data
2740 },
2741 };
2742
2743 self.recent_stable_signals
2745 .push_back((stable_signal.clone(), std::time::Instant::now()));
2746 while self.recent_stable_signals.len() > 10 {
2748 self.recent_stable_signals.pop_front();
2749 }
2750
2751 return Ok(ActionResult::StableSignal(stable_signal));
2752 } else if attempt >= max_retries {
2753 let nan = f32::NAN;
2755 log::warn!(
2756 "Signal not stable after {} attempts: std_dev={:.3} Hz (thresh {:.3}), drift={:.3} Hz/s (thresh {:.3}), n={} — returning raw data",
2757 attempt + 1,
2758 metrics.get("std_dev").copied().unwrap_or(nan),
2759 metrics.get("max_std_dev_threshold").copied().unwrap_or(nan),
2760 metrics.get("abs_slope").copied().unwrap_or(nan),
2761 metrics.get("max_slope_threshold").copied().unwrap_or(nan),
2762 metrics.get("data_points").copied().unwrap_or(nan) as usize,
2763 );
2764 let values: Vec<f64> =
2765 signal_data.iter().map(|&x| x as f64).collect();
2766 return Ok(ActionResult::Values(values));
2767 } else {
2768 let nan = f32::NAN;
2770 log::debug!(
2771 "Signal not stable on attempt {} (std_dev={:.3} Hz, drift={:.3} Hz/s), retrying...",
2772 attempt + 1,
2773 metrics.get("std_dev").copied().unwrap_or(nan),
2774 metrics.get("abs_slope").copied().unwrap_or(nan),
2775 );
2776 }
2777 }
2778 Err(e) => {
2779 log::warn!("Data collection failed on attempt {}: {}", attempt + 1, e);
2780
2781 if attempt >= max_retries {
2782 return Err(e);
2783 }
2784 }
2785 }
2786
2787 attempt += 1;
2788
2789 if attempt <= max_retries {
2791 let delay_ms = 100 * (1 << (attempt - 1).min(4)); log::debug!(
2793 "Waiting {}ms before retry attempt {}",
2794 delay_ms,
2795 attempt + 1
2796 );
2797 std::thread::sleep(Duration::from_millis(delay_ms));
2798 }
2799 }
2800 }
2801 Action::ReachedTargedAmplitude => {
2802 let ampl_setpoint = self.client_mut().pll_amp_ctrl_setpnt_get(1)?;
2803
2804 let ampl_current = match self
2805 .run(Action::ReadStableSignal {
2806 signal: Signal::new("Amplitude".to_string(), 75, None).unwrap(),
2807 data_points: Some(50),
2808 use_new_data: false,
2809 stability_method:
2810 crate::actions::SignalStabilityMethod::RelativeStandardDeviation {
2811 threshold_percent: 0.2,
2812 },
2813 timeout: Duration::from_millis(10),
2814 retry_count: Some(3), })
2816 .go()? {
2817 ActionResult::Values(values) => values.iter().map(|v| *v as f32).sum::<f32>() / values.len() as f32,
2818 ActionResult::StableSignal(value) => value.stable_value,
2819 other => {
2820 return Err(NanonisError::Protocol(format!(
2821 "CheckAmplitudeStability returned unexpected result type. Expected Values or StableSignal, got {:?}",
2822 std::mem::discriminant(&other)
2823 )))
2824 }
2825 };
2826
2827 let status = (ampl_setpoint - 5e-12..ampl_setpoint + 5e-12).contains(&l_current);
2828
2829 Ok(ActionResult::Status(status))
2830 }
2831 }
2832 }
2833
2834 fn check_safetip_status(&mut self, context: &str) -> Result<(), NanonisError> {
2835 if let Ok(status) = self.client_mut().z_ctrl_status_get() {
2836 if matches!(status, nanonis_rs::z_ctrl::ZControllerStatus::SafeTip) {
2837 return Err(NanonisError::Protocol(format!(
2838 "SafeTip triggered ({}), abort!",
2839 context
2840 )));
2841 }
2842 }
2843
2844 Ok(())
2845 }
2846
2847 fn effective_sample_rate_hz(&self) -> f32 {
2853 const BASE_RATE_HZ: f32 = 2000.0; match &self.tcp_reader_config {
2855 Some(config) => BASE_RATE_HZ / (config.oversampling as f32).max(1.0),
2856 None => BASE_RATE_HZ,
2857 }
2858 }
2859
2860 fn attempt_stable_signal_read(
2862 &self,
2863 signal_channel_idx: usize,
2864 data_points: usize,
2865 use_new_data: bool,
2866 timeout: Duration,
2867 stability_method: &crate::actions::SignalStabilityMethod,
2868 ) -> Result<(Vec<f32>, bool, std::collections::HashMap<String, f32>), NanonisError> {
2869 let signal_data: Vec<f32> = if use_new_data {
2871 self.collect_new_signal_data(signal_channel_idx, data_points, timeout)?
2873 } else {
2874 self.extract_buffered_signal_data(signal_channel_idx, data_points)?
2876 };
2877
2878 if signal_data.is_empty() {
2879 return Err(NanonisError::Protocol(
2880 "No signal data available".to_string(),
2881 ));
2882 }
2883
2884 let (is_stable, metrics) = Self::analyze_signal_stability(
2887 &signal_data,
2888 stability_method,
2889 self.effective_sample_rate_hz(),
2890 );
2891
2892 Ok((signal_data, is_stable, metrics))
2893 }
2894
2895 fn collect_new_signal_data(
2897 &self,
2898 signal_channel_idx: usize,
2899 data_points: usize,
2900 timeout: Duration,
2901 ) -> Result<Vec<f32>, NanonisError> {
2902 use std::time::Instant;
2903
2904 let tcp_reader = self
2905 .tcp_reader
2906 .as_ref()
2907 .ok_or_else(|| NanonisError::Protocol("TCP reader not available".to_string()))?;
2908
2909 let start_time = Instant::now();
2910 let mut collected_data = Vec::with_capacity(data_points);
2911
2912 log::debug!(
2913 "Collecting {} new data points for signal channel {} with timeout {:.1}s",
2914 data_points,
2915 signal_channel_idx,
2916 timeout.as_secs_f32()
2917 );
2918
2919 while collected_data.len() < data_points && start_time.elapsed() < timeout {
2920 let recent_frames = tcp_reader.get_recent_data(Duration::from_millis(100));
2922
2923 for frame in recent_frames {
2924 if collected_data.len() >= data_points {
2925 break;
2926 }
2927
2928 if let Some(&value) = frame.signal_frame.data.get(signal_channel_idx) {
2929 collected_data.push(value);
2930 }
2931 }
2932
2933 if collected_data.len() < data_points {
2934 std::thread::sleep(Duration::from_millis(50)); }
2936 }
2937
2938 if collected_data.is_empty() {
2939 log::warn!("No data collected within timeout");
2940 } else {
2941 log::debug!("Collected {} data points", collected_data.len());
2942 }
2943
2944 Ok(collected_data)
2945 }
2946
2947 fn extract_buffered_signal_data(
2949 &self,
2950 signal_channel_idx: usize,
2951 data_points: usize,
2952 ) -> Result<Vec<f32>, NanonisError> {
2953 let tcp_reader = self
2954 .tcp_reader
2955 .as_ref()
2956 .ok_or_else(|| NanonisError::Protocol("TCP reader not available".to_string()))?;
2957
2958 let recent_frames = tcp_reader.get_recent_frames(data_points);
2960
2961 let mut signal_data = Vec::new();
2962 for frame in recent_frames.iter().rev().take(data_points) {
2963 if let Some(&value) = frame.signal_frame.data.get(signal_channel_idx) {
2965 signal_data.push(value);
2966 }
2967 }
2968
2969 signal_data.reverse(); log::info!("Extracted {} buffered data points", signal_data.len());
2972 Ok(signal_data)
2973 }
2974
2975 fn analyze_signal_stability(
2977 data: &[f32],
2978 method: &crate::actions::SignalStabilityMethod,
2979 sample_rate_hz: f32,
2980 ) -> (bool, std::collections::HashMap<String, f32>) {
2981 use crate::actions::SignalStabilityMethod;
2982
2983 if data.len() < 2 {
2984 return (false, std::collections::HashMap::new());
2985 }
2986
2987 let mut metrics = std::collections::HashMap::new();
2988 let mean = data.iter().sum::<f32>() / data.len() as f32;
2989 let variance = data.iter().map(|v| (v - mean).powi(2)).sum::<f32>() / data.len() as f32;
2990 let std_dev = variance.sqrt();
2991
2992 metrics.insert("mean".to_string(), mean);
2993 metrics.insert("std_dev".to_string(), std_dev);
2994 metrics.insert("variance".to_string(), variance);
2995
2996 let is_stable = match method {
2997 SignalStabilityMethod::StandardDeviation { threshold } => {
2998 metrics.insert("threshold".to_string(), *threshold);
2999 std_dev <= *threshold
3000 }
3001
3002 SignalStabilityMethod::RelativeStandardDeviation { threshold_percent } => {
3003 let relative_std = if mean.abs() > 1e-12 {
3004 (std_dev / mean.abs()) * 100.0
3005 } else {
3006 f32::INFINITY
3007 };
3008 metrics.insert("relative_std_percent".to_string(), relative_std);
3009 metrics.insert("threshold_percent".to_string(), *threshold_percent);
3010 relative_std <= *threshold_percent
3011 }
3012
3013 SignalStabilityMethod::MovingWindow {
3014 window_size,
3015 max_variation,
3016 } => {
3017 if data.len() < *window_size {
3018 return (false, metrics);
3019 }
3020
3021 let mut max_window_variation = 0.0f32;
3022 for window in data.windows(*window_size) {
3023 let window_min = window.iter().fold(f32::INFINITY, |a, &b| a.min(b));
3024 let window_max = window.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
3025 let variation = window_max - window_min;
3026 max_window_variation = max_window_variation.max(variation);
3027 }
3028
3029 metrics.insert("max_window_variation".to_string(), max_window_variation);
3030 metrics.insert("window_size".to_string(), *window_size as f32);
3031 metrics.insert("max_variation_threshold".to_string(), *max_variation);
3032 max_window_variation <= *max_variation
3033 }
3034
3035 SignalStabilityMethod::TrendAnalysis { max_slope } => {
3036 let n = data.len() as f32;
3038 let x_mean = (n - 1.0) / 2.0; let y_mean = mean;
3040
3041 let mut numerator = 0.0;
3042 let mut denominator = 0.0;
3043 for (i, &y) in data.iter().enumerate() {
3044 let x = i as f32;
3045 numerator += (x - x_mean) * (y - y_mean);
3046 denominator += (x - x_mean).powi(2);
3047 }
3048
3049 let slope_per_sample = if denominator > 1e-12 {
3050 numerator / denominator
3051 } else {
3052 0.0
3053 };
3054 let slope = slope_per_sample * sample_rate_hz;
3056 let abs_slope = slope.abs();
3057
3058 metrics.insert("slope_per_sample".to_string(), slope_per_sample);
3059 metrics.insert("slope".to_string(), slope); metrics.insert("abs_slope".to_string(), abs_slope); metrics.insert("max_slope_threshold".to_string(), *max_slope);
3062 abs_slope <= *max_slope
3063 }
3064
3065 SignalStabilityMethod::Combined {
3066 max_std_dev,
3067 max_slope,
3068 } => {
3069 let n = data.len() as f32;
3071 let x_mean = (n - 1.0) / 2.0;
3072 let y_mean = mean;
3073
3074 let mut numerator = 0.0;
3075 let mut denominator = 0.0;
3076 for (i, &y) in data.iter().enumerate() {
3077 let x = i as f32;
3078 numerator += (x - x_mean) * (y - y_mean);
3079 denominator += (x - x_mean).powi(2);
3080 }
3081
3082 let slope_per_sample = if denominator > 1e-12 {
3083 numerator / denominator
3084 } else {
3085 0.0
3086 };
3087 let slope = slope_per_sample * sample_rate_hz;
3089 let abs_slope = slope.abs();
3090
3091 let noise_ok = std_dev <= *max_std_dev;
3093 let drift_ok = abs_slope <= *max_slope;
3094
3095 metrics.insert("slope_per_sample".to_string(), slope_per_sample);
3096 metrics.insert("slope".to_string(), slope); metrics.insert("abs_slope".to_string(), abs_slope); metrics.insert("max_slope_threshold".to_string(), *max_slope);
3099 metrics.insert("max_std_dev_threshold".to_string(), *max_std_dev);
3100 metrics.insert("noise_ok".to_string(), if noise_ok { 1.0 } else { 0.0 });
3101 metrics.insert("drift_ok".to_string(), if drift_ok { 1.0 } else { 0.0 });
3102 noise_ok && drift_ok
3103 }
3104 };
3105
3106 metrics.insert("data_points".to_string(), data.len() as f32);
3107
3108 (is_stable, metrics)
3109 }
3110
3111 pub fn execute_expecting<T>(&mut self, action: Action) -> Result<T, NanonisError>
3132 where
3133 ActionResult: ExpectFromAction<T>,
3134 {
3135 let result = self.execute(action.clone())?;
3136 Ok(result.expect_from_action(&action))
3137 }
3138
3139 fn find_stable_oscilloscope_data(
3145 &mut self,
3146 _data_to_get: DataToGet,
3147 readings: u32,
3148 timeout: std::time::Duration,
3149 relative_threshold: f64,
3150 absolute_threshold: f64,
3151 min_window_percent: f64,
3152 stability_fn: Option<fn(&[f64]) -> bool>,
3153 ) -> Result<Option<OsciData>, NanonisError> {
3154 match poll_with_timeout(
3155 || {
3156 for _attempt in 0..readings {
3158 let (t0, dt, size, data) = self.client.osci1t_data_get(2)?; if let Some(stable_osci_data) = self.analyze_stability_window(
3161 t0,
3162 dt,
3163 size,
3164 data,
3165 relative_threshold,
3166 absolute_threshold,
3167 min_window_percent,
3168 stability_fn,
3169 )? {
3170 return Ok(Some(stable_osci_data));
3171 }
3172
3173 std::thread::sleep(std::time::Duration::from_millis(100));
3175 }
3176
3177 Ok(None)
3179 },
3180 timeout,
3181 std::time::Duration::from_millis(50), ) {
3183 Ok(Some(result)) => Ok(Some(result)),
3184 Ok(None) => Ok(None), Err(PollError::ConditionError(e)) => Err(e),
3186 Err(PollError::Timeout) => unreachable!(), }
3188 }
3189
3190 fn analyze_stability_window(
3192 &self,
3193 t0: f64,
3194 dt: f64,
3195 size: i32,
3196 data: Vec<f64>,
3197 relative_threshold: f64,
3198 absolute_threshold: f64,
3199 min_window_percent: f64,
3200 stability_fn: Option<fn(&[f64]) -> bool>,
3201 ) -> Result<Option<OsciData>, NanonisError> {
3202 let min_window = (size as f64 * min_window_percent) as usize;
3203 let mut start = 0;
3204 let mut end = size as usize;
3205
3206 while (end - start) > min_window {
3207 let window = &data[start..end];
3208 let arr = Array1::from_vec(window.to_vec());
3209 let mean = arr.mean().expect(
3210 "There must be an non-empty array, osci1t_data_get would have returned early.",
3211 );
3212 let std_dev = arr.std(0.0);
3213 let relative_std = std_dev / mean.abs();
3214
3215 let is_stable = if let Some(stability_fn) = stability_fn {
3217 stability_fn(window)
3218 } else {
3219 let is_relative_stable = relative_std < relative_threshold;
3221 let is_absolute_stable = std_dev < absolute_threshold;
3222 is_relative_stable || is_absolute_stable
3223 };
3224
3225 if is_stable {
3226 let stable_data = window.to_vec();
3227 let stability_method = if stability_fn.is_some() {
3228 "custom".to_string()
3229 } else {
3230 let is_relative_stable = relative_std < relative_threshold;
3232 let is_absolute_stable = std_dev < absolute_threshold;
3233 match (is_relative_stable, is_absolute_stable) {
3234 (true, true) => "both".to_string(),
3235 (true, false) => "relative".to_string(),
3236 (false, true) => "absolute".to_string(),
3237 (false, false) => unreachable!(),
3238 }
3239 };
3240
3241 let stats = SignalStats {
3242 mean,
3243 std_dev,
3244 relative_std,
3245 window_size: stable_data.len(),
3246 stability_method,
3247 };
3248
3249 let mut osci_data =
3250 OsciData::new_with_stats(t0, dt, stable_data.len() as i32, stable_data, stats);
3251 osci_data.is_stable = true; return Ok(Some(osci_data));
3253 }
3254
3255 let shrink = ((end - start) / 10).max(1);
3256 start += shrink;
3257 end -= shrink;
3258 }
3259
3260 Ok(None)
3262 }
3263
3264 fn find_stable_oscilloscope_data_with_fallback(
3271 &mut self,
3272 data_to_get: DataToGet,
3273 readings: u32,
3274 timeout: std::time::Duration,
3275 relative_threshold: f64,
3276 absolute_threshold: f64,
3277 min_window_percent: f64,
3278 stability_fn: Option<fn(&[f64]) -> bool>,
3279 ) -> Result<OsciData, NanonisError> {
3280 if let Some(stable_osci_data) = self.find_stable_oscilloscope_data(
3282 data_to_get,
3283 readings,
3284 timeout,
3285 relative_threshold,
3286 absolute_threshold,
3287 min_window_percent,
3288 stability_fn,
3289 )? {
3290 return Ok(stable_osci_data);
3291 }
3292
3293 let (t0, dt, size, data) = self.client.osci1t_data_get(1)?; let fallback_value = if !data.is_empty() {
3298 data.iter().sum::<f64>() / data.len() as f64
3299 } else {
3300 0.0
3301 };
3302
3303 Ok(OsciData::new_unstable_with_fallback(
3304 t0,
3305 dt,
3306 size,
3307 data,
3308 fallback_value,
3309 ))
3310 }
3311
3312 pub fn execute_chain(
3314 &mut self,
3315 chain: impl Into<ActionChain>,
3316 ) -> Result<Vec<ActionResult>, NanonisError> {
3317 let chain = chain.into();
3318 let mut results = Vec::with_capacity(chain.len());
3319
3320 for action in chain.into_iter() {
3321 let result = self.execute(action)?;
3322 results.push(result);
3323 }
3324
3325 Ok(results)
3326 }
3327
3328 pub fn execute_chain_final(
3330 &mut self,
3331 chain: impl Into<ActionChain>,
3332 ) -> Result<ActionResult, NanonisError> {
3333 let results = self.execute_chain(chain)?;
3334 Ok(results.into_iter().last().unwrap_or(ActionResult::None))
3335 }
3336
3337 pub fn execute_chain_partial(
3339 &mut self,
3340 chain: impl Into<ActionChain>,
3341 ) -> Result<Vec<ActionResult>, (Vec<ActionResult>, NanonisError)> {
3342 let chain = chain.into();
3343 let mut results = Vec::new();
3344
3345 for action in chain.into_iter() {
3346 match self.execute(action) {
3347 Ok(result) => results.push(result),
3348 Err(error) => return Err((results, error)),
3349 }
3350 }
3351
3352 Ok(results)
3353 }
3354
3355 pub fn execute_chain_deferred(
3372 &mut self,
3373 chain: impl Into<ActionChain>,
3374 ) -> Result<Vec<ActionResult>, NanonisError> {
3375 let chain = chain.into();
3376 let start_time = chrono::Utc::now();
3377 let start_instant = std::time::Instant::now();
3378
3379 let mut results = Vec::with_capacity(chain.len());
3380
3381 for action in chain.iter() {
3383 let result = self.execute_internal(action.clone())?;
3384 results.push(result);
3385 }
3386
3387 let duration = start_instant.elapsed();
3388
3389 if self.action_logging_enabled && self.action_logger.is_some() {
3391 let chain_summary = format!("Chain: {}", chain.summary());
3392 let final_result = results.last().unwrap_or(&ActionResult::None);
3393
3394 let log_entry = ActionLogEntry::new(
3395 &crate::actions::Action::Wait {
3396 duration: Duration::from_millis(0),
3397 }, final_result,
3399 start_time,
3400 duration,
3401 )
3402 .with_metadata("type", "chain_execution")
3403 .with_metadata("chain_summary", chain_summary)
3404 .with_metadata("action_count", results.len().to_string());
3405
3406 if let Err(log_error) = self.action_logger.as_mut().unwrap().add(log_entry) {
3407 log::warn!("Failed to log chain execution: {}", log_error);
3408 }
3409 }
3410
3411 Ok(results)
3412 }
3413
3414 pub fn clear_storage(&mut self) {
3416 self.stored_values.clear();
3417 }
3418
3419 pub fn stored_keys(&self) -> Vec<&String> {
3421 self.stored_values.keys().collect()
3422 }
3423
3424 pub fn set_action_logging_enabled(&mut self, enabled: bool) -> bool {
3442 let previous = self.action_logging_enabled;
3443 self.action_logging_enabled = enabled;
3444 previous
3445 }
3446
3447 pub fn is_action_logging_enabled(&self) -> bool {
3449 self.action_logging_enabled && self.action_logger.is_some()
3450 }
3451
3452 pub fn flush_action_log(&mut self) -> Result<(), NanonisError> {
3461 if let Some(ref mut logger) = self.action_logger {
3462 logger.flush()?;
3463 }
3464 Ok(())
3465 }
3466
3467 pub fn action_log_stats(&self) -> Option<(usize, bool)> {
3475 self.action_logger
3476 .as_ref()
3477 .map(|logger| (logger.len(), self.action_logging_enabled))
3478 }
3479
3480 pub fn finalize_action_log(&mut self) -> Result<(), NanonisError> {
3490 if let Some(ref mut logger) = self.action_logger {
3491 logger.finalize_as_json()?;
3492 }
3493 Ok(())
3494 }
3495
3496 pub fn read_oscilloscope(
3498 &mut self,
3499 signal: Signal,
3500 trigger: Option<TriggerConfig>,
3501 data_to_get: DataToGet,
3502 ) -> Result<Option<OsciData>, NanonisError> {
3503 match self.execute(Action::ReadOsci {
3504 signal,
3505 trigger,
3506 data_to_get,
3507 is_stable: None,
3508 })? {
3509 ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
3510 ActionResult::None => Ok(None),
3511 _ => Err(NanonisError::Protocol("Expected oscilloscope data".into())),
3512 }
3513 }
3514
3515 pub fn read_oscilloscope_with_stability(
3517 &mut self,
3518 signal: Signal,
3519 trigger: Option<TriggerConfig>,
3520 data_to_get: DataToGet,
3521 is_stable: fn(&[f64]) -> bool,
3522 ) -> Result<Option<OsciData>, NanonisError> {
3523 match self.execute(Action::ReadOsci {
3524 signal,
3525 trigger,
3526 data_to_get,
3527 is_stable: Some(is_stable),
3528 })? {
3529 ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
3530 ActionResult::None => Ok(None),
3531 _ => Err(NanonisError::Protocol("Expected oscilloscope data".into())),
3532 }
3533 }
3534}
3535
3536pub mod stability {
3538 pub fn dual_threshold_stability(window: &[f64]) -> bool {
3541 if window.len() < 3 {
3542 return false;
3543 }
3544
3545 let mean = window.iter().sum::<f64>() / window.len() as f64;
3546 let variance = window.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / window.len() as f64;
3547 let std_dev = variance.sqrt();
3548 let relative_std = std_dev / mean.abs();
3549
3550 relative_std < 0.05 || std_dev < 50e-15
3552 }
3553
3554 pub fn trend_analysis_stability(window: &[f64]) -> bool {
3557 if window.len() < 5 {
3558 return false;
3559 }
3560
3561 let n = window.len() as f64;
3563 let x_mean = (n - 1.0) / 2.0; let y_mean = window.iter().sum::<f64>() / n;
3565
3566 let mut numerator = 0.0;
3567 let mut denominator = 0.0;
3568
3569 for (i, &y) in window.iter().enumerate() {
3570 let x = i as f64;
3571 numerator += (x - x_mean) * (y - y_mean);
3572 denominator += (x - x_mean).powi(2);
3573 }
3574
3575 let slope = if denominator != 0.0 {
3576 numerator / denominator
3577 } else {
3578 0.0
3579 };
3580
3581 let signal_level = y_mean.abs();
3583 let noise_level = {
3584 let variance = window.iter().map(|y| (y - y_mean).powi(2)).sum::<f64>() / n;
3585 variance.sqrt()
3586 };
3587
3588 let snr = if noise_level != 0.0 {
3589 signal_level / noise_level
3590 } else {
3591 f64::INFINITY
3592 };
3593
3594 slope.abs() < 0.001 && snr > 10.0
3596 }
3597}
3598
3599#[derive(Debug, Clone)]
3601pub struct ExecutionStats {
3602 pub total_actions: usize,
3603 pub successful_actions: usize,
3604 pub failed_actions: usize,
3605 pub total_duration: std::time::Duration,
3606}
3607
3608impl ExecutionStats {
3609 pub fn success_rate(&self) -> f64 {
3610 if self.total_actions == 0 {
3611 0.0
3612 } else {
3613 self.successful_actions as f64 / self.total_actions as f64
3614 }
3615 }
3616}
3617
3618impl ActionDriver {
3620 pub fn execute_chain_with_stats(
3622 &mut self,
3623 chain: impl Into<ActionChain>,
3624 ) -> Result<(Vec<ActionResult>, ExecutionStats), NanonisError> {
3625 let chain = chain.into();
3626 let start_time = std::time::Instant::now();
3627 let mut results = Vec::with_capacity(chain.len());
3628 let mut successful = 0;
3629 let failed = 0;
3630
3631 for action in chain.into_iter() {
3632 match self.execute(action) {
3633 Ok(result) => {
3634 results.push(result);
3635 successful += 1;
3636 }
3637 Err(e) => {
3638 return Err(e);
3642 }
3643 }
3644 }
3645
3646 let stats = ExecutionStats {
3647 total_actions: results.len(),
3648 successful_actions: successful,
3649 failed_actions: failed,
3650 total_duration: start_time.elapsed(),
3651 };
3652
3653 Ok((results, stats))
3654 }
3655}
3656
3657impl ExpectFromExecution<ActionResult> for ExecutionResult {
3660 fn expect_from_execution(self) -> Result<ActionResult, NanonisError> {
3661 self.into_single()
3662 }
3663}
3664
3665impl ExpectFromExecution<Vec<ActionResult>> for ExecutionResult {
3666 fn expect_from_execution(self) -> Result<Vec<ActionResult>, NanonisError> {
3667 self.into_chain()
3668 }
3669}
3670
3671impl ExpectFromExecution<crate::types::ExperimentData> for ExecutionResult {
3672 fn expect_from_execution(self) -> Result<crate::types::ExperimentData, NanonisError> {
3673 self.into_experiment_data()
3674 }
3675}
3676
3677impl ExpectFromExecution<crate::types::ChainExperimentData> for ExecutionResult {
3678 fn expect_from_execution(self) -> Result<crate::types::ChainExperimentData, NanonisError> {
3679 self.into_chain_experiment_data()
3680 }
3681}
3682
3683impl ExpectFromExecution<f64> for ExecutionResult {
3684 fn expect_from_execution(self) -> Result<f64, NanonisError> {
3685 match self {
3686 ExecutionResult::Single(ActionResult::Value(v)) => Ok(v),
3687 ExecutionResult::Single(ActionResult::Values(mut vs)) if vs.len() == 1 => {
3688 Ok(vs.pop().unwrap())
3689 }
3690 _ => Err(NanonisError::Protocol(
3691 "Expected single numeric value".to_string(),
3692 )),
3693 }
3694 }
3695}
3696
3697impl ExpectFromExecution<Vec<f64>> for ExecutionResult {
3698 fn expect_from_execution(self) -> Result<Vec<f64>, NanonisError> {
3699 match self {
3700 ExecutionResult::Single(ActionResult::Values(vs)) => Ok(vs),
3701 ExecutionResult::Single(ActionResult::Value(v)) => Ok(vec![v]),
3702 _ => Err(NanonisError::Protocol(
3703 "Expected numeric values".to_string(),
3704 )),
3705 }
3706 }
3707}
3708
3709impl ExpectFromExecution<bool> for ExecutionResult {
3710 fn expect_from_execution(self) -> Result<bool, NanonisError> {
3711 match self {
3712 ExecutionResult::Single(ActionResult::Status(b)) => Ok(b),
3713 _ => Err(NanonisError::Protocol(
3714 "Expected boolean status".to_string(),
3715 )),
3716 }
3717 }
3718}
3719
3720impl ExpectFromExecution<Position> for ExecutionResult {
3721 fn expect_from_execution(self) -> Result<Position, NanonisError> {
3722 match self {
3723 ExecutionResult::Single(ActionResult::Position(pos)) => Ok(pos),
3724 _ => Err(NanonisError::Protocol("Expected position data".to_string())),
3725 }
3726 }
3727}
3728
3729impl ExpectFromExecution<OsciData> for ExecutionResult {
3730 fn expect_from_execution(self) -> Result<OsciData, NanonisError> {
3731 match self {
3732 ExecutionResult::Single(ActionResult::OsciData(data)) => Ok(data),
3733 _ => Err(NanonisError::Protocol(
3734 "Expected oscilloscope data".to_string(),
3735 )),
3736 }
3737 }
3738}
3739
3740impl ExpectFromExecution<crate::types::TipShape> for ExecutionResult {
3741 fn expect_from_execution(self) -> Result<crate::types::TipShape, NanonisError> {
3742 match self {
3743 ExecutionResult::Single(ActionResult::TipState(tip_state)) => Ok(tip_state.shape),
3744 _ => Err(NanonisError::Protocol("Expected tip state".to_string())),
3745 }
3746 }
3747}
3748
3749impl ExpectFromExecution<crate::actions::TipState> for ExecutionResult {
3750 fn expect_from_execution(self) -> Result<crate::actions::TipState, NanonisError> {
3751 match self {
3752 ExecutionResult::Single(ActionResult::TipState(tip_state)) => Ok(tip_state),
3753 _ => Err(NanonisError::Protocol("Expected tip state".to_string())),
3754 }
3755 }
3756}
3757
3758impl ExpectFromExecution<crate::actions::StableSignal> for ExecutionResult {
3759 fn expect_from_execution(self) -> Result<crate::actions::StableSignal, NanonisError> {
3760 match self {
3761 ExecutionResult::Single(ActionResult::StableSignal(stable_signal)) => Ok(stable_signal),
3762 _ => Err(NanonisError::Protocol("Expected stable signal".to_string())),
3763 }
3764 }
3765}
3766
3767impl ExpectFromExecution<crate::actions::TCPReaderStatus> for ExecutionResult {
3768 fn expect_from_execution(self) -> Result<crate::actions::TCPReaderStatus, NanonisError> {
3769 match self {
3770 ExecutionResult::Single(ActionResult::TCPReaderStatus(tcp_status)) => Ok(tcp_status),
3771 _ => Err(NanonisError::Protocol(
3772 "Expected TCP reader status".to_string(),
3773 )),
3774 }
3775 }
3776}
3777
3778impl ExpectFromExecution<crate::actions::StabilityResult> for ExecutionResult {
3779 fn expect_from_execution(self) -> Result<crate::actions::StabilityResult, NanonisError> {
3780 match self {
3781 ExecutionResult::Single(ActionResult::StabilityResult(stability_result)) => {
3782 Ok(stability_result)
3783 }
3784 _ => Err(NanonisError::Protocol(
3785 "Expected stability result".to_string(),
3786 )),
3787 }
3788 }
3789}
3790
3791impl ExpectFromExecution<Vec<String>> for ExecutionResult {
3792 fn expect_from_execution(self) -> Result<Vec<String>, NanonisError> {
3793 match self {
3794 ExecutionResult::Single(ActionResult::Text(text)) => Ok(text),
3795 _ => Err(NanonisError::Protocol("Expected text data".to_string())),
3796 }
3797 }
3798}
3799
3800impl Drop for ActionDriver {
3801 fn drop(&mut self) {
3802 log::info!("ActionDriver cleanup starting...");
3803
3804 if let Some(mut reader) = self.tcp_reader.take() {
3806 let final_data = reader.get_all_data();
3807 let _ = reader.stop(); log::info!(
3809 "Stopped TCP buffering, collected {} frames",
3810 final_data.len()
3811 );
3812 }
3813
3814 log::info!("Disabling safe tip protection...");
3816 if let Err(e) = self.client_mut().safe_tip_on_off_set(false) {
3817 log::warn!("Failed to disable safe tip: {}", e);
3818 }
3819
3820 log::info!("Performing safe withdrawal...");
3822 let withdraw_result = self.execute_chain(vec![
3823 Action::Withdraw {
3824 wait_until_finished: true, timeout: Duration::from_secs(5),
3826 },
3827 Action::MoveMotorAxis {
3828 direction: crate::MotorDirection::ZMinus,
3829 steps: 10,
3830 blocking: false,
3831 },
3832 ]);
3833
3834 if let Err(e) = withdraw_result {
3835 log::warn!("Cleanup withdrawal failed: {}", e);
3836 } else {
3837 log::info!("Safe withdrawal completed");
3838 }
3839
3840 log::info!("ActionDriver cleanup complete");
3841 }
3842}
3843
3844#[cfg(test)]
3845mod tests {
3846 use std::time::Duration;
3847
3848 use super::*;
3849 #[test]
3853 fn test_action_translator_interface() {
3854 let driver_result = ActionDriver::new("127.0.0.1", 6501);
3858 match driver_result {
3859 Ok(mut driver) => {
3860 let action = Action::ReadBias;
3862 let _result = driver.execute(action);
3863
3864 let chain = ActionChain::new(vec![
3869 Action::ReadBias,
3870 Action::Wait {
3871 duration: Duration::from_millis(500),
3872 },
3873 Action::SetBias { voltage: 1.0 },
3874 ]);
3875
3876 let _chain_result = driver.execute_chain(chain);
3877 }
3878 Err(_) => {
3879 println!("Signal discovery failed - this is expected without hardware");
3881 }
3882 }
3883 }
3884}
3885
3886fn displacement_to_motor_movements(
3892 d: &crate::types::MotorDisplacement,
3893) -> Vec<(crate::types::MotorDirection, u16)> {
3894 use crate::types::MotorDirection;
3895
3896 let mut movements = Vec::new();
3897 if d.z < 0 {
3898 movements.push((MotorDirection::ZMinus, (-d.z) as u16));
3899 }
3900 if d.x > 0 {
3901 movements.push((MotorDirection::XPlus, d.x as u16));
3902 } else if d.x < 0 {
3903 movements.push((MotorDirection::XMinus, (-d.x) as u16));
3904 }
3905 if d.y > 0 {
3906 movements.push((MotorDirection::YPlus, d.y as u16));
3907 } else if d.y < 0 {
3908 movements.push((MotorDirection::YMinus, (-d.y) as u16));
3909 }
3910 if d.z > 0 {
3911 movements.push((MotorDirection::ZPlus, d.z as u16));
3912 }
3913 movements
3914}