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