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,
18 ExpectFromAction,
19 },
20 buffered_tcp_reader::BufferedTCPReader,
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,
25 ScanDirection, Signal, TipShaperConfig, ZControllerHold,
26};
27
28const TIP_STATE_MAX_STD_DEV: f32 = 1.0;
37
38const TIP_STATE_MAX_SLOPE: f32 = 0.01;
44
45const TIP_STATE_DATA_COLLECTION_DURATION_MS: u64 = 500;
47
48const TIP_STATE_READ_TIMEOUT_SECS: u64 = 15;
50
51const TIP_STATE_READ_RETRY_COUNT: u32 = 3;
53
54#[derive(Debug, Clone)]
56pub struct TCPReaderConfig {
57 pub stream_port: u16,
59 pub channels: Vec<i32>,
61 pub oversampling: i32,
63 pub auto_start: bool,
65 pub buffer_size: Option<usize>,
68}
69
70impl Default for TCPReaderConfig {
71 fn default() -> Self {
72 Self {
73 stream_port: 6590,
74 channels: (0..=23).collect(),
75 oversampling: 20,
76 auto_start: true,
77 buffer_size: Some(10_000),
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub enum ActionRequest {
85 Single(Action),
87 Chain(Vec<Action>),
89}
90
91impl From<Action> for ActionRequest {
92 fn from(action: Action) -> Self {
93 ActionRequest::Single(action)
94 }
95}
96
97impl From<Vec<Action>> for ActionRequest {
98 fn from(actions: Vec<Action>) -> Self {
99 ActionRequest::Chain(actions)
100 }
101}
102
103impl From<ActionChain> for ActionRequest {
104 fn from(chain: ActionChain) -> Self {
105 ActionRequest::Chain(chain.into_iter().collect())
106 }
107}
108
109impl ActionRequest {
110 pub fn is_single(&self) -> bool {
111 matches!(self, ActionRequest::Single(_))
112 }
113
114 pub fn is_chain(&self) -> bool {
115 matches!(self, ActionRequest::Chain(_))
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct ExecutionConfig {
122 pub data_collection: Option<(Duration, Duration)>,
124 pub chain_behavior: ChainBehavior,
126 pub logging_behavior: LoggingBehavior,
128 pub performance_mode: PerformanceMode,
130}
131
132#[derive(Debug, Clone)]
133pub enum ChainBehavior {
134 Complete,
136 FinalOnly,
138 Partial,
140}
141
142#[derive(Debug, Clone)]
143pub enum LoggingBehavior {
144 Normal,
146 Deferred,
148 Disabled,
150}
151
152#[derive(Debug, Clone)]
153pub enum PerformanceMode {
154 Normal,
156 Fast,
158}
159
160impl Default for ExecutionConfig {
161 fn default() -> Self {
162 Self {
163 data_collection: None,
164 chain_behavior: ChainBehavior::Complete,
165 logging_behavior: LoggingBehavior::Normal,
166 performance_mode: PerformanceMode::Normal,
167 }
168 }
169}
170
171impl ExecutionConfig {
172 pub fn new() -> Self {
174 Self::default()
175 }
176
177 pub fn with_data_collection(
179 mut self,
180 pre_duration: Duration,
181 post_duration: Duration,
182 ) -> Self {
183 self.data_collection = Some((pre_duration, post_duration));
184 self
185 }
186
187 pub fn final_only(mut self) -> Self {
189 self.chain_behavior = ChainBehavior::FinalOnly;
190 self
191 }
192
193 pub fn partial(mut self) -> Self {
195 self.chain_behavior = ChainBehavior::Partial;
196 self
197 }
198
199 pub fn deferred_logging(mut self) -> Self {
201 self.logging_behavior = LoggingBehavior::Deferred;
202 self
203 }
204
205 pub fn no_logging(mut self) -> Self {
207 self.logging_behavior = LoggingBehavior::Disabled;
208 self
209 }
210
211 pub fn fast_mode(mut self) -> Self {
213 self.performance_mode = PerformanceMode::Fast;
214 self
215 }
216}
217
218#[derive(Debug)]
220pub enum ExecutionResult {
221 Single(ActionResult),
223 Chain(Vec<ActionResult>),
225 ExperimentData(crate::types::ExperimentData),
227 ChainExperimentData(crate::types::ChainExperimentData),
229 Partial(Vec<ActionResult>, NanonisError),
231}
232
233impl ExecutionResult {
234 pub fn into_single(self) -> Result<ActionResult, NanonisError> {
236 match self {
237 ExecutionResult::Single(result) => Ok(result),
238 ExecutionResult::Chain(mut results) if results.len() == 1 => {
239 Ok(results.pop().unwrap())
240 }
241 _ => Err(NanonisError::Protocol(
242 "Expected single result".to_string(),
243 )),
244 }
245 }
246
247 pub fn into_chain(self) -> Result<Vec<ActionResult>, NanonisError> {
249 match self {
250 ExecutionResult::Chain(results) => Ok(results),
251 ExecutionResult::Single(result) => Ok(vec![result]),
252 ExecutionResult::Partial(results, _) => Ok(results),
253 _ => Err(NanonisError::Protocol(
254 "Expected chain results".to_string(),
255 )),
256 }
257 }
258
259 pub fn into_experiment_data(
261 self,
262 ) -> Result<crate::types::ExperimentData, NanonisError> {
263 match self {
264 ExecutionResult::ExperimentData(data) => Ok(data),
265 _ => Err(NanonisError::Protocol(
266 "Expected experiment data".to_string(),
267 )),
268 }
269 }
270
271 pub fn into_chain_experiment_data(
273 self,
274 ) -> Result<crate::types::ChainExperimentData, NanonisError> {
275 match self {
276 ExecutionResult::ChainExperimentData(data) => Ok(data),
277 _ => Err(NanonisError::Protocol(
278 "Expected chain experiment data".to_string(),
279 )),
280 }
281 }
282
283 pub fn expecting<T>(self) -> Result<T, NanonisError>
285 where
286 Self: ExpectFromExecution<T>,
287 {
288 self.expect_from_execution()
289 }
290}
291
292pub trait ExpectFromExecution<T> {
294 fn expect_from_execution(self) -> Result<T, NanonisError>;
295}
296
297pub struct ExecutionBuilder<'a> {
299 driver: &'a mut ActionDriver,
300 request: ActionRequest,
301 config: ExecutionConfig,
302}
303
304impl<'a> ExecutionBuilder<'a> {
305 fn new(driver: &'a mut ActionDriver, request: ActionRequest) -> Self {
306 Self {
307 driver,
308 request,
309 config: ExecutionConfig::default(),
310 }
311 }
312
313 pub fn with_data_collection(
315 mut self,
316 pre_duration: Duration,
317 post_duration: Duration,
318 ) -> Self {
319 self.config = self
320 .config
321 .with_data_collection(pre_duration, post_duration);
322 self
323 }
324
325 pub fn final_only(mut self) -> Self {
327 self.config = self.config.final_only();
328 self
329 }
330
331 pub fn partial(mut self) -> Self {
333 self.config = self.config.partial();
334 self
335 }
336
337 pub fn deferred_logging(mut self) -> Self {
339 self.config = self.config.deferred_logging();
340 self
341 }
342
343 pub fn no_logging(mut self) -> Self {
345 self.config = self.config.no_logging();
346 self
347 }
348
349 pub fn fast_mode(mut self) -> Self {
351 self.config = self.config.fast_mode();
352 self
353 }
354
355 pub fn expecting<T>(self) -> Result<T, NanonisError>
357 where
358 ExecutionResult: ExpectFromExecution<T>,
359 {
360 let result = self.driver.run_with_config(self.request, self.config)?;
361 result.expecting()
362 }
363
364 pub fn execute(self) -> Result<ExecutionResult, NanonisError> {
366 self.driver.run_with_config(self.request, self.config)
367 }
368}
369
370impl<'a> ExecutionBuilder<'a> {
371 pub fn go(self) -> Result<ActionResult, NanonisError> {
373 match self.request {
374 ActionRequest::Single(_) => {
375 let result =
376 self.driver.run_with_config(self.request, self.config)?;
377 result.into_single()
378 }
379 ActionRequest::Chain(_) => Err(NanonisError::Protocol(
380 "Use .execute() for chains, .go() is only for single actions"
381 .to_string(),
382 )),
383 }
384 }
385}
386
387#[derive(Debug, Clone)]
389pub struct ActionDriverBuilder {
390 addr: String,
391 port: u16,
392 connection_timeout: Option<Duration>,
393 initial_storage: HashMap<String, ActionResult>,
394 tcp_reader_config: Option<TCPReaderConfig>,
395 action_logger_config: Option<(std::path::PathBuf, usize, bool)>, custom_tcp_mapping: Option<Vec<(u8, u8)>>, shutdown_flag: Option<Arc<AtomicBool>>, }
399
400impl ActionDriverBuilder {
401 pub fn new(addr: &str, port: u16) -> Self {
403 Self {
404 addr: addr.to_string(),
405 port,
406 connection_timeout: None,
407 initial_storage: HashMap::new(),
408 tcp_reader_config: None,
409 action_logger_config: None,
410 custom_tcp_mapping: None,
411 shutdown_flag: None,
412 }
413 }
414
415 pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
417 self.connection_timeout = Some(timeout);
418 self
419 }
420
421 pub fn with_initial_storage(
423 mut self,
424 storage: HashMap<String, ActionResult>,
425 ) -> Self {
426 self.initial_storage = storage;
427 self
428 }
429
430 pub fn with_stored_value(
432 mut self,
433 key: String,
434 value: ActionResult,
435 ) -> Self {
436 self.initial_storage.insert(key, value);
437 self
438 }
439
440 pub fn with_tcp_reader(mut self, config: TCPReaderConfig) -> Self {
460 if config.buffer_size.is_none() {
461 log::warn!(
462 "TCPLoggerConfig buffer_size is None - buffering disabled"
463 );
464 }
465 self.tcp_reader_config = Some(config);
466 self
467 }
468
469 pub fn with_action_logging(
494 mut self,
495 file_path: impl Into<std::path::PathBuf>,
496 buffer_size: usize,
497 final_format_json: bool,
498 ) -> Self {
499 self.action_logger_config =
500 Some((file_path.into(), buffer_size, final_format_json));
501 self
502 }
503
504 pub fn with_custom_tcp_mapping(mut self, mapping: &[(u8, u8)]) -> Self {
525 self.custom_tcp_mapping = Some(mapping.to_vec());
526 self
527 }
528
529 pub fn with_shutdown_flag(mut self, flag: Arc<AtomicBool>) -> Self {
534 self.shutdown_flag = Some(flag);
535 self
536 }
537
538 pub fn build(self) -> Result<ActionDriver, NanonisError> {
540 let mut client = {
541 let mut builder =
542 NanonisClient::builder().address(&self.addr).port(self.port);
543
544 if let Some(timeout) = self.connection_timeout {
545 builder = builder.connect_timeout(timeout);
546 }
547
548 builder.build()?
549 };
550
551 let tcp_reader = if let Some(ref config) = self.tcp_reader_config {
552 if let Some(buffer_size) = config.buffer_size {
553 client.tcplog_chs_set(config.channels.clone())?;
555 client.tcplog_oversampl_set(config.oversampling)?;
556
557 let reader =
559 crate::buffered_tcp_reader::BufferedTCPReader::new(
560 "127.0.0.1",
561 config.stream_port,
562 buffer_size,
563 config.channels.len() as u32,
564 config.oversampling as f32,
565 )?;
566 log::debug!(
567 "TCP stream connected, buffer capacity: {} frames",
568 buffer_size
569 );
570
571 if config.auto_start {
573 log::debug!("Stopping TCP logger to ensure clean state");
575 let _ = client.tcplog_stop(); std::thread::sleep(std::time::Duration::from_millis(200)); client.tcplog_start()?;
580 log::debug!("TCP logger started, data collection active");
581 }
582
583 Some(reader)
584 } else {
585 None
586 }
587 } else {
588 None
589 };
590
591 let action_logger =
593 if let Some((file_path, buffer_size, final_format_json)) =
594 self.action_logger_config
595 {
596 Some(crate::logger::Logger::new(
597 file_path,
598 buffer_size,
599 final_format_json,
600 ))
601 } else {
602 None
603 };
604
605 let signal_names = client.signal_names_get()?;
607 let signal_registry =
608 if let Some(ref custom_map) = self.custom_tcp_mapping {
609 log::debug!(
610 "Using custom TCP channel mapping with {} entries",
611 custom_map.len()
612 );
613 SignalRegistry::builder()
614 .with_standard_map()
615 .add_tcp_map(custom_map)
616 .from_signal_names(&signal_names)
617 .create_aliases()
618 .build()
619 } else {
620 SignalRegistry::with_hardcoded_tcp_mapping(&signal_names)
621 };
622
623 Ok(ActionDriver {
624 client,
625 stored_values: self.initial_storage,
626 tcp_reader_config: self.tcp_reader_config,
627 tcp_reader,
628 action_logger,
629 action_logging_enabled: true, signal_registry,
631 recent_stable_signals: std::collections::VecDeque::new(),
632 shutdown_flag: self.shutdown_flag,
633 })
634 }
635}
636
637pub struct ActionDriver {
640 client: NanonisClient,
642 stored_values: HashMap<String, ActionResult>,
644 tcp_reader_config: Option<TCPReaderConfig>,
646 tcp_reader: Option<crate::buffered_tcp_reader::BufferedTCPReader>,
648 action_logger:
650 Option<crate::logger::Logger<crate::actions::ActionLogEntry>>,
651 action_logging_enabled: bool,
653 signal_registry: SignalRegistry,
655 recent_stable_signals: std::collections::VecDeque<(
657 crate::actions::StableSignal,
658 std::time::Instant,
659 )>,
660 shutdown_flag: Option<Arc<AtomicBool>>,
662}
663
664impl ActionDriver {
665 pub fn builder(addr: &str, port: u16) -> ActionDriverBuilder {
667 ActionDriverBuilder::new(addr, port)
668 }
669
670 pub fn new(addr: &str, port: u16) -> Result<Self, NanonisError> {
672 Self::builder(addr, port).build()
673 }
674
675 pub fn with_nanonis_client(mut client: NanonisClient) -> Self {
677 let signal_names = client.signal_names_get().unwrap_or_default();
679 let signal_registry =
680 SignalRegistry::with_hardcoded_tcp_mapping(&signal_names);
681
682 Self {
683 client,
684 stored_values: HashMap::new(),
685 tcp_reader_config: None,
686 tcp_reader: None,
687 action_logger: None,
688 action_logging_enabled: false,
689 signal_registry,
690 recent_stable_signals: std::collections::VecDeque::new(),
691 shutdown_flag: None,
692 }
693 }
694
695 pub fn client(&self) -> &NanonisClient {
697 &self.client
698 }
699
700 pub fn client_mut(&mut self) -> &mut NanonisClient {
702 &mut self.client
703 }
704
705 pub fn set_shutdown_flag(&mut self, flag: Arc<AtomicBool>) {
707 self.shutdown_flag = Some(flag);
708 }
709
710 fn is_shutdown_requested(&self) -> bool {
712 self.shutdown_flag
713 .as_ref()
714 .map(|f| f.load(Ordering::SeqCst))
715 .unwrap_or(false)
716 }
717
718 pub fn auto_approach(
723 &mut self,
724 wait_until_finished: bool,
725 timeout: Duration,
726 ) -> Result<(), NanonisError> {
727 match self.client.auto_approach_on_off_get() {
729 Ok(true) => {
730 log::warn!("Auto-approach already running");
731 return Ok(());
732 }
733 Ok(false) => {
734 log::debug!("Auto-approach is idle, proceeding to start");
735 }
736 Err(_) => {
737 log::warn!(
738 "Auto-approach status unknown, attempting to proceed"
739 );
740 }
741 }
742
743 match self.client.auto_approach_open() {
745 Ok(_) => log::debug!("Opened the auto-approach module"),
746 Err(_) => {
747 log::debug!("Failed to open auto-approach module, already open")
748 }
749 }
750
751 std::thread::sleep(std::time::Duration::from_millis(500));
753
754 if let Err(e) = self.client.auto_approach_on_off_set(true) {
756 log::error!("Failed to start auto-approach: {}", e);
757 return Err(NanonisError::Protocol(format!(
758 "Failed to start auto-approach: {}",
759 e
760 )));
761 }
762
763 if !wait_until_finished {
764 log::debug!("Auto-approach started, not waiting for completion");
765 return Ok(());
766 }
767
768 log::debug!("Waiting for auto-approach to complete...");
770 let poll_interval = std::time::Duration::from_millis(100);
771
772 match poll_until(
773 || {
774 self.client
775 .auto_approach_on_off_get()
776 .map(|running| !running)
777 },
778 timeout,
779 poll_interval,
780 ) {
781 Ok(()) => {
782 log::debug!("Auto-approach completed successfully");
783 Ok(())
784 }
785 Err(PollError::Timeout) => {
786 log::warn!("Auto-approach timed out after {:?}", timeout);
787 let _ = self.client.auto_approach_on_off_set(false);
788 Err(NanonisError::Protocol(
789 "Auto-approach timed out".to_string(),
790 ))
791 }
792 Err(PollError::ConditionError(e)) => {
793 log::error!("Error checking auto-approach status: {}", e);
794 Err(NanonisError::Protocol(format!(
795 "Status check error: {}",
796 e
797 )))
798 }
799 }
800 }
801
802 pub fn center_freq_shift(&mut self) -> Result<(), NanonisError> {
804 let modulator_index = 1;
805 log::debug!("Centering frequency shift");
806 self.client.pll_freq_shift_auto_center(modulator_index)
807 }
808
809 pub fn tcp_reader_config(&self) -> Option<&TCPReaderConfig> {
811 self.tcp_reader_config.as_ref()
812 }
813
814 pub fn has_tcp_reader(&self) -> bool {
816 self.tcp_reader.is_some()
817 }
818
819 pub fn tcp_reader_mut(&mut self) -> Option<&mut BufferedTCPReader> {
820 self.tcp_reader.as_mut()
821 }
822
823 pub fn clear_tcp_buffer(&self) {
828 if let Some(ref tcp_reader) = self.tcp_reader {
829 tcp_reader.clear_buffer();
830 debug!("TCP reader buffer cleared");
831 } else {
832 warn!("No TCP reader available to clear");
833 }
834 }
835
836 pub fn signal_registry(&self) -> &SignalRegistry {
838 &self.signal_registry
839 }
840
841 fn calculate_samples_for_duration(
855 &self,
856 target_duration: Duration,
857 ) -> Option<usize> {
858 if let Some(ref config) = self.tcp_reader_config {
859 let base_rate = 2000.0; let effective_rate = base_rate / config.oversampling as f64;
864 let samples = (effective_rate * target_duration.as_secs_f64())
865 .ceil() as usize;
866 log::debug!(
867 "Calculated {} samples for {:.0}ms (base: {}Hz, oversampling: {}, effective: {:.1}Hz)",
868 samples,
869 target_duration.as_millis(),
870 base_rate,
871 config.oversampling,
872 effective_rate
873 );
874 Some(samples.max(50)) } else {
876 None
877 }
878 }
879
880 pub fn run<R>(&mut self, request: R) -> ExecutionBuilder<'_>
901 where
902 R: Into<ActionRequest>,
903 {
904 ExecutionBuilder::new(self, request.into())
905 }
906
907 pub fn run_with_config(
909 &mut self,
910 request: ActionRequest,
911 config: ExecutionConfig,
912 ) -> Result<ExecutionResult, NanonisError> {
913 match (&request, &config.data_collection) {
914 (
916 ActionRequest::Single(action),
917 Some((pre_duration, post_duration)),
918 ) => {
919 let experiment_data = self.execute_with_data_collection(
920 action.clone(),
921 *pre_duration,
922 *post_duration,
923 )?;
924 Ok(ExecutionResult::ExperimentData(experiment_data))
925 }
926
927 (
929 ActionRequest::Chain(actions),
930 Some((pre_duration, post_duration)),
931 ) => {
932 let chain_experiment_data = self
933 .execute_chain_with_data_collection(
934 actions.clone(),
935 *pre_duration,
936 *post_duration,
937 )?;
938 Ok(ExecutionResult::ChainExperimentData(chain_experiment_data))
939 }
940
941 (ActionRequest::Single(action), None) => {
943 let result = match config.logging_behavior {
944 LoggingBehavior::Disabled => {
945 let previous_state =
946 self.set_action_logging_enabled(false);
947 let result = self.execute(action.clone());
948 self.set_action_logging_enabled(previous_state);
949 result
950 }
951 _ => self.execute(action.clone()),
952 }?;
953 Ok(ExecutionResult::Single(result))
954 }
955
956 (ActionRequest::Chain(actions), None) => {
958 let results =
959 match (&config.chain_behavior, &config.logging_behavior) {
960 (ChainBehavior::Complete, LoggingBehavior::Normal) => {
961 self.execute_chain(actions.clone())?
962 }
963 (
964 ChainBehavior::Complete,
965 LoggingBehavior::Deferred,
966 ) => self.execute_chain_deferred(actions.clone())?,
967 (
968 ChainBehavior::Complete,
969 LoggingBehavior::Disabled,
970 ) => {
971 let previous_state =
972 self.set_action_logging_enabled(false);
973 let result = self.execute_chain(actions.clone());
974 self.set_action_logging_enabled(previous_state);
975 result?
976 }
977 (ChainBehavior::FinalOnly, _) => {
978 let results = match config.logging_behavior {
979 LoggingBehavior::Deferred => self
980 .execute_chain_deferred(actions.clone())?,
981 LoggingBehavior::Disabled => {
982 let previous_state =
983 self.set_action_logging_enabled(false);
984 let result =
985 self.execute_chain(actions.clone());
986 self.set_action_logging_enabled(
987 previous_state,
988 );
989 result?
990 }
991 _ => self.execute_chain(actions.clone())?,
992 };
993 vec![results
994 .into_iter()
995 .last()
996 .unwrap_or(ActionResult::None)]
997 }
998 (ChainBehavior::Partial, _) => {
999 match self.execute_chain_partial(actions.clone()) {
1000 Ok(results) => results,
1001 Err((partial_results, error)) => {
1002 return Ok(ExecutionResult::Partial(
1003 partial_results,
1004 error,
1005 ));
1006 }
1007 }
1008 }
1009 };
1010
1011 Ok(ExecutionResult::Chain(results))
1012 }
1013 }
1014 }
1015
1016 pub fn get_recent_tcp_data(
1030 &self,
1031 duration: Duration,
1032 ) -> Vec<crate::types::TimestampedSignalFrame> {
1033 self.tcp_reader
1034 .as_ref()
1035 .map(|reader| reader.get_recent_data(duration))
1036 .unwrap_or_default()
1037 }
1038
1039 pub fn execute_with_data_collection(
1055 &mut self,
1056 action: Action,
1057 pre_duration: Duration,
1058 post_duration: Duration,
1059 ) -> Result<crate::types::ExperimentData, NanonisError> {
1060 if self.tcp_reader.is_none() {
1061 return Err(NanonisError::Protocol(
1062 "TCP buffering not active".to_string(),
1063 ));
1064 }
1065
1066 let action_start = Instant::now();
1067 let action_result = self.execute(action.clone())?;
1068 let action_end = Instant::now();
1069
1070 std::thread::sleep(post_duration);
1071
1072 let window_start = action_start - pre_duration;
1073 let window_end = action_end + post_duration;
1074
1075 let signal_frames = self
1076 .tcp_reader
1077 .as_ref()
1078 .unwrap()
1079 .get_data_between(window_start, window_end);
1080 let tcp_config = self.tcp_reader_config.as_ref().unwrap().clone();
1081
1082 let experiment_data = crate::types::ExperimentData {
1083 action_result,
1084 signal_frames,
1085 tcp_config,
1086 action_start,
1087 action_end,
1088 total_duration: action_end.duration_since(action_start),
1089 };
1090
1091 if self.action_logging_enabled && self.action_logger.is_some() {
1093 let log_entry = ActionLogEntry {
1094 action: format!("Data Collection: {}", action.description()),
1095 result: ActionLogResult::from_experiment_data(&experiment_data),
1096 start_time: chrono::Utc::now(),
1097 duration_ms: experiment_data.total_duration.as_millis() as u64,
1098 metadata: Some(
1099 [
1100 (
1101 "type".to_string(),
1102 "experiment_data_collection".to_string(),
1103 ),
1104 (
1105 "pre_duration_ms".to_string(),
1106 pre_duration.as_millis().to_string(),
1107 ),
1108 (
1109 "post_duration_ms".to_string(),
1110 post_duration.as_millis().to_string(),
1111 ),
1112 (
1113 "signal_frame_count".to_string(),
1114 experiment_data.signal_frames.len().to_string(),
1115 ),
1116 ]
1117 .into_iter()
1118 .collect(),
1119 ),
1120 };
1121
1122 if let Err(log_error) =
1123 self.action_logger.as_mut().unwrap().add(log_entry)
1124 {
1125 log::warn!("Failed to log experiment data: {}", log_error);
1126 }
1127 }
1128
1129 Ok(experiment_data)
1130 }
1131
1132 pub fn pulse_with_data_collection(
1143 &mut self,
1144 pulse_voltage: f32,
1145 pulse_duration: Duration,
1146 pre_duration: Duration,
1147 post_duration: Duration,
1148 ) -> Result<crate::types::ExperimentData, NanonisError> {
1149 self.execute_with_data_collection(
1150 Action::BiasPulse {
1151 wait_until_done: true,
1152 bias_value_v: pulse_voltage,
1153 pulse_width: pulse_duration,
1154 z_controller_hold: crate::types::ZControllerHold::Hold as u16,
1155 pulse_mode: crate::types::PulseMode::Absolute as u16,
1156 },
1157 pre_duration,
1158 post_duration,
1159 )
1160 }
1161
1162 pub fn tcp_buffer_stats(&self) -> Option<(usize, usize, Duration)> {
1170 self.tcp_reader.as_ref().map(|reader| reader.buffer_stats())
1171 }
1172
1173 pub fn stop_tcp_buffering(
1182 &mut self,
1183 ) -> Result<Vec<crate::types::TimestampedSignalFrame>, NanonisError> {
1184 if let Some(mut reader) = self.tcp_reader.take() {
1185 let final_data = reader.get_all_data();
1186 reader.stop()?;
1187 log::info!(
1188 "Manually stopped TCP buffering, collected {} frames",
1189 final_data.len()
1190 );
1191 Ok(final_data)
1192 } else {
1193 Ok(Vec::new())
1194 }
1195 }
1196
1197 pub fn execute_chain_with_data_collection(
1213 &mut self,
1214 actions: Vec<Action>,
1215 pre_duration: Duration,
1216 post_duration: Duration,
1217 ) -> Result<crate::types::ChainExperimentData, NanonisError> {
1218 if self.tcp_reader.is_none() {
1219 return Err(NanonisError::Protocol(
1220 "TCP buffering not active".to_string(),
1221 ));
1222 }
1223
1224 let chain_start = Instant::now();
1225 let mut action_results = Vec::with_capacity(actions.len());
1226 let mut action_timings = Vec::with_capacity(actions.len());
1227
1228 for action in actions {
1230 let action_start = Instant::now();
1231 let action_result = self.execute(action)?;
1232 let action_end = Instant::now();
1233
1234 action_results.push(action_result);
1235 action_timings.push((action_start, action_end));
1236 }
1237
1238 let chain_end = Instant::now();
1239
1240 std::thread::sleep(post_duration);
1242
1243 let window_start = chain_start - pre_duration;
1245 let window_end = chain_end + post_duration;
1246
1247 let signal_frames = self
1248 .tcp_reader
1249 .as_ref()
1250 .unwrap()
1251 .get_data_between(window_start, window_end);
1252 let tcp_config = self.tcp_reader_config.as_ref().unwrap().clone();
1253
1254 let chain_experiment_data = crate::types::ChainExperimentData {
1255 action_results,
1256 signal_frames,
1257 tcp_config,
1258 action_timings,
1259 chain_start,
1260 chain_end,
1261 total_duration: chain_end.duration_since(chain_start),
1262 };
1263
1264 if self.action_logging_enabled && self.action_logger.is_some() {
1266 let log_entry = ActionLogEntry {
1267 action: format!(
1268 "Chain Data Collection: {} actions",
1269 chain_experiment_data.action_results.len()
1270 ),
1271 result: ActionLogResult::from_chain_experiment_data(
1272 &chain_experiment_data,
1273 ),
1274 start_time: chrono::Utc::now(),
1275 duration_ms: chain_experiment_data.total_duration.as_millis()
1276 as u64,
1277 metadata: Some(
1278 [
1279 (
1280 "type".to_string(),
1281 "chain_experiment_data_collection".to_string(),
1282 ),
1283 (
1284 "pre_duration_ms".to_string(),
1285 pre_duration.as_millis().to_string(),
1286 ),
1287 (
1288 "post_duration_ms".to_string(),
1289 post_duration.as_millis().to_string(),
1290 ),
1291 (
1292 "action_count".to_string(),
1293 chain_experiment_data
1294 .action_results
1295 .len()
1296 .to_string(),
1297 ),
1298 (
1299 "signal_frame_count".to_string(),
1300 chain_experiment_data
1301 .signal_frames
1302 .len()
1303 .to_string(),
1304 ),
1305 ]
1306 .into_iter()
1307 .collect(),
1308 ),
1309 };
1310
1311 if let Err(log_error) =
1312 self.action_logger.as_mut().unwrap().add(log_entry)
1313 {
1314 log::warn!(
1315 "Failed to log chain experiment data: {}",
1316 log_error
1317 );
1318 }
1319 }
1320
1321 Ok(chain_experiment_data)
1322 }
1323
1324 pub fn start_tcp_logger(&mut self) -> Result<(), NanonisError> {
1326 self.client.tcplog_start()
1327 }
1328
1329 pub fn stop_tcp_logger(&mut self) -> Result<(), NanonisError> {
1331 self.client.tcplog_stop()
1332 }
1333
1334 pub fn set_tcp_logger_channels(
1336 &mut self,
1337 channels: Vec<i32>,
1338 ) -> Result<(), NanonisError> {
1339 self.client.tcplog_chs_set(channels)
1340 }
1341
1342 pub fn set_tcp_logger_oversampling(
1344 &mut self,
1345 oversampling: i32,
1346 ) -> Result<(), NanonisError> {
1347 self.client.tcplog_oversampl_set(oversampling)
1348 }
1349
1350 pub fn get_tcp_logger_status(
1352 &mut self,
1353 ) -> Result<crate::types::TCPLogStatus, NanonisError> {
1354 self.client.tcplog_status_get()
1355 }
1356
1357 pub fn execute(
1359 &mut self,
1360 action: Action,
1361 ) -> Result<ActionResult, NanonisError> {
1362 let start_time = chrono::Utc::now();
1363 let start_instant = std::time::Instant::now();
1364
1365 let result = self.execute_internal(action.clone());
1366
1367 let duration = start_instant.elapsed();
1368
1369 if self.action_logging_enabled && self.action_logger.is_some() {
1371 let log_entry = match &result {
1372 Ok(action_result) => ActionLogEntry::new(
1373 &action,
1374 action_result,
1375 start_time,
1376 duration,
1377 ),
1378 Err(error) => ActionLogEntry::new_error(
1379 &action, error, start_time, duration,
1380 ),
1381 };
1382
1383 if let Err(log_error) =
1384 self.action_logger.as_mut().unwrap().add(log_entry)
1385 {
1386 log::warn!("Failed to log action: {}", log_error);
1387 }
1388 }
1389
1390 result
1391 }
1392
1393 pub fn execute_with_options(
1416 &mut self,
1417 action: Action,
1418 data_collection: bool,
1419 pre_duration: Duration,
1420 post_duration: Duration,
1421 ) -> Result<ActionResult, NanonisError> {
1422 if data_collection && self.tcp_reader.is_some() {
1423 let _experiment_data = self.execute_with_data_collection(
1425 action,
1426 pre_duration,
1427 post_duration,
1428 )?;
1429 Ok(ActionResult::Success) } else {
1432 self.execute(action)
1434 }
1435 }
1436
1437 pub fn execute_chain_with_options(
1448 &mut self,
1449 chain: impl Into<ActionChain>,
1450 data_collection: bool,
1451 pre_duration: Duration,
1452 post_duration: Duration,
1453 ) -> Result<Vec<ActionResult>, NanonisError> {
1454 if data_collection && self.tcp_reader.is_some() {
1455 let chain_experiment_data = self
1457 .execute_chain_with_data_collection(
1458 chain.into().into_iter().collect(),
1459 pre_duration,
1460 post_duration,
1461 )?;
1462 Ok(chain_experiment_data.action_results)
1464 } else {
1465 self.execute_chain(chain)
1467 }
1468 }
1469
1470 fn execute_internal(
1472 &mut self,
1473 action: Action,
1474 ) -> Result<ActionResult, NanonisError> {
1475 match action {
1476 Action::ReadSignal {
1478 signal,
1479 wait_for_newest,
1480 } => {
1481 let value = self.client.signals_vals_get(
1482 vec![SignalIndex::new(signal.index).into()],
1483 wait_for_newest,
1484 )?;
1485 Ok(ActionResult::Value(value[0] as f64))
1486 }
1487
1488 Action::ReadSignals {
1489 signals,
1490 wait_for_newest,
1491 } => {
1492 let indices: Vec<i32> = signals
1493 .iter()
1494 .map(|s| SignalIndex::new(s.index).into())
1495 .collect();
1496 let values =
1497 self.client.signals_vals_get(indices, wait_for_newest)?;
1498 Ok(ActionResult::Values(
1499 values.into_iter().map(|v| v as f64).collect(),
1500 ))
1501 }
1502
1503 Action::ReadSignalNames => {
1504 let names = self.client.signal_names_get()?;
1505 Ok(ActionResult::Text(names))
1506 }
1507
1508 Action::ReadBias => {
1510 let bias = self.client.bias_get()?;
1511 Ok(ActionResult::Value(bias as f64))
1512 }
1513
1514 Action::SetBias { voltage } => {
1515 self.client.bias_set(voltage)?;
1516 Ok(ActionResult::Success)
1517 }
1518
1519 Action::ReadOsci {
1521 signal,
1522 trigger,
1523 data_to_get,
1524 is_stable,
1525 } => {
1526 self.client.osci1t_run()?;
1527
1528 self.client.osci1t_ch_set(signal.index as i32)?;
1529
1530 if let Some(trigger) = trigger {
1531 self.client.osci1t_trig_set(
1532 trigger.mode.into(),
1533 trigger.slope.into(),
1534 trigger.level,
1535 trigger.hysteresis,
1536 )?;
1537 }
1538
1539 match data_to_get {
1540 crate::types::DataToGet::Stable { readings, timeout } => {
1541 let osci_data = self
1542 .find_stable_oscilloscope_data_with_fallback(
1543 data_to_get,
1544 readings,
1545 timeout,
1546 0.01,
1547 50e-15,
1548 0.8,
1549 is_stable,
1550 )?;
1551 Ok(ActionResult::OsciData(osci_data))
1552 }
1553 _ => {
1554 let data_mode = match data_to_get {
1556 DataToGet::Current => 0,
1557 DataToGet::NextTrigger => 1,
1558 DataToGet::Wait2Triggers => 2,
1559 DataToGet::Stable { .. } => 1, };
1561 let (t0, dt, size, data) =
1562 self.client.osci1t_data_get(data_mode)?;
1563 let osci_data =
1564 OsciData::new_stable(t0, dt, size, data);
1565 Ok(ActionResult::OsciData(osci_data))
1566 }
1567 }
1568 }
1569
1570 Action::ReadPiezoPosition {
1572 wait_for_newest_data,
1573 } => {
1574 let pos = self.client.folme_xy_pos_get(wait_for_newest_data)?;
1575 Ok(ActionResult::Position(pos))
1576 }
1577
1578 Action::SetPiezoPosition {
1579 position,
1580 wait_until_finished,
1581 } => {
1582 self.client
1583 .folme_xy_pos_set(position, wait_until_finished)?;
1584 Ok(ActionResult::Success)
1585 }
1586
1587 Action::MovePiezoRelative { delta } => {
1588 let current = self.client.folme_xy_pos_get(true)?;
1590 info!("Current position: {current:?}");
1591 let new_position = Position {
1592 x: current.x + delta.x,
1593 y: current.y + delta.y,
1594 };
1595 self.client.folme_xy_pos_set(new_position, true)?;
1596 Ok(ActionResult::Success)
1597 }
1598
1599 Action::MoveMotorAxis {
1601 direction,
1602 steps,
1603 blocking,
1604 } => {
1605 self.client.motor_start_move(
1606 direction,
1607 steps,
1608 MotorGroup::Group1,
1609 blocking,
1610 )?;
1611 Ok(ActionResult::Success)
1612 }
1613
1614 Action::MoveMotor3D {
1615 displacement,
1616 blocking,
1617 } => {
1618 let movements = displacement.to_motor_movements();
1620
1621 for (direction, steps) in movements {
1623 self.client.motor_start_move(
1624 direction,
1625 steps,
1626 MotorGroup::Group1,
1627 blocking,
1628 )?;
1629 }
1630 Ok(ActionResult::Success)
1631 }
1632
1633 Action::MoveMotorClosedLoop { target, mode } => {
1634 self.client.motor_start_closed_loop(
1635 mode,
1636 target,
1637 true, MotorGroup::Group1,
1639 )?;
1640 Ok(ActionResult::Success)
1641 }
1642
1643 Action::StopMotor => {
1644 self.client.motor_stop_move()?;
1645 Ok(ActionResult::Success)
1646 }
1647
1648 Action::AutoApproach {
1650 wait_until_finished,
1651 timeout,
1652 center_freq_shift,
1653 } => {
1654 log::debug!(
1655 "Starting auto-approach (wait: {}, timeout: {:?}, center_freq: {})",
1656 wait_until_finished,
1657 timeout,
1658 center_freq_shift
1659 );
1660
1661 if center_freq_shift {
1663 self.auto_approach(true, timeout)?;
1665
1666 std::thread::sleep(Duration::from_millis(200));
1668
1669 if let Ok(safetip_state) =
1671 self.client_mut().safe_tip_on_off_get()
1672 {
1673 if !safetip_state {
1674 self.client_mut().safe_tip_on_off_set(true)?;
1675 }
1676 } else {
1677 log::warn!(
1678 "Failed to read safe tip state, setting true"
1679 );
1680 self.client_mut().safe_tip_on_off_set(true)?;
1681 }
1682
1683 self.check_safetip_status("after enabling safe tip")?;
1684
1685 self.client_mut().z_ctrl_home()?;
1687
1688 self.check_safetip_status("after z_ctrl_home")?;
1689
1690 std::thread::sleep(Duration::from_millis(500));
1692
1693 self.check_safetip_status("after 500ms settle")?;
1694
1695 if let Err(e) = self.center_freq_shift() {
1697 log::warn!("Failed to center frequency shift: {}", e);
1698 }
1700
1701 self.check_safetip_status("after center_freq_shift")?;
1702
1703 self.auto_approach(wait_until_finished, timeout)?;
1705
1706 self.check_safetip_status("after final auto_approach")?;
1707
1708 if let Ok(safetip_state) =
1710 self.client_mut().safe_tip_on_off_get()
1711 {
1712 if safetip_state {
1713 self.client_mut().safe_tip_on_off_set(false)?;
1714 }
1715 } else {
1716 log::warn!(
1717 "Failed to read safe tip state, setting false"
1718 );
1719 self.client_mut().safe_tip_on_off_set(false)?;
1720 }
1721 } else {
1722 self.auto_approach(wait_until_finished, timeout)?;
1723 }
1724
1725 Ok(ActionResult::Success)
1726 }
1727
1728 Action::Withdraw {
1729 wait_until_finished,
1730 timeout,
1731 } => {
1732 self.client.z_ctrl_withdraw(wait_until_finished, timeout)?;
1733 Ok(ActionResult::Success)
1734 }
1735
1736 Action::SafeReposition { x_steps, y_steps } => {
1737 let displacement =
1739 crate::types::MotorDisplacement::new(x_steps, y_steps, -3);
1740 let withdraw_timeout = Duration::from_secs(5);
1741 let approach_timeout = Duration::from_secs(10);
1742 let stabilization_wait = Duration::from_millis(500);
1743
1744 self.client.z_ctrl_withdraw(true, withdraw_timeout)?;
1747
1748 let movements = displacement.to_motor_movements();
1750 for (direction, steps) in movements {
1751 self.client.motor_start_move(
1752 direction,
1753 steps,
1754 MotorGroup::Group1,
1755 true,
1756 )?;
1757 }
1758
1759 thread::sleep(Duration::from_millis(500));
1760
1761 self.run(Action::AutoApproach {
1763 wait_until_finished: true,
1764 timeout: approach_timeout,
1765 center_freq_shift: true,
1766 })
1767 .go()?;
1768
1769 thread::sleep(stabilization_wait);
1771
1772 Ok(ActionResult::Success)
1773 }
1774
1775 Action::SetZSetpoint { setpoint } => {
1776 self.client.z_ctrl_setpoint_set(setpoint)?;
1777 Ok(ActionResult::Success)
1778 }
1779
1780 Action::ScanControl { action } => {
1782 self.client.scan_action(action, ScanDirection::Up)?;
1783 Ok(ActionResult::Success)
1784 }
1785
1786 Action::ReadScanStatus => {
1787 let is_scanning = self.client.scan_status_get()?;
1788 Ok(ActionResult::Status(is_scanning))
1789 }
1790
1791 Action::BiasPulse {
1793 wait_until_done,
1794 pulse_width,
1795 bias_value_v,
1796 z_controller_hold,
1797 pulse_mode,
1798 } => {
1799 let hold_enum = match z_controller_hold {
1801 0 => ZControllerHold::NoChange,
1802 1 => ZControllerHold::Hold,
1803 2 => ZControllerHold::Release,
1804 _ => ZControllerHold::NoChange, };
1806
1807 let mode_enum = match pulse_mode {
1808 0 => PulseMode::Keep,
1809 1 => PulseMode::Relative,
1810 2 => PulseMode::Absolute,
1811 _ => PulseMode::Keep, };
1813
1814 self.client.bias_pulse(
1815 wait_until_done,
1816 pulse_width.as_secs_f32(),
1817 bias_value_v,
1818 hold_enum.into(),
1819 mode_enum.into(),
1820 )?;
1821
1822 Ok(ActionResult::Success)
1823 }
1824
1825 Action::TipShaper {
1826 config,
1827 wait_until_finished,
1828 timeout,
1829 } => {
1830 self.client.tip_shaper_props_set(config)?;
1832
1833 self.client.tip_shaper_start(wait_until_finished, timeout)?;
1835
1836 Ok(ActionResult::Success)
1837 }
1838
1839 Action::PulseRetract {
1840 pulse_width,
1841 pulse_height_v,
1842 } => {
1843 let current_bias =
1844 self.client_mut().bias_get().unwrap_or(500e-3);
1845
1846 let config = TipShaperConfig {
1847 switch_off_delay: std::time::Duration::from_millis(10),
1848 change_bias: true,
1849 bias_v: pulse_height_v,
1850 tip_lift_m: 0.0,
1851 lift_time_1: pulse_width,
1852 bias_lift_v: current_bias,
1853 bias_settling_time: std::time::Duration::from_millis(50),
1854 lift_height_m: 100e-9,
1855 lift_time_2: std::time::Duration::from_millis(100),
1856 end_wait_time: std::time::Duration::from_millis(50),
1857 restore_feedback: false,
1858 };
1859
1860 self.client_mut().tip_shaper_props_set(config)?;
1862 self.client_mut()
1863 .tip_shaper_start(true, Duration::from_secs(5))?;
1864
1865 Ok(ActionResult::Success)
1866 }
1867
1868 Action::Wait { duration } => {
1869 thread::sleep(duration);
1870 Ok(ActionResult::None)
1871 }
1872
1873 Action::Store { key, action } => {
1875 let result = self.execute(*action)?;
1876 self.stored_values.insert(key, result.clone());
1877 Ok(result) }
1879
1880 Action::Retrieve { key } => match self.stored_values.get(&key) {
1881 Some(value) => Ok(value.clone()), None => Err(NanonisError::Protocol(format!(
1883 "No stored value found for key: {}",
1884 key
1885 ))),
1886 },
1887
1888 Action::StartTCPLogger => {
1890 self.start_tcp_logger()?;
1891 Ok(ActionResult::Success)
1892 }
1893
1894 Action::StopTCPLogger => {
1895 self.stop_tcp_logger()?;
1896 Ok(ActionResult::Success)
1897 }
1898
1899 Action::GetTCPLoggerStatus => {
1900 use crate::actions::TCPReaderStatus;
1901 let status = self.get_tcp_logger_status()?;
1902 let config = self.tcp_reader_config();
1903
1904 Ok(ActionResult::TCPReaderStatus(TCPReaderStatus {
1905 status,
1906 channels: config
1907 .map(|c| c.channels.clone())
1908 .unwrap_or_default(),
1909 oversampling: config.map(|c| c.oversampling).unwrap_or(0),
1910 }))
1911 }
1912
1913 Action::ConfigureTCPLogger {
1914 channels,
1915 oversampling,
1916 } => {
1917 self.set_tcp_logger_channels(channels)?;
1918 self.set_tcp_logger_oversampling(oversampling)?;
1919 Ok(ActionResult::Success)
1920 }
1921
1922 Action::CheckTipState { method } => {
1923 use std::collections::HashMap;
1924
1925 use crate::{
1926 actions::{TipCheckMethod, TipState},
1927 types::TipShape,
1928 };
1929
1930 let (tip_shape, measured_signals, mut metadata) = match method {
1931 TipCheckMethod::SignalBounds { signal, bounds } => {
1932 if let Some(ref tcp_reader) = self.tcp_reader {
1934 let (frame_count, _max_capacity, time_span) =
1935 tcp_reader.buffer_stats();
1936 log::debug!("CheckTipState: TCP reader available with {} frames, timespan: {}ms",
1937 frame_count, time_span.as_millis());
1938 } else {
1939 log::warn!(
1940 "CheckTipState: No TCP reader available for signal {}",
1941 signal.index
1942 );
1943 }
1944
1945 log::debug!(
1947 "CheckTipState: Calling ReadStableSignal for signal {}",
1948 signal.index
1949 );
1950
1951 let data_points = self
1953 .calculate_samples_for_duration(
1954 Duration::from_millis(
1955 TIP_STATE_DATA_COLLECTION_DURATION_MS,
1956 ),
1957 )
1958 .unwrap_or(100); let stable_result = self
1961 .run(Action::ReadStableSignal {
1962 signal: signal.clone(),
1963 data_points: Some(data_points),
1964 use_new_data: true, stability_method: crate::actions::SignalStabilityMethod::Combined {
1966 max_std_dev: TIP_STATE_MAX_STD_DEV,
1967 max_slope: TIP_STATE_MAX_SLOPE,
1968 },
1969 timeout: Duration::from_secs(TIP_STATE_READ_TIMEOUT_SECS),
1970 retry_count: Some(TIP_STATE_READ_RETRY_COUNT),
1971 })
1972 .execute();
1973
1974 let (value, raw_data, read_method) = match stable_result
1975 {
1976 Ok(exec_result) => match exec_result {
1977 ExecutionResult::Single(
1978 ActionResult::StableSignal(stable_signal),
1979 ) => {
1980 log::debug!("CheckTipState: ReadStableSignal succeeded with {} data points", stable_signal.raw_data.len());
1982 (
1983 stable_signal.stable_value,
1984 stable_signal.raw_data,
1985 "stable_signal",
1986 )
1987 }
1988 ExecutionResult::Single(
1989 ActionResult::Values(values),
1990 ) => {
1991 log::warn!("CheckTipState: ReadStableSignal failed but returned {} raw values, using minimum as fallback", values.len());
1993 let raw_data: Vec<f32> = values
1994 .iter()
1995 .map(|&v| v as f32)
1996 .collect();
1997 let min_value = raw_data
1998 .iter()
1999 .cloned()
2000 .fold(f32::INFINITY, f32::min);
2001 (min_value, raw_data, "fallback_minimum")
2002 }
2003 _ => {
2004 log::warn!("CheckTipState: ReadStableSignal returned unexpected result type, falling back to single read");
2006 let single_value = self
2007 .client
2008 .signal_val_get(signal.index, true)?;
2009 (
2010 single_value,
2011 vec![single_value],
2012 "single_read_fallback",
2013 )
2014 }
2015 },
2016 Err(e) => {
2017 log::warn!("CheckTipState: ReadStableSignal failed with error: {}, falling back to single read", e);
2019 let single_value = self
2020 .client
2021 .signal_val_get(signal.index, true)?;
2022 (
2023 single_value,
2024 vec![single_value],
2025 "single_read_fallback",
2026 )
2027 }
2028 };
2029
2030 let mut measured = HashMap::new();
2031 measured.insert(SignalIndex::new(signal.index), value);
2032
2033 let shape = if value >= bounds.0 && value <= bounds.1 {
2034 TipShape::Sharp
2035 } else {
2036 TipShape::Blunt
2037 };
2038
2039 let bounds_center = (bounds.0 + bounds.1) / 2.0;
2041 let bounds_width = (bounds.1 - bounds.0).abs();
2042 let distance_from_center =
2043 (value - bounds_center).abs();
2044 let relative_distance = if bounds_width > 0.0 {
2045 distance_from_center / (bounds_width / 2.0)
2046 } else {
2047 0.0
2048 };
2049 let mut metadata = HashMap::new();
2050 metadata.insert(
2051 "method".to_string(),
2052 "signal_bounds".to_string(),
2053 );
2054 metadata.insert(
2055 "signal_index".to_string(),
2056 signal.index.to_string(),
2057 );
2058 metadata.insert(
2059 "measured_value".to_string(),
2060 format!("{:.6e}", value),
2061 );
2062 metadata.insert(
2063 "bounds_lower".to_string(),
2064 format!("{:.6e}", bounds.0),
2065 );
2066 metadata.insert(
2067 "bounds_upper".to_string(),
2068 format!("{:.6e}", bounds.1),
2069 );
2070 metadata.insert(
2071 "bounds_center".to_string(),
2072 format!("{:.6e}", bounds_center),
2073 );
2074 metadata.insert(
2075 "bounds_width".to_string(),
2076 format!("{:.6e}", bounds_width),
2077 );
2078 metadata.insert(
2079 "distance_from_center".to_string(),
2080 format!("{:.6e}", distance_from_center),
2081 );
2082 metadata.insert(
2083 "relative_distance".to_string(),
2084 format!("{:.3}", relative_distance),
2085 );
2086 metadata.insert(
2087 "within_bounds".to_string(),
2088 (shape == TipShape::Sharp).to_string(),
2089 );
2090 metadata.insert(
2091 "read_method".to_string(),
2092 read_method.to_string(),
2093 );
2094 metadata.insert(
2095 "dataset_size".to_string(),
2096 raw_data.len().to_string(),
2097 );
2098
2099 let raw_data_summary = if raw_data.len() <= 10 {
2101 raw_data
2102 .iter()
2103 .map(|x| format!("{:.3e}", x))
2104 .collect::<Vec<_>>()
2105 .join(",")
2106 } else {
2107 let first_5: String = raw_data
2108 .iter()
2109 .take(5)
2110 .map(|x| format!("{:.3e}", x))
2111 .collect::<Vec<_>>()
2112 .join(",");
2113 let last_5: String = raw_data
2114 .iter()
2115 .rev()
2116 .take(5)
2117 .rev()
2118 .map(|x| format!("{:.3e}", x))
2119 .collect::<Vec<_>>()
2120 .join(",");
2121 format!("{},...,{}", first_5, last_5)
2122 };
2123 metadata.insert(
2124 "raw_dataset_summary".to_string(),
2125 format!("[{}]", raw_data_summary),
2126 );
2127
2128 if shape == TipShape::Blunt {
2129 let margin_violation = if value < bounds.0 {
2130 bounds.0 - value
2131 } else {
2132 value - bounds.1
2133 };
2134 metadata.insert(
2135 "margin_violation".to_string(),
2136 format!("{:.6e}", margin_violation),
2137 );
2138 metadata.insert(
2139 "violation_direction".to_string(),
2140 if value < bounds.0 {
2141 "below_lower_bound".to_string()
2142 } else {
2143 "above_upper_bound".to_string()
2144 },
2145 );
2146 }
2147
2148 (shape, measured, metadata)
2149 }
2150
2151 TipCheckMethod::MultiSignalBounds { ref signals } => {
2152 let mut measured = HashMap::new();
2153 let mut violations = Vec::new();
2154 let mut all_good = true;
2155 let mut all_datasets = Vec::new();
2156 let mut read_methods = Vec::new();
2157
2158 let data_points = self
2160 .calculate_samples_for_duration(
2161 Duration::from_millis(
2162 TIP_STATE_DATA_COLLECTION_DURATION_MS,
2163 ),
2164 )
2165 .unwrap_or(100); for (signal, bounds) in signals.iter() {
2169 let stable_result = self
2170 .run(Action::ReadStableSignal {
2171 signal: signal.clone(),
2172 data_points: Some(data_points),
2173 use_new_data: true, stability_method:
2175 crate::actions::SignalStabilityMethod::Combined {
2176 max_std_dev: TIP_STATE_MAX_STD_DEV,
2177 max_slope: TIP_STATE_MAX_SLOPE,
2178 },
2179 timeout: Duration::from_secs(TIP_STATE_READ_TIMEOUT_SECS),
2180 retry_count: Some(TIP_STATE_READ_RETRY_COUNT),
2181 })
2182 .execute();
2183
2184 let (value, raw_data, read_method) =
2185 match stable_result {
2186 Ok(exec_result) => match exec_result {
2187 ExecutionResult::Single(
2188 ActionResult::StableSignal(
2189 stable_signal,
2190 ),
2191 ) => (
2192 stable_signal.stable_value,
2193 stable_signal.raw_data,
2194 "stable_signal",
2195 ),
2196 ExecutionResult::Single(
2197 ActionResult::Values(values),
2198 ) => {
2199 let raw_data: Vec<f32> = values
2200 .iter()
2201 .map(|&v| v as f32)
2202 .collect();
2203 let min_value = raw_data
2204 .iter()
2205 .cloned()
2206 .fold(f32::INFINITY, f32::min);
2207 (
2208 min_value,
2209 raw_data,
2210 "fallback_minimum",
2211 )
2212 }
2213 _ => {
2214 let single_value =
2215 self.client.signal_val_get(
2216 signal.index,
2217 true,
2218 )?;
2219 (
2220 single_value,
2221 vec![single_value],
2222 "single_read_fallback",
2223 )
2224 }
2225 },
2226 Err(_) => {
2227 let single_value =
2228 self.client.signal_val_get(
2229 signal.index,
2230 true,
2231 )?;
2232 (
2233 single_value,
2234 vec![single_value],
2235 "single_read_fallback",
2236 )
2237 }
2238 };
2239
2240 measured
2241 .insert(SignalIndex::new(signal.index), value);
2242 all_datasets.push(raw_data);
2243 read_methods.push(read_method);
2244
2245 let in_bounds =
2246 value >= bounds.0 && value <= bounds.1;
2247 if !in_bounds {
2248 violations.push((
2249 signal.clone(),
2250 value,
2251 *bounds,
2252 ));
2253 all_good = false;
2254 }
2255 }
2256
2257 let shape = if all_good {
2258 TipShape::Sharp
2259 } else {
2260 TipShape::Blunt
2261 };
2262
2263 let mut metadata = HashMap::new();
2265 metadata.insert(
2266 "method".to_string(),
2267 "multi_signal_bounds".to_string(),
2268 );
2269 metadata.insert(
2270 "signal_count".to_string(),
2271 signals.len().to_string(),
2272 );
2273 metadata.insert(
2274 "signals_in_bounds".to_string(),
2275 (signals.len() - violations.len()).to_string(),
2276 );
2277 metadata.insert(
2278 "violation_count".to_string(),
2279 violations.len().to_string(),
2280 );
2281 metadata.insert(
2282 "overall_pass".to_string(),
2283 all_good.to_string(),
2284 );
2285
2286 for (i, ((signal, bounds), dataset)) in
2288 signals.iter().zip(all_datasets.iter()).enumerate()
2289 {
2290 let prefix = format!("signal_{}", i);
2291 let value =
2292 measured[&SignalIndex::new(signal.index)];
2293
2294 metadata.insert(
2295 format!("{}_index", prefix),
2296 signal.index.to_string(),
2297 );
2298 metadata.insert(
2299 format!("{}_value", prefix),
2300 format!("{:.6e}", value),
2301 );
2302 metadata.insert(
2303 format!("{}_bounds", prefix),
2304 format!("[{:.3e}, {:.3e}]", bounds.0, bounds.1),
2305 );
2306 metadata.insert(
2307 format!("{}_in_bounds", prefix),
2308 (value >= bounds.0 && value <= bounds.1)
2309 .to_string(),
2310 );
2311 metadata.insert(
2312 format!("{}_read_method", prefix),
2313 read_methods[i].to_string(),
2314 );
2315 metadata.insert(
2316 format!("{}_dataset_size", prefix),
2317 dataset.len().to_string(),
2318 );
2319
2320 let dataset_summary = if dataset.len() <= 10 {
2322 dataset
2323 .iter()
2324 .map(|x| format!("{:.3e}", x))
2325 .collect::<Vec<_>>()
2326 .join(",")
2327 } else {
2328 let first_3: String = dataset
2329 .iter()
2330 .take(3)
2331 .map(|x| format!("{:.3e}", x))
2332 .collect::<Vec<_>>()
2333 .join(",");
2334 let last_3: String = dataset
2335 .iter()
2336 .rev()
2337 .take(3)
2338 .rev()
2339 .map(|x| format!("{:.3e}", x))
2340 .collect::<Vec<_>>()
2341 .join(",");
2342 format!("{},...,{}", first_3, last_3)
2343 };
2344 metadata.insert(
2345 format!("{}_dataset_summary", prefix),
2346 format!("[{}]", dataset_summary),
2347 );
2348 }
2349
2350 (shape, measured, metadata)
2351 }
2352 };
2353
2354 if let Some(ref tcp_reader) = self.tcp_reader {
2356 let (frame_count, _max_capacity, time_span) =
2357 tcp_reader.buffer_stats();
2358 metadata.insert(
2359 "tcp_buffer_frames".to_string(),
2360 frame_count.to_string(),
2361 );
2362 metadata.insert(
2363 "tcp_buffer_utilization".to_string(),
2364 format!("{:.2}", tcp_reader.buffer_utilization()),
2365 );
2366 metadata.insert(
2367 "tcp_data_timespan_ms".to_string(),
2368 time_span.as_millis().to_string(),
2369 );
2370 metadata.insert(
2371 "tcp_uptime_ms".to_string(),
2372 tcp_reader.uptime().as_millis().to_string(),
2373 );
2374
2375 for signal_idx in measured_signals.keys() {
2377 if tcp_reader.frame_count() >= 20 {
2378 let recent_frames =
2380 tcp_reader.get_recent_frames(50); let signal_values: Vec<f32> = recent_frames
2384 .iter()
2385 .filter_map(|frame| {
2386 let idx = signal_idx.get() as usize;
2388 if idx < frame.signal_frame.data.len() {
2389 Some(frame.signal_frame.data[idx])
2390 } else {
2391 None
2392 }
2393 })
2394 .collect();
2395
2396 if signal_values.len() >= 10 {
2397 let mean = signal_values.iter().sum::<f32>()
2399 / signal_values.len() as f32;
2400 let variance = signal_values
2401 .iter()
2402 .map(|x| (x - mean).powi(2))
2403 .sum::<f32>()
2404 / signal_values.len() as f32;
2405 let std_dev = variance.sqrt();
2406 let relative_std = if mean.abs() > 1e-15 {
2407 (std_dev / mean.abs()) * 100.0
2408 } else {
2409 0.0
2410 };
2411
2412 let x_values: Vec<f32> = (0..signal_values
2414 .len())
2415 .map(|i| i as f32)
2416 .collect();
2417 let x_mean = x_values.iter().sum::<f32>()
2418 / x_values.len() as f32;
2419 let y_mean = mean;
2420
2421 let numerator: f32 = x_values
2422 .iter()
2423 .zip(signal_values.iter())
2424 .map(|(x, y)| (x - x_mean) * (y - y_mean))
2425 .sum();
2426 let denominator: f32 = x_values
2427 .iter()
2428 .map(|x| (x - x_mean).powi(2))
2429 .sum();
2430
2431 let trend_slope = if denominator.abs() > 1e-15 {
2432 numerator / denominator
2433 } else {
2434 0.0
2435 };
2436
2437 let signal_prefix =
2438 format!("tcp_signal_{}", signal_idx.get());
2439 metadata.insert(
2440 format!("{}_recent_samples", signal_prefix),
2441 signal_values.len().to_string(),
2442 );
2443 metadata.insert(
2444 format!("{}_recent_mean", signal_prefix),
2445 format!("{:.6e}", mean),
2446 );
2447 metadata.insert(
2448 format!("{}_recent_std", signal_prefix),
2449 format!("{:.6e}", std_dev),
2450 );
2451 metadata.insert(
2452 format!(
2453 "{}_recent_relative_std_pct",
2454 signal_prefix
2455 ),
2456 format!("{:.3}", relative_std),
2457 );
2458 metadata.insert(
2459 format!("{}_trend_slope", signal_prefix),
2460 format!("{:.6e}", trend_slope),
2461 );
2462 metadata.insert(
2463 format!(
2464 "{}_current_vs_recent_mean",
2465 signal_prefix
2466 ),
2467 format!(
2468 "{:.6e}",
2469 measured_signals[signal_idx] - mean
2470 ),
2471 );
2472
2473 let is_stable_signal = relative_std < 5.0
2475 && trend_slope.abs() < (std_dev * 0.1);
2476 metadata.insert(
2477 format!("{}_appears_stable", signal_prefix),
2478 is_stable_signal.to_string(),
2479 );
2480
2481 let min_recent = signal_values
2483 .iter()
2484 .cloned()
2485 .fold(f32::INFINITY, f32::min);
2486 let max_recent = signal_values
2487 .iter()
2488 .cloned()
2489 .fold(f32::NEG_INFINITY, f32::max);
2490 let current_in_recent_range =
2491 measured_signals[signal_idx] >= min_recent
2492 && measured_signals[signal_idx]
2493 <= max_recent;
2494 metadata.insert(
2495 format!(
2496 "{}_current_in_recent_range",
2497 signal_prefix
2498 ),
2499 current_in_recent_range.to_string(),
2500 );
2501 metadata.insert(
2502 format!("{}_recent_range", signal_prefix),
2503 format!(
2504 "[{:.6e}, {:.6e}]",
2505 min_recent, max_recent
2506 ),
2507 );
2508 }
2509 }
2510 }
2511 }
2512
2513 let now = std::time::Instant::now();
2515 let recent_signals: Vec<_> = self
2516 .recent_stable_signals
2517 .iter()
2518 .filter(|(_, timestamp)| {
2519 now.duration_since(*timestamp)
2520 < std::time::Duration::from_secs(300)
2521 }) .collect();
2523
2524 if !recent_signals.is_empty() {
2525 metadata.insert(
2526 "recent_stable_signals_count".to_string(),
2527 recent_signals.len().to_string(),
2528 );
2529
2530 if let Some((most_recent_signal, timestamp)) =
2532 recent_signals.last()
2533 {
2534 let age_ms = now.duration_since(*timestamp).as_millis();
2535 metadata.insert(
2536 "most_recent_stable_signal_age_ms".to_string(),
2537 age_ms.to_string(),
2538 );
2539 metadata.insert(
2540 "most_recent_stable_value".to_string(),
2541 format!("{:.6e}", most_recent_signal.stable_value),
2542 );
2543 metadata.insert(
2544 "most_recent_data_points".to_string(),
2545 most_recent_signal.data_points_used.to_string(),
2546 );
2547 metadata.insert(
2548 "most_recent_analysis_duration_ms".to_string(),
2549 most_recent_signal
2550 .analysis_duration
2551 .as_millis()
2552 .to_string(),
2553 );
2554
2555 let raw_data = &most_recent_signal.raw_data;
2557 let raw_data_summary = if raw_data.len() <= 10 {
2558 raw_data
2560 .iter()
2561 .map(|x| format!("{:.3e}", x))
2562 .collect::<Vec<_>>()
2563 .join(",")
2564 } else {
2565 let first_5: String = raw_data
2567 .iter()
2568 .take(5)
2569 .map(|x| format!("{:.3e}", x))
2570 .collect::<Vec<_>>()
2571 .join(",");
2572 let last_5: String = raw_data
2573 .iter()
2574 .rev()
2575 .take(5)
2576 .rev()
2577 .map(|x| format!("{:.3e}", x))
2578 .collect::<Vec<_>>()
2579 .join(",");
2580 format!("{},...,{}", first_5, last_5)
2581 };
2582 metadata.insert(
2583 "most_recent_raw_data_summary".to_string(),
2584 format!("[{}]", raw_data_summary),
2585 );
2586 metadata.insert(
2587 "most_recent_raw_data_full_count".to_string(),
2588 raw_data.len().to_string(),
2589 );
2590
2591 for (metric_name, metric_value) in
2593 &most_recent_signal.stability_metrics
2594 {
2595 metadata.insert(
2596 format!("most_recent_metric_{}", metric_name),
2597 format!("{:.6e}", metric_value),
2598 );
2599 }
2600 }
2601 }
2602
2603 metadata.insert(
2605 "execution_timestamp".to_string(),
2606 chrono::Utc::now().to_rfc3339(),
2607 );
2608
2609 let signal_values_str = measured_signals
2611 .iter()
2612 .map(|(signal_idx, value)| {
2613 format!("signal_{}={:.3}", signal_idx.get(), value)
2614 })
2615 .collect::<Vec<_>>()
2616 .join(", ");
2617
2618 log::info!(
2619 "CheckTipState: shape={:?}, signals=[{}]",
2620 tip_shape,
2621 signal_values_str
2622 );
2623
2624 log::debug!("CheckTipState detail: read_method={}, dataset_size={}, recent_stable_count={}",
2625 metadata.get("read_method").map(|s| s.as_str()).unwrap_or("unknown"),
2626 metadata.get("dataset_size").map(|s| s.as_str()).unwrap_or("unknown"),
2627 recent_signals.len());
2628
2629 Ok(ActionResult::TipState(TipState {
2630 shape: tip_shape,
2631 measured_signals,
2632 metadata,
2633 }))
2634 }
2635
2636 Action::CheckTipStability {
2637 method,
2638 max_duration: _,
2639 } => {
2640 use std::collections::HashMap;
2641
2642 use crate::actions::{StabilityResult, TipStabilityMethod};
2643
2644 let start_time = std::time::Instant::now();
2645 let mut metrics = HashMap::new();
2646 let mut recommendations = Vec::new();
2647
2648 let (is_stable, measured_values) = match method {
2649 TipStabilityMethod::ExtendedMonitoring {
2650 signal: _,
2651 duration: _,
2652 sampling_interval: _,
2653 stability_threshold: _,
2654 } => {
2655 todo!("ExtendedMonitoring not yet implemented");
2656 }
2657
2658 TipStabilityMethod::BiasSweepResponse {
2659 ref signal,
2660 bias_range,
2661 bias_steps,
2662 step_duration,
2663 allowed_signal_change,
2664 } => {
2665 log::info!(
2666 "Performing simple bias sweep stability test: {:.2}V to {:.2}V",
2667 bias_range.0,
2668 bias_range.1
2669 );
2670
2671 let tcp_channel = signal.tcp_channel.ok_or_else(|| {
2673 NanonisError::Protocol(format!(
2674 "Signal {} (Nanonis index) has no TCP channel mapping",
2675 signal.index
2676 ))
2677 })?;
2678
2679 log::info!("Reading current scan properties...");
2681 let original_props = self.client.scan_props_get()?;
2682 log::info!(
2683 "Original scan props: continuous={}, bouncy={}",
2684 original_props.continuous_scan,
2685 original_props.bouncy_scan
2686 );
2687
2688 log::info!(
2690 "Configuring scan: continuous=true, bouncy=true"
2691 );
2692 let scan_props =
2693 nanonis_rs::scan::ScanPropsBuilder::new()
2694 .continuous_scan(true)
2695 .bouncy_scan(true);
2696 self.client.scan_props_set(scan_props)?;
2697 log::info!("Scan properties configured");
2698
2699 let initial_bias = self.client.bias_get()?;
2701 log::info!(
2702 "Initial bias: {:.3} V (will restore after sweep)",
2703 initial_bias
2704 );
2705
2706 let baseline_value = {
2708 let tcp_reader =
2709 self.tcp_reader_mut().ok_or_else(|| {
2710 NanonisError::Protocol(
2711 "TCP reader not available".to_string(),
2712 )
2713 })?;
2714
2715 let recent_frames = tcp_reader.get_recent_frames(1);
2716 if recent_frames.is_empty() {
2717 return Err(NanonisError::Protocol(
2718 "No frames available from TCP reader"
2719 .to_string(),
2720 ));
2721 }
2722
2723 recent_frames[0].signal_frame.data
2724 [tcp_channel as usize]
2725 };
2726
2727 log::info!(
2728 "Baseline signal: {:.3}, threshold: {:.3}",
2729 baseline_value,
2730 allowed_signal_change
2731 );
2732
2733 self.client.scan_action(
2735 ScanAction::Start,
2736 ScanDirection::Down,
2737 )?;
2738 log::info!("Scan started");
2739
2740 let mut scan_started = false;
2742 for _ in 0..50 {
2743 if self.is_shutdown_requested() {
2745 log::info!("Shutdown requested while waiting for scan to start");
2746 let _ = self.client.scan_action(
2747 ScanAction::Stop,
2748 ScanDirection::Up,
2749 );
2750 let _ = self.client.bias_set(initial_bias);
2751 return Err(NanonisError::Protocol(
2752 "Shutdown requested".to_string(),
2753 ));
2754 }
2755 std::thread::sleep(Duration::from_millis(100));
2756 let is_scanning = self.client.scan_status_get()?;
2757 if is_scanning {
2758 scan_started = true;
2759 log::info!("Scan started successfully");
2760 break;
2761 }
2762 }
2763
2764 if !scan_started {
2765 return Err(NanonisError::Protocol(
2766 "Scan failed to start within 5 seconds"
2767 .to_string(),
2768 ));
2769 }
2770
2771 let bias_step_size =
2773 (bias_range.1 - bias_range.0) / (bias_steps as f32);
2774 let mut current_bias = bias_range.0;
2775
2776 for step_num in 0..bias_steps {
2777 if self.is_shutdown_requested() {
2779 log::info!("Shutdown requested during bias sweep at step {}/{}", step_num + 1, bias_steps);
2780 let _ = self.client.scan_action(
2781 ScanAction::Stop,
2782 ScanDirection::Up,
2783 );
2784 let _ = self.client.bias_set(initial_bias);
2785 return Err(NanonisError::Protocol(
2786 "Shutdown requested".to_string(),
2787 ));
2788 }
2789 self.client.bias_set(current_bias)?;
2790 log::debug!(
2791 "Step {}/{}: bias={:.2}V",
2792 step_num + 1,
2793 bias_steps,
2794 current_bias
2795 );
2796 let sleep_chunks =
2798 (step_duration.as_millis() / 10).max(1) as u32;
2799 let chunk_duration = step_duration / sleep_chunks;
2800 for _ in 0..sleep_chunks {
2801 if self.is_shutdown_requested() {
2802 log::info!("Shutdown requested during bias sweep step sleep");
2803 let _ = self.client.scan_action(
2804 ScanAction::Stop,
2805 ScanDirection::Up,
2806 );
2807 let _ = self.client.bias_set(initial_bias);
2808 return Err(NanonisError::Protocol(
2809 "Shutdown requested".to_string(),
2810 ));
2811 }
2812 std::thread::sleep(chunk_duration);
2813 }
2814 current_bias += bias_step_size;
2815 }
2816
2817 log::info!("Bias sweep completed");
2818
2819 let final_value = {
2821 let tcp_reader =
2822 self.tcp_reader_mut().ok_or_else(|| {
2823 NanonisError::Protocol(
2824 "TCP reader not available".to_string(),
2825 )
2826 })?;
2827
2828 let recent_frames = tcp_reader.get_recent_frames(1);
2829 if recent_frames.is_empty() {
2830 return Err(NanonisError::Protocol(
2831 "No frames available from TCP reader"
2832 .to_string(),
2833 ));
2834 }
2835
2836 recent_frames[0].signal_frame.data
2837 [tcp_channel as usize]
2838 };
2839
2840 let _ = self
2842 .client
2843 .scan_action(ScanAction::Stop, ScanDirection::Up);
2844
2845 if let Err(e) = self
2847 .client
2848 .z_ctrl_withdraw(true, Duration::from_secs(5))
2849 {
2850 log::error!(
2851 "Failed to withdraw before restoring bias: {}",
2852 e
2853 );
2854 }
2855
2856 std::thread::sleep(Duration::from_millis(200));
2858
2859 if let Err(e) = self.client.bias_set(initial_bias) {
2860 log::error!(
2861 "Failed to restore initial bias: {}",
2862 e
2863 );
2864 } else {
2865 log::info!(
2866 "Bias restored to {:.3} V",
2867 initial_bias
2868 );
2869 }
2870
2871 let signal_change =
2873 (final_value - baseline_value).abs();
2874 let is_stable = signal_change <= allowed_signal_change;
2875
2876 log::info!(
2877 "Bias sweep result: baseline={:.3}, final={:.3}, change={:.3}, threshold={:.3}, stable={}",
2878 baseline_value,
2879 final_value,
2880 signal_change,
2881 allowed_signal_change,
2882 is_stable
2883 );
2884
2885 metrics.insert(
2887 "baseline_value".to_string(),
2888 baseline_value,
2889 );
2890 metrics.insert("final_value".to_string(), final_value);
2891 metrics
2892 .insert("signal_change".to_string(), signal_change);
2893 metrics.insert(
2894 "threshold".to_string(),
2895 allowed_signal_change,
2896 );
2897
2898 if is_stable {
2900 recommendations.push(format!(
2901 "Tip is stable - signal change {:.3} within threshold {:.3}",
2902 signal_change, allowed_signal_change
2903 ));
2904 } else {
2905 recommendations.push(format!(
2906 "Tip is blunt - signal change {:.3} exceeded threshold {:.3}. Tip shaping recommended.",
2907 signal_change, allowed_signal_change
2908 ));
2909 }
2910
2911 let mut measured_values = HashMap::new();
2913 measured_values.insert(
2914 signal.clone(),
2915 vec![baseline_value, final_value],
2916 );
2917
2918 (is_stable, measured_values)
2919 }
2920 };
2921
2922 let analysis_duration = start_time.elapsed();
2923 let result = StabilityResult {
2924 is_stable,
2925 method_used: format!("{:?}", method.clone()),
2926 measured_values,
2927 analysis_duration,
2928 metrics,
2929 potential_damage_detected: !is_stable
2930 && matches!(
2931 method,
2932 TipStabilityMethod::BiasSweepResponse { .. }
2933 ),
2934 recommendations,
2935 };
2936
2937 Ok(ActionResult::StabilityResult(result))
2938 }
2939
2940 Action::ReadStableSignal {
2941 signal,
2942 data_points,
2943 use_new_data,
2944 stability_method,
2945 timeout,
2946 retry_count,
2947 } => {
2948 use std::time::Instant;
2949
2950 let start_time = Instant::now();
2951 let data_points = data_points.unwrap_or(50);
2952 let max_retries = retry_count.unwrap_or(0);
2953
2954 let tcp_config =
2956 self.tcp_reader_config.as_ref().ok_or_else(|| {
2957 NanonisError::Protocol(
2958 "TCP logger not configured".to_string(),
2959 )
2960 })?;
2961
2962 log::debug!(
2964 "ReadStableSignal: Looking up signal {} in signal registry",
2965 signal.index
2966 );
2967
2968 let registry_signal = self
2970 .signal_registry
2971 .get_by_index(signal.index)
2972 .ok_or_else(|| {
2973 NanonisError::Protocol(format!(
2974 "Signal {} not found in registry",
2975 signal.index
2976 ))
2977 })?;
2978
2979 let tcp_channel = registry_signal.tcp_channel.ok_or_else(|| {
2980 log::error!(
2981 "ReadStableSignal: Signal {} (Nanonis index) has no TCP channel mapping",
2982 signal.index
2983 );
2984 NanonisError::Protocol(format!(
2985 "Signal {} (Nanonis index) has no TCP channel mapping",
2986 signal.index
2987 ))
2988 })?;
2989
2990 log::debug!(
2991 "ReadStableSignal: Signal {} mapped to TCP channel {}",
2992 signal.index,
2993 tcp_channel
2994 );
2995
2996 log::debug!(
2998 "ReadStableSignal: Signal {} (Nanonis) maps to TCP channel {}",
2999 signal.index,
3000 tcp_channel
3001 );
3002 log::debug!(
3003 "ReadStableSignal: Available TCP channels: {:?}",
3004 tcp_config.channels
3005 );
3006 let signal_channel_idx = tcp_config
3007 .channels
3008 .iter()
3009 .position(|&ch| ch == tcp_channel as i32)
3010 .ok_or_else(|| {
3011 log::error!("ReadStableSignal: TCP channel {} for signal {} (Nanonis) not found in TCP logger configuration. Available channels: {:?}",
3012 tcp_channel, signal.index, tcp_config.channels);
3013 NanonisError::Protocol(format!(
3014 "TCP channel {} for signal {} (Nanonis) not found in TCP logger configuration. Available: {:?}",
3015 tcp_channel, signal.index, tcp_config.channels
3016 ))
3017 })?;
3018
3019 log::debug!(
3020 "ReadStableSignal: Signal {} (Nanonis) -> TCP channel {} -> Array position {}",
3021 signal.index,
3022 tcp_channel,
3023 signal_channel_idx
3024 );
3025 log::debug!(
3026 "ReadStableSignal: Full TCP channel list: {:?}",
3027 tcp_config.channels
3028 );
3029
3030 let mut attempt = 0;
3032
3033 loop {
3034 match self.attempt_stable_signal_read(
3035 signal_channel_idx,
3036 data_points,
3037 use_new_data,
3038 timeout,
3039 &stability_method,
3040 ) {
3041 Ok((signal_data, is_stable, metrics)) => {
3042 let analysis_duration = start_time.elapsed();
3043
3044 if is_stable {
3045 let stable_value =
3047 signal_data.iter().sum::<f32>()
3048 / signal_data.len() as f32;
3049
3050 use crate::actions::StableSignal;
3051 log::info!(
3052 "Stable signal acquired on attempt {} (retries: {})",
3053 attempt + 1,
3054 attempt
3055 );
3056
3057 let stable_signal = StableSignal {
3058 stable_value,
3059 data_points_used: signal_data.len(),
3060 analysis_duration,
3061 stability_metrics: metrics,
3062 raw_data: if is_stable {
3065 vec![stable_value]
3066 } else {
3067 signal_data
3068 },
3069 };
3070
3071 self.recent_stable_signals.push_back((
3073 stable_signal.clone(),
3074 std::time::Instant::now(),
3075 ));
3076 while self.recent_stable_signals.len() > 10 {
3078 self.recent_stable_signals.pop_front();
3079 }
3080
3081 return Ok(ActionResult::StableSignal(
3082 stable_signal,
3083 ));
3084 } else if attempt >= max_retries {
3085 log::warn!(
3087 "Signal not stable after {} attempts, returning raw data",
3088 attempt + 1
3089 );
3090 let values: Vec<f64> = signal_data
3091 .iter()
3092 .map(|&x| x as f64)
3093 .collect();
3094 return Ok(ActionResult::Values(values));
3095 } else {
3096 log::debug!(
3098 "Signal not stable on attempt {}, retrying...",
3099 attempt + 1
3100 );
3101 }
3102 }
3103 Err(e) => {
3104 log::warn!(
3105 "Data collection failed on attempt {}: {}",
3106 attempt + 1,
3107 e
3108 );
3109
3110 if attempt >= max_retries {
3111 return Err(e);
3112 }
3113 }
3114 }
3115
3116 attempt += 1;
3117
3118 if attempt <= max_retries {
3120 let delay_ms = 100 * (1 << (attempt - 1).min(4)); log::debug!(
3122 "Waiting {}ms before retry attempt {}",
3123 delay_ms,
3124 attempt + 1
3125 );
3126 std::thread::sleep(Duration::from_millis(delay_ms));
3127 }
3128 }
3129 }
3130 Action::ReachedTargedAmplitude => {
3131 let ampl_setpoint =
3132 self.client_mut().pll_amp_ctrl_setpnt_get(1)?;
3133
3134 let ampl_current = match self
3135 .run(Action::ReadStableSignal {
3136 signal: Signal::new("Amplitude".to_string(), 75, None).unwrap(),
3137 data_points: Some(50),
3138 use_new_data: false,
3139 stability_method:
3140 crate::actions::SignalStabilityMethod::RelativeStandardDeviation {
3141 threshold_percent: 0.2,
3142 },
3143 timeout: Duration::from_millis(10),
3144 retry_count: Some(3), })
3146 .go()? {
3147 ActionResult::Values(values) => values.iter().map(|v| *v as f32).sum::<f32>() / values.len() as f32,
3148 ActionResult::StableSignal(value) => value.stable_value,
3149 other => {
3150 return Err(NanonisError::Protocol(format!(
3151 "CheckAmplitudeStability returned unexpected result type. Expected Values or StableSignal, got {:?}",
3152 std::mem::discriminant(&other)
3153 )))
3154 }
3155 };
3156
3157 let status = (ampl_setpoint - 5e-12..ampl_setpoint + 5e-12)
3158 .contains(&l_current);
3159
3160 Ok(ActionResult::Status(status))
3161 }
3162 }
3163 }
3164
3165 fn check_safetip_status(&mut self, context: &str) -> Result<(), NanonisError> {
3166 if let Ok(status) = self.client_mut().z_ctrl_status_get() {
3167 if matches!(status, nanonis_rs::z_ctrl::ZControllerStatus::SafeTip)
3168 {
3169 return Err(NanonisError::Protocol(
3170 format!("SafeTip triggered ({}), abort!", context),
3171 ));
3172 }
3173 }
3174
3175 Ok(())
3176 }
3177
3178 fn attempt_stable_signal_read(
3180 &self,
3181 signal_channel_idx: usize,
3182 data_points: usize,
3183 use_new_data: bool,
3184 timeout: Duration,
3185 stability_method: &crate::actions::SignalStabilityMethod,
3186 ) -> Result<
3187 (Vec<f32>, bool, std::collections::HashMap<String, f32>),
3188 NanonisError,
3189 > {
3190 let signal_data: Vec<f32> = if use_new_data {
3192 self.collect_new_signal_data(
3194 signal_channel_idx,
3195 data_points,
3196 timeout,
3197 )?
3198 } else {
3199 self.extract_buffered_signal_data(signal_channel_idx, data_points)?
3201 };
3202
3203 if signal_data.is_empty() {
3204 return Err(NanonisError::Protocol(
3205 "No signal data available".to_string(),
3206 ));
3207 }
3208
3209 let (is_stable, metrics) =
3211 Self::analyze_signal_stability(&signal_data, stability_method);
3212
3213 Ok((signal_data, is_stable, metrics))
3214 }
3215
3216 fn collect_new_signal_data(
3218 &self,
3219 signal_channel_idx: usize,
3220 data_points: usize,
3221 timeout: Duration,
3222 ) -> Result<Vec<f32>, NanonisError> {
3223 use std::time::Instant;
3224
3225 let tcp_reader = self.tcp_reader.as_ref().ok_or_else(|| {
3226 NanonisError::Protocol("TCP reader not available".to_string())
3227 })?;
3228
3229 let start_time = Instant::now();
3230 let mut collected_data = Vec::with_capacity(data_points);
3231
3232 log::debug!(
3233 "Collecting {} new data points for signal channel {} with timeout {:.1}s",
3234 data_points,
3235 signal_channel_idx,
3236 timeout.as_secs_f32()
3237 );
3238
3239 while collected_data.len() < data_points
3240 && start_time.elapsed() < timeout
3241 {
3242 let recent_frames =
3244 tcp_reader.get_recent_data(Duration::from_millis(100));
3245
3246 for frame in recent_frames {
3247 if collected_data.len() >= data_points {
3248 break;
3249 }
3250
3251 if let Some(&value) =
3252 frame.signal_frame.data.get(signal_channel_idx)
3253 {
3254 collected_data.push(value);
3255 }
3256 }
3257
3258 if collected_data.len() < data_points {
3259 std::thread::sleep(Duration::from_millis(50)); }
3261 }
3262
3263 if collected_data.is_empty() {
3264 log::warn!("No data collected within timeout");
3265 } else {
3266 log::debug!("Collected {} data points", collected_data.len());
3267 }
3268
3269 Ok(collected_data)
3270 }
3271
3272 fn extract_buffered_signal_data(
3274 &self,
3275 signal_channel_idx: usize,
3276 data_points: usize,
3277 ) -> Result<Vec<f32>, NanonisError> {
3278 let tcp_reader = self.tcp_reader.as_ref().ok_or_else(|| {
3279 NanonisError::Protocol("TCP reader not available".to_string())
3280 })?;
3281
3282 let recent_frames = tcp_reader.get_recent_frames(data_points);
3284
3285 let mut signal_data = Vec::new();
3286 for frame in recent_frames.iter().rev().take(data_points) {
3287 if let Some(&value) =
3289 frame.signal_frame.data.get(signal_channel_idx)
3290 {
3291 signal_data.push(value);
3292 }
3293 }
3294
3295 signal_data.reverse(); log::info!("Extracted {} buffered data points", signal_data.len());
3298 Ok(signal_data)
3299 }
3300
3301 fn analyze_signal_stability(
3303 data: &[f32],
3304 method: &crate::actions::SignalStabilityMethod,
3305 ) -> (bool, std::collections::HashMap<String, f32>) {
3306 use crate::actions::SignalStabilityMethod;
3307
3308 if data.len() < 2 {
3309 return (false, std::collections::HashMap::new());
3310 }
3311
3312 let mut metrics = std::collections::HashMap::new();
3313 let mean = data.iter().sum::<f32>() / data.len() as f32;
3314 let variance = data.iter().map(|v| (v - mean).powi(2)).sum::<f32>()
3315 / data.len() as f32;
3316 let std_dev = variance.sqrt();
3317
3318 metrics.insert("mean".to_string(), mean);
3319 metrics.insert("std_dev".to_string(), std_dev);
3320 metrics.insert("variance".to_string(), variance);
3321
3322 let is_stable = match method {
3323 SignalStabilityMethod::StandardDeviation { threshold } => {
3324 metrics.insert("threshold".to_string(), *threshold);
3325 std_dev <= *threshold
3326 }
3327
3328 SignalStabilityMethod::RelativeStandardDeviation {
3329 threshold_percent,
3330 } => {
3331 let relative_std = if mean.abs() > 1e-12 {
3332 (std_dev / mean.abs()) * 100.0
3333 } else {
3334 f32::INFINITY
3335 };
3336 metrics
3337 .insert("relative_std_percent".to_string(), relative_std);
3338 metrics.insert(
3339 "threshold_percent".to_string(),
3340 *threshold_percent,
3341 );
3342 relative_std <= *threshold_percent
3343 }
3344
3345 SignalStabilityMethod::MovingWindow {
3346 window_size,
3347 max_variation,
3348 } => {
3349 if data.len() < *window_size {
3350 return (false, metrics);
3351 }
3352
3353 let mut max_window_variation = 0.0f32;
3354 for window in data.windows(*window_size) {
3355 let window_min =
3356 window.iter().fold(f32::INFINITY, |a, &b| a.min(b));
3357 let window_max =
3358 window.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
3359 let variation = window_max - window_min;
3360 max_window_variation = max_window_variation.max(variation);
3361 }
3362
3363 metrics.insert(
3364 "max_window_variation".to_string(),
3365 max_window_variation,
3366 );
3367 metrics.insert("window_size".to_string(), *window_size as f32);
3368 metrics.insert(
3369 "max_variation_threshold".to_string(),
3370 *max_variation,
3371 );
3372 max_window_variation <= *max_variation
3373 }
3374
3375 SignalStabilityMethod::TrendAnalysis { max_slope } => {
3376 let n = data.len() as f32;
3378 let x_mean = (n - 1.0) / 2.0; let y_mean = mean;
3380
3381 let mut numerator = 0.0;
3382 let mut denominator = 0.0;
3383 for (i, &y) in data.iter().enumerate() {
3384 let x = i as f32;
3385 numerator += (x - x_mean) * (y - y_mean);
3386 denominator += (x - x_mean).powi(2);
3387 }
3388
3389 let slope = if denominator > 1e-12 {
3390 numerator / denominator
3391 } else {
3392 0.0
3393 };
3394 let abs_slope = slope.abs();
3395
3396 metrics.insert("slope".to_string(), slope);
3397 metrics.insert("abs_slope".to_string(), abs_slope);
3398 metrics.insert("max_slope_threshold".to_string(), *max_slope);
3399 abs_slope <= *max_slope
3400 }
3401
3402 SignalStabilityMethod::Combined {
3403 max_std_dev,
3404 max_slope,
3405 } => {
3406 let n = data.len() as f32;
3408 let x_mean = (n - 1.0) / 2.0;
3409 let y_mean = mean;
3410
3411 let mut numerator = 0.0;
3412 let mut denominator = 0.0;
3413 for (i, &y) in data.iter().enumerate() {
3414 let x = i as f32;
3415 numerator += (x - x_mean) * (y - y_mean);
3416 denominator += (x - x_mean).powi(2);
3417 }
3418
3419 let slope = if denominator > 1e-12 {
3420 numerator / denominator
3421 } else {
3422 0.0
3423 };
3424 let abs_slope = slope.abs();
3425
3426 let noise_ok = std_dev <= *max_std_dev;
3428 let drift_ok = abs_slope <= *max_slope;
3429
3430 metrics.insert("slope".to_string(), slope);
3431 metrics.insert("abs_slope".to_string(), abs_slope);
3432 metrics.insert("max_slope_threshold".to_string(), *max_slope);
3433 metrics
3434 .insert("max_std_dev_threshold".to_string(), *max_std_dev);
3435 metrics.insert(
3436 "noise_ok".to_string(),
3437 if noise_ok { 1.0 } else { 0.0 },
3438 );
3439 metrics.insert(
3440 "drift_ok".to_string(),
3441 if drift_ok { 1.0 } else { 0.0 },
3442 );
3443 noise_ok && drift_ok
3444 }
3445 };
3446
3447 metrics.insert("data_points".to_string(), data.len() as f32);
3448
3449 (is_stable, metrics)
3450 }
3451
3452 pub fn execute_expecting<T>(
3473 &mut self,
3474 action: Action,
3475 ) -> Result<T, NanonisError>
3476 where
3477 ActionResult: ExpectFromAction<T>,
3478 {
3479 let result = self.execute(action.clone())?;
3480 Ok(result.expect_from_action(&action))
3481 }
3482
3483 fn find_stable_oscilloscope_data(
3489 &mut self,
3490 _data_to_get: DataToGet,
3491 readings: u32,
3492 timeout: std::time::Duration,
3493 relative_threshold: f64,
3494 absolute_threshold: f64,
3495 min_window_percent: f64,
3496 stability_fn: Option<fn(&[f64]) -> bool>,
3497 ) -> Result<Option<OsciData>, NanonisError> {
3498 match poll_with_timeout(
3499 || {
3500 for _attempt in 0..readings {
3502 let (t0, dt, size, data) =
3503 self.client.osci1t_data_get(2)?; if let Some(stable_osci_data) = self
3506 .analyze_stability_window(
3507 t0,
3508 dt,
3509 size,
3510 data,
3511 relative_threshold,
3512 absolute_threshold,
3513 min_window_percent,
3514 stability_fn,
3515 )?
3516 {
3517 return Ok(Some(stable_osci_data));
3518 }
3519
3520 std::thread::sleep(std::time::Duration::from_millis(100));
3522 }
3523
3524 Ok(None)
3526 },
3527 timeout,
3528 std::time::Duration::from_millis(50), ) {
3530 Ok(Some(result)) => Ok(Some(result)),
3531 Ok(None) => Ok(None), Err(PollError::ConditionError(e)) => Err(e),
3533 Err(PollError::Timeout) => unreachable!(), }
3535 }
3536
3537 fn analyze_stability_window(
3539 &self,
3540 t0: f64,
3541 dt: f64,
3542 size: i32,
3543 data: Vec<f64>,
3544 relative_threshold: f64,
3545 absolute_threshold: f64,
3546 min_window_percent: f64,
3547 stability_fn: Option<fn(&[f64]) -> bool>,
3548 ) -> Result<Option<OsciData>, NanonisError> {
3549 let min_window = (size as f64 * min_window_percent) as usize;
3550 let mut start = 0;
3551 let mut end = size as usize;
3552
3553 while (end - start) > min_window {
3554 let window = &data[start..end];
3555 let arr = Array1::from_vec(window.to_vec());
3556 let mean = arr.mean().expect(
3557 "There must be an non-empty array, osci1t_data_get would have returned early.",
3558 );
3559 let std_dev = arr.std(0.0);
3560 let relative_std = std_dev / mean.abs();
3561
3562 let is_stable = if let Some(stability_fn) = stability_fn {
3564 stability_fn(window)
3565 } else {
3566 let is_relative_stable = relative_std < relative_threshold;
3568 let is_absolute_stable = std_dev < absolute_threshold;
3569 is_relative_stable || is_absolute_stable
3570 };
3571
3572 if is_stable {
3573 let stable_data = window.to_vec();
3574 let stability_method = if stability_fn.is_some() {
3575 "custom".to_string()
3576 } else {
3577 let is_relative_stable = relative_std < relative_threshold;
3579 let is_absolute_stable = std_dev < absolute_threshold;
3580 match (is_relative_stable, is_absolute_stable) {
3581 (true, true) => "both".to_string(),
3582 (true, false) => "relative".to_string(),
3583 (false, true) => "absolute".to_string(),
3584 (false, false) => unreachable!(),
3585 }
3586 };
3587
3588 let stats = SignalStats {
3589 mean,
3590 std_dev,
3591 relative_std,
3592 window_size: stable_data.len(),
3593 stability_method,
3594 };
3595
3596 let mut osci_data = OsciData::new_with_stats(
3597 t0,
3598 dt,
3599 stable_data.len() as i32,
3600 stable_data,
3601 stats,
3602 );
3603 osci_data.is_stable = true; return Ok(Some(osci_data));
3605 }
3606
3607 let shrink = ((end - start) / 10).max(1);
3608 start += shrink;
3609 end -= shrink;
3610 }
3611
3612 Ok(None)
3614 }
3615
3616 fn find_stable_oscilloscope_data_with_fallback(
3623 &mut self,
3624 data_to_get: DataToGet,
3625 readings: u32,
3626 timeout: std::time::Duration,
3627 relative_threshold: f64,
3628 absolute_threshold: f64,
3629 min_window_percent: f64,
3630 stability_fn: Option<fn(&[f64]) -> bool>,
3631 ) -> Result<OsciData, NanonisError> {
3632 if let Some(stable_osci_data) = self.find_stable_oscilloscope_data(
3634 data_to_get,
3635 readings,
3636 timeout,
3637 relative_threshold,
3638 absolute_threshold,
3639 min_window_percent,
3640 stability_fn,
3641 )? {
3642 return Ok(stable_osci_data);
3643 }
3644
3645 let (t0, dt, size, data) = self.client.osci1t_data_get(1)?; let fallback_value = if !data.is_empty() {
3650 data.iter().sum::<f64>() / data.len() as f64
3651 } else {
3652 0.0
3653 };
3654
3655 Ok(OsciData::new_unstable_with_fallback(
3656 t0,
3657 dt,
3658 size,
3659 data,
3660 fallback_value,
3661 ))
3662 }
3663
3664 pub fn execute_chain(
3666 &mut self,
3667 chain: impl Into<ActionChain>,
3668 ) -> Result<Vec<ActionResult>, NanonisError> {
3669 let chain = chain.into();
3670 let mut results = Vec::with_capacity(chain.len());
3671
3672 for action in chain.into_iter() {
3673 let result = self.execute(action)?;
3674 results.push(result);
3675 }
3676
3677 Ok(results)
3678 }
3679
3680 pub fn execute_chain_final(
3682 &mut self,
3683 chain: impl Into<ActionChain>,
3684 ) -> Result<ActionResult, NanonisError> {
3685 let results = self.execute_chain(chain)?;
3686 Ok(results.into_iter().last().unwrap_or(ActionResult::None))
3687 }
3688
3689 pub fn execute_chain_partial(
3691 &mut self,
3692 chain: impl Into<ActionChain>,
3693 ) -> Result<Vec<ActionResult>, (Vec<ActionResult>, NanonisError)> {
3694 let chain = chain.into();
3695 let mut results = Vec::new();
3696
3697 for action in chain.into_iter() {
3698 match self.execute(action) {
3699 Ok(result) => results.push(result),
3700 Err(error) => return Err((results, error)),
3701 }
3702 }
3703
3704 Ok(results)
3705 }
3706
3707 pub fn execute_chain_deferred(
3724 &mut self,
3725 chain: impl Into<ActionChain>,
3726 ) -> Result<Vec<ActionResult>, NanonisError> {
3727 let chain = chain.into();
3728 let start_time = chrono::Utc::now();
3729 let start_instant = std::time::Instant::now();
3730
3731 let mut results = Vec::with_capacity(chain.len());
3732
3733 for action in chain.iter() {
3735 let result = self.execute_internal(action.clone())?;
3736 results.push(result);
3737 }
3738
3739 let duration = start_instant.elapsed();
3740
3741 if self.action_logging_enabled && self.action_logger.is_some() {
3743 let chain_summary = format!("Chain: {}", chain.summary());
3744 let final_result = results.last().unwrap_or(&ActionResult::None);
3745
3746 let log_entry = ActionLogEntry::new(
3747 &crate::actions::Action::Wait {
3748 duration: Duration::from_millis(0),
3749 }, final_result,
3751 start_time,
3752 duration,
3753 )
3754 .with_metadata("type", "chain_execution")
3755 .with_metadata("chain_summary", chain_summary)
3756 .with_metadata("action_count", results.len().to_string());
3757
3758 if let Err(log_error) =
3759 self.action_logger.as_mut().unwrap().add(log_entry)
3760 {
3761 log::warn!("Failed to log chain execution: {}", log_error);
3762 }
3763 }
3764
3765 Ok(results)
3766 }
3767
3768 pub fn clear_storage(&mut self) {
3770 self.stored_values.clear();
3771 }
3772
3773 pub fn stored_keys(&self) -> Vec<&String> {
3775 self.stored_values.keys().collect()
3776 }
3777
3778 pub fn set_action_logging_enabled(&mut self, enabled: bool) -> bool {
3796 let previous = self.action_logging_enabled;
3797 self.action_logging_enabled = enabled;
3798 previous
3799 }
3800
3801 pub fn is_action_logging_enabled(&self) -> bool {
3803 self.action_logging_enabled && self.action_logger.is_some()
3804 }
3805
3806 pub fn flush_action_log(&mut self) -> Result<(), NanonisError> {
3815 if let Some(ref mut logger) = self.action_logger {
3816 logger.flush()?;
3817 }
3818 Ok(())
3819 }
3820
3821 pub fn action_log_stats(&self) -> Option<(usize, bool)> {
3829 self.action_logger
3830 .as_ref()
3831 .map(|logger| (logger.len(), self.action_logging_enabled))
3832 }
3833
3834 pub fn finalize_action_log(&mut self) -> Result<(), NanonisError> {
3844 if let Some(ref mut logger) = self.action_logger {
3845 logger.finalize_as_json()?;
3846 }
3847 Ok(())
3848 }
3849
3850 pub fn read_oscilloscope(
3852 &mut self,
3853 signal: Signal,
3854 trigger: Option<TriggerConfig>,
3855 data_to_get: DataToGet,
3856 ) -> Result<Option<OsciData>, NanonisError> {
3857 match self.execute(Action::ReadOsci {
3858 signal,
3859 trigger,
3860 data_to_get,
3861 is_stable: None,
3862 })? {
3863 ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
3864 ActionResult::None => Ok(None),
3865 _ => {
3866 Err(NanonisError::Protocol("Expected oscilloscope data".into()))
3867 }
3868 }
3869 }
3870
3871 pub fn read_oscilloscope_with_stability(
3873 &mut self,
3874 signal: Signal,
3875 trigger: Option<TriggerConfig>,
3876 data_to_get: DataToGet,
3877 is_stable: fn(&[f64]) -> bool,
3878 ) -> Result<Option<OsciData>, NanonisError> {
3879 match self.execute(Action::ReadOsci {
3880 signal,
3881 trigger,
3882 data_to_get,
3883 is_stable: Some(is_stable),
3884 })? {
3885 ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
3886 ActionResult::None => Ok(None),
3887 _ => {
3888 Err(NanonisError::Protocol("Expected oscilloscope data".into()))
3889 }
3890 }
3891 }
3892}
3893
3894pub mod stability {
3896 pub fn dual_threshold_stability(window: &[f64]) -> bool {
3899 if window.len() < 3 {
3900 return false;
3901 }
3902
3903 let mean = window.iter().sum::<f64>() / window.len() as f64;
3904 let variance = window.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
3905 / window.len() as f64;
3906 let std_dev = variance.sqrt();
3907 let relative_std = std_dev / mean.abs();
3908
3909 relative_std < 0.05 || std_dev < 50e-15
3911 }
3912
3913 pub fn trend_analysis_stability(window: &[f64]) -> bool {
3916 if window.len() < 5 {
3917 return false;
3918 }
3919
3920 let n = window.len() as f64;
3922 let x_mean = (n - 1.0) / 2.0; let y_mean = window.iter().sum::<f64>() / n;
3924
3925 let mut numerator = 0.0;
3926 let mut denominator = 0.0;
3927
3928 for (i, &y) in window.iter().enumerate() {
3929 let x = i as f64;
3930 numerator += (x - x_mean) * (y - y_mean);
3931 denominator += (x - x_mean).powi(2);
3932 }
3933
3934 let slope = if denominator != 0.0 {
3935 numerator / denominator
3936 } else {
3937 0.0
3938 };
3939
3940 let signal_level = y_mean.abs();
3942 let noise_level = {
3943 let variance =
3944 window.iter().map(|y| (y - y_mean).powi(2)).sum::<f64>() / n;
3945 variance.sqrt()
3946 };
3947
3948 let snr = if noise_level != 0.0 {
3949 signal_level / noise_level
3950 } else {
3951 f64::INFINITY
3952 };
3953
3954 slope.abs() < 0.001 && snr > 10.0
3956 }
3957}
3958
3959#[derive(Debug, Clone)]
3961pub struct ExecutionStats {
3962 pub total_actions: usize,
3963 pub successful_actions: usize,
3964 pub failed_actions: usize,
3965 pub total_duration: std::time::Duration,
3966}
3967
3968impl ExecutionStats {
3969 pub fn success_rate(&self) -> f64 {
3970 if self.total_actions == 0 {
3971 0.0
3972 } else {
3973 self.successful_actions as f64 / self.total_actions as f64
3974 }
3975 }
3976}
3977
3978impl ActionDriver {
3980 pub fn execute_chain_with_stats(
3982 &mut self,
3983 chain: impl Into<ActionChain>,
3984 ) -> Result<(Vec<ActionResult>, ExecutionStats), NanonisError> {
3985 let chain = chain.into();
3986 let start_time = std::time::Instant::now();
3987 let mut results = Vec::with_capacity(chain.len());
3988 let mut successful = 0;
3989 let failed = 0;
3990
3991 for action in chain.into_iter() {
3992 match self.execute(action) {
3993 Ok(result) => {
3994 results.push(result);
3995 successful += 1;
3996 }
3997 Err(e) => {
3998 return Err(e);
4002 }
4003 }
4004 }
4005
4006 let stats = ExecutionStats {
4007 total_actions: results.len(),
4008 successful_actions: successful,
4009 failed_actions: failed,
4010 total_duration: start_time.elapsed(),
4011 };
4012
4013 Ok((results, stats))
4014 }
4015}
4016
4017impl ExpectFromExecution<ActionResult> for ExecutionResult {
4020 fn expect_from_execution(self) -> Result<ActionResult, NanonisError> {
4021 self.into_single()
4022 }
4023}
4024
4025impl ExpectFromExecution<Vec<ActionResult>> for ExecutionResult {
4026 fn expect_from_execution(self) -> Result<Vec<ActionResult>, NanonisError> {
4027 self.into_chain()
4028 }
4029}
4030
4031impl ExpectFromExecution<crate::types::ExperimentData> for ExecutionResult {
4032 fn expect_from_execution(
4033 self,
4034 ) -> Result<crate::types::ExperimentData, NanonisError> {
4035 self.into_experiment_data()
4036 }
4037}
4038
4039impl ExpectFromExecution<crate::types::ChainExperimentData>
4040 for ExecutionResult
4041{
4042 fn expect_from_execution(
4043 self,
4044 ) -> Result<crate::types::ChainExperimentData, NanonisError> {
4045 self.into_chain_experiment_data()
4046 }
4047}
4048
4049impl ExpectFromExecution<f64> for ExecutionResult {
4050 fn expect_from_execution(self) -> Result<f64, NanonisError> {
4051 match self {
4052 ExecutionResult::Single(ActionResult::Value(v)) => Ok(v),
4053 ExecutionResult::Single(ActionResult::Values(mut vs))
4054 if vs.len() == 1 =>
4055 {
4056 Ok(vs.pop().unwrap())
4057 }
4058 _ => Err(NanonisError::Protocol(
4059 "Expected single numeric value".to_string(),
4060 )),
4061 }
4062 }
4063}
4064
4065impl ExpectFromExecution<Vec<f64>> for ExecutionResult {
4066 fn expect_from_execution(self) -> Result<Vec<f64>, NanonisError> {
4067 match self {
4068 ExecutionResult::Single(ActionResult::Values(vs)) => Ok(vs),
4069 ExecutionResult::Single(ActionResult::Value(v)) => Ok(vec![v]),
4070 _ => Err(NanonisError::Protocol(
4071 "Expected numeric values".to_string(),
4072 )),
4073 }
4074 }
4075}
4076
4077impl ExpectFromExecution<bool> for ExecutionResult {
4078 fn expect_from_execution(self) -> Result<bool, NanonisError> {
4079 match self {
4080 ExecutionResult::Single(ActionResult::Status(b)) => Ok(b),
4081 _ => Err(NanonisError::Protocol(
4082 "Expected boolean status".to_string(),
4083 )),
4084 }
4085 }
4086}
4087
4088impl ExpectFromExecution<Position> for ExecutionResult {
4089 fn expect_from_execution(self) -> Result<Position, NanonisError> {
4090 match self {
4091 ExecutionResult::Single(ActionResult::Position(pos)) => Ok(pos),
4092 _ => Err(NanonisError::Protocol(
4093 "Expected position data".to_string(),
4094 )),
4095 }
4096 }
4097}
4098
4099impl ExpectFromExecution<OsciData> for ExecutionResult {
4100 fn expect_from_execution(self) -> Result<OsciData, NanonisError> {
4101 match self {
4102 ExecutionResult::Single(ActionResult::OsciData(data)) => Ok(data),
4103 _ => Err(NanonisError::Protocol(
4104 "Expected oscilloscope data".to_string(),
4105 )),
4106 }
4107 }
4108}
4109
4110impl ExpectFromExecution<crate::types::TipShape> for ExecutionResult {
4111 fn expect_from_execution(
4112 self,
4113 ) -> Result<crate::types::TipShape, NanonisError> {
4114 match self {
4115 ExecutionResult::Single(ActionResult::TipState(tip_state)) => {
4116 Ok(tip_state.shape)
4117 }
4118 _ => Err(NanonisError::Protocol("Expected tip state".to_string())),
4119 }
4120 }
4121}
4122
4123impl ExpectFromExecution<crate::actions::TipState> for ExecutionResult {
4124 fn expect_from_execution(
4125 self,
4126 ) -> Result<crate::actions::TipState, NanonisError> {
4127 match self {
4128 ExecutionResult::Single(ActionResult::TipState(tip_state)) => {
4129 Ok(tip_state)
4130 }
4131 _ => Err(NanonisError::Protocol("Expected tip state".to_string())),
4132 }
4133 }
4134}
4135
4136impl ExpectFromExecution<crate::actions::StableSignal> for ExecutionResult {
4137 fn expect_from_execution(
4138 self,
4139 ) -> Result<crate::actions::StableSignal, NanonisError> {
4140 match self {
4141 ExecutionResult::Single(ActionResult::StableSignal(
4142 stable_signal,
4143 )) => Ok(stable_signal),
4144 _ => Err(NanonisError::Protocol(
4145 "Expected stable signal".to_string(),
4146 )),
4147 }
4148 }
4149}
4150
4151impl ExpectFromExecution<crate::actions::TCPReaderStatus> for ExecutionResult {
4152 fn expect_from_execution(
4153 self,
4154 ) -> Result<crate::actions::TCPReaderStatus, NanonisError> {
4155 match self {
4156 ExecutionResult::Single(ActionResult::TCPReaderStatus(
4157 tcp_status,
4158 )) => Ok(tcp_status),
4159 _ => Err(NanonisError::Protocol(
4160 "Expected TCP reader status".to_string(),
4161 )),
4162 }
4163 }
4164}
4165
4166impl ExpectFromExecution<crate::actions::StabilityResult> for ExecutionResult {
4167 fn expect_from_execution(
4168 self,
4169 ) -> Result<crate::actions::StabilityResult, NanonisError> {
4170 match self {
4171 ExecutionResult::Single(ActionResult::StabilityResult(
4172 stability_result,
4173 )) => Ok(stability_result),
4174 _ => Err(NanonisError::Protocol(
4175 "Expected stability result".to_string(),
4176 )),
4177 }
4178 }
4179}
4180
4181impl ExpectFromExecution<Vec<String>> for ExecutionResult {
4182 fn expect_from_execution(self) -> Result<Vec<String>, NanonisError> {
4183 match self {
4184 ExecutionResult::Single(ActionResult::Text(text)) => Ok(text),
4185 _ => Err(NanonisError::Protocol("Expected text data".to_string())),
4186 }
4187 }
4188}
4189
4190impl Drop for ActionDriver {
4191 fn drop(&mut self) {
4192 log::info!("ActionDriver cleanup starting...");
4193
4194 if let Some(mut reader) = self.tcp_reader.take() {
4196 let final_data = reader.get_all_data();
4197 let _ = reader.stop(); log::info!(
4199 "Stopped TCP buffering, collected {} frames",
4200 final_data.len()
4201 );
4202 }
4203
4204 log::info!("Disabling safe tip protection...");
4206 if let Err(e) = self.client_mut().safe_tip_on_off_set(false) {
4207 log::warn!("Failed to disable safe tip: {}", e);
4208 }
4209
4210 log::info!("Performing safe withdrawal...");
4212 let withdraw_result = self.execute_chain(vec![
4213 Action::Withdraw {
4214 wait_until_finished: true, timeout: Duration::from_secs(5),
4216 },
4217 Action::MoveMotorAxis {
4218 direction: crate::MotorDirection::ZMinus,
4219 steps: 10,
4220 blocking: false,
4221 },
4222 ]);
4223
4224 if let Err(e) = withdraw_result {
4225 log::warn!("Cleanup withdrawal failed: {}", e);
4226 } else {
4227 log::info!("Safe withdrawal completed");
4228 }
4229
4230 log::info!("ActionDriver cleanup complete");
4231 }
4232}
4233
4234#[cfg(test)]
4235mod tests {
4236 use std::time::Duration;
4237
4238 use super::*;
4239 #[test]
4243 fn test_action_translator_interface() {
4244 let driver_result = ActionDriver::new("127.0.0.1", 6501);
4248 match driver_result {
4249 Ok(mut driver) => {
4250 let action = Action::ReadBias;
4252 let _result = driver.execute(action);
4253
4254 let chain = ActionChain::new(vec![
4259 Action::ReadBias,
4260 Action::Wait {
4261 duration: Duration::from_millis(500),
4262 },
4263 Action::SetBias { voltage: 1.0 },
4264 ]);
4265
4266 let _chain_result = driver.execute_chain(chain);
4267 }
4268 Err(_) => {
4269 println!("Signal discovery failed - this is expected without hardware");
4271 }
4272 }
4273 }
4274}