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