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                data_2d.push(flat_data[start_idx..end_idx].to_vec());
94            }
95
96            let parameters = result[7].as_f32_array()?.to_vec();
97            Ok((channel_names, data_2d, parameters))
98        } else {
99            Err(NanonisError::Protocol(
100                "Invalid Z spectroscopy start response".to_string(),
101            ))
102        }
103    }
104
105    /// Stop the current Z spectroscopy measurement.
106    ///
107    /// Immediately stops any running Z spectroscopy measurement and returns
108    /// the tip to its original position.
109    ///
110    /// # Errors
111    /// Returns `NanonisError` if communication fails.
112    ///
113    /// # Examples
114    /// ```no_run
115    /// use nanonis_rs::NanonisClient;
116    ///
117    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
118    ///
119    /// // Start a measurement
120    /// let (_, _, _) = client.z_spectr_start(false, "")?;
121    ///
122    /// // Stop it after some condition
123    /// client.z_spectr_stop()?;
124    /// println!("Z spectroscopy stopped");
125    /// # Ok::<(), Box<dyn std::error::Error>>(())
126    /// ```
127    pub fn z_spectr_stop(&mut self) -> Result<(), NanonisError> {
128        self.quick_send("ZSpectr.Stop", vec![], vec![], vec![])?;
129        Ok(())
130    }
131
132    /// Get the status of Z spectroscopy measurement.
133    ///
134    /// Returns whether a Z spectroscopy measurement is currently running.
135    ///
136    /// # Returns
137    /// `true` if measurement is running, `false` if stopped.
138    ///
139    /// # Errors
140    /// Returns `NanonisError` if communication fails.
141    ///
142    /// # Examples
143    /// ```no_run
144    /// use nanonis_rs::NanonisClient;
145    ///
146    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
147    ///
148    /// if client.z_spectr_status_get()? {
149    ///     println!("Z spectroscopy is running");
150    /// } else {
151    ///     println!("Z spectroscopy is stopped");
152    /// }
153    /// # Ok::<(), Box<dyn std::error::Error>>(())
154    /// ```
155    pub fn z_spectr_status_get(&mut self) -> Result<bool, NanonisError> {
156        let result =
157            self.quick_send("ZSpectr.StatusGet", vec![], vec![], vec!["I"])?;
158
159        match result.first() {
160            Some(value) => Ok(value.as_u32()? == 1),
161            None => Err(NanonisError::Protocol(
162                "No Z spectroscopy status returned".to_string(),
163            )),
164        }
165    }
166
167    /// Set the channels to record during Z spectroscopy.
168    ///
169    /// Configures which signals will be recorded during the Z spectroscopy measurement.
170    /// Channel indexes correspond to the 24 signals assigned in the Signals Manager (0-23).
171    ///
172    /// # Arguments
173    /// * `channel_indexes` - Vector of channel indexes to record (0-23)
174    ///
175    /// # Errors
176    /// Returns `NanonisError` if communication fails or invalid channel indexes provided.
177    ///
178    /// # Examples
179    /// ```no_run
180    /// use nanonis_rs::NanonisClient;
181    ///
182    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
183    ///
184    /// // Record current (0), Z position (1), and bias voltage (2)
185    /// client.z_spectr_chs_set(vec![0, 1, 2])?;
186    ///
187    /// // Record more comprehensive dataset
188    /// client.z_spectr_chs_set(vec![0, 1, 2, 3, 4, 5])?;
189    /// # Ok::<(), Box<dyn std::error::Error>>(())
190    /// ```
191    pub fn z_spectr_chs_set(
192        &mut self,
193        channel_indexes: Vec<i32>,
194    ) -> Result<(), NanonisError> {
195        self.quick_send(
196            "ZSpectr.ChsSet",
197            vec![NanonisValue::ArrayI32(channel_indexes)],
198            vec!["+*i"],
199            vec![],
200        )?;
201        Ok(())
202    }
203
204    /// Get the channels configured for Z spectroscopy recording.
205    ///
206    /// Returns the channel indexes and names that will be recorded during measurements.
207    ///
208    /// # Returns
209    /// A tuple containing:
210    /// - `Vec<i32>` - Channel indexes (0-23 for Signals Manager slots)
211    /// - `Vec<String>` - Channel names corresponding to the indexes
212    ///
213    /// # Errors
214    /// Returns `NanonisError` if communication fails.
215    ///
216    /// # Examples
217    /// ```no_run
218    /// use nanonis_rs::NanonisClient;
219    ///
220    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
221    ///
222    /// let (indexes, names) = client.z_spectr_chs_get()?;
223    /// println!("Recording {} channels:", indexes.len());
224    /// for (idx, name) in indexes.iter().zip(names.iter()) {
225    ///     println!("  Channel {}: {}", idx, name);
226    /// }
227    /// # Ok::<(), Box<dyn std::error::Error>>(())
228    /// ```
229    pub fn z_spectr_chs_get(
230        &mut self,
231    ) -> 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 =
316            self.quick_send("ZSpectr.RangeGet", vec![], vec![], vec!["f", "f"])?;
317
318        if result.len() >= 2 {
319            Ok((result[0].as_f32()?, result[1].as_f32()?))
320        } else {
321            Err(NanonisError::Protocol(
322                "Invalid Z spectroscopy range response".to_string(),
323            ))
324        }
325    }
326
327    /// Set the timing parameters for Z spectroscopy.
328    ///
329    /// Configures timing-related parameters that control the speed and quality
330    /// of the Z spectroscopy measurement.
331    ///
332    /// # Arguments
333    /// * `z_averaging_time_s` - Time to average signals at each Z position
334    /// * `initial_settling_time_s` - Initial settling time before measurement
335    /// * `maximum_slew_rate_vdivs` - Maximum slew rate in V/s
336    /// * `settling_time_s` - Settling time between measurement points
337    /// * `integration_time_s` - Integration time for each measurement point
338    /// * `end_settling_time_s` - Settling time at the end of sweep
339    ///
340    /// # Errors
341    /// Returns `NanonisError` if communication fails or invalid timing parameters.
342    ///
343    /// # Examples
344    /// ```no_run
345    /// use nanonis_rs::NanonisClient;
346    ///
347    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
348    ///
349    /// // Fast spectroscopy settings
350    /// client.z_spectr_timing_set(0.01, 0.1, 1000.0, 0.01, 0.01, 0.1)?;
351    ///
352    /// // High-quality slow spectroscopy
353    /// client.z_spectr_timing_set(0.1, 0.5, 100.0, 0.05, 0.05, 0.2)?;
354    /// # Ok::<(), Box<dyn std::error::Error>>(())
355    /// ```
356    pub fn z_spectr_timing_set(
357        &mut self,
358        z_averaging_time_s: f32,
359        initial_settling_time_s: f32,
360        maximum_slew_rate_vdivs: f32,
361        settling_time_s: f32,
362        integration_time_s: f32,
363        end_settling_time_s: f32,
364    ) -> Result<(), NanonisError> {
365        self.quick_send(
366            "ZSpectr.TimingSet",
367            vec![
368                NanonisValue::F32(z_averaging_time_s),
369                NanonisValue::F32(initial_settling_time_s),
370                NanonisValue::F32(maximum_slew_rate_vdivs),
371                NanonisValue::F32(settling_time_s),
372                NanonisValue::F32(integration_time_s),
373                NanonisValue::F32(end_settling_time_s),
374            ],
375            vec!["f", "f", "f", "f", "f", "f"],
376            vec![],
377        )?;
378        Ok(())
379    }
380
381    /// Get the current timing parameters for Z spectroscopy.
382    ///
383    /// Returns all timing-related configuration parameters.
384    ///
385    /// # Returns
386    /// A tuple containing:
387    /// - `f32` - Z averaging time (s)
388    /// - `f32` - Initial settling time (s)
389    /// - `f32` - Maximum slew rate (V/s)
390    /// - `f32` - Settling time (s)
391    /// - `f32` - Integration time (s)
392    /// - `f32` - End settling time (s)
393    ///
394    /// # Errors
395    /// Returns `NanonisError` if communication fails.
396    ///
397    /// # Examples
398    /// ```no_run
399    /// use nanonis_rs::NanonisClient;
400    ///
401    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
402    ///
403    /// let (z_avg, init_settle, slew_rate, settle, integrate, end_settle) =
404    ///     client.z_spectr_timing_get()?;
405    /// println!("Integration time: {:.3} s, settling: {:.3} s", integrate, settle);
406    /// # Ok::<(), Box<dyn std::error::Error>>(())
407    /// ```
408    pub fn z_spectr_timing_get(
409        &mut self,
410    ) -> Result<(f32, f32, f32, f32, f32, f32), NanonisError> {
411        let result = self.quick_send(
412            "ZSpectr.TimingGet",
413            vec![],
414            vec![],
415            vec!["f", "f", "f", "f", "f", "f"],
416        )?;
417
418        if result.len() >= 6 {
419            Ok((
420                result[0].as_f32()?,
421                result[1].as_f32()?,
422                result[2].as_f32()?,
423                result[3].as_f32()?,
424                result[4].as_f32()?,
425                result[5].as_f32()?,
426            ))
427        } else {
428            Err(NanonisError::Protocol(
429                "Invalid Z spectroscopy timing response".to_string(),
430            ))
431        }
432    }
433
434    /// Set the retraction parameters for tip protection during Z spectroscopy.
435    ///
436    /// Configures automatic tip retraction based on signal thresholds to prevent
437    /// tip crashes during approach spectroscopy.
438    ///
439    /// # Arguments
440    /// * `enable` - Enable/disable automatic retraction
441    /// * `threshold` - Signal threshold value for retraction trigger
442    /// * `signal_index` - Index of signal to monitor (0-23)
443    /// * `comparison` - Comparison type: 0=greater than, 1=less than
444    ///
445    /// # Errors
446    /// Returns `NanonisError` if communication fails or invalid parameters.
447    ///
448    /// # Examples
449    /// ```no_run
450    /// use nanonis_rs::NanonisClient;
451    ///
452    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
453    ///
454    /// // Enable retraction when current exceeds 1 nA (signal 0, greater than)
455    /// client.z_spectr_retract_set(true, 1e-9, 0, 0)?;
456    ///
457    /// // Disable retraction
458    /// client.z_spectr_retract_set(false, 0.0, 0, 0)?;
459    /// # Ok::<(), Box<dyn std::error::Error>>(())
460    /// ```
461    pub fn z_spectr_retract_set(
462        &mut self,
463        enable: bool,
464        threshold: f32,
465        signal_index: i32,
466        comparison: u16,
467    ) -> Result<(), NanonisError> {
468        let enable_flag = if enable { 1u16 } else { 0u16 };
469
470        self.quick_send(
471            "ZSpectr.RetractSet",
472            vec![
473                NanonisValue::U16(enable_flag),
474                NanonisValue::F32(threshold),
475                NanonisValue::I32(signal_index),
476                NanonisValue::U16(comparison),
477            ],
478            vec!["H", "f", "i", "H"],
479            vec![],
480        )?;
481        Ok(())
482    }
483
484    /// Get the current retraction configuration for Z spectroscopy.
485    ///
486    /// Returns the tip protection settings that prevent crashes during measurements.
487    ///
488    /// # Returns
489    /// A tuple containing:
490    /// - `bool` - Retraction enabled/disabled
491    /// - `f32` - Threshold value for retraction
492    /// - `i32` - Signal index being monitored
493    /// - `u16` - Comparison type (0=greater, 1=less than)
494    ///
495    /// # Errors
496    /// Returns `NanonisError` if communication fails.
497    ///
498    /// # Examples
499    /// ```no_run
500    /// use nanonis_rs::NanonisClient;
501    ///
502    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
503    ///
504    /// let (enabled, threshold, signal_idx, comparison) = client.z_spectr_retract_get()?;
505    /// if enabled {
506    ///     let comp_str = if comparison == 0 { ">" } else { "<" };
507    ///     println!("Retraction: signal[{}] {} {:.3e}", signal_idx, comp_str, threshold);
508    /// }
509    /// # Ok::<(), Box<dyn std::error::Error>>(())
510    /// ```
511    pub fn z_spectr_retract_get(
512        &mut self,
513    ) -> Result<(bool, f32, i32, u16), NanonisError> {
514        let result = self.quick_send(
515            "ZSpectr.RetractGet",
516            vec![],
517            vec![],
518            vec!["H", "f", "i", "H"],
519        )?;
520
521        if result.len() >= 4 {
522            let enabled = result[0].as_u16()? == 1;
523            let threshold = result[1].as_f32()?;
524            let signal_index = result[2].as_i32()?;
525            let comparison = result[3].as_u16()?;
526            Ok((enabled, threshold, signal_index, comparison))
527        } else {
528            Err(NanonisError::Protocol(
529                "Invalid Z spectroscopy retract response".to_string(),
530            ))
531        }
532    }
533
534    /// Set the Z spectroscopy properties.
535    ///
536    /// # Arguments
537    /// * `backward_sweep` - 0=no change, 1=enable backward sweep, 2=disable
538    /// * `num_points` - Number of points (0=no change)
539    /// * `num_sweeps` - Number of sweeps to average (0=no change)
540    /// * `autosave` - 0=no change, 1=enable autosave, 2=disable
541    /// * `show_save_dialog` - 0=no change, 1=show dialog, 2=don't show
542    /// * `save_all` - 0=no change, 1=save individual sweeps, 2=don't save
543    ///
544    /// # Errors
545    /// Returns `NanonisError` if communication fails.
546    pub fn z_spectr_props_set(
547        &mut self,
548        backward_sweep: u16,
549        num_points: i32,
550        num_sweeps: u16,
551        autosave: u16,
552        show_save_dialog: u16,
553        save_all: u16,
554    ) -> Result<(), NanonisError> {
555        self.quick_send(
556            "ZSpectr.PropsSet",
557            vec![
558                NanonisValue::U16(backward_sweep),
559                NanonisValue::I32(num_points),
560                NanonisValue::U16(num_sweeps),
561                NanonisValue::U16(autosave),
562                NanonisValue::U16(show_save_dialog),
563                NanonisValue::U16(save_all),
564            ],
565            vec!["H", "i", "H", "H", "H", "H"],
566            vec![],
567        )?;
568        Ok(())
569    }
570
571    /// Get the Z spectroscopy properties.
572    ///
573    /// Returns the current property configuration.
574    ///
575    /// # Returns
576    /// A tuple containing:
577    /// - `bool` - Backward sweep enabled
578    /// - `i32` - Number of points
579    /// - `u16` - Number of sweeps to average
580    /// - `bool` - Autosave enabled
581    /// - `bool` - Show save dialog
582    /// - `bool` - Save all individual sweeps
583    ///
584    /// # Errors
585    /// Returns `NanonisError` if communication fails.
586    ///
587    /// # Examples
588    /// ```no_run
589    /// use nanonis_rs::NanonisClient;
590    ///
591    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
592    ///
593    /// let (backward, points, sweeps, autosave, dialog, save_all) =
594    ///     client.z_spectr_props_get()?;
595    /// println!("Points: {}, Sweeps: {}, Backward: {}", points, sweeps, backward);
596    /// # Ok::<(), Box<dyn std::error::Error>>(())
597    /// ```
598    pub fn z_spectr_props_get(
599        &mut self,
600    ) -> Result<(bool, i32, u16, bool, bool, bool), NanonisError> {
601        let result = self.quick_send(
602            "ZSpectr.PropsGet",
603            vec![],
604            vec![],
605            vec!["H", "i", "H", "I", "I", "I"],
606        )?;
607
608        if result.len() >= 6 {
609            Ok((
610                result[0].as_u16()? != 0,
611                result[1].as_i32()?,
612                result[2].as_u16()?,
613                result[3].as_u32()? != 0,
614                result[4].as_u32()? != 0,
615                result[5].as_u32()? != 0,
616            ))
617        } else {
618            Err(NanonisError::Protocol(
619                "Invalid Z spectroscopy props response".to_string(),
620            ))
621        }
622    }
623
624    /// Set the advanced Z spectroscopy properties.
625    ///
626    /// # Arguments
627    /// * `time_between_sweeps_s` - Time between forward and backward sweep
628    /// * `record_final_z` - 0=no change, 1=on, 2=off
629    /// * `lockin_run` - 0=no change, 1=on, 2=off
630    /// * `reset_z` - 0=no change, 1=on, 2=off
631    ///
632    /// # Errors
633    /// Returns `NanonisError` if communication fails.
634    pub fn z_spectr_adv_props_set(
635        &mut self,
636        time_between_sweeps_s: f32,
637        record_final_z: u16,
638        lockin_run: u16,
639        reset_z: u16,
640    ) -> Result<(), NanonisError> {
641        self.quick_send(
642            "ZSpectr.AdvPropsSet",
643            vec![
644                NanonisValue::F32(time_between_sweeps_s),
645                NanonisValue::U16(record_final_z),
646                NanonisValue::U16(lockin_run),
647                NanonisValue::U16(reset_z),
648            ],
649            vec!["f", "H", "H", "H"],
650            vec![],
651        )?;
652        Ok(())
653    }
654
655    /// Get the advanced Z spectroscopy properties.
656    ///
657    /// # Returns
658    /// Tuple of (time_between_sweeps, record_final_z, lockin_run, reset_z).
659    ///
660    /// # Errors
661    /// Returns `NanonisError` if communication fails.
662    pub fn z_spectr_adv_props_get(&mut self) -> Result<(f32, bool, bool, bool), NanonisError> {
663        let result = self.quick_send(
664            "ZSpectr.AdvPropsGet",
665            vec![],
666            vec![],
667            vec!["f", "H", "H", "H"],
668        )?;
669
670        Ok((
671            result[0].as_f32()?,
672            result[1].as_u16()? != 0,
673            result[2].as_u16()? != 0,
674            result[3].as_u16()? != 0,
675        ))
676    }
677
678    /// Set the retract delay.
679    ///
680    /// # Arguments
681    /// * `delay_s` - Delay in seconds between forward and backward sweep
682    ///
683    /// # Errors
684    /// Returns `NanonisError` if communication fails.
685    pub fn z_spectr_retract_delay_set(&mut self, delay_s: f32) -> Result<(), NanonisError> {
686        self.quick_send(
687            "ZSpectr.RetractDelaySet",
688            vec![NanonisValue::F32(delay_s)],
689            vec!["f"],
690            vec![],
691        )?;
692        Ok(())
693    }
694
695    /// Get the retract delay.
696    ///
697    /// # Returns
698    /// Delay in seconds.
699    ///
700    /// # Errors
701    /// Returns `NanonisError` if communication fails.
702    pub fn z_spectr_retract_delay_get(&mut self) -> Result<f32, NanonisError> {
703        let result = self.quick_send("ZSpectr.RetractDelayGet", vec![], vec![], vec!["f"])?;
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
753        Ok((
754            result[0].as_i32()?,
755            result[1].as_f32()?,
756            result[2].as_i32()?,
757            result[3].as_u16()?,
758        ))
759    }
760
761    /// Set the digital synchronization mode.
762    ///
763    /// # Arguments
764    /// * `dig_sync` - 0=no change, 1=off, 2=TTL sync, 3=pulse sequence
765    ///
766    /// # Errors
767    /// Returns `NanonisError` if communication fails.
768    pub fn z_spectr_dig_sync_set(&mut self, dig_sync: u16) -> Result<(), NanonisError> {
769        self.quick_send(
770            "ZSpectr.DigSyncSet",
771            vec![NanonisValue::U16(dig_sync)],
772            vec!["H"],
773            vec![],
774        )?;
775        Ok(())
776    }
777
778    /// Get the digital synchronization mode.
779    ///
780    /// # Returns
781    /// Sync mode (0=off, 1=TTL sync, 2=pulse sequence).
782    ///
783    /// # Errors
784    /// Returns `NanonisError` if communication fails.
785    pub fn z_spectr_dig_sync_get(&mut self) -> Result<u16, NanonisError> {
786        let result = self.quick_send("ZSpectr.DigSyncGet", vec![], vec![], vec!["H"])?;
787        result[0].as_u16()
788    }
789
790    /// Set the TTL synchronization parameters.
791    ///
792    /// # Arguments
793    /// * `ttl_line` - 0=no change, 1-4=HS line number
794    /// * `polarity` - 0=no change, 1=low active, 2=high active
795    /// * `time_to_on_s` - Time to wait before activation
796    /// * `on_duration_s` - Duration of activation
797    ///
798    /// # Errors
799    /// Returns `NanonisError` if communication fails.
800    pub fn z_spectr_ttl_sync_set(
801        &mut self,
802        ttl_line: u16,
803        polarity: u16,
804        time_to_on_s: f32,
805        on_duration_s: f32,
806    ) -> Result<(), NanonisError> {
807        self.quick_send(
808            "ZSpectr.TTLSyncSet",
809            vec![
810                NanonisValue::U16(ttl_line),
811                NanonisValue::U16(polarity),
812                NanonisValue::F32(time_to_on_s),
813                NanonisValue::F32(on_duration_s),
814            ],
815            vec!["H", "H", "f", "f"],
816            vec![],
817        )?;
818        Ok(())
819    }
820
821    /// Get the TTL synchronization parameters.
822    ///
823    /// # Returns
824    /// Tuple of (ttl_line, polarity, time_to_on_s, on_duration_s).
825    ///
826    /// # Errors
827    /// Returns `NanonisError` if communication fails.
828    pub fn z_spectr_ttl_sync_get(&mut self) -> Result<(u16, u16, f32, f32), NanonisError> {
829        let result = self.quick_send(
830            "ZSpectr.TTLSyncGet",
831            vec![],
832            vec![],
833            vec!["H", "H", "f", "f"],
834        )?;
835
836        Ok((
837            result[0].as_u16()?,
838            result[1].as_u16()?,
839            result[2].as_f32()?,
840            result[3].as_f32()?,
841        ))
842    }
843
844    /// Set the pulse sequence synchronization.
845    ///
846    /// # Arguments
847    /// * `pulse_seq_nr` - Pulse sequence number (0=no change)
848    /// * `num_periods` - Number of periods
849    ///
850    /// # Errors
851    /// Returns `NanonisError` if communication fails.
852    pub fn z_spectr_pulse_seq_sync_set(
853        &mut self,
854        pulse_seq_nr: u16,
855        num_periods: u32,
856    ) -> Result<(), NanonisError> {
857        self.quick_send(
858            "ZSpectr.PulseSeqSyncSet",
859            vec![
860                NanonisValue::U16(pulse_seq_nr),
861                NanonisValue::U32(num_periods),
862            ],
863            vec!["H", "I"],
864            vec![],
865        )?;
866        Ok(())
867    }
868
869    /// Get the pulse sequence synchronization.
870    ///
871    /// # Returns
872    /// Tuple of (pulse_seq_nr, num_periods).
873    ///
874    /// # Errors
875    /// Returns `NanonisError` if communication fails.
876    pub fn z_spectr_pulse_seq_sync_get(&mut self) -> Result<(u16, u32), NanonisError> {
877        let result = self.quick_send(
878            "ZSpectr.PulseSeqSyncGet",
879            vec![],
880            vec![],
881            vec!["H", "I"],
882        )?;
883
884        Ok((result[0].as_u16()?, result[1].as_u32()?))
885    }
886}