Skip to main content

nanonis_rs/client/
z_spectr.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4
5/// Return type for Z spectroscopy start operation (channel names, data, bias values)
6pub type ZSpectroscopyResult = (Vec<String>, Vec<Vec<f32>>, Vec<f32>);
7
8impl NanonisClient {
9    /// Open the Z Spectroscopy module.
10    ///
11    /// Opens and initializes the Z Spectroscopy module for distance-dependent
12    /// measurements. This must be called before performing spectroscopy operations.
13    ///
14    /// # Errors
15    /// Returns `NanonisError` if communication fails or module cannot be opened.
16    ///
17    /// # Examples
18    /// ```no_run
19    /// use nanonis_rs::NanonisClient;
20    ///
21    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
22    ///
23    /// // Open Z spectroscopy module
24    /// client.z_spectr_open()?;
25    /// println!("Z Spectroscopy module opened");
26    /// # Ok::<(), Box<dyn std::error::Error>>(())
27    /// ```
28    pub fn z_spectr_open(&mut self) -> Result<(), NanonisError> {
29        self.quick_send("ZSpectr.Open", vec![], vec![], vec![])?;
30        Ok(())
31    }
32
33    /// Start a Z spectroscopy measurement.
34    ///
35    /// Initiates a Z spectroscopy measurement with the configured parameters.
36    /// The tip is moved through a range of Z positions while recording selected channels.
37    ///
38    /// # Arguments
39    /// * `get_data` - If `true`, returns measurement data; if `false`, only starts measurement
40    /// * `save_base_name` - Base filename for saving data (empty for no change)
41    ///
42    /// # Returns
43    /// If `get_data` is true, returns a tuple containing:
44    /// - `Vec<String>` - Channel names
45    /// - `Vec<Vec<f32>>` - 2D measurement data \[rows\]\[columns\]
46    /// - `Vec<f32>` - Fixed parameters and settings
47    ///
48    /// # Errors
49    /// Returns `NanonisError` if communication fails or measurement cannot start.
50    ///
51    /// # Examples
52    /// ```no_run
53    /// use nanonis_rs::NanonisClient;
54    ///
55    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
56    ///
57    /// // Start measurement and get data
58    /// let (channels, data, params) = client.z_spectr_start(true, "approach_001")?;
59    /// println!("Recorded {} channels with {} points", channels.len(), data.len());
60    ///
61    /// // Just start measurement without getting data
62    /// let (_, _, _) = client.z_spectr_start(false, "")?;
63    /// # Ok::<(), Box<dyn std::error::Error>>(())
64    /// ```
65    pub fn z_spectr_start(
66        &mut self,
67        get_data: bool,
68        save_base_name: &str,
69    ) -> Result<ZSpectroscopyResult, NanonisError> {
70        let get_data_flag = if get_data { 1u32 } else { 0u32 };
71
72        let result = self.quick_send(
73            "ZSpectr.Start",
74            vec![
75                NanonisValue::U32(get_data_flag),
76                NanonisValue::String(save_base_name.to_string()),
77            ],
78            vec!["I", "+*c"],
79            vec!["i", "i", "*+c", "i", "i", "2f", "i", "*f"],
80        )?;
81
82        if result.len() >= 8 {
83            let channel_names = result[2].as_string_array()?.to_vec();
84            let rows = result[3].as_i32()? as usize;
85            let cols = result[4].as_i32()? as usize;
86
87            // Parse 2D data array
88            let flat_data = result[5].as_f32_array()?;
89            let mut data_2d = Vec::with_capacity(rows);
90            for row in 0..rows {
91                let start_idx = row * cols;
92                let end_idx = start_idx + cols;
93                if end_idx <= flat_data.len() {
94                    data_2d.push(flat_data[start_idx..end_idx].to_vec());
95                } else {
96                    return Err(NanonisError::Protocol(
97                        "Truncated Z-spectroscopy data".to_string(),
98                    ));
99                }
100            }
101
102            let parameters = result[7].as_f32_array()?.to_vec();
103            Ok((channel_names, data_2d, parameters))
104        } else {
105            Err(NanonisError::Protocol(
106                "Invalid Z spectroscopy start response".to_string(),
107            ))
108        }
109    }
110
111    /// Stop the current Z spectroscopy measurement.
112    ///
113    /// Immediately stops any running Z spectroscopy measurement and returns
114    /// the tip to its original position.
115    ///
116    /// # Errors
117    /// Returns `NanonisError` if communication fails.
118    ///
119    /// # Examples
120    /// ```no_run
121    /// use nanonis_rs::NanonisClient;
122    ///
123    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
124    ///
125    /// // Start a measurement
126    /// let (_, _, _) = client.z_spectr_start(false, "")?;
127    ///
128    /// // Stop it after some condition
129    /// client.z_spectr_stop()?;
130    /// println!("Z spectroscopy stopped");
131    /// # Ok::<(), Box<dyn std::error::Error>>(())
132    /// ```
133    pub fn z_spectr_stop(&mut self) -> Result<(), NanonisError> {
134        self.quick_send("ZSpectr.Stop", vec![], vec![], vec![])?;
135        Ok(())
136    }
137
138    /// Get the status of Z spectroscopy measurement.
139    ///
140    /// Returns whether a Z spectroscopy measurement is currently running.
141    ///
142    /// # Returns
143    /// `true` if measurement is running, `false` if stopped.
144    ///
145    /// # Errors
146    /// Returns `NanonisError` if communication fails.
147    ///
148    /// # Examples
149    /// ```no_run
150    /// use nanonis_rs::NanonisClient;
151    ///
152    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
153    ///
154    /// if client.z_spectr_status_get()? {
155    ///     println!("Z spectroscopy is running");
156    /// } else {
157    ///     println!("Z spectroscopy is stopped");
158    /// }
159    /// # Ok::<(), Box<dyn std::error::Error>>(())
160    /// ```
161    pub fn z_spectr_status_get(&mut self) -> Result<bool, NanonisError> {
162        let result = self.quick_send("ZSpectr.StatusGet", vec![], vec![], vec!["I"])?;
163
164        match result.first() {
165            Some(value) => Ok(value.as_u32()? == 1),
166            None => Err(NanonisError::Protocol(
167                "No Z spectroscopy status returned".to_string(),
168            )),
169        }
170    }
171
172    /// Set the channels to record during Z spectroscopy.
173    ///
174    /// Configures which signals will be recorded during the Z spectroscopy measurement.
175    /// Channel indexes correspond to the 24 signals assigned in the Signals Manager (0-23).
176    ///
177    /// # Arguments
178    /// * `channel_indexes` - Vector of channel indexes to record (0-23)
179    ///
180    /// # Errors
181    /// Returns `NanonisError` if communication fails or invalid channel indexes provided.
182    ///
183    /// # Examples
184    /// ```no_run
185    /// use nanonis_rs::NanonisClient;
186    ///
187    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
188    ///
189    /// // Record current (0), Z position (1), and bias voltage (2)
190    /// client.z_spectr_chs_set(vec![0, 1, 2])?;
191    ///
192    /// // Record more comprehensive dataset
193    /// client.z_spectr_chs_set(vec![0, 1, 2, 3, 4, 5])?;
194    /// # Ok::<(), Box<dyn std::error::Error>>(())
195    /// ```
196    pub fn z_spectr_chs_set(&mut self, channel_indexes: Vec<i32>) -> Result<(), NanonisError> {
197        self.quick_send(
198            "ZSpectr.ChsSet",
199            vec![NanonisValue::ArrayI32(channel_indexes)],
200            vec!["+*i"],
201            vec![],
202        )?;
203        Ok(())
204    }
205
206    /// Get the channels configured for Z spectroscopy recording.
207    ///
208    /// Returns the channel indexes and names that will be recorded during measurements.
209    ///
210    /// # Returns
211    /// A tuple containing:
212    /// - `Vec<i32>` - Channel indexes (0-23 for Signals Manager slots)
213    /// - `Vec<String>` - Channel names corresponding to the indexes
214    ///
215    /// # Errors
216    /// Returns `NanonisError` if communication fails.
217    ///
218    /// # Examples
219    /// ```no_run
220    /// use nanonis_rs::NanonisClient;
221    ///
222    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
223    ///
224    /// let (indexes, names) = client.z_spectr_chs_get()?;
225    /// println!("Recording {} channels:", indexes.len());
226    /// for (idx, name) in indexes.iter().zip(names.iter()) {
227    ///     println!("  Channel {}: {}", idx, name);
228    /// }
229    /// # Ok::<(), Box<dyn std::error::Error>>(())
230    /// ```
231    pub fn z_spectr_chs_get(&mut self) -> Result<(Vec<i32>, Vec<String>), NanonisError> {
232        let result = self.quick_send(
233            "ZSpectr.ChsGet",
234            vec![],
235            vec![],
236            vec!["i", "*i", "i", "i", "*+c"],
237        )?;
238
239        if result.len() >= 5 {
240            let channel_indexes = result[1].as_i32_array()?.to_vec();
241            let channel_names = result[4].as_string_array()?.to_vec();
242            Ok((channel_indexes, channel_names))
243        } else {
244            Err(NanonisError::Protocol(
245                "Invalid Z spectroscopy channels response".to_string(),
246            ))
247        }
248    }
249
250    /// Set the Z range for spectroscopy measurements.
251    ///
252    /// Configures the Z offset and sweep distance for the spectroscopy measurement.
253    /// The tip will move from (offset - distance/2) to (offset + distance/2).
254    ///
255    /// # Arguments
256    /// * `z_offset_m` - Z offset position in meters (center of sweep)
257    /// * `z_sweep_distance_m` - Total sweep distance in meters
258    ///
259    /// # Errors
260    /// Returns `NanonisError` if communication fails or invalid range parameters.
261    ///
262    /// # Examples
263    /// ```no_run
264    /// use nanonis_rs::NanonisClient;
265    ///
266    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
267    ///
268    /// // Sweep ±5 nm around current position
269    /// client.z_spectr_range_set(0.0, 10e-9)?;
270    ///
271    /// // Sweep from current position up to +20 nm
272    /// client.z_spectr_range_set(10e-9, 20e-9)?;
273    /// # Ok::<(), Box<dyn std::error::Error>>(())
274    /// ```
275    pub fn z_spectr_range_set(
276        &mut self,
277        z_offset_m: f32,
278        z_sweep_distance_m: f32,
279    ) -> Result<(), NanonisError> {
280        self.quick_send(
281            "ZSpectr.RangeSet",
282            vec![
283                NanonisValue::F32(z_offset_m),
284                NanonisValue::F32(z_sweep_distance_m),
285            ],
286            vec!["f", "f"],
287            vec![],
288        )?;
289        Ok(())
290    }
291
292    /// Get the current Z range configuration for spectroscopy.
293    ///
294    /// Returns the configured Z offset and sweep distance.
295    ///
296    /// # Returns
297    /// A tuple containing:
298    /// - `f32` - Z offset in meters (center position)
299    /// - `f32` - Z sweep distance in meters (total range)
300    ///
301    /// # Errors
302    /// Returns `NanonisError` if communication fails.
303    ///
304    /// # Examples
305    /// ```no_run
306    /// use nanonis_rs::NanonisClient;
307    ///
308    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
309    ///
310    /// let (offset, distance) = client.z_spectr_range_get()?;
311    /// println!("Z sweep: {:.1} nm ± {:.1} nm", offset * 1e9, distance * 1e9 / 2.0);
312    /// # Ok::<(), Box<dyn std::error::Error>>(())
313    /// ```
314    pub fn z_spectr_range_get(&mut self) -> Result<(f32, f32), NanonisError> {
315        let result = self.quick_send("ZSpectr.RangeGet", vec![], vec![], vec!["f", "f"])?;
316
317        if result.len() >= 2 {
318            Ok((result[0].as_f32()?, result[1].as_f32()?))
319        } else {
320            Err(NanonisError::Protocol(
321                "Invalid Z spectroscopy range response".to_string(),
322            ))
323        }
324    }
325
326    /// Set the timing parameters for Z spectroscopy.
327    ///
328    /// Configures timing-related parameters that control the speed and quality
329    /// of the Z spectroscopy measurement.
330    ///
331    /// # Arguments
332    /// * `z_averaging_time_s` - Time to average signals at each Z position
333    /// * `initial_settling_time_s` - Initial settling time before measurement
334    /// * `maximum_slew_rate_vdivs` - Maximum slew rate in V/s
335    /// * `settling_time_s` - Settling time between measurement points
336    /// * `integration_time_s` - Integration time for each measurement point
337    /// * `end_settling_time_s` - Settling time at the end of sweep
338    ///
339    /// # Errors
340    /// Returns `NanonisError` if communication fails or invalid timing parameters.
341    ///
342    /// # Examples
343    /// ```no_run
344    /// use nanonis_rs::NanonisClient;
345    ///
346    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
347    ///
348    /// // Fast spectroscopy settings
349    /// client.z_spectr_timing_set(0.01, 0.1, 1000.0, 0.01, 0.01, 0.1)?;
350    ///
351    /// // High-quality slow spectroscopy
352    /// client.z_spectr_timing_set(0.1, 0.5, 100.0, 0.05, 0.05, 0.2)?;
353    /// # Ok::<(), Box<dyn std::error::Error>>(())
354    /// ```
355    pub fn z_spectr_timing_set(
356        &mut self,
357        z_averaging_time_s: f32,
358        initial_settling_time_s: f32,
359        maximum_slew_rate_vdivs: f32,
360        settling_time_s: f32,
361        integration_time_s: f32,
362        end_settling_time_s: f32,
363    ) -> Result<(), NanonisError> {
364        self.quick_send(
365            "ZSpectr.TimingSet",
366            vec![
367                NanonisValue::F32(z_averaging_time_s),
368                NanonisValue::F32(initial_settling_time_s),
369                NanonisValue::F32(maximum_slew_rate_vdivs),
370                NanonisValue::F32(settling_time_s),
371                NanonisValue::F32(integration_time_s),
372                NanonisValue::F32(end_settling_time_s),
373            ],
374            vec!["f", "f", "f", "f", "f", "f"],
375            vec![],
376        )?;
377        Ok(())
378    }
379
380    /// Get the current timing parameters for Z spectroscopy.
381    ///
382    /// Returns all timing-related configuration parameters.
383    ///
384    /// # Returns
385    /// A tuple containing:
386    /// - `f32` - Z averaging time (s)
387    /// - `f32` - Initial settling time (s)
388    /// - `f32` - Maximum slew rate (V/s)
389    /// - `f32` - Settling time (s)
390    /// - `f32` - Integration time (s)
391    /// - `f32` - End settling time (s)
392    ///
393    /// # Errors
394    /// Returns `NanonisError` if communication fails.
395    ///
396    /// # Examples
397    /// ```no_run
398    /// use nanonis_rs::NanonisClient;
399    ///
400    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
401    ///
402    /// let (z_avg, init_settle, slew_rate, settle, integrate, end_settle) =
403    ///     client.z_spectr_timing_get()?;
404    /// println!("Integration time: {:.3} s, settling: {:.3} s", integrate, settle);
405    /// # Ok::<(), Box<dyn std::error::Error>>(())
406    /// ```
407    pub fn z_spectr_timing_get(&mut self) -> Result<(f32, f32, f32, f32, f32, f32), NanonisError> {
408        let result = self.quick_send(
409            "ZSpectr.TimingGet",
410            vec![],
411            vec![],
412            vec!["f", "f", "f", "f", "f", "f"],
413        )?;
414
415        if result.len() >= 6 {
416            Ok((
417                result[0].as_f32()?,
418                result[1].as_f32()?,
419                result[2].as_f32()?,
420                result[3].as_f32()?,
421                result[4].as_f32()?,
422                result[5].as_f32()?,
423            ))
424        } else {
425            Err(NanonisError::Protocol(
426                "Invalid Z spectroscopy timing response".to_string(),
427            ))
428        }
429    }
430
431    /// Set the retraction parameters for tip protection during Z spectroscopy.
432    ///
433    /// Configures automatic tip retraction based on signal thresholds to prevent
434    /// tip crashes during approach spectroscopy.
435    ///
436    /// # Arguments
437    /// * `enable` - Enable/disable automatic retraction
438    /// * `threshold` - Signal threshold value for retraction trigger
439    /// * `signal_index` - Index of signal to monitor (0-23)
440    /// * `comparison` - Comparison type: 0=greater than, 1=less than
441    ///
442    /// # Errors
443    /// Returns `NanonisError` if communication fails or invalid parameters.
444    ///
445    /// # Examples
446    /// ```no_run
447    /// use nanonis_rs::NanonisClient;
448    ///
449    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
450    ///
451    /// // Enable retraction when current exceeds 1 nA (signal 0, greater than)
452    /// client.z_spectr_retract_set(true, 1e-9, 0, 0)?;
453    ///
454    /// // Disable retraction
455    /// client.z_spectr_retract_set(false, 0.0, 0, 0)?;
456    /// # Ok::<(), Box<dyn std::error::Error>>(())
457    /// ```
458    pub fn z_spectr_retract_set(
459        &mut self,
460        enable: bool,
461        threshold: f32,
462        signal_index: i32,
463        comparison: u16,
464    ) -> Result<(), NanonisError> {
465        let enable_flag = if enable { 1u16 } else { 0u16 };
466
467        self.quick_send(
468            "ZSpectr.RetractSet",
469            vec![
470                NanonisValue::U16(enable_flag),
471                NanonisValue::F32(threshold),
472                NanonisValue::I32(signal_index),
473                NanonisValue::U16(comparison),
474            ],
475            vec!["H", "f", "i", "H"],
476            vec![],
477        )?;
478        Ok(())
479    }
480
481    /// Get the current retraction configuration for Z spectroscopy.
482    ///
483    /// Returns the tip protection settings that prevent crashes during measurements.
484    ///
485    /// # Returns
486    /// A tuple containing:
487    /// - `bool` - Retraction enabled/disabled
488    /// - `f32` - Threshold value for retraction
489    /// - `i32` - Signal index being monitored
490    /// - `u16` - Comparison type (0=greater, 1=less than)
491    ///
492    /// # Errors
493    /// Returns `NanonisError` if communication fails.
494    ///
495    /// # Examples
496    /// ```no_run
497    /// use nanonis_rs::NanonisClient;
498    ///
499    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
500    ///
501    /// let (enabled, threshold, signal_idx, comparison) = client.z_spectr_retract_get()?;
502    /// if enabled {
503    ///     let comp_str = if comparison == 0 { ">" } else { "<" };
504    ///     println!("Retraction: signal[{}] {} {:.3e}", signal_idx, comp_str, threshold);
505    /// }
506    /// # Ok::<(), Box<dyn std::error::Error>>(())
507    /// ```
508    pub fn z_spectr_retract_get(&mut self) -> Result<(bool, f32, i32, u16), NanonisError> {
509        let result = self.quick_send(
510            "ZSpectr.RetractGet",
511            vec![],
512            vec![],
513            vec!["H", "f", "i", "H"],
514        )?;
515
516        if result.len() >= 4 {
517            let enabled = result[0].as_u16()? == 1;
518            let threshold = result[1].as_f32()?;
519            let signal_index = result[2].as_i32()?;
520            let comparison = result[3].as_u16()?;
521            Ok((enabled, threshold, signal_index, comparison))
522        } else {
523            Err(NanonisError::Protocol(
524                "Invalid Z spectroscopy retract response".to_string(),
525            ))
526        }
527    }
528
529    /// Set the Z spectroscopy properties.
530    ///
531    /// # Arguments
532    /// * `backward_sweep` - 0=no change, 1=enable backward sweep, 2=disable
533    /// * `num_points` - Number of points (0=no change)
534    /// * `num_sweeps` - Number of sweeps to average (0=no change)
535    /// * `autosave` - 0=no change, 1=enable autosave, 2=disable
536    /// * `show_save_dialog` - 0=no change, 1=show dialog, 2=don't show
537    /// * `save_all` - 0=no change, 1=save individual sweeps, 2=don't save
538    ///
539    /// # Errors
540    /// Returns `NanonisError` if communication fails.
541    pub fn z_spectr_props_set(
542        &mut self,
543        backward_sweep: u16,
544        num_points: i32,
545        num_sweeps: u16,
546        autosave: u16,
547        show_save_dialog: u16,
548        save_all: u16,
549    ) -> Result<(), NanonisError> {
550        self.quick_send(
551            "ZSpectr.PropsSet",
552            vec![
553                NanonisValue::U16(backward_sweep),
554                NanonisValue::I32(num_points),
555                NanonisValue::U16(num_sweeps),
556                NanonisValue::U16(autosave),
557                NanonisValue::U16(show_save_dialog),
558                NanonisValue::U16(save_all),
559            ],
560            vec!["H", "i", "H", "H", "H", "H"],
561            vec![],
562        )?;
563        Ok(())
564    }
565
566    /// Get the Z spectroscopy properties.
567    ///
568    /// Returns the current property configuration.
569    ///
570    /// # Returns
571    /// A tuple containing:
572    /// - `bool` - Backward sweep enabled
573    /// - `i32` - Number of points
574    /// - `u16` - Number of sweeps to average
575    /// - `bool` - Autosave enabled
576    /// - `bool` - Show save dialog
577    /// - `bool` - Save all individual sweeps
578    ///
579    /// # Errors
580    /// Returns `NanonisError` if communication fails.
581    ///
582    /// # Examples
583    /// ```no_run
584    /// use nanonis_rs::NanonisClient;
585    ///
586    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
587    ///
588    /// let (backward, points, sweeps, autosave, dialog, save_all) =
589    ///     client.z_spectr_props_get()?;
590    /// println!("Points: {}, Sweeps: {}, Backward: {}", points, sweeps, backward);
591    /// # Ok::<(), Box<dyn std::error::Error>>(())
592    /// ```
593    pub fn z_spectr_props_get(
594        &mut self,
595    ) -> Result<(bool, i32, u16, bool, bool, bool), NanonisError> {
596        let result = self.quick_send(
597            "ZSpectr.PropsGet",
598            vec![],
599            vec![],
600            vec!["H", "i", "H", "I", "I", "I"],
601        )?;
602
603        if result.len() >= 6 {
604            Ok((
605                result[0].as_u16()? != 0,
606                result[1].as_i32()?,
607                result[2].as_u16()?,
608                result[3].as_u32()? != 0,
609                result[4].as_u32()? != 0,
610                result[5].as_u32()? != 0,
611            ))
612        } else {
613            Err(NanonisError::Protocol(
614                "Invalid Z spectroscopy props response".to_string(),
615            ))
616        }
617    }
618
619    /// Set the advanced Z spectroscopy properties.
620    ///
621    /// # Arguments
622    /// * `time_between_sweeps_s` - Time between forward and backward sweep
623    /// * `record_final_z` - 0=no change, 1=on, 2=off
624    /// * `lockin_run` - 0=no change, 1=on, 2=off
625    /// * `reset_z` - 0=no change, 1=on, 2=off
626    ///
627    /// # Errors
628    /// Returns `NanonisError` if communication fails.
629    pub fn z_spectr_adv_props_set(
630        &mut self,
631        time_between_sweeps_s: f32,
632        record_final_z: u16,
633        lockin_run: u16,
634        reset_z: u16,
635    ) -> Result<(), NanonisError> {
636        self.quick_send(
637            "ZSpectr.AdvPropsSet",
638            vec![
639                NanonisValue::F32(time_between_sweeps_s),
640                NanonisValue::U16(record_final_z),
641                NanonisValue::U16(lockin_run),
642                NanonisValue::U16(reset_z),
643            ],
644            vec!["f", "H", "H", "H"],
645            vec![],
646        )?;
647        Ok(())
648    }
649
650    /// Get the advanced Z spectroscopy properties.
651    ///
652    /// # Returns
653    /// Tuple of (time_between_sweeps, record_final_z, lockin_run, reset_z).
654    ///
655    /// # Errors
656    /// Returns `NanonisError` if communication fails.
657    pub fn z_spectr_adv_props_get(&mut self) -> Result<(f32, bool, bool, bool), NanonisError> {
658        let result = self.quick_send(
659            "ZSpectr.AdvPropsGet",
660            vec![],
661            vec![],
662            vec!["f", "H", "H", "H"],
663        )?;
664
665        Ok((
666            result[0].as_f32()?,
667            result[1].as_u16()? != 0,
668            result[2].as_u16()? != 0,
669            result[3].as_u16()? != 0,
670        ))
671    }
672
673    /// Set the retract delay.
674    ///
675    /// # Arguments
676    /// * `delay_s` - Delay in seconds between forward and backward sweep
677    ///
678    /// # Errors
679    /// Returns `NanonisError` if communication fails.
680    pub fn z_spectr_retract_delay_set(&mut self, delay_s: f32) -> Result<(), NanonisError> {
681        self.quick_send(
682            "ZSpectr.RetractDelaySet",
683            vec![NanonisValue::F32(delay_s)],
684            vec!["f"],
685            vec![],
686        )?;
687        Ok(())
688    }
689
690    /// Get the retract delay.
691    ///
692    /// # Returns
693    /// Delay in seconds.
694    ///
695    /// # Errors
696    /// Returns `NanonisError` if communication fails.
697    pub fn z_spectr_retract_delay_get(&mut self) -> Result<f32, NanonisError> {
698        let result = self.quick_send("ZSpectr.RetractDelayGet", vec![], vec![], vec!["f"])?;
699        if result.is_empty() {
700            return Err(NanonisError::Protocol(
701                "Empty response from ZSpectr.RetractDelayGet".to_string(),
702            ));
703        }
704        result[0].as_f32()
705    }
706
707    /// Set the second retraction condition.
708    ///
709    /// # Arguments
710    /// * `condition` - 0=no change, 1=disabled, 2=OR, 3=AND, 4=THEN
711    /// * `threshold` - Threshold value
712    /// * `signal_index` - Signal index (0-127, -1 for no change)
713    /// * `comparison` - 0=greater than, 1=less than, 2=no change
714    ///
715    /// # Errors
716    /// Returns `NanonisError` if communication fails.
717    pub fn z_spectr_retract_second_set(
718        &mut self,
719        condition: i32,
720        threshold: f32,
721        signal_index: i32,
722        comparison: u16,
723    ) -> Result<(), NanonisError> {
724        self.quick_send(
725            "ZSpectr.RetractSecondSet",
726            vec![
727                NanonisValue::I32(condition),
728                NanonisValue::F32(threshold),
729                NanonisValue::I32(signal_index),
730                NanonisValue::U16(comparison),
731            ],
732            vec!["i", "f", "i", "H"],
733            vec![],
734        )?;
735        Ok(())
736    }
737
738    /// Get the second retraction condition.
739    ///
740    /// # Returns
741    /// Tuple of (condition, threshold, signal_index, comparison).
742    ///
743    /// # Errors
744    /// Returns `NanonisError` if communication fails.
745    pub fn z_spectr_retract_second_get(&mut self) -> Result<(i32, f32, i32, u16), NanonisError> {
746        let result = self.quick_send(
747            "ZSpectr.RetractSecondGet",
748            vec![],
749            vec![],
750            vec!["i", "f", "i", "H"],
751        )?;
752        if result.len() < 4 {
753            return Err(NanonisError::Protocol(
754                "Truncated response from ZSpectr.RetractSecondGet".to_string(),
755            ));
756        }
757        Ok((
758            result[0].as_i32()?,
759            result[1].as_f32()?,
760            result[2].as_i32()?,
761            result[3].as_u16()?,
762        ))
763    }
764
765    /// Set the digital synchronization mode.
766    ///
767    /// # Arguments
768    /// * `dig_sync` - 0=no change, 1=off, 2=TTL sync, 3=pulse sequence
769    ///
770    /// # Errors
771    /// Returns `NanonisError` if communication fails.
772    pub fn z_spectr_dig_sync_set(&mut self, dig_sync: u16) -> Result<(), NanonisError> {
773        self.quick_send(
774            "ZSpectr.DigSyncSet",
775            vec![NanonisValue::U16(dig_sync)],
776            vec!["H"],
777            vec![],
778        )?;
779        Ok(())
780    }
781
782    /// Get the digital synchronization mode.
783    ///
784    /// # Returns
785    /// Sync mode (0=off, 1=TTL sync, 2=pulse sequence).
786    ///
787    /// # Errors
788    /// Returns `NanonisError` if communication fails.
789    pub fn z_spectr_dig_sync_get(&mut self) -> Result<u16, NanonisError> {
790        let result = self.quick_send("ZSpectr.DigSyncGet", vec![], vec![], vec!["H"])?;
791        if result.is_empty() {
792            return Err(NanonisError::Protocol(
793                "Empty response from ZSpectr.DigSyncGet".to_string(),
794            ));
795        }
796        result[0].as_u16()
797    }
798
799    /// Set the TTL synchronization parameters.
800    ///
801    /// # Arguments
802    /// * `ttl_line` - 0=no change, 1-4=HS line number
803    /// * `polarity` - 0=no change, 1=low active, 2=high active
804    /// * `time_to_on_s` - Time to wait before activation
805    /// * `on_duration_s` - Duration of activation
806    ///
807    /// # Errors
808    /// Returns `NanonisError` if communication fails.
809    pub fn z_spectr_ttl_sync_set(
810        &mut self,
811        ttl_line: u16,
812        polarity: u16,
813        time_to_on_s: f32,
814        on_duration_s: f32,
815    ) -> Result<(), NanonisError> {
816        self.quick_send(
817            "ZSpectr.TTLSyncSet",
818            vec![
819                NanonisValue::U16(ttl_line),
820                NanonisValue::U16(polarity),
821                NanonisValue::F32(time_to_on_s),
822                NanonisValue::F32(on_duration_s),
823            ],
824            vec!["H", "H", "f", "f"],
825            vec![],
826        )?;
827        Ok(())
828    }
829
830    /// Get the TTL synchronization parameters.
831    ///
832    /// # Returns
833    /// Tuple of (ttl_line, polarity, time_to_on_s, on_duration_s).
834    ///
835    /// # Errors
836    /// Returns `NanonisError` if communication fails.
837    pub fn z_spectr_ttl_sync_get(&mut self) -> Result<(u16, u16, f32, f32), NanonisError> {
838        let result = self.quick_send(
839            "ZSpectr.TTLSyncGet",
840            vec![],
841            vec![],
842            vec!["H", "H", "f", "f"],
843        )?;
844        if result.len() < 4 {
845            return Err(NanonisError::Protocol(
846                "Truncated response from ZSpectr.TTLSyncGet".to_string(),
847            ));
848        }
849        Ok((
850            result[0].as_u16()?,
851            result[1].as_u16()?,
852            result[2].as_f32()?,
853            result[3].as_f32()?,
854        ))
855    }
856
857    /// Set the pulse sequence synchronization.
858    ///
859    /// # Arguments
860    /// * `pulse_seq_nr` - Pulse sequence number (0=no change)
861    /// * `num_periods` - Number of periods
862    ///
863    /// # Errors
864    /// Returns `NanonisError` if communication fails.
865    pub fn z_spectr_pulse_seq_sync_set(
866        &mut self,
867        pulse_seq_nr: u16,
868        num_periods: u32,
869    ) -> Result<(), NanonisError> {
870        self.quick_send(
871            "ZSpectr.PulseSeqSyncSet",
872            vec![
873                NanonisValue::U16(pulse_seq_nr),
874                NanonisValue::U32(num_periods),
875            ],
876            vec!["H", "I"],
877            vec![],
878        )?;
879        Ok(())
880    }
881
882    /// Get the pulse sequence synchronization.
883    ///
884    /// # Returns
885    /// Tuple of (pulse_seq_nr, num_periods).
886    ///
887    /// # Errors
888    /// Returns `NanonisError` if communication fails.
889    pub fn z_spectr_pulse_seq_sync_get(&mut self) -> Result<(u16, u32), NanonisError> {
890        let result = self.quick_send("ZSpectr.PulseSeqSyncGet", vec![], vec![], vec!["H", "I"])?;
891        if result.len() < 2 {
892            return Err(NanonisError::Protocol(
893                "Truncated response from ZSpectr.PulseSeqSyncGet".to_string(),
894            ));
895        }
896        Ok((result[0].as_u16()?, result[1].as_u32()?))
897    }
898}