Skip to main content

rusty_tip/
action_driver.rs

1use std::{
2    collections::HashMap,
3    sync::{
4        atomic::{AtomicBool, Ordering},
5        Arc,
6    },
7    thread,
8    time::{Duration, Instant},
9};
10
11use log::{debug, info, warn};
12use nanonis_rs::signals::SignalIndex;
13use ndarray::Array1;
14
15use crate::{
16    actions::{
17        Action, ActionChain, ActionLogEntry, ActionLogResult, ActionResult,
18        ExpectFromAction,
19    },
20    buffered_tcp_reader::BufferedTCPReader,
21    signal_registry::SignalRegistry,
22    types::{DataToGet, OsciData, SignalStats, TriggerConfig},
23    utils::{poll_until, poll_with_timeout, PollError},
24    MotorGroup, NanonisClient, NanonisError, Position, PulseMode, ScanAction,
25    ScanDirection, Signal, TipShaperConfig, ZControllerHold,
26};
27
28// ========================================================================
29// TIP STATE CHECKING CONSTANTS
30// ========================================================================
31
32/// Maximum standard deviation for stable signal (Hz)
33/// This checks the noise level in the signal
34/// Typical values: 0.3-0.5 for frequency shift signals with moderate noise
35/// Increased to 1.0 for noisy signals - adjust based on your actual noise level
36const TIP_STATE_MAX_STD_DEV: f32 = 1.0;
37
38/// Maximum slope for stable signal (Hz per sample)
39/// This checks for drift/trend in the signal
40/// Slope is calculated via linear regression over the data window
41/// Typical values: 0.001-0.01 depending on your signal drift rate
42/// Increased to 0.01 for drifting signals - adjust based on your actual drift
43const TIP_STATE_MAX_SLOPE: f32 = 0.01;
44
45/// Duration of data collection for tip state checking (milliseconds)
46const TIP_STATE_DATA_COLLECTION_DURATION_MS: u64 = 500;
47
48/// Timeout for stable signal reading during tip state check (seconds)
49const TIP_STATE_READ_TIMEOUT_SECS: u64 = 15;
50
51/// Number of retries for stable signal reading during tip state check
52const TIP_STATE_READ_RETRY_COUNT: u32 = 3;
53
54/// Configuration for TCP Logger integration with always-buffer support
55#[derive(Debug, Clone)]
56pub struct TCPReaderConfig {
57    /// TCP data stream port (typically 6590)
58    pub stream_port: u16,
59    /// Signal channel indices to record (0-23)
60    pub channels: Vec<i32>,
61    /// Oversampling rate multiplier (0-1000)
62    pub oversampling: i32,
63    /// Whether to start logging automatically on connection
64    pub auto_start: bool,
65    /// Buffer size for always-buffer mode (None = no buffering)
66    /// When Some(size), BufferedTCPReader starts automatically
67    pub buffer_size: Option<usize>,
68}
69
70impl Default for TCPReaderConfig {
71    fn default() -> Self {
72        Self {
73            stream_port: 6590,
74            channels: (0..=23).collect(),
75            oversampling: 20,
76            auto_start: true,
77            buffer_size: Some(10_000),
78        }
79    }
80}
81
82/// Unified input type for run() method - accepts single actions or chains
83#[derive(Debug, Clone)]
84pub enum ActionRequest {
85    /// Single action
86    Single(Action),
87    /// Multiple actions as chain
88    Chain(Vec<Action>),
89}
90
91impl From<Action> for ActionRequest {
92    fn from(action: Action) -> Self {
93        ActionRequest::Single(action)
94    }
95}
96
97impl From<Vec<Action>> for ActionRequest {
98    fn from(actions: Vec<Action>) -> Self {
99        ActionRequest::Chain(actions)
100    }
101}
102
103impl From<ActionChain> for ActionRequest {
104    fn from(chain: ActionChain) -> Self {
105        ActionRequest::Chain(chain.into_iter().collect())
106    }
107}
108
109impl ActionRequest {
110    pub fn is_single(&self) -> bool {
111        matches!(self, ActionRequest::Single(_))
112    }
113
114    pub fn is_chain(&self) -> bool {
115        matches!(self, ActionRequest::Chain(_))
116    }
117}
118
119/// Configuration for execution behavior in the unified run() method
120#[derive(Debug, Clone)]
121pub struct ExecutionConfig {
122    /// Enable data collection with pre/post durations
123    pub data_collection: Option<(Duration, Duration)>,
124    /// Chain execution behavior
125    pub chain_behavior: ChainBehavior,
126    /// Logging behavior
127    pub logging_behavior: LoggingBehavior,
128    /// Performance optimizations
129    pub performance_mode: PerformanceMode,
130}
131
132#[derive(Debug, Clone)]
133pub enum ChainBehavior {
134    /// Execute all actions, return all results (default)
135    Complete,
136    /// Execute all actions, return only final result
137    FinalOnly,
138    /// Execute until error, return partial results
139    Partial,
140}
141
142#[derive(Debug, Clone)]
143pub enum LoggingBehavior {
144    /// Normal logging (default)
145    Normal,
146    /// No per-action logging, single chain log
147    Deferred,
148    /// Disable logging completely for this execution
149    Disabled,
150}
151
152#[derive(Debug, Clone)]
153pub enum PerformanceMode {
154    /// Normal execution (default)
155    Normal,
156    /// Optimized for timing-critical operations
157    Fast,
158}
159
160impl Default for ExecutionConfig {
161    fn default() -> Self {
162        Self {
163            data_collection: None,
164            chain_behavior: ChainBehavior::Complete,
165            logging_behavior: LoggingBehavior::Normal,
166            performance_mode: PerformanceMode::Normal,
167        }
168    }
169}
170
171impl ExecutionConfig {
172    /// Create new config with default settings
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    /// Enable data collection with specified pre/post durations
178    pub fn with_data_collection(
179        mut self,
180        pre_duration: Duration,
181        post_duration: Duration,
182    ) -> Self {
183        self.data_collection = Some((pre_duration, post_duration));
184        self
185    }
186
187    /// Set chain to return only final result
188    pub fn final_only(mut self) -> Self {
189        self.chain_behavior = ChainBehavior::FinalOnly;
190        self
191    }
192
193    /// Set chain to allow partial execution on error
194    pub fn partial(mut self) -> Self {
195        self.chain_behavior = ChainBehavior::Partial;
196        self
197    }
198
199    /// Use deferred logging (single chain entry instead of per-action)
200    pub fn deferred_logging(mut self) -> Self {
201        self.logging_behavior = LoggingBehavior::Deferred;
202        self
203    }
204
205    /// Disable logging for this execution
206    pub fn no_logging(mut self) -> Self {
207        self.logging_behavior = LoggingBehavior::Disabled;
208        self
209    }
210
211    /// Enable fast performance mode
212    pub fn fast_mode(mut self) -> Self {
213        self.performance_mode = PerformanceMode::Fast;
214        self
215    }
216}
217
218/// Result container for unified run() method
219#[derive(Debug)]
220pub enum ExecutionResult {
221    /// Single action result
222    Single(ActionResult),
223    /// Multiple action results
224    Chain(Vec<ActionResult>),
225    /// Experiment data with signal collection
226    ExperimentData(crate::types::ExperimentData),
227    /// Chain experiment data with signal collection
228    ChainExperimentData(crate::types::ChainExperimentData),
229    /// Partial chain results (on error)
230    Partial(Vec<ActionResult>, NanonisError),
231}
232
233impl ExecutionResult {
234    /// Extract single result or error if not single
235    pub fn into_single(self) -> Result<ActionResult, NanonisError> {
236        match self {
237            ExecutionResult::Single(result) => Ok(result),
238            ExecutionResult::Chain(mut results) if results.len() == 1 => {
239                Ok(results.pop().unwrap())
240            }
241            _ => Err(NanonisError::Protocol(
242                "Expected single result".to_string(),
243            )),
244        }
245    }
246
247    /// Extract chain results or error if not chain
248    pub fn into_chain(self) -> Result<Vec<ActionResult>, NanonisError> {
249        match self {
250            ExecutionResult::Chain(results) => Ok(results),
251            ExecutionResult::Single(result) => Ok(vec![result]),
252            ExecutionResult::Partial(results, _) => Ok(results),
253            _ => Err(NanonisError::Protocol(
254                "Expected chain results".to_string(),
255            )),
256        }
257    }
258
259    /// Extract experiment data or error if not experiment
260    pub fn into_experiment_data(
261        self,
262    ) -> Result<crate::types::ExperimentData, NanonisError> {
263        match self {
264            ExecutionResult::ExperimentData(data) => Ok(data),
265            _ => Err(NanonisError::Protocol(
266                "Expected experiment data".to_string(),
267            )),
268        }
269    }
270
271    /// Extract chain experiment data or error if not chain experiment
272    pub fn into_chain_experiment_data(
273        self,
274    ) -> Result<crate::types::ChainExperimentData, NanonisError> {
275        match self {
276            ExecutionResult::ChainExperimentData(data) => Ok(data),
277            _ => Err(NanonisError::Protocol(
278                "Expected chain experiment data".to_string(),
279            )),
280        }
281    }
282
283    /// Type-safe extraction with action validation
284    pub fn expecting<T>(self) -> Result<T, NanonisError>
285    where
286        Self: ExpectFromExecution<T>,
287    {
288        self.expect_from_execution()
289    }
290}
291
292/// Trait for type-safe extraction from ExecutionResult
293pub trait ExpectFromExecution<T> {
294    fn expect_from_execution(self) -> Result<T, NanonisError>;
295}
296
297/// Builder for fluent configuration of execution
298pub struct ExecutionBuilder<'a> {
299    driver: &'a mut ActionDriver,
300    request: ActionRequest,
301    config: ExecutionConfig,
302}
303
304impl<'a> ExecutionBuilder<'a> {
305    fn new(driver: &'a mut ActionDriver, request: ActionRequest) -> Self {
306        Self {
307            driver,
308            request,
309            config: ExecutionConfig::default(),
310        }
311    }
312
313    /// Enable data collection with specified durations
314    pub fn with_data_collection(
315        mut self,
316        pre_duration: Duration,
317        post_duration: Duration,
318    ) -> Self {
319        self.config = self
320            .config
321            .with_data_collection(pre_duration, post_duration);
322        self
323    }
324
325    /// Return only final result for chains
326    pub fn final_only(mut self) -> Self {
327        self.config = self.config.final_only();
328        self
329    }
330
331    /// Allow partial execution on error
332    pub fn partial(mut self) -> Self {
333        self.config = self.config.partial();
334        self
335    }
336
337    /// Use deferred logging
338    pub fn deferred_logging(mut self) -> Self {
339        self.config = self.config.deferred_logging();
340        self
341    }
342
343    /// Disable logging for this execution
344    pub fn no_logging(mut self) -> Self {
345        self.config = self.config.no_logging();
346        self
347    }
348
349    /// Enable fast performance mode
350    pub fn fast_mode(mut self) -> Self {
351        self.config = self.config.fast_mode();
352        self
353    }
354
355    /// Execute with type-safe result extraction
356    pub fn expecting<T>(self) -> Result<T, NanonisError>
357    where
358        ExecutionResult: ExpectFromExecution<T>,
359    {
360        let result = self.driver.run_with_config(self.request, self.config)?;
361        result.expecting()
362    }
363
364    /// Execute and return ExecutionResult
365    pub fn execute(self) -> Result<ExecutionResult, NanonisError> {
366        self.driver.run_with_config(self.request, self.config)
367    }
368}
369
370impl<'a> ExecutionBuilder<'a> {
371    /// Convenience method for single actions - returns ActionResult directly
372    pub fn go(self) -> Result<ActionResult, NanonisError> {
373        match self.request {
374            ActionRequest::Single(_) => {
375                let result =
376                    self.driver.run_with_config(self.request, self.config)?;
377                result.into_single()
378            }
379            ActionRequest::Chain(_) => Err(NanonisError::Protocol(
380                "Use .execute() for chains, .go() is only for single actions"
381                    .to_string(),
382            )),
383        }
384    }
385}
386
387/// Builder for configuring ActionDriver with optional parameters
388#[derive(Debug, Clone)]
389pub struct ActionDriverBuilder {
390    addr: String,
391    port: u16,
392    connection_timeout: Option<Duration>,
393    initial_storage: HashMap<String, ActionResult>,
394    tcp_reader_config: Option<TCPReaderConfig>,
395    action_logger_config: Option<(std::path::PathBuf, usize, bool)>, // (file_path, buffer_size, final_format_json)
396    custom_tcp_mapping: Option<Vec<(u8, u8)>>, // Custom Nanonis to TCP channel mapping
397    shutdown_flag: Option<Arc<AtomicBool>>,    // Graceful shutdown support
398}
399
400impl ActionDriverBuilder {
401    /// Create a new builder with required connection parameters
402    pub fn new(addr: &str, port: u16) -> Self {
403        Self {
404            addr: addr.to_string(),
405            port,
406            connection_timeout: None,
407            initial_storage: HashMap::new(),
408            tcp_reader_config: None,
409            action_logger_config: None,
410            custom_tcp_mapping: None,
411            shutdown_flag: None,
412        }
413    }
414
415    /// Set connection timeout for the underlying NanonisClient
416    pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
417        self.connection_timeout = Some(timeout);
418        self
419    }
420
421    /// Initialize with pre-stored values
422    pub fn with_initial_storage(
423        mut self,
424        storage: HashMap<String, ActionResult>,
425    ) -> Self {
426        self.initial_storage = storage;
427        self
428    }
429
430    /// Add a single pre-stored value
431    pub fn with_stored_value(
432        mut self,
433        key: String,
434        value: ActionResult,
435    ) -> Self {
436        self.initial_storage.insert(key, value);
437        self
438    }
439
440    /// Configure TCP Logger with always-buffer mode (recommended)
441    /// This automatically starts BufferedTCPReader when ActionDriver is built
442    ///
443    /// # Arguments
444    /// * `config` - TCP logger configuration with buffer_size set
445    ///
446    /// # Usage
447    /// ```rust,ignore
448    /// let driver = ActionDriver::builder("127.0.0.1", 6501)
449    ///     .with_tcp_reader(TCPReaderConfig {
450    ///         stream_port: 6590,
451    ///         channels: vec![0, 8],
452    ///         oversampling: 100,
453    ///         auto_start: true,
454    ///         buffer_size: Some(10_000),
455    ///     })
456    ///     .build()?;
457    /// // Buffering is now active and ready for immediate data queries
458    /// ```
459    pub fn with_tcp_reader(mut self, config: TCPReaderConfig) -> Self {
460        if config.buffer_size.is_none() {
461            log::warn!(
462                "TCPLoggerConfig buffer_size is None - buffering disabled"
463            );
464        }
465        self.tcp_reader_config = Some(config);
466        self
467    }
468
469    /// Configure action logging with buffered file output
470    ///
471    /// # Arguments
472    /// * `file_path` - Base path where action logs will be written (extension added automatically)
473    /// * `buffer_size` - Number of actions to buffer before auto-flushing to file
474    /// * `final_format_json` - If true, convert to JSON array on final flush; if false, keep JSONL format
475    ///
476    /// # File Extensions
477    /// File extensions are added automatically based on the final format:
478    /// - `final_format_json = false` → `.jsonl` extension (efficient streaming)
479    /// - `final_format_json = true` → `.json` extension (post-analysis friendly)
480    ///
481    /// # Usage
482    /// ```rust,ignore
483    /// // JSONL format (efficient, streaming) → experiment_actions.jsonl
484    /// let driver = ActionDriver::builder("127.0.0.1", 6501)
485    ///     .with_action_logging("experiment_actions", 100, false)
486    ///     .build()?;
487    ///
488    /// // JSON format (better for post-analysis) → experiment_data.json
489    /// let driver = ActionDriver::builder("127.0.0.1", 6501)
490    ///     .with_action_logging("experiment_data", 100, true)
491    ///     .build()?;
492    /// ```
493    pub fn with_action_logging(
494        mut self,
495        file_path: impl Into<std::path::PathBuf>,
496        buffer_size: usize,
497        final_format_json: bool,
498    ) -> Self {
499        self.action_logger_config =
500            Some((file_path.into(), buffer_size, final_format_json));
501        self
502    }
503
504    /// Provide custom Nanonis to TCP channel mapping
505    ///
506    /// Override the default hardcoded mappings with your own. This is useful when
507    /// your Nanonis configuration has different signal indices.
508    ///
509    /// # Arguments
510    /// * `mapping` - Array of (nanonis_index, tcp_channel) tuples
511    ///
512    /// # Example
513    /// ```rust,ignore
514    /// let custom_map = [
515    ///     (76, 18),  // Frequency shift
516    ///     (0, 0),    // Current
517    ///     (24, 8),   // Bias
518    /// ];
519    ///
520    /// let driver = ActionDriver::builder("127.0.0.1", 6501)
521    ///     .with_custom_tcp_mapping(&custom_map)
522    ///     .build()?;
523    /// ```
524    pub fn with_custom_tcp_mapping(mut self, mapping: &[(u8, u8)]) -> Self {
525        self.custom_tcp_mapping = Some(mapping.to_vec());
526        self
527    }
528
529    /// Set shutdown flag for graceful termination of long-running operations
530    ///
531    /// When set, operations like stability checks will periodically check this flag
532    /// and return early with `NanonisError::Protocol("Shutdown requested".to_string())` if it becomes true.
533    pub fn with_shutdown_flag(mut self, flag: Arc<AtomicBool>) -> Self {
534        self.shutdown_flag = Some(flag);
535        self
536    }
537
538    /// Build the ActionDriver with configured parameters and optional automatic buffering
539    pub fn build(self) -> Result<ActionDriver, NanonisError> {
540        let mut client = {
541            let mut builder =
542                NanonisClient::builder().address(&self.addr).port(self.port);
543
544            if let Some(timeout) = self.connection_timeout {
545                builder = builder.connect_timeout(timeout);
546            }
547
548            builder.build()?
549        };
550
551        let tcp_reader = if let Some(ref config) = self.tcp_reader_config {
552            if let Some(buffer_size) = config.buffer_size {
553                // 1. Configure TCP logger settings first
554                client.tcplog_chs_set(config.channels.clone())?;
555                client.tcplog_oversampl_set(config.oversampling)?;
556
557                // 2. Connect TCP stream BEFORE starting logger (critical sequence!)
558                let reader =
559                    crate::buffered_tcp_reader::BufferedTCPReader::new(
560                        "127.0.0.1",
561                        config.stream_port,
562                        buffer_size,
563                        config.channels.len() as u32,
564                        config.oversampling as f32,
565                    )?;
566                log::debug!(
567                    "TCP stream connected, buffer capacity: {} frames",
568                    buffer_size
569                );
570
571                // 3. NOW start TCP logger (data flows to connected reader)
572                if config.auto_start {
573                    // Reset TCP logger state first to ensure clean start
574                    log::debug!("Stopping TCP logger to ensure clean state");
575                    let _ = client.tcplog_stop(); // Ignore errors - might not be running
576                    std::thread::sleep(std::time::Duration::from_millis(200)); // Give it time to stop
577
578                    // Now start TCP logger
579                    client.tcplog_start()?;
580                    log::debug!("TCP logger started, data collection active");
581                }
582
583                Some(reader)
584            } else {
585                None
586            }
587        } else {
588            None
589        };
590
591        // Create action logger if configured
592        let action_logger =
593            if let Some((file_path, buffer_size, final_format_json)) =
594                self.action_logger_config
595            {
596                Some(crate::logger::Logger::new(
597                    file_path,
598                    buffer_size,
599                    final_format_json,
600                ))
601            } else {
602                None
603            };
604
605        // Auto-initialize signal registry with custom or hardcoded mapping
606        let signal_names = client.signal_names_get()?;
607        let signal_registry =
608            if let Some(ref custom_map) = self.custom_tcp_mapping {
609                log::debug!(
610                    "Using custom TCP channel mapping with {} entries",
611                    custom_map.len()
612                );
613                SignalRegistry::builder()
614                    .with_standard_map()
615                    .add_tcp_map(custom_map)
616                    .from_signal_names(&signal_names)
617                    .create_aliases()
618                    .build()
619            } else {
620                SignalRegistry::with_hardcoded_tcp_mapping(&signal_names)
621            };
622
623        Ok(ActionDriver {
624            client,
625            stored_values: self.initial_storage,
626            tcp_reader_config: self.tcp_reader_config,
627            tcp_reader,
628            action_logger,
629            action_logging_enabled: true, // Default to enabled if logger is configured
630            signal_registry,
631            recent_stable_signals: std::collections::VecDeque::new(),
632            shutdown_flag: self.shutdown_flag,
633        })
634    }
635}
636
637/// Direct 1:1 translation layer between Actions and NanonisClient calls
638/// Now with integrated always-buffer TCP data collection capability
639pub struct ActionDriver {
640    /// Nanonis control client for sending commands
641    client: NanonisClient,
642    /// Storage for Store/Retrieve actions
643    stored_values: HashMap<String, ActionResult>,
644    /// TCP Logger configuration for data collection
645    tcp_reader_config: Option<TCPReaderConfig>,
646    /// Buffered TCP reader for always-buffer mode (automatically started if configured)
647    tcp_reader: Option<crate::buffered_tcp_reader::BufferedTCPReader>,
648    /// Action logger for execution tracking
649    action_logger:
650        Option<crate::logger::Logger<crate::actions::ActionLogEntry>>,
651    /// Enable/disable action logging at runtime
652    action_logging_enabled: bool,
653    /// Signal registry for name-based lookup and TCP mapping
654    signal_registry: SignalRegistry,
655    /// Recent ReadStableSignal results for correlation with CheckTipState
656    recent_stable_signals: std::collections::VecDeque<(
657        crate::actions::StableSignal,
658        std::time::Instant,
659    )>,
660    /// Shutdown flag for graceful termination of long-running operations
661    shutdown_flag: Option<Arc<AtomicBool>>,
662}
663
664impl ActionDriver {
665    /// Create a builder for configuring ActionDriver
666    pub fn builder(addr: &str, port: u16) -> ActionDriverBuilder {
667        ActionDriverBuilder::new(addr, port)
668    }
669
670    /// Create a new ActionDriver with default configuration (backward compatibility)
671    pub fn new(addr: &str, port: u16) -> Result<Self, NanonisError> {
672        Self::builder(addr, port).build()
673    }
674
675    /// Convenience method to create with existing NanonisClient (backward compatibility)
676    pub fn with_nanonis_client(mut client: NanonisClient) -> Self {
677        // Initialize signal registry even for this convenience method
678        let signal_names = client.signal_names_get().unwrap_or_default();
679        let signal_registry =
680            SignalRegistry::with_hardcoded_tcp_mapping(&signal_names);
681
682        Self {
683            client,
684            stored_values: HashMap::new(),
685            tcp_reader_config: None,
686            tcp_reader: None,
687            action_logger: None,
688            action_logging_enabled: false,
689            signal_registry,
690            recent_stable_signals: std::collections::VecDeque::new(),
691            shutdown_flag: None,
692        }
693    }
694
695    /// Get a reference to the underlying NanonisClient
696    pub fn client(&self) -> &NanonisClient {
697        &self.client
698    }
699
700    /// Get a mutable reference to the underlying NanonisClient
701    pub fn client_mut(&mut self) -> &mut NanonisClient {
702        &mut self.client
703    }
704
705    /// Set shutdown flag for graceful termination of long-running operations
706    pub fn set_shutdown_flag(&mut self, flag: Arc<AtomicBool>) {
707        self.shutdown_flag = Some(flag);
708    }
709
710    /// Check if shutdown has been requested
711    fn is_shutdown_requested(&self) -> bool {
712        self.shutdown_flag
713            .as_ref()
714            .map(|f| f.load(Ordering::SeqCst))
715            .unwrap_or(false)
716    }
717
718    /// Execute an auto-approach operation.
719    ///
720    /// If `wait_until_finished` is true, blocks until approach completes or timeout.
721    /// If false, starts the approach and returns immediately.
722    pub fn auto_approach(
723        &mut self,
724        wait_until_finished: bool,
725        timeout: Duration,
726    ) -> Result<(), NanonisError> {
727        // Check if already running
728        match self.client.auto_approach_on_off_get() {
729            Ok(true) => {
730                log::warn!("Auto-approach already running");
731                return Ok(());
732            }
733            Ok(false) => {
734                log::debug!("Auto-approach is idle, proceeding to start");
735            }
736            Err(_) => {
737                log::warn!(
738                    "Auto-approach status unknown, attempting to proceed"
739                );
740            }
741        }
742
743        // Open auto-approach module
744        match self.client.auto_approach_open() {
745            Ok(_) => log::debug!("Opened the auto-approach module"),
746            Err(_) => {
747                log::debug!("Failed to open auto-approach module, already open")
748            }
749        }
750
751        // Wait for module initialization
752        std::thread::sleep(std::time::Duration::from_millis(500));
753
754        // Start auto-approach
755        if let Err(e) = self.client.auto_approach_on_off_set(true) {
756            log::error!("Failed to start auto-approach: {}", e);
757            return Err(NanonisError::Protocol(format!(
758                "Failed to start auto-approach: {}",
759                e
760            )));
761        }
762
763        if !wait_until_finished {
764            log::debug!("Auto-approach started, not waiting for completion");
765            return Ok(());
766        }
767
768        // Wait for completion with timeout
769        log::debug!("Waiting for auto-approach to complete...");
770        let poll_interval = std::time::Duration::from_millis(100);
771
772        match poll_until(
773            || {
774                self.client
775                    .auto_approach_on_off_get()
776                    .map(|running| !running)
777            },
778            timeout,
779            poll_interval,
780        ) {
781            Ok(()) => {
782                log::debug!("Auto-approach completed successfully");
783                Ok(())
784            }
785            Err(PollError::Timeout) => {
786                log::warn!("Auto-approach timed out after {:?}", timeout);
787                let _ = self.client.auto_approach_on_off_set(false);
788                Err(NanonisError::Protocol(
789                    "Auto-approach timed out".to_string(),
790                ))
791            }
792            Err(PollError::ConditionError(e)) => {
793                log::error!("Error checking auto-approach status: {}", e);
794                Err(NanonisError::Protocol(format!(
795                    "Status check error: {}",
796                    e
797                )))
798            }
799        }
800    }
801
802    /// Center the frequency shift using the PLL auto-center function.
803    pub fn center_freq_shift(&mut self) -> Result<(), NanonisError> {
804        let modulator_index = 1;
805        log::debug!("Centering frequency shift");
806        self.client.pll_freq_shift_auto_center(modulator_index)
807    }
808
809    /// Get TCP Logger configuration if set
810    pub fn tcp_reader_config(&self) -> Option<&TCPReaderConfig> {
811        self.tcp_reader_config.as_ref()
812    }
813
814    /// Check if TCP logger is configured and available
815    pub fn has_tcp_reader(&self) -> bool {
816        self.tcp_reader.is_some()
817    }
818
819    pub fn tcp_reader_mut(&mut self) -> Option<&mut BufferedTCPReader> {
820        self.tcp_reader.as_mut()
821    }
822
823    /// Clear the TCP reader buffer
824    ///
825    /// This removes all buffered data, which is useful to discard stale values
826    /// before starting a new measurement or tip preparation sequence.
827    pub fn clear_tcp_buffer(&self) {
828        if let Some(ref tcp_reader) = self.tcp_reader {
829            tcp_reader.clear_buffer();
830            debug!("TCP reader buffer cleared");
831        } else {
832            warn!("No TCP reader available to clear");
833        }
834    }
835
836    /// Get reference to the signal registry
837    pub fn signal_registry(&self) -> &SignalRegistry {
838        &self.signal_registry
839    }
840
841    /// Calculate number of data points needed for a target duration
842    ///
843    /// Based on the TCP reader configuration (oversampling), calculates how many
844    /// samples are needed to cover the specified duration.
845    ///
846    /// # Arguments
847    /// * `target_duration` - Desired time window for data collection
848    ///
849    /// # Returns
850    /// Number of samples, or None if TCP reader is not configured
851    ///
852    /// # Example
853    /// For 500ms at 2000 Hz effective rate: returns 1000 samples
854    fn calculate_samples_for_duration(
855        &self,
856        target_duration: Duration,
857    ) -> Option<usize> {
858        if let Some(ref config) = self.tcp_reader_config {
859            // Effective sample rate = base_rate / oversampling
860            // For oversampling=1 at 2kHz base: 2000 samples/sec
861            // For 500ms: 2000 * 0.5 = 1000 samples
862            let base_rate = 2000.0; // Typical Nanonis base rate in Hz
863            let effective_rate = base_rate / config.oversampling as f64;
864            let samples = (effective_rate * target_duration.as_secs_f64())
865                .ceil() as usize;
866            log::debug!(
867                "Calculated {} samples for {:.0}ms (base: {}Hz, oversampling: {}, effective: {:.1}Hz)",
868                samples,
869                target_duration.as_millis(),
870                base_rate,
871                config.oversampling,
872                effective_rate
873            );
874            Some(samples.max(50)) // Minimum 50 samples
875        } else {
876            None
877        }
878    }
879
880    // ==================== Unified Execution API ====================
881
882    /// Unified execution method with fluent configuration
883    ///
884    /// # Usage
885    /// ```rust,ignore
886    /// // Simple execution
887    /// let result = driver.run(action)?;
888    /// let results = driver.run(actions)?;
889    ///
890    /// // With data collection
891    /// let data = driver.run(action).with_data_collection(pre, post).execute()?;
892    ///
893    /// // Type-safe extraction
894    /// let signal: f64 = driver.run(read_signal).expecting()?;
895    ///
896    /// // Performance modes
897    /// let results = driver.run(actions).deferred_logging().execute()?;
898    /// let final_result = driver.run(actions).final_only().execute()?;
899    /// ```
900    pub fn run<R>(&mut self, request: R) -> ExecutionBuilder<'_>
901    where
902        R: Into<ActionRequest>,
903    {
904        ExecutionBuilder::new(self, request.into())
905    }
906
907    /// Execute with explicit configuration (for advanced use)
908    pub fn run_with_config(
909        &mut self,
910        request: ActionRequest,
911        config: ExecutionConfig,
912    ) -> Result<ExecutionResult, NanonisError> {
913        match (&request, &config.data_collection) {
914            // Single action with data collection
915            (
916                ActionRequest::Single(action),
917                Some((pre_duration, post_duration)),
918            ) => {
919                let experiment_data = self.execute_with_data_collection(
920                    action.clone(),
921                    *pre_duration,
922                    *post_duration,
923                )?;
924                Ok(ExecutionResult::ExperimentData(experiment_data))
925            }
926
927            // Chain with data collection
928            (
929                ActionRequest::Chain(actions),
930                Some((pre_duration, post_duration)),
931            ) => {
932                let chain_experiment_data = self
933                    .execute_chain_with_data_collection(
934                        actions.clone(),
935                        *pre_duration,
936                        *post_duration,
937                    )?;
938                Ok(ExecutionResult::ChainExperimentData(chain_experiment_data))
939            }
940
941            // Single action without data collection
942            (ActionRequest::Single(action), None) => {
943                let result = match config.logging_behavior {
944                    LoggingBehavior::Disabled => {
945                        let previous_state =
946                            self.set_action_logging_enabled(false);
947                        let result = self.execute(action.clone());
948                        self.set_action_logging_enabled(previous_state);
949                        result
950                    }
951                    _ => self.execute(action.clone()),
952                }?;
953                Ok(ExecutionResult::Single(result))
954            }
955
956            // Chain without data collection
957            (ActionRequest::Chain(actions), None) => {
958                let results =
959                    match (&config.chain_behavior, &config.logging_behavior) {
960                        (ChainBehavior::Complete, LoggingBehavior::Normal) => {
961                            self.execute_chain(actions.clone())?
962                        }
963                        (
964                            ChainBehavior::Complete,
965                            LoggingBehavior::Deferred,
966                        ) => self.execute_chain_deferred(actions.clone())?,
967                        (
968                            ChainBehavior::Complete,
969                            LoggingBehavior::Disabled,
970                        ) => {
971                            let previous_state =
972                                self.set_action_logging_enabled(false);
973                            let result = self.execute_chain(actions.clone());
974                            self.set_action_logging_enabled(previous_state);
975                            result?
976                        }
977                        (ChainBehavior::FinalOnly, _) => {
978                            let results = match config.logging_behavior {
979                                LoggingBehavior::Deferred => self
980                                    .execute_chain_deferred(actions.clone())?,
981                                LoggingBehavior::Disabled => {
982                                    let previous_state =
983                                        self.set_action_logging_enabled(false);
984                                    let result =
985                                        self.execute_chain(actions.clone());
986                                    self.set_action_logging_enabled(
987                                        previous_state,
988                                    );
989                                    result?
990                                }
991                                _ => self.execute_chain(actions.clone())?,
992                            };
993                            vec![results
994                                .into_iter()
995                                .last()
996                                .unwrap_or(ActionResult::None)]
997                        }
998                        (ChainBehavior::Partial, _) => {
999                            match self.execute_chain_partial(actions.clone()) {
1000                                Ok(results) => results,
1001                                Err((partial_results, error)) => {
1002                                    return Ok(ExecutionResult::Partial(
1003                                        partial_results,
1004                                        error,
1005                                    ));
1006                                }
1007                            }
1008                        }
1009                    };
1010
1011                Ok(ExecutionResult::Chain(results))
1012            }
1013        }
1014    }
1015
1016    // ==================== Always-Buffer TCP Data Collection Methods ====================
1017
1018    /// Get recent TCP signal data (always available if buffering enabled)
1019    ///
1020    /// # Arguments
1021    /// * `duration` - How far back to collect data from current time
1022    ///
1023    /// # Returns
1024    /// Vector of recent timestamped signal frames, empty if buffering not active
1025    ///
1026    /// # Usage
1027    /// Perfect for real-time monitoring and checking recent signal trends without
1028    /// needing to plan data collection in advance
1029    pub fn get_recent_tcp_data(
1030        &self,
1031        duration: Duration,
1032    ) -> Vec<crate::types::TimestampedSignalFrame> {
1033        self.tcp_reader
1034            .as_ref()
1035            .map(|reader| reader.get_recent_data(duration))
1036            .unwrap_or_default()
1037    }
1038
1039    /// Execute action with time-windowed data collection
1040    ///
1041    /// This is the core method for synchronized data collection during SPM operations.
1042    /// It captures data before, during, and after action execution using the always-buffer.
1043    ///
1044    /// # Arguments
1045    /// * `action` - The SPM action to execute
1046    /// * `pre_duration` - How much data to collect before action starts
1047    /// * `post_duration` - How much data to collect after action ends
1048    ///
1049    /// # Returns
1050    /// ExperimentData containing both action result and time-windowed signal data
1051    ///
1052    /// # Errors
1053    /// Returns error if buffering is not active or action execution fails
1054    pub fn execute_with_data_collection(
1055        &mut self,
1056        action: Action,
1057        pre_duration: Duration,
1058        post_duration: Duration,
1059    ) -> Result<crate::types::ExperimentData, NanonisError> {
1060        if self.tcp_reader.is_none() {
1061            return Err(NanonisError::Protocol(
1062                "TCP buffering not active".to_string(),
1063            ));
1064        }
1065
1066        let action_start = Instant::now();
1067        let action_result = self.execute(action.clone())?;
1068        let action_end = Instant::now();
1069
1070        std::thread::sleep(post_duration);
1071
1072        let window_start = action_start - pre_duration;
1073        let window_end = action_end + post_duration;
1074
1075        let signal_frames = self
1076            .tcp_reader
1077            .as_ref()
1078            .unwrap()
1079            .get_data_between(window_start, window_end);
1080        let tcp_config = self.tcp_reader_config.as_ref().unwrap().clone();
1081
1082        let experiment_data = crate::types::ExperimentData {
1083            action_result,
1084            signal_frames,
1085            tcp_config,
1086            action_start,
1087            action_end,
1088            total_duration: action_end.duration_since(action_start),
1089        };
1090
1091        // Log the complete experiment data if logging is enabled
1092        if self.action_logging_enabled && self.action_logger.is_some() {
1093            let log_entry = ActionLogEntry {
1094                action: format!("Data Collection: {}", action.description()),
1095                result: ActionLogResult::from_experiment_data(&experiment_data),
1096                start_time: chrono::Utc::now(),
1097                duration_ms: experiment_data.total_duration.as_millis() as u64,
1098                metadata: Some(
1099                    [
1100                        (
1101                            "type".to_string(),
1102                            "experiment_data_collection".to_string(),
1103                        ),
1104                        (
1105                            "pre_duration_ms".to_string(),
1106                            pre_duration.as_millis().to_string(),
1107                        ),
1108                        (
1109                            "post_duration_ms".to_string(),
1110                            post_duration.as_millis().to_string(),
1111                        ),
1112                        (
1113                            "signal_frame_count".to_string(),
1114                            experiment_data.signal_frames.len().to_string(),
1115                        ),
1116                    ]
1117                    .into_iter()
1118                    .collect(),
1119                ),
1120            };
1121
1122            if let Err(log_error) =
1123                self.action_logger.as_mut().unwrap().add(log_entry)
1124            {
1125                log::warn!("Failed to log experiment data: {}", log_error);
1126            }
1127        }
1128
1129        Ok(experiment_data)
1130    }
1131
1132    /// Convenience method for bias pulse with data collection
1133    ///
1134    /// # Arguments
1135    /// * `pulse_voltage` - Bias voltage for the pulse (V)
1136    /// * `pulse_duration` - Duration of the pulse
1137    /// * `pre_duration` - Data collection before pulse
1138    /// * `post_duration` - Data collection after pulse
1139    ///
1140    /// # Returns
1141    /// ExperimentData with pulse results and synchronized signal data
1142    pub fn pulse_with_data_collection(
1143        &mut self,
1144        pulse_voltage: f32,
1145        pulse_duration: Duration,
1146        pre_duration: Duration,
1147        post_duration: Duration,
1148    ) -> Result<crate::types::ExperimentData, NanonisError> {
1149        self.execute_with_data_collection(
1150            Action::BiasPulse {
1151                wait_until_done: true,
1152                bias_value_v: pulse_voltage,
1153                pulse_width: pulse_duration,
1154                z_controller_hold: crate::types::ZControllerHold::Hold as u16,
1155                pulse_mode: crate::types::PulseMode::Absolute as u16,
1156            },
1157            pre_duration,
1158            post_duration,
1159        )
1160    }
1161
1162    /// Get current buffer statistics if buffering is active
1163    ///
1164    /// # Returns
1165    /// Optional tuple of (current_count, max_capacity, time_span) or None if no buffering
1166    ///
1167    /// # Usage
1168    /// Monitor buffer health, detect overruns, check data collection status
1169    pub fn tcp_buffer_stats(&self) -> Option<(usize, usize, Duration)> {
1170        self.tcp_reader.as_ref().map(|reader| reader.buffer_stats())
1171    }
1172
1173    /// Stop TCP buffering and return final buffer state
1174    ///
1175    /// # Returns
1176    /// Vector containing all buffered data, or empty if buffering wasn't active
1177    ///
1178    /// # Usage
1179    /// Optional manual cleanup - this happens automatically via Drop trait.
1180    /// Call this only if you need to access the final buffered data before ActionDriver is dropped.
1181    pub fn stop_tcp_buffering(
1182        &mut self,
1183    ) -> Result<Vec<crate::types::TimestampedSignalFrame>, NanonisError> {
1184        if let Some(mut reader) = self.tcp_reader.take() {
1185            let final_data = reader.get_all_data();
1186            reader.stop()?;
1187            log::info!(
1188                "Manually stopped TCP buffering, collected {} frames",
1189                final_data.len()
1190            );
1191            Ok(final_data)
1192        } else {
1193            Ok(Vec::new())
1194        }
1195    }
1196
1197    /// Execute action chain with time-windowed data collection
1198    ///
1199    /// This executes a sequence of actions while continuously collecting signal data,
1200    /// providing precise timing information for each action in the chain.
1201    ///
1202    /// # Arguments
1203    /// * `actions` - Vector of actions to execute in sequence
1204    /// * `pre_duration` - How much data to collect before chain starts
1205    /// * `post_duration` - How much data to collect after chain ends
1206    ///
1207    /// # Returns
1208    /// ChainExperimentData containing results and timing for each action plus synchronized signal data
1209    ///
1210    /// # Errors
1211    /// Returns error if buffering is not active or any action execution fails
1212    pub fn execute_chain_with_data_collection(
1213        &mut self,
1214        actions: Vec<Action>,
1215        pre_duration: Duration,
1216        post_duration: Duration,
1217    ) -> Result<crate::types::ChainExperimentData, NanonisError> {
1218        if self.tcp_reader.is_none() {
1219            return Err(NanonisError::Protocol(
1220                "TCP buffering not active".to_string(),
1221            ));
1222        }
1223
1224        let chain_start = Instant::now();
1225        let mut action_results = Vec::with_capacity(actions.len());
1226        let mut action_timings = Vec::with_capacity(actions.len());
1227
1228        // Execute each action and track timing
1229        for action in actions {
1230            let action_start = Instant::now();
1231            let action_result = self.execute(action)?;
1232            let action_end = Instant::now();
1233
1234            action_results.push(action_result);
1235            action_timings.push((action_start, action_end));
1236        }
1237
1238        let chain_end = Instant::now();
1239
1240        // Wait for post-chain data to be collected
1241        std::thread::sleep(post_duration);
1242
1243        // Query buffered data for the entire time window
1244        let window_start = chain_start - pre_duration;
1245        let window_end = chain_end + post_duration;
1246
1247        let signal_frames = self
1248            .tcp_reader
1249            .as_ref()
1250            .unwrap()
1251            .get_data_between(window_start, window_end);
1252        let tcp_config = self.tcp_reader_config.as_ref().unwrap().clone();
1253
1254        let chain_experiment_data = crate::types::ChainExperimentData {
1255            action_results,
1256            signal_frames,
1257            tcp_config,
1258            action_timings,
1259            chain_start,
1260            chain_end,
1261            total_duration: chain_end.duration_since(chain_start),
1262        };
1263
1264        // Log the complete chain experiment data if logging is enabled
1265        if self.action_logging_enabled && self.action_logger.is_some() {
1266            let log_entry = ActionLogEntry {
1267                action: format!(
1268                    "Chain Data Collection: {} actions",
1269                    chain_experiment_data.action_results.len()
1270                ),
1271                result: ActionLogResult::from_chain_experiment_data(
1272                    &chain_experiment_data,
1273                ),
1274                start_time: chrono::Utc::now(),
1275                duration_ms: chain_experiment_data.total_duration.as_millis()
1276                    as u64,
1277                metadata: Some(
1278                    [
1279                        (
1280                            "type".to_string(),
1281                            "chain_experiment_data_collection".to_string(),
1282                        ),
1283                        (
1284                            "pre_duration_ms".to_string(),
1285                            pre_duration.as_millis().to_string(),
1286                        ),
1287                        (
1288                            "post_duration_ms".to_string(),
1289                            post_duration.as_millis().to_string(),
1290                        ),
1291                        (
1292                            "action_count".to_string(),
1293                            chain_experiment_data
1294                                .action_results
1295                                .len()
1296                                .to_string(),
1297                        ),
1298                        (
1299                            "signal_frame_count".to_string(),
1300                            chain_experiment_data
1301                                .signal_frames
1302                                .len()
1303                                .to_string(),
1304                        ),
1305                    ]
1306                    .into_iter()
1307                    .collect(),
1308                ),
1309            };
1310
1311            if let Err(log_error) =
1312                self.action_logger.as_mut().unwrap().add(log_entry)
1313            {
1314                log::warn!(
1315                    "Failed to log chain experiment data: {}",
1316                    log_error
1317                );
1318            }
1319        }
1320
1321        Ok(chain_experiment_data)
1322    }
1323
1324    /// Start TCP logger
1325    pub fn start_tcp_logger(&mut self) -> Result<(), NanonisError> {
1326        self.client.tcplog_start()
1327    }
1328
1329    /// Stop TCP logger
1330    pub fn stop_tcp_logger(&mut self) -> Result<(), NanonisError> {
1331        self.client.tcplog_stop()
1332    }
1333
1334    /// Configure TCP logger channels
1335    pub fn set_tcp_logger_channels(
1336        &mut self,
1337        channels: Vec<i32>,
1338    ) -> Result<(), NanonisError> {
1339        self.client.tcplog_chs_set(channels)
1340    }
1341
1342    /// Set TCP logger oversampling
1343    pub fn set_tcp_logger_oversampling(
1344        &mut self,
1345        oversampling: i32,
1346    ) -> Result<(), NanonisError> {
1347        self.client.tcplog_oversampl_set(oversampling)
1348    }
1349
1350    /// Get TCP logger status
1351    pub fn get_tcp_logger_status(
1352        &mut self,
1353    ) -> Result<crate::types::TCPLogStatus, NanonisError> {
1354        self.client.tcplog_status_get()
1355    }
1356
1357    /// Execute a single action
1358    pub fn execute(
1359        &mut self,
1360        action: Action,
1361    ) -> Result<ActionResult, NanonisError> {
1362        let start_time = chrono::Utc::now();
1363        let start_instant = std::time::Instant::now();
1364
1365        let result = self.execute_internal(action.clone());
1366
1367        let duration = start_instant.elapsed();
1368
1369        // Log the action execution if logging is enabled
1370        if self.action_logging_enabled && self.action_logger.is_some() {
1371            let log_entry = match &result {
1372                Ok(action_result) => ActionLogEntry::new(
1373                    &action,
1374                    action_result,
1375                    start_time,
1376                    duration,
1377                ),
1378                Err(error) => ActionLogEntry::new_error(
1379                    &action, error, start_time, duration,
1380                ),
1381            };
1382
1383            if let Err(log_error) =
1384                self.action_logger.as_mut().unwrap().add(log_entry)
1385            {
1386                log::warn!("Failed to log action: {}", log_error);
1387            }
1388        }
1389
1390        result
1391    }
1392
1393    /// Execute action with optional data collection (unified interface)
1394    ///
1395    /// This provides a single interface for both normal execution and data collection.
1396    /// When data_collection is true, this method collects TCP signal data alongside action execution.
1397    ///
1398    /// # Arguments
1399    /// * `action` - The action to execute
1400    /// * `data_collection` - If true, collect TCP signal data (requires TCP reader to be active)
1401    /// * `pre_duration` - How much data to collect before action (only used if data_collection=true)
1402    /// * `post_duration` - How much data to collect after action (only used if data_collection=true)
1403    ///
1404    /// # Returns
1405    /// ActionResult for normal execution, or ActionResult::ExperimentData for data collection
1406    ///
1407    /// # Usage
1408    /// ```rust,ignore
1409    /// // Normal execution
1410    /// let result = driver.execute_with_options(action, false, Duration::ZERO, Duration::ZERO)?;
1411    ///
1412    /// // With data collection
1413    /// let result = driver.execute_with_options(action, true, Duration::from_millis(100), Duration::from_millis(200))?;
1414    /// ```
1415    pub fn execute_with_options(
1416        &mut self,
1417        action: Action,
1418        data_collection: bool,
1419        pre_duration: Duration,
1420        post_duration: Duration,
1421    ) -> Result<ActionResult, NanonisError> {
1422        if data_collection && self.tcp_reader.is_some() {
1423            // Use data collection execution
1424            let _experiment_data = self.execute_with_data_collection(
1425                action,
1426                pre_duration,
1427                post_duration,
1428            )?;
1429            // Convert ExperimentData to ActionResult for unified return type
1430            Ok(ActionResult::Success) // For now, return Success - could extend ActionResult to include ExperimentData
1431        } else {
1432            // Use normal execution
1433            self.execute(action)
1434        }
1435    }
1436
1437    /// Execute chain with optional data collection (unified interface)
1438    ///
1439    /// # Arguments
1440    /// * `chain` - The action chain to execute
1441    /// * `data_collection` - If true, collect TCP signal data for the entire chain
1442    /// * `pre_duration` - How much data to collect before chain starts
1443    /// * `post_duration` - How much data to collect after chain ends
1444    ///
1445    /// # Returns
1446    /// Vector of ActionResults
1447    pub fn execute_chain_with_options(
1448        &mut self,
1449        chain: impl Into<ActionChain>,
1450        data_collection: bool,
1451        pre_duration: Duration,
1452        post_duration: Duration,
1453    ) -> Result<Vec<ActionResult>, NanonisError> {
1454        if data_collection && self.tcp_reader.is_some() {
1455            // Use data collection execution
1456            let chain_experiment_data = self
1457                .execute_chain_with_data_collection(
1458                    chain.into().into_iter().collect(),
1459                    pre_duration,
1460                    post_duration,
1461                )?;
1462            // Return the action results from the chain
1463            Ok(chain_experiment_data.action_results)
1464        } else {
1465            // Use normal execution
1466            self.execute_chain(chain)
1467        }
1468    }
1469
1470    /// Internal execute method without logging (for performance-critical chains)
1471    fn execute_internal(
1472        &mut self,
1473        action: Action,
1474    ) -> Result<ActionResult, NanonisError> {
1475        match action {
1476            // === Signal Operations ===
1477            Action::ReadSignal {
1478                signal,
1479                wait_for_newest,
1480            } => {
1481                let value = self.client.signals_vals_get(
1482                    vec![SignalIndex::new(signal.index).into()],
1483                    wait_for_newest,
1484                )?;
1485                Ok(ActionResult::Value(value[0] as f64))
1486            }
1487
1488            Action::ReadSignals {
1489                signals,
1490                wait_for_newest,
1491            } => {
1492                let indices: Vec<i32> = signals
1493                    .iter()
1494                    .map(|s| SignalIndex::new(s.index).into())
1495                    .collect();
1496                let values =
1497                    self.client.signals_vals_get(indices, wait_for_newest)?;
1498                Ok(ActionResult::Values(
1499                    values.into_iter().map(|v| v as f64).collect(),
1500                ))
1501            }
1502
1503            Action::ReadSignalNames => {
1504                let names = self.client.signal_names_get()?;
1505                Ok(ActionResult::Text(names))
1506            }
1507
1508            // === Bias Operations ===
1509            Action::ReadBias => {
1510                let bias = self.client.bias_get()?;
1511                Ok(ActionResult::Value(bias as f64))
1512            }
1513
1514            Action::SetBias { voltage } => {
1515                self.client.bias_set(voltage)?;
1516                Ok(ActionResult::Success)
1517            }
1518
1519            // === Oscilloscope Operations ===
1520            Action::ReadOsci {
1521                signal,
1522                trigger,
1523                data_to_get,
1524                is_stable,
1525            } => {
1526                self.client.osci1t_run()?;
1527
1528                self.client.osci1t_ch_set(signal.index as i32)?;
1529
1530                if let Some(trigger) = trigger {
1531                    self.client.osci1t_trig_set(
1532                        trigger.mode.into(),
1533                        trigger.slope.into(),
1534                        trigger.level,
1535                        trigger.hysteresis,
1536                    )?;
1537                }
1538
1539                match data_to_get {
1540                    crate::types::DataToGet::Stable { readings, timeout } => {
1541                        let osci_data = self
1542                            .find_stable_oscilloscope_data_with_fallback(
1543                                data_to_get,
1544                                readings,
1545                                timeout,
1546                                0.01,
1547                                50e-15,
1548                                0.8,
1549                                is_stable,
1550                            )?;
1551                        Ok(ActionResult::OsciData(osci_data))
1552                    }
1553                    _ => {
1554                        // Use NextTrigger for actual data reading - Stable is just for our algorithm
1555                        let data_mode = match data_to_get {
1556                            DataToGet::Current => 0,
1557                            DataToGet::NextTrigger => 1,
1558                            DataToGet::Wait2Triggers => 2,
1559                            DataToGet::Stable { .. } => 1, // Use NextTrigger for stable
1560                        };
1561                        let (t0, dt, size, data) =
1562                            self.client.osci1t_data_get(data_mode)?;
1563                        let osci_data =
1564                            OsciData::new_stable(t0, dt, size, data);
1565                        Ok(ActionResult::OsciData(osci_data))
1566                    }
1567                }
1568            }
1569
1570            // === Fine Positioning Operations (Piezo) ===
1571            Action::ReadPiezoPosition {
1572                wait_for_newest_data,
1573            } => {
1574                let pos = self.client.folme_xy_pos_get(wait_for_newest_data)?;
1575                Ok(ActionResult::Position(pos))
1576            }
1577
1578            Action::SetPiezoPosition {
1579                position,
1580                wait_until_finished,
1581            } => {
1582                self.client
1583                    .folme_xy_pos_set(position, wait_until_finished)?;
1584                Ok(ActionResult::Success)
1585            }
1586
1587            Action::MovePiezoRelative { delta } => {
1588                // Get current position and add delta
1589                let current = self.client.folme_xy_pos_get(true)?;
1590                info!("Current position: {current:?}");
1591                let new_position = Position {
1592                    x: current.x + delta.x,
1593                    y: current.y + delta.y,
1594                };
1595                self.client.folme_xy_pos_set(new_position, true)?;
1596                Ok(ActionResult::Success)
1597            }
1598
1599            // === Coarse Positioning Operations (Motor) ===
1600            Action::MoveMotorAxis {
1601                direction,
1602                steps,
1603                blocking,
1604            } => {
1605                self.client.motor_start_move(
1606                    direction,
1607                    steps,
1608                    MotorGroup::Group1,
1609                    blocking,
1610                )?;
1611                Ok(ActionResult::Success)
1612            }
1613
1614            Action::MoveMotor3D {
1615                displacement,
1616                blocking,
1617            } => {
1618                // Convert 3D displacement to sequence of motor movements
1619                let movements = displacement.to_motor_movements();
1620
1621                // Execute each movement in sequence
1622                for (direction, steps) in movements {
1623                    self.client.motor_start_move(
1624                        direction,
1625                        steps,
1626                        MotorGroup::Group1,
1627                        blocking,
1628                    )?;
1629                }
1630                Ok(ActionResult::Success)
1631            }
1632
1633            Action::MoveMotorClosedLoop { target, mode } => {
1634                self.client.motor_start_closed_loop(
1635                    mode,
1636                    target,
1637                    true, // wait_until_finished
1638                    MotorGroup::Group1,
1639                )?;
1640                Ok(ActionResult::Success)
1641            }
1642
1643            Action::StopMotor => {
1644                self.client.motor_stop_move()?;
1645                Ok(ActionResult::Success)
1646            }
1647
1648            // === Control Operations ===
1649            Action::AutoApproach {
1650                wait_until_finished,
1651                timeout,
1652                center_freq_shift,
1653            } => {
1654                log::debug!(
1655                    "Starting auto-approach (wait: {}, timeout: {:?}, center_freq: {})",
1656                    wait_until_finished,
1657                    timeout,
1658                    center_freq_shift
1659                );
1660
1661                // Center frequency shift if requested
1662                if center_freq_shift {
1663                    // Approach to the surface
1664                    self.auto_approach(true, timeout)?;
1665
1666                    // Sleep for 0.2 secs
1667                    std::thread::sleep(Duration::from_millis(200));
1668
1669                    // Toggle on the safe tip
1670                    if let Ok(safetip_state) =
1671                        self.client_mut().safe_tip_on_off_get()
1672                    {
1673                        if !safetip_state {
1674                            self.client_mut().safe_tip_on_off_set(true)?;
1675                        }
1676                    } else {
1677                        log::warn!(
1678                            "Failed to read safe tip state, setting true"
1679                        );
1680                        self.client_mut().safe_tip_on_off_set(true)?;
1681                    }
1682
1683                    self.check_safetip_status("after enabling safe tip")?;
1684
1685                    // Home 50nm away from the surface
1686                    self.client_mut().z_ctrl_home()?;
1687
1688                    self.check_safetip_status("after z_ctrl_home")?;
1689
1690                    // Sleep for 0.5 secs
1691                    std::thread::sleep(Duration::from_millis(500));
1692
1693                    self.check_safetip_status("after 500ms settle")?;
1694
1695                    // Center the freq shift
1696                    if let Err(e) = self.center_freq_shift() {
1697                        log::warn!("Failed to center frequency shift: {}", e);
1698                        // Continue anyway, this is not critical
1699                    }
1700
1701                    self.check_safetip_status("after center_freq_shift")?;
1702
1703                    // Approach again
1704                    self.auto_approach(wait_until_finished, timeout)?;
1705
1706                    self.check_safetip_status("after final auto_approach")?;
1707
1708                    // Toggle of the safe tip
1709                    if let Ok(safetip_state) =
1710                        self.client_mut().safe_tip_on_off_get()
1711                    {
1712                        if safetip_state {
1713                            self.client_mut().safe_tip_on_off_set(false)?;
1714                        }
1715                    } else {
1716                        log::warn!(
1717                            "Failed to read safe tip state, setting false"
1718                        );
1719                        self.client_mut().safe_tip_on_off_set(false)?;
1720                    }
1721                } else {
1722                    self.auto_approach(wait_until_finished, timeout)?;
1723                }
1724
1725                Ok(ActionResult::Success)
1726            }
1727
1728            Action::Withdraw {
1729                wait_until_finished,
1730                timeout,
1731            } => {
1732                self.client.z_ctrl_withdraw(wait_until_finished, timeout)?;
1733                Ok(ActionResult::Success)
1734            }
1735
1736            Action::SafeReposition { x_steps, y_steps } => {
1737                // Safe repositioning with hardcoded defaults
1738                let displacement =
1739                    crate::types::MotorDisplacement::new(x_steps, y_steps, -3);
1740                let withdraw_timeout = Duration::from_secs(5);
1741                let approach_timeout = Duration::from_secs(10);
1742                let stabilization_wait = Duration::from_millis(500);
1743
1744                // Execute the safe repositioning sequence
1745                // 1. Withdraw
1746                self.client.z_ctrl_withdraw(true, withdraw_timeout)?;
1747
1748                // 2. Move motor 3D (using the same logic as MoveMotor3D)
1749                let movements = displacement.to_motor_movements();
1750                for (direction, steps) in movements {
1751                    self.client.motor_start_move(
1752                        direction,
1753                        steps,
1754                        MotorGroup::Group1,
1755                        true,
1756                    )?;
1757                }
1758
1759                thread::sleep(Duration::from_millis(500));
1760
1761                // 3. Center frequency and auto approach
1762                self.run(Action::AutoApproach {
1763                    wait_until_finished: true,
1764                    timeout: approach_timeout,
1765                    center_freq_shift: true,
1766                })
1767                .go()?;
1768
1769                // 4. Wait for stabilization
1770                thread::sleep(stabilization_wait);
1771
1772                Ok(ActionResult::Success)
1773            }
1774
1775            Action::SetZSetpoint { setpoint } => {
1776                self.client.z_ctrl_setpoint_set(setpoint)?;
1777                Ok(ActionResult::Success)
1778            }
1779
1780            // === Scan Operations ===
1781            Action::ScanControl { action } => {
1782                self.client.scan_action(action, ScanDirection::Up)?;
1783                Ok(ActionResult::Success)
1784            }
1785
1786            Action::ReadScanStatus => {
1787                let is_scanning = self.client.scan_status_get()?;
1788                Ok(ActionResult::Status(is_scanning))
1789            }
1790
1791            // === Advanced Operations ===
1792            Action::BiasPulse {
1793                wait_until_done,
1794                pulse_width,
1795                bias_value_v,
1796                z_controller_hold,
1797                pulse_mode,
1798            } => {
1799                // Convert u16 parameters to enums (safe conversion with fallback)
1800                let hold_enum = match z_controller_hold {
1801                    0 => ZControllerHold::NoChange,
1802                    1 => ZControllerHold::Hold,
1803                    2 => ZControllerHold::Release,
1804                    _ => ZControllerHold::NoChange, // Safe fallback
1805                };
1806
1807                let mode_enum = match pulse_mode {
1808                    0 => PulseMode::Keep,
1809                    1 => PulseMode::Relative,
1810                    2 => PulseMode::Absolute,
1811                    _ => PulseMode::Keep, // Safe fallback
1812                };
1813
1814                self.client.bias_pulse(
1815                    wait_until_done,
1816                    pulse_width.as_secs_f32(),
1817                    bias_value_v,
1818                    hold_enum.into(),
1819                    mode_enum.into(),
1820                )?;
1821
1822                Ok(ActionResult::Success)
1823            }
1824
1825            Action::TipShaper {
1826                config,
1827                wait_until_finished,
1828                timeout,
1829            } => {
1830                // Set tip shaper configuration
1831                self.client.tip_shaper_props_set(config)?;
1832
1833                // Start tip shaper
1834                self.client.tip_shaper_start(wait_until_finished, timeout)?;
1835
1836                Ok(ActionResult::Success)
1837            }
1838
1839            Action::PulseRetract {
1840                pulse_width,
1841                pulse_height_v,
1842            } => {
1843                let current_bias =
1844                    self.client_mut().bias_get().unwrap_or(500e-3);
1845
1846                let config = TipShaperConfig {
1847                    switch_off_delay: std::time::Duration::from_millis(10),
1848                    change_bias: true,
1849                    bias_v: pulse_height_v,
1850                    tip_lift_m: 0.0,
1851                    lift_time_1: pulse_width,
1852                    bias_lift_v: current_bias,
1853                    bias_settling_time: std::time::Duration::from_millis(50),
1854                    lift_height_m: 100e-9,
1855                    lift_time_2: std::time::Duration::from_millis(100),
1856                    end_wait_time: std::time::Duration::from_millis(50),
1857                    restore_feedback: false,
1858                };
1859
1860                // Set tip shaper configuration and start
1861                self.client_mut().tip_shaper_props_set(config)?;
1862                self.client_mut()
1863                    .tip_shaper_start(true, Duration::from_secs(5))?;
1864
1865                Ok(ActionResult::Success)
1866            }
1867
1868            Action::Wait { duration } => {
1869                thread::sleep(duration);
1870                Ok(ActionResult::None)
1871            }
1872
1873            // === Data Management ===
1874            Action::Store { key, action } => {
1875                let result = self.execute(*action)?;
1876                self.stored_values.insert(key, result.clone());
1877                Ok(result) // Return the original result directly
1878            }
1879
1880            Action::Retrieve { key } => match self.stored_values.get(&key) {
1881                Some(value) => Ok(value.clone()), // Return the stored result directly
1882                None => Err(NanonisError::Protocol(format!(
1883                    "No stored value found for key: {}",
1884                    key
1885                ))),
1886            },
1887
1888            // === TCP Logger Operations ===
1889            Action::StartTCPLogger => {
1890                self.start_tcp_logger()?;
1891                Ok(ActionResult::Success)
1892            }
1893
1894            Action::StopTCPLogger => {
1895                self.stop_tcp_logger()?;
1896                Ok(ActionResult::Success)
1897            }
1898
1899            Action::GetTCPLoggerStatus => {
1900                use crate::actions::TCPReaderStatus;
1901                let status = self.get_tcp_logger_status()?;
1902                let config = self.tcp_reader_config();
1903
1904                Ok(ActionResult::TCPReaderStatus(TCPReaderStatus {
1905                    status,
1906                    channels: config
1907                        .map(|c| c.channels.clone())
1908                        .unwrap_or_default(),
1909                    oversampling: config.map(|c| c.oversampling).unwrap_or(0),
1910                }))
1911            }
1912
1913            Action::ConfigureTCPLogger {
1914                channels,
1915                oversampling,
1916            } => {
1917                self.set_tcp_logger_channels(channels)?;
1918                self.set_tcp_logger_oversampling(oversampling)?;
1919                Ok(ActionResult::Success)
1920            }
1921
1922            Action::CheckTipState { method } => {
1923                use std::collections::HashMap;
1924
1925                use crate::{
1926                    actions::{TipCheckMethod, TipState},
1927                    types::TipShape,
1928                };
1929
1930                let (tip_shape, measured_signals, mut metadata) = match method {
1931                    TipCheckMethod::SignalBounds { signal, bounds } => {
1932                        // Debug TCP logger status before calling ReadStableSignal
1933                        if let Some(ref tcp_reader) = self.tcp_reader {
1934                            let (frame_count, _max_capacity, time_span) =
1935                                tcp_reader.buffer_stats();
1936                            log::debug!("CheckTipState: TCP reader available with {} frames, timespan: {}ms", 
1937                                frame_count, time_span.as_millis());
1938                        } else {
1939                            log::warn!(
1940                                "CheckTipState: No TCP reader available for signal {}",
1941                                signal.index
1942                                );
1943                        }
1944
1945                        // Use ReadStableSignal instead of single instantaneous read
1946                        log::debug!(
1947                            "CheckTipState: Calling ReadStableSignal for signal {}",
1948                            signal.index
1949                        );
1950
1951                        // Calculate samples needed for configured data collection duration
1952                        let data_points = self
1953                            .calculate_samples_for_duration(
1954                                Duration::from_millis(
1955                                    TIP_STATE_DATA_COLLECTION_DURATION_MS,
1956                                ),
1957                            )
1958                            .unwrap_or(100); // Fallback to 100 if TCP not configured
1959
1960                        let stable_result = self
1961                            .run(Action::ReadStableSignal {
1962                                signal: signal.clone(),
1963                                data_points: Some(data_points),
1964                                use_new_data: true, // Get fresh data for tip state checking
1965                                stability_method: crate::actions::SignalStabilityMethod::Combined {
1966                                    max_std_dev: TIP_STATE_MAX_STD_DEV,
1967                                    max_slope: TIP_STATE_MAX_SLOPE,
1968                                },
1969                                timeout: Duration::from_secs(TIP_STATE_READ_TIMEOUT_SECS),
1970                                retry_count: Some(TIP_STATE_READ_RETRY_COUNT),
1971                            })
1972                            .execute();
1973
1974                        let (value, raw_data, read_method) = match stable_result
1975                        {
1976                            Ok(exec_result) => match exec_result {
1977                                ExecutionResult::Single(
1978                                    ActionResult::StableSignal(stable_signal),
1979                                ) => {
1980                                    // Use stable value from ReadStableSignal
1981                                    log::debug!("CheckTipState: ReadStableSignal succeeded with {} data points", stable_signal.raw_data.len());
1982                                    (
1983                                        stable_signal.stable_value,
1984                                        stable_signal.raw_data,
1985                                        "stable_signal",
1986                                    )
1987                                }
1988                                ExecutionResult::Single(
1989                                    ActionResult::Values(values),
1990                                ) => {
1991                                    // ReadStableSignal failed but returned raw data, use minimum as fallback
1992                                    log::warn!("CheckTipState: ReadStableSignal failed but returned {} raw values, using minimum as fallback", values.len());
1993                                    let raw_data: Vec<f32> = values
1994                                        .iter()
1995                                        .map(|&v| v as f32)
1996                                        .collect();
1997                                    let min_value = raw_data
1998                                        .iter()
1999                                        .cloned()
2000                                        .fold(f32::INFINITY, f32::min);
2001                                    (min_value, raw_data, "fallback_minimum")
2002                                }
2003                                _ => {
2004                                    // Unexpected result type, fallback to single read
2005                                    log::warn!("CheckTipState: ReadStableSignal returned unexpected result type, falling back to single read");
2006                                    let single_value = self
2007                                        .client
2008                                        .signal_val_get(signal.index, true)?;
2009                                    (
2010                                        single_value,
2011                                        vec![single_value],
2012                                        "single_read_fallback",
2013                                    )
2014                                }
2015                            },
2016                            Err(e) => {
2017                                // Complete fallback to single read
2018                                log::warn!("CheckTipState: ReadStableSignal failed with error: {}, falling back to single read", e);
2019                                let single_value = self
2020                                    .client
2021                                    .signal_val_get(signal.index, true)?;
2022                                (
2023                                    single_value,
2024                                    vec![single_value],
2025                                    "single_read_fallback",
2026                                )
2027                            }
2028                        };
2029
2030                        let mut measured = HashMap::new();
2031                        measured.insert(SignalIndex::new(signal.index), value);
2032
2033                        let shape = if value >= bounds.0 && value <= bounds.1 {
2034                            TipShape::Sharp
2035                        } else {
2036                            TipShape::Blunt
2037                        };
2038
2039                        // Populate metadata with analysis context and dataset
2040                        let bounds_center = (bounds.0 + bounds.1) / 2.0;
2041                        let bounds_width = (bounds.1 - bounds.0).abs();
2042                        let distance_from_center =
2043                            (value - bounds_center).abs();
2044                        let relative_distance = if bounds_width > 0.0 {
2045                            distance_from_center / (bounds_width / 2.0)
2046                        } else {
2047                            0.0
2048                        };
2049                        let mut metadata = HashMap::new();
2050                        metadata.insert(
2051                            "method".to_string(),
2052                            "signal_bounds".to_string(),
2053                        );
2054                        metadata.insert(
2055                            "signal_index".to_string(),
2056                            signal.index.to_string(),
2057                        );
2058                        metadata.insert(
2059                            "measured_value".to_string(),
2060                            format!("{:.6e}", value),
2061                        );
2062                        metadata.insert(
2063                            "bounds_lower".to_string(),
2064                            format!("{:.6e}", bounds.0),
2065                        );
2066                        metadata.insert(
2067                            "bounds_upper".to_string(),
2068                            format!("{:.6e}", bounds.1),
2069                        );
2070                        metadata.insert(
2071                            "bounds_center".to_string(),
2072                            format!("{:.6e}", bounds_center),
2073                        );
2074                        metadata.insert(
2075                            "bounds_width".to_string(),
2076                            format!("{:.6e}", bounds_width),
2077                        );
2078                        metadata.insert(
2079                            "distance_from_center".to_string(),
2080                            format!("{:.6e}", distance_from_center),
2081                        );
2082                        metadata.insert(
2083                            "relative_distance".to_string(),
2084                            format!("{:.3}", relative_distance),
2085                        );
2086                        metadata.insert(
2087                            "within_bounds".to_string(),
2088                            (shape == TipShape::Sharp).to_string(),
2089                        );
2090                        metadata.insert(
2091                            "read_method".to_string(),
2092                            read_method.to_string(),
2093                        );
2094                        metadata.insert(
2095                            "dataset_size".to_string(),
2096                            raw_data.len().to_string(),
2097                        );
2098
2099                        // Store raw dataset for debugging stability measures
2100                        let raw_data_summary = if raw_data.len() <= 10 {
2101                            raw_data
2102                                .iter()
2103                                .map(|x| format!("{:.3e}", x))
2104                                .collect::<Vec<_>>()
2105                                .join(",")
2106                        } else {
2107                            let first_5: String = raw_data
2108                                .iter()
2109                                .take(5)
2110                                .map(|x| format!("{:.3e}", x))
2111                                .collect::<Vec<_>>()
2112                                .join(",");
2113                            let last_5: String = raw_data
2114                                .iter()
2115                                .rev()
2116                                .take(5)
2117                                .rev()
2118                                .map(|x| format!("{:.3e}", x))
2119                                .collect::<Vec<_>>()
2120                                .join(",");
2121                            format!("{},...,{}", first_5, last_5)
2122                        };
2123                        metadata.insert(
2124                            "raw_dataset_summary".to_string(),
2125                            format!("[{}]", raw_data_summary),
2126                        );
2127
2128                        if shape == TipShape::Blunt {
2129                            let margin_violation = if value < bounds.0 {
2130                                bounds.0 - value
2131                            } else {
2132                                value - bounds.1
2133                            };
2134                            metadata.insert(
2135                                "margin_violation".to_string(),
2136                                format!("{:.6e}", margin_violation),
2137                            );
2138                            metadata.insert(
2139                                "violation_direction".to_string(),
2140                                if value < bounds.0 {
2141                                    "below_lower_bound".to_string()
2142                                } else {
2143                                    "above_upper_bound".to_string()
2144                                },
2145                            );
2146                        }
2147
2148                        (shape, measured, metadata)
2149                    }
2150
2151                    TipCheckMethod::MultiSignalBounds { ref signals } => {
2152                        let mut measured = HashMap::new();
2153                        let mut violations = Vec::new();
2154                        let mut all_good = true;
2155                        let mut all_datasets = Vec::new();
2156                        let mut read_methods = Vec::new();
2157
2158                        // Calculate samples needed for configured data collection duration
2159                        let data_points = self
2160                            .calculate_samples_for_duration(
2161                                Duration::from_millis(
2162                                    TIP_STATE_DATA_COLLECTION_DURATION_MS,
2163                                ),
2164                            )
2165                            .unwrap_or(100); // Fallback to 100 if TCP not configured
2166
2167                        // Read each signal using ReadStableSignal
2168                        for (signal, bounds) in signals.iter() {
2169                            let stable_result = self
2170                                .run(Action::ReadStableSignal {
2171                                    signal: signal.clone(),
2172                                    data_points: Some(data_points),
2173                                    use_new_data: true, // Get fresh data for tip state checking
2174                                    stability_method:
2175                                        crate::actions::SignalStabilityMethod::Combined {
2176                                            max_std_dev: TIP_STATE_MAX_STD_DEV,
2177                                            max_slope: TIP_STATE_MAX_SLOPE,
2178                                        },
2179                                    timeout: Duration::from_secs(TIP_STATE_READ_TIMEOUT_SECS),
2180                                    retry_count: Some(TIP_STATE_READ_RETRY_COUNT),
2181                                })
2182                                .execute();
2183
2184                            let (value, raw_data, read_method) =
2185                                match stable_result {
2186                                    Ok(exec_result) => match exec_result {
2187                                        ExecutionResult::Single(
2188                                            ActionResult::StableSignal(
2189                                                stable_signal,
2190                                            ),
2191                                        ) => (
2192                                            stable_signal.stable_value,
2193                                            stable_signal.raw_data,
2194                                            "stable_signal",
2195                                        ),
2196                                        ExecutionResult::Single(
2197                                            ActionResult::Values(values),
2198                                        ) => {
2199                                            let raw_data: Vec<f32> = values
2200                                                .iter()
2201                                                .map(|&v| v as f32)
2202                                                .collect();
2203                                            let min_value = raw_data
2204                                                .iter()
2205                                                .cloned()
2206                                                .fold(f32::INFINITY, f32::min);
2207                                            (
2208                                                min_value,
2209                                                raw_data,
2210                                                "fallback_minimum",
2211                                            )
2212                                        }
2213                                        _ => {
2214                                            let single_value =
2215                                                self.client.signal_val_get(
2216                                                    signal.index,
2217                                                    true,
2218                                                )?;
2219                                            (
2220                                                single_value,
2221                                                vec![single_value],
2222                                                "single_read_fallback",
2223                                            )
2224                                        }
2225                                    },
2226                                    Err(_) => {
2227                                        let single_value =
2228                                            self.client.signal_val_get(
2229                                                signal.index,
2230                                                true,
2231                                            )?;
2232                                        (
2233                                            single_value,
2234                                            vec![single_value],
2235                                            "single_read_fallback",
2236                                        )
2237                                    }
2238                                };
2239
2240                            measured
2241                                .insert(SignalIndex::new(signal.index), value);
2242                            all_datasets.push(raw_data);
2243                            read_methods.push(read_method);
2244
2245                            let in_bounds =
2246                                value >= bounds.0 && value <= bounds.1;
2247                            if !in_bounds {
2248                                violations.push((
2249                                    signal.clone(),
2250                                    value,
2251                                    *bounds,
2252                                ));
2253                                all_good = false;
2254                            }
2255                        }
2256
2257                        let shape = if all_good {
2258                            TipShape::Sharp
2259                        } else {
2260                            TipShape::Blunt
2261                        };
2262
2263                        // Populate metadata with multi-signal analysis and datasets
2264                        let mut metadata = HashMap::new();
2265                        metadata.insert(
2266                            "method".to_string(),
2267                            "multi_signal_bounds".to_string(),
2268                        );
2269                        metadata.insert(
2270                            "signal_count".to_string(),
2271                            signals.len().to_string(),
2272                        );
2273                        metadata.insert(
2274                            "signals_in_bounds".to_string(),
2275                            (signals.len() - violations.len()).to_string(),
2276                        );
2277                        metadata.insert(
2278                            "violation_count".to_string(),
2279                            violations.len().to_string(),
2280                        );
2281                        metadata.insert(
2282                            "overall_pass".to_string(),
2283                            all_good.to_string(),
2284                        );
2285
2286                        // Add individual signal details with datasets
2287                        for (i, ((signal, bounds), dataset)) in
2288                            signals.iter().zip(all_datasets.iter()).enumerate()
2289                        {
2290                            let prefix = format!("signal_{}", i);
2291                            let value =
2292                                measured[&SignalIndex::new(signal.index)];
2293
2294                            metadata.insert(
2295                                format!("{}_index", prefix),
2296                                signal.index.to_string(),
2297                            );
2298                            metadata.insert(
2299                                format!("{}_value", prefix),
2300                                format!("{:.6e}", value),
2301                            );
2302                            metadata.insert(
2303                                format!("{}_bounds", prefix),
2304                                format!("[{:.3e}, {:.3e}]", bounds.0, bounds.1),
2305                            );
2306                            metadata.insert(
2307                                format!("{}_in_bounds", prefix),
2308                                (value >= bounds.0 && value <= bounds.1)
2309                                    .to_string(),
2310                            );
2311                            metadata.insert(
2312                                format!("{}_read_method", prefix),
2313                                read_methods[i].to_string(),
2314                            );
2315                            metadata.insert(
2316                                format!("{}_dataset_size", prefix),
2317                                dataset.len().to_string(),
2318                            );
2319
2320                            // Store dataset summary for debugging
2321                            let dataset_summary = if dataset.len() <= 10 {
2322                                dataset
2323                                    .iter()
2324                                    .map(|x| format!("{:.3e}", x))
2325                                    .collect::<Vec<_>>()
2326                                    .join(",")
2327                            } else {
2328                                let first_3: String = dataset
2329                                    .iter()
2330                                    .take(3)
2331                                    .map(|x| format!("{:.3e}", x))
2332                                    .collect::<Vec<_>>()
2333                                    .join(",");
2334                                let last_3: String = dataset
2335                                    .iter()
2336                                    .rev()
2337                                    .take(3)
2338                                    .rev()
2339                                    .map(|x| format!("{:.3e}", x))
2340                                    .collect::<Vec<_>>()
2341                                    .join(",");
2342                                format!("{},...,{}", first_3, last_3)
2343                            };
2344                            metadata.insert(
2345                                format!("{}_dataset_summary", prefix),
2346                                format!("[{}]", dataset_summary),
2347                            );
2348                        }
2349
2350                        (shape, measured, metadata)
2351                    }
2352                };
2353
2354                // Add TCP buffer context and recent signal trends if available
2355                if let Some(ref tcp_reader) = self.tcp_reader {
2356                    let (frame_count, _max_capacity, time_span) =
2357                        tcp_reader.buffer_stats();
2358                    metadata.insert(
2359                        "tcp_buffer_frames".to_string(),
2360                        frame_count.to_string(),
2361                    );
2362                    metadata.insert(
2363                        "tcp_buffer_utilization".to_string(),
2364                        format!("{:.2}", tcp_reader.buffer_utilization()),
2365                    );
2366                    metadata.insert(
2367                        "tcp_data_timespan_ms".to_string(),
2368                        time_span.as_millis().to_string(),
2369                    );
2370                    metadata.insert(
2371                        "tcp_uptime_ms".to_string(),
2372                        tcp_reader.uptime().as_millis().to_string(),
2373                    );
2374
2375                    // Add recent signal trend analysis for correlation with stable signal data
2376                    for signal_idx in measured_signals.keys() {
2377                        if tcp_reader.frame_count() >= 20 {
2378                            // Need minimum data for trend analysis
2379                            let recent_frames =
2380                                tcp_reader.get_recent_frames(50); // Last 50 data points
2381
2382                            // Extract signal values for this specific signal from recent TCP data
2383                            let signal_values: Vec<f32> = recent_frames
2384                                .iter()
2385                                .filter_map(|frame| {
2386                                    // Find the signal in the frame data (assuming signal index maps to data array position)
2387                                    let idx = signal_idx.get() as usize;
2388                                    if idx < frame.signal_frame.data.len() {
2389                                        Some(frame.signal_frame.data[idx])
2390                                    } else {
2391                                        None
2392                                    }
2393                                })
2394                                .collect();
2395
2396                            if signal_values.len() >= 10 {
2397                                // Minimum for meaningful statistics
2398                                let mean = signal_values.iter().sum::<f32>()
2399                                    / signal_values.len() as f32;
2400                                let variance = signal_values
2401                                    .iter()
2402                                    .map(|x| (x - mean).powi(2))
2403                                    .sum::<f32>()
2404                                    / signal_values.len() as f32;
2405                                let std_dev = variance.sqrt();
2406                                let relative_std = if mean.abs() > 1e-15 {
2407                                    (std_dev / mean.abs()) * 100.0
2408                                } else {
2409                                    0.0
2410                                };
2411
2412                                // Calculate trend (simple linear regression slope)
2413                                let x_values: Vec<f32> = (0..signal_values
2414                                    .len())
2415                                    .map(|i| i as f32)
2416                                    .collect();
2417                                let x_mean = x_values.iter().sum::<f32>()
2418                                    / x_values.len() as f32;
2419                                let y_mean = mean;
2420
2421                                let numerator: f32 = x_values
2422                                    .iter()
2423                                    .zip(signal_values.iter())
2424                                    .map(|(x, y)| (x - x_mean) * (y - y_mean))
2425                                    .sum();
2426                                let denominator: f32 = x_values
2427                                    .iter()
2428                                    .map(|x| (x - x_mean).powi(2))
2429                                    .sum();
2430
2431                                let trend_slope = if denominator.abs() > 1e-15 {
2432                                    numerator / denominator
2433                                } else {
2434                                    0.0
2435                                };
2436
2437                                let signal_prefix =
2438                                    format!("tcp_signal_{}", signal_idx.get());
2439                                metadata.insert(
2440                                    format!("{}_recent_samples", signal_prefix),
2441                                    signal_values.len().to_string(),
2442                                );
2443                                metadata.insert(
2444                                    format!("{}_recent_mean", signal_prefix),
2445                                    format!("{:.6e}", mean),
2446                                );
2447                                metadata.insert(
2448                                    format!("{}_recent_std", signal_prefix),
2449                                    format!("{:.6e}", std_dev),
2450                                );
2451                                metadata.insert(
2452                                    format!(
2453                                        "{}_recent_relative_std_pct",
2454                                        signal_prefix
2455                                    ),
2456                                    format!("{:.3}", relative_std),
2457                                );
2458                                metadata.insert(
2459                                    format!("{}_trend_slope", signal_prefix),
2460                                    format!("{:.6e}", trend_slope),
2461                                );
2462                                metadata.insert(
2463                                    format!(
2464                                        "{}_current_vs_recent_mean",
2465                                        signal_prefix
2466                                    ),
2467                                    format!(
2468                                        "{:.6e}",
2469                                        measured_signals[signal_idx] - mean
2470                                    ),
2471                                );
2472
2473                                // Classify signal stability based on recent data
2474                                let is_stable_signal = relative_std < 5.0
2475                                    && trend_slope.abs() < (std_dev * 0.1);
2476                                metadata.insert(
2477                                    format!("{}_appears_stable", signal_prefix),
2478                                    is_stable_signal.to_string(),
2479                                );
2480
2481                                // Check if current measurement is within recent range
2482                                let min_recent = signal_values
2483                                    .iter()
2484                                    .cloned()
2485                                    .fold(f32::INFINITY, f32::min);
2486                                let max_recent = signal_values
2487                                    .iter()
2488                                    .cloned()
2489                                    .fold(f32::NEG_INFINITY, f32::max);
2490                                let current_in_recent_range =
2491                                    measured_signals[signal_idx] >= min_recent
2492                                        && measured_signals[signal_idx]
2493                                            <= max_recent;
2494                                metadata.insert(
2495                                    format!(
2496                                        "{}_current_in_recent_range",
2497                                        signal_prefix
2498                                    ),
2499                                    current_in_recent_range.to_string(),
2500                                );
2501                                metadata.insert(
2502                                    format!("{}_recent_range", signal_prefix),
2503                                    format!(
2504                                        "[{:.6e}, {:.6e}]",
2505                                        min_recent, max_recent
2506                                    ),
2507                                );
2508                            }
2509                        }
2510                    }
2511                }
2512
2513                // Add recent ReadStableSignal data for correlation and debugging
2514                let now = std::time::Instant::now();
2515                let recent_signals: Vec<_> = self
2516                    .recent_stable_signals
2517                    .iter()
2518                    .filter(|(_, timestamp)| {
2519                        now.duration_since(*timestamp)
2520                            < std::time::Duration::from_secs(300)
2521                    }) // Last 5 minutes
2522                    .collect();
2523
2524                if !recent_signals.is_empty() {
2525                    metadata.insert(
2526                        "recent_stable_signals_count".to_string(),
2527                        recent_signals.len().to_string(),
2528                    );
2529
2530                    // Add details of the most recent ReadStableSignal for debugging
2531                    if let Some((most_recent_signal, timestamp)) =
2532                        recent_signals.last()
2533                    {
2534                        let age_ms = now.duration_since(*timestamp).as_millis();
2535                        metadata.insert(
2536                            "most_recent_stable_signal_age_ms".to_string(),
2537                            age_ms.to_string(),
2538                        );
2539                        metadata.insert(
2540                            "most_recent_stable_value".to_string(),
2541                            format!("{:.6e}", most_recent_signal.stable_value),
2542                        );
2543                        metadata.insert(
2544                            "most_recent_data_points".to_string(),
2545                            most_recent_signal.data_points_used.to_string(),
2546                        );
2547                        metadata.insert(
2548                            "most_recent_analysis_duration_ms".to_string(),
2549                            most_recent_signal
2550                                .analysis_duration
2551                                .as_millis()
2552                                .to_string(),
2553                        );
2554
2555                        // Include raw data summary for debugging (first 5, last 5 values to avoid huge logs)
2556                        let raw_data = &most_recent_signal.raw_data;
2557                        let raw_data_summary = if raw_data.len() <= 10 {
2558                            // Small dataset, include all
2559                            raw_data
2560                                .iter()
2561                                .map(|x| format!("{:.3e}", x))
2562                                .collect::<Vec<_>>()
2563                                .join(",")
2564                        } else {
2565                            // Large dataset, show first 5 and last 5
2566                            let first_5: String = raw_data
2567                                .iter()
2568                                .take(5)
2569                                .map(|x| format!("{:.3e}", x))
2570                                .collect::<Vec<_>>()
2571                                .join(",");
2572                            let last_5: String = raw_data
2573                                .iter()
2574                                .rev()
2575                                .take(5)
2576                                .rev()
2577                                .map(|x| format!("{:.3e}", x))
2578                                .collect::<Vec<_>>()
2579                                .join(",");
2580                            format!("{},...,{}", first_5, last_5)
2581                        };
2582                        metadata.insert(
2583                            "most_recent_raw_data_summary".to_string(),
2584                            format!("[{}]", raw_data_summary),
2585                        );
2586                        metadata.insert(
2587                            "most_recent_raw_data_full_count".to_string(),
2588                            raw_data.len().to_string(),
2589                        );
2590
2591                        // Include stability metrics
2592                        for (metric_name, metric_value) in
2593                            &most_recent_signal.stability_metrics
2594                        {
2595                            metadata.insert(
2596                                format!("most_recent_metric_{}", metric_name),
2597                                format!("{:.6e}", metric_value),
2598                            );
2599                        }
2600                    }
2601                }
2602
2603                // Add execution timestamp
2604                metadata.insert(
2605                    "execution_timestamp".to_string(),
2606                    chrono::Utc::now().to_rfc3339(),
2607                );
2608
2609                // Log a concise summary; full details at debug level
2610                let signal_values_str = measured_signals
2611                    .iter()
2612                    .map(|(signal_idx, value)| {
2613                        format!("signal_{}={:.3}", signal_idx.get(), value)
2614                    })
2615                    .collect::<Vec<_>>()
2616                    .join(", ");
2617
2618                log::info!(
2619                    "CheckTipState: shape={:?}, signals=[{}]",
2620                    tip_shape,
2621                    signal_values_str
2622                );
2623
2624                log::debug!("CheckTipState detail: read_method={}, dataset_size={}, recent_stable_count={}",
2625                    metadata.get("read_method").map(|s| s.as_str()).unwrap_or("unknown"),
2626                    metadata.get("dataset_size").map(|s| s.as_str()).unwrap_or("unknown"),
2627                    recent_signals.len());
2628
2629                Ok(ActionResult::TipState(TipState {
2630                    shape: tip_shape,
2631                    measured_signals,
2632                    metadata,
2633                }))
2634            }
2635
2636            Action::CheckTipStability {
2637                method,
2638                max_duration: _,
2639            } => {
2640                use std::collections::HashMap;
2641
2642                use crate::actions::{StabilityResult, TipStabilityMethod};
2643
2644                let start_time = std::time::Instant::now();
2645                let mut metrics = HashMap::new();
2646                let mut recommendations = Vec::new();
2647
2648                let (is_stable, measured_values) = match method {
2649                    TipStabilityMethod::ExtendedMonitoring {
2650                        signal: _,
2651                        duration: _,
2652                        sampling_interval: _,
2653                        stability_threshold: _,
2654                    } => {
2655                        todo!("ExtendedMonitoring not yet implemented");
2656                    }
2657
2658                    TipStabilityMethod::BiasSweepResponse {
2659                        ref signal,
2660                        bias_range,
2661                        bias_steps,
2662                        step_duration,
2663                        allowed_signal_change,
2664                    } => {
2665                        log::info!(
2666                            "Performing simple bias sweep stability test: {:.2}V to {:.2}V",
2667                            bias_range.0,
2668                            bias_range.1
2669                        );
2670
2671                        // 1. Get signal channel index for TCP reader
2672                        let tcp_channel = signal.tcp_channel.ok_or_else(|| {
2673                            NanonisError::Protocol(format!(
2674                                "Signal {} (Nanonis index) has no TCP channel mapping",
2675                                signal.index
2676                            ))
2677                        })?;
2678
2679                        // 2. Save and configure scan properties
2680                        log::info!("Reading current scan properties...");
2681                        let original_props = self.client.scan_props_get()?;
2682                        log::info!(
2683                            "Original scan props: continuous={}, bouncy={}",
2684                            original_props.continuous_scan,
2685                            original_props.bouncy_scan
2686                        );
2687
2688                        // Configure scan for stability check
2689                        log::info!(
2690                            "Configuring scan: continuous=true, bouncy=true"
2691                        );
2692                        let scan_props =
2693                            nanonis_rs::scan::ScanPropsBuilder::new()
2694                                .continuous_scan(true)
2695                                .bouncy_scan(true);
2696                        self.client.scan_props_set(scan_props)?;
2697                        log::info!("Scan properties configured");
2698
2699                        // 3. Get initial bias for restoration
2700                        let initial_bias = self.client.bias_get()?;
2701                        log::info!(
2702                            "Initial bias: {:.3} V (will restore after sweep)",
2703                            initial_bias
2704                        );
2705
2706                        // 4. Read baseline signal value once before starting
2707                        let baseline_value = {
2708                            let tcp_reader =
2709                                self.tcp_reader_mut().ok_or_else(|| {
2710                                    NanonisError::Protocol(
2711                                        "TCP reader not available".to_string(),
2712                                    )
2713                                })?;
2714
2715                            let recent_frames = tcp_reader.get_recent_frames(1);
2716                            if recent_frames.is_empty() {
2717                                return Err(NanonisError::Protocol(
2718                                    "No frames available from TCP reader"
2719                                        .to_string(),
2720                                ));
2721                            }
2722
2723                            recent_frames[0].signal_frame.data
2724                                [tcp_channel as usize]
2725                        };
2726
2727                        log::info!(
2728                            "Baseline signal: {:.3}, threshold: {:.3}",
2729                            baseline_value,
2730                            allowed_signal_change
2731                        );
2732
2733                        // 4. Start scan
2734                        self.client.scan_action(
2735                            ScanAction::Start,
2736                            ScanDirection::Down,
2737                        )?;
2738                        log::info!("Scan started");
2739
2740                        // Wait for scan to actually start (max 5 seconds)
2741                        let mut scan_started = false;
2742                        for _ in 0..50 {
2743                            // Check for shutdown request
2744                            if self.is_shutdown_requested() {
2745                                log::info!("Shutdown requested while waiting for scan to start");
2746                                let _ = self.client.scan_action(
2747                                    ScanAction::Stop,
2748                                    ScanDirection::Up,
2749                                );
2750                                let _ = self.client.bias_set(initial_bias);
2751                                return Err(NanonisError::Protocol(
2752                                    "Shutdown requested".to_string(),
2753                                ));
2754                            }
2755                            std::thread::sleep(Duration::from_millis(100));
2756                            let is_scanning = self.client.scan_status_get()?;
2757                            if is_scanning {
2758                                scan_started = true;
2759                                log::info!("Scan started successfully");
2760                                break;
2761                            }
2762                        }
2763
2764                        if !scan_started {
2765                            return Err(NanonisError::Protocol(
2766                                "Scan failed to start within 5 seconds"
2767                                    .to_string(),
2768                            ));
2769                        }
2770
2771                        // 5. Sweep bias from upper to lower
2772                        let bias_step_size =
2773                            (bias_range.1 - bias_range.0) / (bias_steps as f32);
2774                        let mut current_bias = bias_range.0;
2775
2776                        for step_num in 0..bias_steps {
2777                            // Check for shutdown request
2778                            if self.is_shutdown_requested() {
2779                                log::info!("Shutdown requested during bias sweep at step {}/{}", step_num + 1, bias_steps);
2780                                let _ = self.client.scan_action(
2781                                    ScanAction::Stop,
2782                                    ScanDirection::Up,
2783                                );
2784                                let _ = self.client.bias_set(initial_bias);
2785                                return Err(NanonisError::Protocol(
2786                                    "Shutdown requested".to_string(),
2787                                ));
2788                            }
2789                            self.client.bias_set(current_bias)?;
2790                            log::debug!(
2791                                "Step {}/{}: bias={:.2}V",
2792                                step_num + 1,
2793                                bias_steps,
2794                                current_bias
2795                            );
2796                            // Interruptible sleep: split into 10ms chunks for responsive shutdown
2797                            let sleep_chunks =
2798                                (step_duration.as_millis() / 10).max(1) as u32;
2799                            let chunk_duration = step_duration / sleep_chunks;
2800                            for _ in 0..sleep_chunks {
2801                                if self.is_shutdown_requested() {
2802                                    log::info!("Shutdown requested during bias sweep step sleep");
2803                                    let _ = self.client.scan_action(
2804                                        ScanAction::Stop,
2805                                        ScanDirection::Up,
2806                                    );
2807                                    let _ = self.client.bias_set(initial_bias);
2808                                    return Err(NanonisError::Protocol(
2809                                        "Shutdown requested".to_string(),
2810                                    ));
2811                                }
2812                                std::thread::sleep(chunk_duration);
2813                            }
2814                            current_bias += bias_step_size;
2815                        }
2816
2817                        log::info!("Bias sweep completed");
2818
2819                        // 6. Read final signal value once after finishing
2820                        let final_value = {
2821                            let tcp_reader =
2822                                self.tcp_reader_mut().ok_or_else(|| {
2823                                    NanonisError::Protocol(
2824                                        "TCP reader not available".to_string(),
2825                                    )
2826                                })?;
2827
2828                            let recent_frames = tcp_reader.get_recent_frames(1);
2829                            if recent_frames.is_empty() {
2830                                return Err(NanonisError::Protocol(
2831                                    "No frames available from TCP reader"
2832                                        .to_string(),
2833                                ));
2834                            }
2835
2836                            recent_frames[0].signal_frame.data
2837                                [tcp_channel as usize]
2838                        };
2839
2840                        // 7. Stop scan, withdraw, then restore bias
2841                        let _ = self
2842                            .client
2843                            .scan_action(ScanAction::Stop, ScanDirection::Up);
2844
2845                        // Withdraw before changing bias
2846                        if let Err(e) = self
2847                            .client
2848                            .z_ctrl_withdraw(true, Duration::from_secs(5))
2849                        {
2850                            log::error!(
2851                                "Failed to withdraw before restoring bias: {}",
2852                                e
2853                            );
2854                        }
2855
2856                        // Delay before changing bias
2857                        std::thread::sleep(Duration::from_millis(200));
2858
2859                        if let Err(e) = self.client.bias_set(initial_bias) {
2860                            log::error!(
2861                                "Failed to restore initial bias: {}",
2862                                e
2863                            );
2864                        } else {
2865                            log::info!(
2866                                "Bias restored to {:.3} V",
2867                                initial_bias
2868                            );
2869                        }
2870
2871                        // 8. Check if change exceeded threshold
2872                        let signal_change =
2873                            (final_value - baseline_value).abs();
2874                        let is_stable = signal_change <= allowed_signal_change;
2875
2876                        log::info!(
2877                            "Bias sweep result: baseline={:.3}, final={:.3}, change={:.3}, threshold={:.3}, stable={}",
2878                            baseline_value,
2879                            final_value,
2880                            signal_change,
2881                            allowed_signal_change,
2882                            is_stable
2883                        );
2884
2885                        // 9. Populate metrics
2886                        metrics.insert(
2887                            "baseline_value".to_string(),
2888                            baseline_value,
2889                        );
2890                        metrics.insert("final_value".to_string(), final_value);
2891                        metrics
2892                            .insert("signal_change".to_string(), signal_change);
2893                        metrics.insert(
2894                            "threshold".to_string(),
2895                            allowed_signal_change,
2896                        );
2897
2898                        // 10. Add recommendations
2899                        if is_stable {
2900                            recommendations.push(format!(
2901                                "Tip is stable - signal change {:.3} within threshold {:.3}",
2902                                signal_change, allowed_signal_change
2903                            ));
2904                        } else {
2905                            recommendations.push(format!(
2906                                "Tip is blunt - signal change {:.3} exceeded threshold {:.3}. Tip shaping recommended.",
2907                                signal_change, allowed_signal_change
2908                            ));
2909                        }
2910
2911                        // Create measured values map
2912                        let mut measured_values = HashMap::new();
2913                        measured_values.insert(
2914                            signal.clone(),
2915                            vec![baseline_value, final_value],
2916                        );
2917
2918                        (is_stable, measured_values)
2919                    }
2920                };
2921
2922                let analysis_duration = start_time.elapsed();
2923                let result = StabilityResult {
2924                    is_stable,
2925                    method_used: format!("{:?}", method.clone()),
2926                    measured_values,
2927                    analysis_duration,
2928                    metrics,
2929                    potential_damage_detected: !is_stable
2930                        && matches!(
2931                            method,
2932                            TipStabilityMethod::BiasSweepResponse { .. }
2933                        ),
2934                    recommendations,
2935                };
2936
2937                Ok(ActionResult::StabilityResult(result))
2938            }
2939
2940            Action::ReadStableSignal {
2941                signal,
2942                data_points,
2943                use_new_data,
2944                stability_method,
2945                timeout,
2946                retry_count,
2947            } => {
2948                use std::time::Instant;
2949
2950                let start_time = Instant::now();
2951                let data_points = data_points.unwrap_or(50);
2952                let max_retries = retry_count.unwrap_or(0);
2953
2954                // Validate TCP logger is configured and active
2955                let tcp_config =
2956                    self.tcp_reader_config.as_ref().ok_or_else(|| {
2957                        NanonisError::Protocol(
2958                            "TCP logger not configured".to_string(),
2959                        )
2960                    })?;
2961
2962                // Convert Nanonis signal index to TCP channel using registry
2963                log::debug!(
2964                    "ReadStableSignal: Looking up signal {} in signal registry",
2965                    signal.index
2966                );
2967
2968                // Look up the signal from registry to get TCP channel
2969                let registry_signal = self
2970                    .signal_registry
2971                    .get_by_index(signal.index)
2972                    .ok_or_else(|| {
2973                        NanonisError::Protocol(format!(
2974                            "Signal {} not found in registry",
2975                            signal.index
2976                        ))
2977                    })?;
2978
2979                let tcp_channel = registry_signal.tcp_channel.ok_or_else(|| {
2980                    log::error!(
2981                        "ReadStableSignal: Signal {} (Nanonis index) has no TCP channel mapping",
2982                        signal.index
2983                    );
2984                    NanonisError::Protocol(format!(
2985                        "Signal {} (Nanonis index) has no TCP channel mapping",
2986                        signal.index
2987                    ))
2988                })?;
2989
2990                log::debug!(
2991                    "ReadStableSignal: Signal {} mapped to TCP channel {}",
2992                    signal.index,
2993                    tcp_channel
2994                );
2995
2996                // Find TCP channel in TCP config channels
2997                log::debug!(
2998                    "ReadStableSignal: Signal {} (Nanonis) maps to TCP channel {}",
2999                    signal.index,
3000                    tcp_channel
3001                );
3002                log::debug!(
3003                    "ReadStableSignal: Available TCP channels: {:?}",
3004                    tcp_config.channels
3005                );
3006                let signal_channel_idx = tcp_config
3007                    .channels
3008                    .iter()
3009                    .position(|&ch| ch == tcp_channel as i32)
3010                    .ok_or_else(|| {
3011                        log::error!("ReadStableSignal: TCP channel {} for signal {} (Nanonis) not found in TCP logger configuration. Available channels: {:?}",
3012                            tcp_channel, signal.index, tcp_config.channels);
3013                        NanonisError::Protocol(format!(
3014                            "TCP channel {} for signal {} (Nanonis) not found in TCP logger configuration. Available: {:?}",
3015                            tcp_channel, signal.index, tcp_config.channels
3016                        ))
3017                    })?;
3018
3019                log::debug!(
3020                    "ReadStableSignal: Signal {} (Nanonis) -> TCP channel {} -> Array position {}",
3021                    signal.index,
3022                    tcp_channel,
3023                    signal_channel_idx
3024                );
3025                log::debug!(
3026                    "ReadStableSignal: Full TCP channel list: {:?}",
3027                    tcp_config.channels
3028                );
3029
3030                // Retry loop for data collection and stability analysis
3031                let mut attempt = 0;
3032
3033                loop {
3034                    match self.attempt_stable_signal_read(
3035                        signal_channel_idx,
3036                        data_points,
3037                        use_new_data,
3038                        timeout,
3039                        &stability_method,
3040                    ) {
3041                        Ok((signal_data, is_stable, metrics)) => {
3042                            let analysis_duration = start_time.elapsed();
3043
3044                            if is_stable {
3045                                // Calculate stable value (mean of the data)
3046                                let stable_value =
3047                                    signal_data.iter().sum::<f32>()
3048                                        / signal_data.len() as f32;
3049
3050                                use crate::actions::StableSignal;
3051                                log::info!(
3052                                    "Stable signal acquired on attempt {} (retries: {})",
3053                                    attempt + 1,
3054                                    attempt
3055                                );
3056
3057                                let stable_signal = StableSignal {
3058                                    stable_value,
3059                                    data_points_used: signal_data.len(),
3060                                    analysis_duration,
3061                                    stability_metrics: metrics,
3062                                    // Only include full buffer when not stable (for debugging)
3063                                    // When stable, only keep the mean value to reduce log file size
3064                                    raw_data: if is_stable {
3065                                        vec![stable_value]
3066                                    } else {
3067                                        signal_data
3068                                    },
3069                                };
3070
3071                                // Store for correlation with future CheckTipState calls
3072                                self.recent_stable_signals.push_back((
3073                                    stable_signal.clone(),
3074                                    std::time::Instant::now(),
3075                                ));
3076                                // Keep only last 10 stable signal results
3077                                while self.recent_stable_signals.len() > 10 {
3078                                    self.recent_stable_signals.pop_front();
3079                                }
3080
3081                                return Ok(ActionResult::StableSignal(
3082                                    stable_signal,
3083                                ));
3084                            } else if attempt >= max_retries {
3085                                // No more retries, return raw data as fallback
3086                                log::warn!(
3087                                    "Signal not stable after {} attempts, returning raw data",
3088                                    attempt + 1
3089                                );
3090                                let values: Vec<f64> = signal_data
3091                                    .iter()
3092                                    .map(|&x| x as f64)
3093                                    .collect();
3094                                return Ok(ActionResult::Values(values));
3095                            } else {
3096                                // Signal not stable, but we can retry
3097                                log::debug!(
3098                                    "Signal not stable on attempt {}, retrying...",
3099                                    attempt + 1
3100                                );
3101                            }
3102                        }
3103                        Err(e) => {
3104                            log::warn!(
3105                                "Data collection failed on attempt {}: {}",
3106                                attempt + 1,
3107                                e
3108                            );
3109
3110                            if attempt >= max_retries {
3111                                return Err(e);
3112                            }
3113                        }
3114                    }
3115
3116                    attempt += 1;
3117
3118                    // Add delay between retries (exponential backoff)
3119                    if attempt <= max_retries {
3120                        let delay_ms = 100 * (1 << (attempt - 1).min(4)); // Cap at 1.6s delay
3121                        log::debug!(
3122                            "Waiting {}ms before retry attempt {}",
3123                            delay_ms,
3124                            attempt + 1
3125                        );
3126                        std::thread::sleep(Duration::from_millis(delay_ms));
3127                    }
3128                }
3129            }
3130            Action::ReachedTargedAmplitude => {
3131                let ampl_setpoint =
3132                    self.client_mut().pll_amp_ctrl_setpnt_get(1)?;
3133
3134                let ampl_current = match self
3135                    .run(Action::ReadStableSignal {
3136                        signal: Signal::new("Amplitude".to_string(), 75, None).unwrap(),
3137                        data_points: Some(50),
3138                        use_new_data: false,
3139                        stability_method:
3140                            crate::actions::SignalStabilityMethod::RelativeStandardDeviation {
3141                                threshold_percent: 0.2,
3142                            },
3143                        timeout: Duration::from_millis(10),
3144                        retry_count: Some(3), // 3 retries for amplitude check
3145                    })
3146                    .go()? {
3147                        ActionResult::Values(values) => values.iter().map(|v| *v as f32).sum::<f32>() / values.len() as f32,
3148                        ActionResult::StableSignal(value) => value.stable_value,
3149                        other => {
3150                            return Err(NanonisError::Protocol(format!(
3151                                "CheckAmplitudeStability returned unexpected result type. Expected Values or StableSignal, got {:?}",
3152                                std::mem::discriminant(&other)
3153                            )))
3154                        }
3155                    };
3156
3157                let status = (ampl_setpoint - 5e-12..ampl_setpoint + 5e-12)
3158                    .contains(&ampl_current);
3159
3160                Ok(ActionResult::Status(status))
3161            }
3162        }
3163    }
3164
3165    fn check_safetip_status(&mut self, context: &str) -> Result<(), NanonisError> {
3166        if let Ok(status) = self.client_mut().z_ctrl_status_get() {
3167            if matches!(status, nanonis_rs::z_ctrl::ZControllerStatus::SafeTip)
3168            {
3169                return Err(NanonisError::Protocol(
3170                    format!("SafeTip triggered ({}), abort!", context),
3171                ));
3172            }
3173        }
3174
3175        Ok(())
3176    }
3177
3178    /// Attempt a single stable signal read (used by retry logic)
3179    fn attempt_stable_signal_read(
3180        &self,
3181        signal_channel_idx: usize,
3182        data_points: usize,
3183        use_new_data: bool,
3184        timeout: Duration,
3185        stability_method: &crate::actions::SignalStabilityMethod,
3186    ) -> Result<
3187        (Vec<f32>, bool, std::collections::HashMap<String, f32>),
3188        NanonisError,
3189    > {
3190        // Collect signal data based on use_new_data flag
3191        let signal_data: Vec<f32> = if use_new_data {
3192            // Wait for new data with timeout
3193            self.collect_new_signal_data(
3194                signal_channel_idx,
3195                data_points,
3196                timeout,
3197            )?
3198        } else {
3199            // Use buffered data
3200            self.extract_buffered_signal_data(signal_channel_idx, data_points)?
3201        };
3202
3203        if signal_data.is_empty() {
3204            return Err(NanonisError::Protocol(
3205                "No signal data available".to_string(),
3206            ));
3207        }
3208
3209        // Analyze stability using the specified method
3210        let (is_stable, metrics) =
3211            Self::analyze_signal_stability(&signal_data, stability_method);
3212
3213        Ok((signal_data, is_stable, metrics))
3214    }
3215
3216    /// Collect new signal data from TCP logger with timeout
3217    fn collect_new_signal_data(
3218        &self,
3219        signal_channel_idx: usize,
3220        data_points: usize,
3221        timeout: Duration,
3222    ) -> Result<Vec<f32>, NanonisError> {
3223        use std::time::Instant;
3224
3225        let tcp_reader = self.tcp_reader.as_ref().ok_or_else(|| {
3226            NanonisError::Protocol("TCP reader not available".to_string())
3227        })?;
3228
3229        let start_time = Instant::now();
3230        let mut collected_data = Vec::with_capacity(data_points);
3231
3232        log::debug!(
3233            "Collecting {} new data points for signal channel {} with timeout {:.1}s",
3234            data_points,
3235            signal_channel_idx,
3236            timeout.as_secs_f32()
3237        );
3238
3239        while collected_data.len() < data_points
3240            && start_time.elapsed() < timeout
3241        {
3242            // Get recent data in small chunks to avoid blocking too long
3243            let recent_frames =
3244                tcp_reader.get_recent_data(Duration::from_millis(100));
3245
3246            for frame in recent_frames {
3247                if collected_data.len() >= data_points {
3248                    break;
3249                }
3250
3251                if let Some(&value) =
3252                    frame.signal_frame.data.get(signal_channel_idx)
3253                {
3254                    collected_data.push(value);
3255                }
3256            }
3257
3258            if collected_data.len() < data_points {
3259                std::thread::sleep(Duration::from_millis(50)); // Small delay before next check
3260            }
3261        }
3262
3263        if collected_data.is_empty() {
3264            log::warn!("No data collected within timeout");
3265        } else {
3266            log::debug!("Collected {} data points", collected_data.len());
3267        }
3268
3269        Ok(collected_data)
3270    }
3271
3272    /// Extract buffered signal data from TCP logger
3273    fn extract_buffered_signal_data(
3274        &self,
3275        signal_channel_idx: usize,
3276        data_points: usize,
3277    ) -> Result<Vec<f32>, NanonisError> {
3278        let tcp_reader = self.tcp_reader.as_ref().ok_or_else(|| {
3279            NanonisError::Protocol("TCP reader not available".to_string())
3280        })?;
3281
3282        // Get recent data based on how many points we need
3283        let recent_frames = tcp_reader.get_recent_frames(data_points);
3284
3285        let mut signal_data = Vec::new();
3286        for frame in recent_frames.iter().rev().take(data_points) {
3287            // Take most recent data points
3288            if let Some(&value) =
3289                frame.signal_frame.data.get(signal_channel_idx)
3290            {
3291                signal_data.push(value);
3292            }
3293        }
3294
3295        signal_data.reverse(); // Return in chronological order
3296
3297        log::info!("Extracted {} buffered data points", signal_data.len());
3298        Ok(signal_data)
3299    }
3300
3301    /// Analyze signal stability using the specified method
3302    fn analyze_signal_stability(
3303        data: &[f32],
3304        method: &crate::actions::SignalStabilityMethod,
3305    ) -> (bool, std::collections::HashMap<String, f32>) {
3306        use crate::actions::SignalStabilityMethod;
3307
3308        if data.len() < 2 {
3309            return (false, std::collections::HashMap::new());
3310        }
3311
3312        let mut metrics = std::collections::HashMap::new();
3313        let mean = data.iter().sum::<f32>() / data.len() as f32;
3314        let variance = data.iter().map(|v| (v - mean).powi(2)).sum::<f32>()
3315            / data.len() as f32;
3316        let std_dev = variance.sqrt();
3317
3318        metrics.insert("mean".to_string(), mean);
3319        metrics.insert("std_dev".to_string(), std_dev);
3320        metrics.insert("variance".to_string(), variance);
3321
3322        let is_stable = match method {
3323            SignalStabilityMethod::StandardDeviation { threshold } => {
3324                metrics.insert("threshold".to_string(), *threshold);
3325                std_dev <= *threshold
3326            }
3327
3328            SignalStabilityMethod::RelativeStandardDeviation {
3329                threshold_percent,
3330            } => {
3331                let relative_std = if mean.abs() > 1e-12 {
3332                    (std_dev / mean.abs()) * 100.0
3333                } else {
3334                    f32::INFINITY
3335                };
3336                metrics
3337                    .insert("relative_std_percent".to_string(), relative_std);
3338                metrics.insert(
3339                    "threshold_percent".to_string(),
3340                    *threshold_percent,
3341                );
3342                relative_std <= *threshold_percent
3343            }
3344
3345            SignalStabilityMethod::MovingWindow {
3346                window_size,
3347                max_variation,
3348            } => {
3349                if data.len() < *window_size {
3350                    return (false, metrics);
3351                }
3352
3353                let mut max_window_variation = 0.0f32;
3354                for window in data.windows(*window_size) {
3355                    let window_min =
3356                        window.iter().fold(f32::INFINITY, |a, &b| a.min(b));
3357                    let window_max =
3358                        window.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
3359                    let variation = window_max - window_min;
3360                    max_window_variation = max_window_variation.max(variation);
3361                }
3362
3363                metrics.insert(
3364                    "max_window_variation".to_string(),
3365                    max_window_variation,
3366                );
3367                metrics.insert("window_size".to_string(), *window_size as f32);
3368                metrics.insert(
3369                    "max_variation_threshold".to_string(),
3370                    *max_variation,
3371                );
3372                max_window_variation <= *max_variation
3373            }
3374
3375            SignalStabilityMethod::TrendAnalysis { max_slope } => {
3376                // Simple linear regression to detect trend
3377                let n = data.len() as f32;
3378                let x_mean = (n - 1.0) / 2.0; // indices 0, 1, 2, ... n-1
3379                let y_mean = mean;
3380
3381                let mut numerator = 0.0;
3382                let mut denominator = 0.0;
3383                for (i, &y) in data.iter().enumerate() {
3384                    let x = i as f32;
3385                    numerator += (x - x_mean) * (y - y_mean);
3386                    denominator += (x - x_mean).powi(2);
3387                }
3388
3389                let slope = if denominator > 1e-12 {
3390                    numerator / denominator
3391                } else {
3392                    0.0
3393                };
3394                let abs_slope = slope.abs();
3395
3396                metrics.insert("slope".to_string(), slope);
3397                metrics.insert("abs_slope".to_string(), abs_slope);
3398                metrics.insert("max_slope_threshold".to_string(), *max_slope);
3399                abs_slope <= *max_slope
3400            }
3401
3402            SignalStabilityMethod::Combined {
3403                max_std_dev,
3404                max_slope,
3405            } => {
3406                // Calculate slope via linear regression
3407                let n = data.len() as f32;
3408                let x_mean = (n - 1.0) / 2.0;
3409                let y_mean = mean;
3410
3411                let mut numerator = 0.0;
3412                let mut denominator = 0.0;
3413                for (i, &y) in data.iter().enumerate() {
3414                    let x = i as f32;
3415                    numerator += (x - x_mean) * (y - y_mean);
3416                    denominator += (x - x_mean).powi(2);
3417                }
3418
3419                let slope = if denominator > 1e-12 {
3420                    numerator / denominator
3421                } else {
3422                    0.0
3423                };
3424                let abs_slope = slope.abs();
3425
3426                // Check both conditions: noise AND drift
3427                let noise_ok = std_dev <= *max_std_dev;
3428                let drift_ok = abs_slope <= *max_slope;
3429
3430                metrics.insert("slope".to_string(), slope);
3431                metrics.insert("abs_slope".to_string(), abs_slope);
3432                metrics.insert("max_slope_threshold".to_string(), *max_slope);
3433                metrics
3434                    .insert("max_std_dev_threshold".to_string(), *max_std_dev);
3435                metrics.insert(
3436                    "noise_ok".to_string(),
3437                    if noise_ok { 1.0 } else { 0.0 },
3438                );
3439                metrics.insert(
3440                    "drift_ok".to_string(),
3441                    if drift_ok { 1.0 } else { 0.0 },
3442                );
3443                noise_ok && drift_ok
3444            }
3445        };
3446
3447        metrics.insert("data_points".to_string(), data.len() as f32);
3448
3449        (is_stable, metrics)
3450    }
3451
3452    /// Execute action and extract specific type with validation
3453    ///
3454    /// This is a convenience method that combines execute() with type extraction,
3455    /// providing better ergonomics while preserving type safety.
3456    ///
3457    /// # Example
3458    /// ```ignore
3459    /// use rusty_tip::{ActionDriver, Action, Signal};
3460    /// use rusty_tip::types::{DataToGet, OsciData};
3461    ///
3462    /// let mut driver = ActionDriver::new("127.0.0.1", 6501)?;
3463    /// let signal = Signal::new("Frequency Shift", 24, None).unwrap();
3464    /// let osci_data: OsciData = driver.execute_expecting(Action::ReadOsci {
3465    ///     signal,
3466    ///     trigger: None,
3467    ///     data_to_get: DataToGet::Current,
3468    ///     is_stable: None,
3469    /// })?;
3470    /// # Ok::<(), Box<dyn std::error::Error>>(())
3471    /// ```
3472    pub fn execute_expecting<T>(
3473        &mut self,
3474        action: Action,
3475    ) -> Result<T, NanonisError>
3476    where
3477        ActionResult: ExpectFromAction<T>,
3478    {
3479        let result = self.execute(action.clone())?;
3480        Ok(result.expect_from_action(&action))
3481    }
3482
3483    /// Find stable oscilloscope data with proper timeout handling
3484    ///
3485    /// This method implements stability detection logic with dual-threshold
3486    /// approach and timeout handling. It repeatedly reads oscilloscope data until
3487    /// stable values are found or timeout is reached.
3488    fn find_stable_oscilloscope_data(
3489        &mut self,
3490        _data_to_get: DataToGet,
3491        readings: u32,
3492        timeout: std::time::Duration,
3493        relative_threshold: f64,
3494        absolute_threshold: f64,
3495        min_window_percent: f64,
3496        stability_fn: Option<fn(&[f64]) -> bool>,
3497    ) -> Result<Option<OsciData>, NanonisError> {
3498        match poll_with_timeout(
3499            || {
3500                // Try to find stable data in a batch of readings
3501                for _attempt in 0..readings {
3502                    let (t0, dt, size, data) =
3503                        self.client.osci1t_data_get(2)?; // Wait2Triggers = 2
3504
3505                    if let Some(stable_osci_data) = self
3506                        .analyze_stability_window(
3507                            t0,
3508                            dt,
3509                            size,
3510                            data,
3511                            relative_threshold,
3512                            absolute_threshold,
3513                            min_window_percent,
3514                            stability_fn,
3515                        )?
3516                    {
3517                        return Ok(Some(stable_osci_data));
3518                    }
3519
3520                    // Small delay between attempts to avoid overwhelming the system
3521                    std::thread::sleep(std::time::Duration::from_millis(100));
3522                }
3523
3524                // No stable data found in this batch, continue polling
3525                Ok(None)
3526            },
3527            timeout,
3528            std::time::Duration::from_millis(50), // Brief pause between reading cycles
3529        ) {
3530            Ok(Some(result)) => Ok(Some(result)),
3531            Ok(None) => Ok(None), // Timeout reached
3532            Err(PollError::ConditionError(e)) => Err(e),
3533            Err(PollError::Timeout) => unreachable!(), // poll_with_timeout returns Ok(None) on timeout
3534        }
3535    }
3536
3537    /// Analyze a single oscilloscope data window for stability
3538    fn analyze_stability_window(
3539        &self,
3540        t0: f64,
3541        dt: f64,
3542        size: i32,
3543        data: Vec<f64>,
3544        relative_threshold: f64,
3545        absolute_threshold: f64,
3546        min_window_percent: f64,
3547        stability_fn: Option<fn(&[f64]) -> bool>,
3548    ) -> Result<Option<OsciData>, NanonisError> {
3549        let min_window = (size as f64 * min_window_percent) as usize;
3550        let mut start = 0;
3551        let mut end = size as usize;
3552
3553        while (end - start) > min_window {
3554            let window = &data[start..end];
3555            let arr = Array1::from_vec(window.to_vec());
3556            let mean = arr.mean().expect(
3557                "There must be an non-empty array, osci1t_data_get would have returned early.",
3558            );
3559            let std_dev = arr.std(0.0);
3560            let relative_std = std_dev / mean.abs();
3561
3562            // Use custom stability function if provided, otherwise default dual-threshold
3563            let is_stable = if let Some(stability_fn) = stability_fn {
3564                stability_fn(window)
3565            } else {
3566                // Default dual-threshold approach: relative OR absolute
3567                let is_relative_stable = relative_std < relative_threshold;
3568                let is_absolute_stable = std_dev < absolute_threshold;
3569                is_relative_stable || is_absolute_stable
3570            };
3571
3572            if is_stable {
3573                let stable_data = window.to_vec();
3574                let stability_method = if stability_fn.is_some() {
3575                    "custom".to_string()
3576                } else {
3577                    // Default dual-threshold logic
3578                    let is_relative_stable = relative_std < relative_threshold;
3579                    let is_absolute_stable = std_dev < absolute_threshold;
3580                    match (is_relative_stable, is_absolute_stable) {
3581                        (true, true) => "both".to_string(),
3582                        (true, false) => "relative".to_string(),
3583                        (false, true) => "absolute".to_string(),
3584                        (false, false) => unreachable!(),
3585                    }
3586                };
3587
3588                let stats = SignalStats {
3589                    mean,
3590                    std_dev,
3591                    relative_std,
3592                    window_size: stable_data.len(),
3593                    stability_method,
3594                };
3595
3596                let mut osci_data = OsciData::new_with_stats(
3597                    t0,
3598                    dt,
3599                    stable_data.len() as i32,
3600                    stable_data,
3601                    stats,
3602                );
3603                osci_data.is_stable = true; // Mark as stable since we found stable data
3604                return Ok(Some(osci_data));
3605            }
3606
3607            let shrink = ((end - start) / 10).max(1);
3608            start += shrink;
3609            end -= shrink;
3610        }
3611
3612        // No stable window found in this data
3613        Ok(None)
3614    }
3615
3616    /// Find stable oscilloscope data with fallback to single value
3617    ///
3618    /// This method attempts to find stable oscilloscope data. If successful,
3619    /// it returns OsciData with is_stable=true. If no stable data is found
3620    /// within the timeout, it returns OsciData with is_stable=false and
3621    /// a fallback single value reading.
3622    fn find_stable_oscilloscope_data_with_fallback(
3623        &mut self,
3624        data_to_get: DataToGet,
3625        readings: u32,
3626        timeout: std::time::Duration,
3627        relative_threshold: f64,
3628        absolute_threshold: f64,
3629        min_window_percent: f64,
3630        stability_fn: Option<fn(&[f64]) -> bool>,
3631    ) -> Result<OsciData, NanonisError> {
3632        // First try to find stable data
3633        if let Some(stable_osci_data) = self.find_stable_oscilloscope_data(
3634            data_to_get,
3635            readings,
3636            timeout,
3637            relative_threshold,
3638            absolute_threshold,
3639            min_window_percent,
3640            stability_fn,
3641        )? {
3642            return Ok(stable_osci_data);
3643        }
3644
3645        // If no stable data found, get a single reading as fallback
3646        let (t0, dt, size, data) = self.client.osci1t_data_get(1)?; // NextTrigger = 1
3647
3648        // Calculate fallback value (mean of the data)
3649        let fallback_value = if !data.is_empty() {
3650            data.iter().sum::<f64>() / data.len() as f64
3651        } else {
3652            0.0
3653        };
3654
3655        Ok(OsciData::new_unstable_with_fallback(
3656            t0,
3657            dt,
3658            size,
3659            data,
3660            fallback_value,
3661        ))
3662    }
3663
3664    /// Execute a chain of actions sequentially
3665    pub fn execute_chain(
3666        &mut self,
3667        chain: impl Into<ActionChain>,
3668    ) -> Result<Vec<ActionResult>, NanonisError> {
3669        let chain = chain.into();
3670        let mut results = Vec::with_capacity(chain.len());
3671
3672        for action in chain.into_iter() {
3673            let result = self.execute(action)?;
3674            results.push(result);
3675        }
3676
3677        Ok(results)
3678    }
3679
3680    /// Execute chain and return only the final result
3681    pub fn execute_chain_final(
3682        &mut self,
3683        chain: impl Into<ActionChain>,
3684    ) -> Result<ActionResult, NanonisError> {
3685        let results = self.execute_chain(chain)?;
3686        Ok(results.into_iter().last().unwrap_or(ActionResult::None))
3687    }
3688
3689    /// Execute chain with early termination on error, returning partial results
3690    pub fn execute_chain_partial(
3691        &mut self,
3692        chain: impl Into<ActionChain>,
3693    ) -> Result<Vec<ActionResult>, (Vec<ActionResult>, NanonisError)> {
3694        let chain = chain.into();
3695        let mut results = Vec::new();
3696
3697        for action in chain.into_iter() {
3698            match self.execute(action) {
3699                Ok(result) => results.push(result),
3700                Err(error) => return Err((results, error)),
3701            }
3702        }
3703
3704        Ok(results)
3705    }
3706
3707    /// Execute chain with deferred logging for timing-critical operations
3708    ///
3709    /// This method executes all actions using execute_internal() (no per-action logging)
3710    /// and then logs the entire chain as a single entry with total timing.
3711    /// Use this when you need precise timing without logging overhead between actions.
3712    ///
3713    /// # Arguments
3714    /// * `chain` - The action chain to execute
3715    ///
3716    /// # Returns
3717    /// Vector of all action results
3718    ///
3719    /// # Logging Behavior
3720    /// - Individual actions are NOT logged during execution
3721    /// - Single log entry created for the entire chain with total duration
3722    /// - Log entry includes chain summary and final result
3723    pub fn execute_chain_deferred(
3724        &mut self,
3725        chain: impl Into<ActionChain>,
3726    ) -> Result<Vec<ActionResult>, NanonisError> {
3727        let chain = chain.into();
3728        let start_time = chrono::Utc::now();
3729        let start_instant = std::time::Instant::now();
3730
3731        let mut results = Vec::with_capacity(chain.len());
3732
3733        // Execute all actions without per-action logging
3734        for action in chain.iter() {
3735            let result = self.execute_internal(action.clone())?;
3736            results.push(result);
3737        }
3738
3739        let duration = start_instant.elapsed();
3740
3741        // Log the entire chain as a single entry if logging is enabled
3742        if self.action_logging_enabled && self.action_logger.is_some() {
3743            let chain_summary = format!("Chain: {}", chain.summary());
3744            let final_result = results.last().unwrap_or(&ActionResult::None);
3745
3746            let log_entry = ActionLogEntry::new(
3747                &crate::actions::Action::Wait {
3748                    duration: Duration::from_millis(0),
3749                }, // Placeholder action
3750                final_result,
3751                start_time,
3752                duration,
3753            )
3754            .with_metadata("type", "chain_execution")
3755            .with_metadata("chain_summary", chain_summary)
3756            .with_metadata("action_count", results.len().to_string());
3757
3758            if let Err(log_error) =
3759                self.action_logger.as_mut().unwrap().add(log_entry)
3760            {
3761                log::warn!("Failed to log chain execution: {}", log_error);
3762            }
3763        }
3764
3765        Ok(results)
3766    }
3767
3768    /// Clear all stored values
3769    pub fn clear_storage(&mut self) {
3770        self.stored_values.clear();
3771    }
3772
3773    /// Get all stored value keys
3774    pub fn stored_keys(&self) -> Vec<&String> {
3775        self.stored_values.keys().collect()
3776    }
3777
3778    // ==================== Action Logging Control Methods ====================
3779
3780    /// Enable or disable action logging at runtime
3781    ///
3782    /// # Arguments
3783    /// * `enabled` - true to enable logging, false to disable
3784    ///
3785    /// # Returns
3786    /// Previous logging state
3787    ///
3788    /// # Usage
3789    /// ```rust,ignore
3790    /// let previous_state = driver.set_action_logging_enabled(false);
3791    /// // Execute timing-critical operations without logging overhead
3792    /// driver.execute(critical_action)?;
3793    /// driver.set_action_logging_enabled(previous_state); // Restore
3794    /// ```
3795    pub fn set_action_logging_enabled(&mut self, enabled: bool) -> bool {
3796        let previous = self.action_logging_enabled;
3797        self.action_logging_enabled = enabled;
3798        previous
3799    }
3800
3801    /// Check if action logging is currently enabled
3802    pub fn is_action_logging_enabled(&self) -> bool {
3803        self.action_logging_enabled && self.action_logger.is_some()
3804    }
3805
3806    /// Manually flush the action log buffer to file
3807    ///
3808    /// # Returns
3809    /// Result indicating if flush was successful
3810    ///
3811    /// # Usage
3812    /// Force immediate write of buffered actions to file, useful before
3813    /// critical operations or at experiment checkpoints
3814    pub fn flush_action_log(&mut self) -> Result<(), NanonisError> {
3815        if let Some(ref mut logger) = self.action_logger {
3816            logger.flush()?;
3817        }
3818        Ok(())
3819    }
3820
3821    /// Get action log buffer statistics
3822    ///
3823    /// # Returns
3824    /// Optional tuple of (current_buffer_count, is_logging_enabled) or None if no logger
3825    ///
3826    /// # Usage
3827    /// Monitor buffer utilization to understand logging overhead and frequency
3828    pub fn action_log_stats(&self) -> Option<(usize, bool)> {
3829        self.action_logger
3830            .as_ref()
3831            .map(|logger| (logger.len(), self.action_logging_enabled))
3832    }
3833
3834    /// Finalize action log as JSON array (if configured for JSON output)
3835    ///
3836    /// # Returns
3837    /// Result indicating if finalization was successful
3838    ///
3839    /// # Usage
3840    /// Call this at the end of your experiment to convert JSONL to JSON format
3841    /// for easier post-experiment analysis. This happens automatically on drop,
3842    /// but you can call it manually for explicit control.
3843    pub fn finalize_action_log(&mut self) -> Result<(), NanonisError> {
3844        if let Some(ref mut logger) = self.action_logger {
3845            logger.finalize_as_json()?;
3846        }
3847        Ok(())
3848    }
3849
3850    /// Convenience method to read oscilloscope data directly
3851    pub fn read_oscilloscope(
3852        &mut self,
3853        signal: Signal,
3854        trigger: Option<TriggerConfig>,
3855        data_to_get: DataToGet,
3856    ) -> Result<Option<OsciData>, NanonisError> {
3857        match self.execute(Action::ReadOsci {
3858            signal,
3859            trigger,
3860            data_to_get,
3861            is_stable: None,
3862        })? {
3863            ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
3864            ActionResult::None => Ok(None),
3865            _ => {
3866                Err(NanonisError::Protocol("Expected oscilloscope data".into()))
3867            }
3868        }
3869    }
3870
3871    /// Convenience method to read oscilloscope data with custom stability function
3872    pub fn read_oscilloscope_with_stability(
3873        &mut self,
3874        signal: Signal,
3875        trigger: Option<TriggerConfig>,
3876        data_to_get: DataToGet,
3877        is_stable: fn(&[f64]) -> bool,
3878    ) -> Result<Option<OsciData>, NanonisError> {
3879        match self.execute(Action::ReadOsci {
3880            signal,
3881            trigger,
3882            data_to_get,
3883            is_stable: Some(is_stable),
3884        })? {
3885            ActionResult::OsciData(osci_data) => Ok(Some(osci_data)),
3886            ActionResult::None => Ok(None),
3887            _ => {
3888                Err(NanonisError::Protocol("Expected oscilloscope data".into()))
3889            }
3890        }
3891    }
3892}
3893
3894/// Simple stability detection functions for oscilloscope windows
3895pub mod stability {
3896    /// Dual threshold stability (current default behavior)
3897    /// Uses relative (1%) OR absolute (50fA) thresholds
3898    pub fn dual_threshold_stability(window: &[f64]) -> bool {
3899        if window.len() < 3 {
3900            return false;
3901        }
3902
3903        let mean = window.iter().sum::<f64>() / window.len() as f64;
3904        let variance = window.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
3905            / window.len() as f64;
3906        let std_dev = variance.sqrt();
3907        let relative_std = std_dev / mean.abs();
3908
3909        // Stable if EITHER relative OR absolute threshold is met
3910        relative_std < 0.05 || std_dev < 50e-15
3911    }
3912
3913    /// Trend analysis stability detector
3914    /// Checks for low slope (no trend) and good signal-to-noise ratio
3915    pub fn trend_analysis_stability(window: &[f64]) -> bool {
3916        if window.len() < 5 {
3917            return false;
3918        }
3919
3920        // Calculate linear regression slope
3921        let n = window.len() as f64;
3922        let x_mean = (n - 1.0) / 2.0; // 0, 1, 2, ... n-1 mean
3923        let y_mean = window.iter().sum::<f64>() / n;
3924
3925        let mut numerator = 0.0;
3926        let mut denominator = 0.0;
3927
3928        for (i, &y) in window.iter().enumerate() {
3929            let x = i as f64;
3930            numerator += (x - x_mean) * (y - y_mean);
3931            denominator += (x - x_mean).powi(2);
3932        }
3933
3934        let slope = if denominator != 0.0 {
3935            numerator / denominator
3936        } else {
3937            0.0
3938        };
3939
3940        // Calculate signal-to-noise ratio
3941        let signal_level = y_mean.abs();
3942        let noise_level = {
3943            let variance =
3944                window.iter().map(|y| (y - y_mean).powi(2)).sum::<f64>() / n;
3945            variance.sqrt()
3946        };
3947
3948        let snr = if noise_level != 0.0 {
3949            signal_level / noise_level
3950        } else {
3951            f64::INFINITY
3952        };
3953
3954        // Thresholds: very low slope and decent SNR
3955        slope.abs() < 0.001 && snr > 10.0
3956    }
3957}
3958
3959/// Statistics about action execution
3960#[derive(Debug, Clone)]
3961pub struct ExecutionStats {
3962    pub total_actions: usize,
3963    pub successful_actions: usize,
3964    pub failed_actions: usize,
3965    pub total_duration: std::time::Duration,
3966}
3967
3968impl ExecutionStats {
3969    pub fn success_rate(&self) -> f64 {
3970        if self.total_actions == 0 {
3971            0.0
3972        } else {
3973            self.successful_actions as f64 / self.total_actions as f64
3974        }
3975    }
3976}
3977
3978/// Extension for ActionDriver with execution statistics
3979impl ActionDriver {
3980    /// Execute chain with detailed statistics
3981    pub fn execute_chain_with_stats(
3982        &mut self,
3983        chain: impl Into<ActionChain>,
3984    ) -> Result<(Vec<ActionResult>, ExecutionStats), NanonisError> {
3985        let chain = chain.into();
3986        let start_time = std::time::Instant::now();
3987        let mut results = Vec::with_capacity(chain.len());
3988        let mut successful = 0;
3989        let failed = 0;
3990
3991        for action in chain.into_iter() {
3992            match self.execute(action) {
3993                Ok(result) => {
3994                    results.push(result);
3995                    successful += 1;
3996                }
3997                Err(e) => {
3998                    // For stats purposes, we want to continue executing but track failures
3999                    // In a real application, you might want to decide whether to continue or stop
4000                    // For now, return the error to maintain proper error handling
4001                    return Err(e);
4002                }
4003            }
4004        }
4005
4006        let stats = ExecutionStats {
4007            total_actions: results.len(),
4008            successful_actions: successful,
4009            failed_actions: failed,
4010            total_duration: start_time.elapsed(),
4011        };
4012
4013        Ok((results, stats))
4014    }
4015}
4016
4017// ==================== Type-Safe Extraction Implementations ====================
4018
4019impl ExpectFromExecution<ActionResult> for ExecutionResult {
4020    fn expect_from_execution(self) -> Result<ActionResult, NanonisError> {
4021        self.into_single()
4022    }
4023}
4024
4025impl ExpectFromExecution<Vec<ActionResult>> for ExecutionResult {
4026    fn expect_from_execution(self) -> Result<Vec<ActionResult>, NanonisError> {
4027        self.into_chain()
4028    }
4029}
4030
4031impl ExpectFromExecution<crate::types::ExperimentData> for ExecutionResult {
4032    fn expect_from_execution(
4033        self,
4034    ) -> Result<crate::types::ExperimentData, NanonisError> {
4035        self.into_experiment_data()
4036    }
4037}
4038
4039impl ExpectFromExecution<crate::types::ChainExperimentData>
4040    for ExecutionResult
4041{
4042    fn expect_from_execution(
4043        self,
4044    ) -> Result<crate::types::ChainExperimentData, NanonisError> {
4045        self.into_chain_experiment_data()
4046    }
4047}
4048
4049impl ExpectFromExecution<f64> for ExecutionResult {
4050    fn expect_from_execution(self) -> Result<f64, NanonisError> {
4051        match self {
4052            ExecutionResult::Single(ActionResult::Value(v)) => Ok(v),
4053            ExecutionResult::Single(ActionResult::Values(mut vs))
4054                if vs.len() == 1 =>
4055            {
4056                Ok(vs.pop().unwrap())
4057            }
4058            _ => Err(NanonisError::Protocol(
4059                "Expected single numeric value".to_string(),
4060            )),
4061        }
4062    }
4063}
4064
4065impl ExpectFromExecution<Vec<f64>> for ExecutionResult {
4066    fn expect_from_execution(self) -> Result<Vec<f64>, NanonisError> {
4067        match self {
4068            ExecutionResult::Single(ActionResult::Values(vs)) => Ok(vs),
4069            ExecutionResult::Single(ActionResult::Value(v)) => Ok(vec![v]),
4070            _ => Err(NanonisError::Protocol(
4071                "Expected numeric values".to_string(),
4072            )),
4073        }
4074    }
4075}
4076
4077impl ExpectFromExecution<bool> for ExecutionResult {
4078    fn expect_from_execution(self) -> Result<bool, NanonisError> {
4079        match self {
4080            ExecutionResult::Single(ActionResult::Status(b)) => Ok(b),
4081            _ => Err(NanonisError::Protocol(
4082                "Expected boolean status".to_string(),
4083            )),
4084        }
4085    }
4086}
4087
4088impl ExpectFromExecution<Position> for ExecutionResult {
4089    fn expect_from_execution(self) -> Result<Position, NanonisError> {
4090        match self {
4091            ExecutionResult::Single(ActionResult::Position(pos)) => Ok(pos),
4092            _ => Err(NanonisError::Protocol(
4093                "Expected position data".to_string(),
4094            )),
4095        }
4096    }
4097}
4098
4099impl ExpectFromExecution<OsciData> for ExecutionResult {
4100    fn expect_from_execution(self) -> Result<OsciData, NanonisError> {
4101        match self {
4102            ExecutionResult::Single(ActionResult::OsciData(data)) => Ok(data),
4103            _ => Err(NanonisError::Protocol(
4104                "Expected oscilloscope data".to_string(),
4105            )),
4106        }
4107    }
4108}
4109
4110impl ExpectFromExecution<crate::types::TipShape> for ExecutionResult {
4111    fn expect_from_execution(
4112        self,
4113    ) -> Result<crate::types::TipShape, NanonisError> {
4114        match self {
4115            ExecutionResult::Single(ActionResult::TipState(tip_state)) => {
4116                Ok(tip_state.shape)
4117            }
4118            _ => Err(NanonisError::Protocol("Expected tip state".to_string())),
4119        }
4120    }
4121}
4122
4123impl ExpectFromExecution<crate::actions::TipState> for ExecutionResult {
4124    fn expect_from_execution(
4125        self,
4126    ) -> Result<crate::actions::TipState, NanonisError> {
4127        match self {
4128            ExecutionResult::Single(ActionResult::TipState(tip_state)) => {
4129                Ok(tip_state)
4130            }
4131            _ => Err(NanonisError::Protocol("Expected tip state".to_string())),
4132        }
4133    }
4134}
4135
4136impl ExpectFromExecution<crate::actions::StableSignal> for ExecutionResult {
4137    fn expect_from_execution(
4138        self,
4139    ) -> Result<crate::actions::StableSignal, NanonisError> {
4140        match self {
4141            ExecutionResult::Single(ActionResult::StableSignal(
4142                stable_signal,
4143            )) => Ok(stable_signal),
4144            _ => Err(NanonisError::Protocol(
4145                "Expected stable signal".to_string(),
4146            )),
4147        }
4148    }
4149}
4150
4151impl ExpectFromExecution<crate::actions::TCPReaderStatus> for ExecutionResult {
4152    fn expect_from_execution(
4153        self,
4154    ) -> Result<crate::actions::TCPReaderStatus, NanonisError> {
4155        match self {
4156            ExecutionResult::Single(ActionResult::TCPReaderStatus(
4157                tcp_status,
4158            )) => Ok(tcp_status),
4159            _ => Err(NanonisError::Protocol(
4160                "Expected TCP reader status".to_string(),
4161            )),
4162        }
4163    }
4164}
4165
4166impl ExpectFromExecution<crate::actions::StabilityResult> for ExecutionResult {
4167    fn expect_from_execution(
4168        self,
4169    ) -> Result<crate::actions::StabilityResult, NanonisError> {
4170        match self {
4171            ExecutionResult::Single(ActionResult::StabilityResult(
4172                stability_result,
4173            )) => Ok(stability_result),
4174            _ => Err(NanonisError::Protocol(
4175                "Expected stability result".to_string(),
4176            )),
4177        }
4178    }
4179}
4180
4181impl ExpectFromExecution<Vec<String>> for ExecutionResult {
4182    fn expect_from_execution(self) -> Result<Vec<String>, NanonisError> {
4183        match self {
4184            ExecutionResult::Single(ActionResult::Text(text)) => Ok(text),
4185            _ => Err(NanonisError::Protocol("Expected text data".to_string())),
4186        }
4187    }
4188}
4189
4190impl Drop for ActionDriver {
4191    fn drop(&mut self) {
4192        log::info!("ActionDriver cleanup starting...");
4193
4194        // Clean up TCP buffering first
4195        if let Some(mut reader) = self.tcp_reader.take() {
4196            let final_data = reader.get_all_data();
4197            let _ = reader.stop(); // Ignore errors during cleanup
4198            log::info!(
4199                "Stopped TCP buffering, collected {} frames",
4200                final_data.len()
4201            );
4202        }
4203
4204        // Disable safe tip protection before cleanup
4205        log::info!("Disabling safe tip protection...");
4206        if let Err(e) = self.client_mut().safe_tip_on_off_set(false) {
4207            log::warn!("Failed to disable safe tip: {}", e);
4208        }
4209
4210        // Perform safe shutdown sequence with blocking operations
4211        log::info!("Performing safe withdrawal...");
4212        let withdraw_result = self.execute_chain(vec![
4213            Action::Withdraw {
4214                wait_until_finished: true, // Make it blocking
4215                timeout: Duration::from_secs(5),
4216            },
4217            Action::MoveMotorAxis {
4218                direction: crate::MotorDirection::ZMinus,
4219                steps: 10,
4220                blocking: false,
4221            },
4222        ]);
4223
4224        if let Err(e) = withdraw_result {
4225            log::warn!("Cleanup withdrawal failed: {}", e);
4226        } else {
4227            log::info!("Safe withdrawal completed");
4228        }
4229
4230        log::info!("ActionDriver cleanup complete");
4231    }
4232}
4233
4234#[cfg(test)]
4235mod tests {
4236    use std::time::Duration;
4237
4238    use super::*;
4239    // Note: These tests will fail without actual Nanonis hardware
4240    // They're included to show the intended interface
4241
4242    #[test]
4243    fn test_action_translator_interface() {
4244        // This test shows how the translator would be used
4245        // It will fail without actual hardware, but demonstrates the API
4246
4247        let driver_result = ActionDriver::new("127.0.0.1", 6501);
4248        match driver_result {
4249            Ok(mut driver) => {
4250                // Test single action
4251                let action = Action::ReadBias;
4252                let _result = driver.execute(action);
4253
4254                // With real hardware, this would succeed
4255                // Without hardware, it will error, which is expected
4256
4257                // Test chain
4258                let chain = ActionChain::new(vec![
4259                    Action::ReadBias,
4260                    Action::Wait {
4261                        duration: Duration::from_millis(500),
4262                    },
4263                    Action::SetBias { voltage: 1.0 },
4264                ]);
4265
4266                let _chain_result = driver.execute_chain(chain);
4267            }
4268            Err(_) => {
4269                // Expected when signals can't be discovered
4270                println!("Signal discovery failed - this is expected without hardware");
4271            }
4272        }
4273    }
4274}