Skip to main content

nanonis_rs/client/bias_spectr/
mod.rs

1mod types;
2pub use types::*;
3
4use super::NanonisClient;
5use crate::error::NanonisError;
6use crate::types::NanonisValue;
7use std::time::Duration;
8
9impl NanonisClient {
10    /// Open the Bias Spectroscopy module.
11    ///
12    /// Opens and initializes the Bias Spectroscopy module for STS measurements.
13    /// This must be called before performing spectroscopy operations.
14    ///
15    /// # Errors
16    /// Returns `NanonisError` if communication fails or module cannot be opened.
17    ///
18    /// # Examples
19    /// ```no_run
20    /// use nanonis_rs::NanonisClient;
21    ///
22    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
23    /// client.bias_spectr_open()?;
24    /// # Ok::<(), Box<dyn std::error::Error>>(())
25    /// ```
26    pub fn bias_spectr_open(&mut self) -> Result<(), NanonisError> {
27        self.quick_send("BiasSpectr.Open", vec![], vec![], vec![])?;
28        Ok(())
29    }
30
31    /// Start a bias spectroscopy measurement.
32    ///
33    /// Starts a bias spectroscopy (STS) measurement with configured parameters.
34    /// Select channels to record before calling this function.
35    ///
36    /// # Arguments
37    /// * `get_data` - If true, returns measurement data
38    /// * `save_base_name` - Base filename for saving (empty for no change)
39    ///
40    /// # Returns
41    /// A [`BiasSpectrResult`] containing channel names, 2D data, and parameters.
42    ///
43    /// # Errors
44    /// Returns `NanonisError` if communication fails or measurement cannot start.
45    ///
46    /// # Examples
47    /// ```no_run
48    /// use nanonis_rs::NanonisClient;
49    ///
50    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
51    ///
52    /// // Start spectroscopy and get data
53    /// let result = client.bias_spectr_start(true, "sts_001")?;
54    /// println!("Recorded {} channels", result.channel_names.len());
55    /// println!("Data shape: {} x {}", result.data.len(),
56    ///          result.data.first().map(|r| r.len()).unwrap_or(0));
57    /// # Ok::<(), Box<dyn std::error::Error>>(())
58    /// ```
59    pub fn bias_spectr_start(
60        &mut self,
61        get_data: bool,
62        save_base_name: &str,
63    ) -> Result<BiasSpectrResult, NanonisError> {
64        let get_data_flag = if get_data { 1u32 } else { 0u32 };
65
66        let result = self.quick_send(
67            "BiasSpectr.Start",
68            vec![
69                NanonisValue::U32(get_data_flag),
70                NanonisValue::String(save_base_name.to_string()),
71            ],
72            vec!["I", "+*c"],
73            vec!["i", "i", "*+c", "i", "i", "2f", "i", "*f"],
74        )?;
75
76        if result.len() >= 8 {
77            let channel_names = result[2].as_string_array()?.to_vec();
78            let rows = result[3].as_i32()? as usize;
79            let cols = result[4].as_i32()? as usize;
80
81            // Parse 2D data array
82            let flat_data = result[5].as_f32_array()?;
83            let mut data_2d = Vec::with_capacity(rows);
84            for row in 0..rows {
85                let start_idx = row * cols;
86                let end_idx = start_idx + cols;
87                if end_idx <= flat_data.len() {
88                    data_2d.push(flat_data[start_idx..end_idx].to_vec());
89                }
90            }
91
92            let parameters = result[7].as_f32_array()?.to_vec();
93
94            Ok(BiasSpectrResult {
95                channel_names,
96                data: data_2d,
97                parameters,
98            })
99        } else {
100            Ok(BiasSpectrResult {
101                channel_names: vec![],
102                data: vec![],
103                parameters: vec![],
104            })
105        }
106    }
107
108    /// Stop the current bias spectroscopy measurement.
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    /// client.bias_spectr_stop()?;
119    /// # Ok::<(), Box<dyn std::error::Error>>(())
120    /// ```
121    pub fn bias_spectr_stop(&mut self) -> Result<(), NanonisError> {
122        self.quick_send("BiasSpectr.Stop", vec![], vec![], vec![])?;
123        Ok(())
124    }
125
126    /// Get the status of the bias spectroscopy measurement.
127    ///
128    /// # Returns
129    /// `true` if measurement is running, `false` otherwise.
130    ///
131    /// # Errors
132    /// Returns `NanonisError` if communication fails.
133    ///
134    /// # Examples
135    /// ```no_run
136    /// use nanonis_rs::NanonisClient;
137    ///
138    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
139    /// if client.bias_spectr_status_get()? {
140    ///     println!("Spectroscopy is running");
141    /// }
142    /// # Ok::<(), Box<dyn std::error::Error>>(())
143    /// ```
144    pub fn bias_spectr_status_get(&mut self) -> Result<bool, NanonisError> {
145        let result = self.quick_send("BiasSpectr.StatusGet", vec![], vec![], vec!["I"])?;
146        if let Some(val) = result.first() {
147            Ok(val.as_u32()? != 0)
148        } else {
149            Err(NanonisError::Protocol(
150                "Invalid status response".to_string(),
151            ))
152        }
153    }
154
155    /// Set the list of recorded channels in bias spectroscopy.
156    ///
157    /// # Arguments
158    /// * `channel_indexes` - Indexes of channels to record (0-23 for signals in Signals Manager)
159    ///
160    /// # Errors
161    /// Returns `NanonisError` if communication fails or invalid indexes provided.
162    ///
163    /// # Examples
164    /// ```no_run
165    /// use nanonis_rs::NanonisClient;
166    ///
167    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
168    /// // Record channels 0, 1, and 5
169    /// client.bias_spectr_chs_set(&[0, 1, 5])?;
170    /// # Ok::<(), Box<dyn std::error::Error>>(())
171    /// ```
172    pub fn bias_spectr_chs_set(&mut self, channel_indexes: &[i32]) -> Result<(), NanonisError> {
173        self.quick_send(
174            "BiasSpectr.ChsSet",
175            vec![NanonisValue::ArrayI32(channel_indexes.to_vec())],
176            vec!["+*i"],
177            vec![],
178        )?;
179        Ok(())
180    }
181
182    /// Get the list of recorded channels in bias spectroscopy.
183    ///
184    /// # Returns
185    /// A tuple containing:
186    /// - `Vec<i32>` - Indexes of recorded channels
187    /// - `Vec<String>` - Names of recorded channels
188    ///
189    /// # Errors
190    /// Returns `NanonisError` if communication fails.
191    ///
192    /// # Examples
193    /// ```no_run
194    /// use nanonis_rs::NanonisClient;
195    ///
196    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
197    /// let (indexes, names) = client.bias_spectr_chs_get()?;
198    /// for (idx, name) in indexes.iter().zip(names.iter()) {
199    ///     println!("Channel {}: {}", idx, name);
200    /// }
201    /// # Ok::<(), Box<dyn std::error::Error>>(())
202    /// ```
203    pub fn bias_spectr_chs_get(&mut self) -> Result<(Vec<i32>, Vec<String>), NanonisError> {
204        let result = self.quick_send(
205            "BiasSpectr.ChsGet",
206            vec![],
207            vec![],
208            vec!["i", "*i", "i", "i", "*+c"],
209        )?;
210
211        if result.len() >= 5 {
212            let indexes = result[1].as_i32_array()?.to_vec();
213            let names = result[4].as_string_array()?.to_vec();
214            Ok((indexes, names))
215        } else {
216            Err(NanonisError::Protocol(
217                "Invalid channels response".to_string(),
218            ))
219        }
220    }
221
222    /// Set the bias spectroscopy properties.
223    ///
224    /// Uses a builder pattern to configure only the properties you want to change.
225    /// Properties not set in the builder will remain unchanged on the instrument.
226    ///
227    /// # Arguments
228    /// * `config` - Configuration builder with properties to set
229    ///
230    /// # Errors
231    /// Returns `NanonisError` if communication fails.
232    ///
233    /// # Examples
234    /// ```no_run
235    /// use nanonis_rs::NanonisClient;
236    /// use nanonis_rs::bias_spectr::{BiasSpectrPropsBuilder, OptionalFlag};
237    ///
238    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
239    ///
240    /// // Configure using builder pattern - only set what you need
241    /// let config = BiasSpectrPropsBuilder::new()
242    ///     .num_sweeps(10)
243    ///     .num_points(200)
244    ///     .backward_sweep(OptionalFlag::On)
245    ///     .autosave(OptionalFlag::On);
246    ///
247    /// client.bias_spectr_props_set(config)?;
248    /// # Ok::<(), Box<dyn std::error::Error>>(())
249    /// ```
250    pub fn bias_spectr_props_set(
251        &mut self,
252        config: BiasSpectrPropsBuilder,
253    ) -> Result<(), NanonisError> {
254        self.quick_send(
255            "BiasSpectr.PropsSet",
256            vec![
257                NanonisValue::U16(config.save_all.into()),
258                NanonisValue::I32(config.num_sweeps),
259                NanonisValue::U16(config.backward_sweep.into()),
260                NanonisValue::I32(config.num_points),
261                NanonisValue::F32(config.z_offset_m),
262                NanonisValue::U16(config.autosave.into()),
263                NanonisValue::U16(config.show_save_dialog.into()),
264            ],
265            vec!["H", "i", "H", "i", "f", "H", "H"],
266            vec![],
267        )?;
268        Ok(())
269    }
270
271    /// Get the bias spectroscopy properties.
272    ///
273    /// # Returns
274    /// A [`BiasSpectrProps`] struct with current configuration.
275    ///
276    /// # Errors
277    /// Returns `NanonisError` if communication fails.
278    ///
279    /// # Examples
280    /// ```no_run
281    /// use nanonis_rs::NanonisClient;
282    ///
283    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
284    /// let props = client.bias_spectr_props_get()?;
285    /// println!("Sweeps: {}, Points: {}", props.num_sweeps, props.num_points);
286    /// # Ok::<(), Box<dyn std::error::Error>>(())
287    /// ```
288    pub fn bias_spectr_props_get(&mut self) -> Result<BiasSpectrProps, NanonisError> {
289        let result = self.quick_send(
290            "BiasSpectr.PropsGet",
291            vec![],
292            vec![],
293            vec![
294                "H", "i", "H", "i", "i", "i", "*+c", "i", "i", "*+c", "i", "i", "*+c",
295            ],
296        )?;
297
298        if result.len() >= 13 {
299            Ok(BiasSpectrProps {
300                save_all: result[0].as_u16()? != 0,
301                num_sweeps: result[1].as_i32()?,
302                backward_sweep: result[2].as_u16()? != 0,
303                num_points: result[3].as_i32()?,
304                channels: result[6].as_string_array()?.to_vec(),
305                parameters: result[9].as_string_array()?.to_vec(),
306                fixed_parameters: result[12].as_string_array()?.to_vec(),
307            })
308        } else {
309            Err(NanonisError::Protocol(
310                "Invalid props response".to_string(),
311            ))
312        }
313    }
314
315    /// Set the advanced bias spectroscopy properties.
316    ///
317    /// # Arguments
318    /// * `reset_bias` - Reset bias to initial value after sweep: NoChange/On/Off
319    /// * `z_controller_hold` - Hold Z-controller during sweep: NoChange/On/Off
320    /// * `record_final_z` - Record final Z position: NoChange/On/Off
321    /// * `lockin_run` - Run lock-in during measurement: NoChange/On/Off
322    ///
323    /// # Errors
324    /// Returns `NanonisError` if communication fails.
325    ///
326    /// # Examples
327    /// ```no_run
328    /// use nanonis_rs::NanonisClient;
329    /// use nanonis_rs::bias_spectr::OptionalFlag;
330    ///
331    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
332    /// client.bias_spectr_adv_props_set(
333    ///     OptionalFlag::On,   // reset_bias
334    ///     OptionalFlag::On,   // z_controller_hold
335    ///     OptionalFlag::Off,  // record_final_z
336    ///     OptionalFlag::Off,  // lockin_run
337    /// )?;
338    /// # Ok::<(), Box<dyn std::error::Error>>(())
339    /// ```
340    pub fn bias_spectr_adv_props_set(
341        &mut self,
342        reset_bias: OptionalFlag,
343        z_controller_hold: OptionalFlag,
344        record_final_z: OptionalFlag,
345        lockin_run: OptionalFlag,
346    ) -> Result<(), NanonisError> {
347        self.quick_send(
348            "BiasSpectr.AdvPropsSet",
349            vec![
350                NanonisValue::U16(reset_bias.into()),
351                NanonisValue::U16(z_controller_hold.into()),
352                NanonisValue::U16(record_final_z.into()),
353                NanonisValue::U16(lockin_run.into()),
354            ],
355            vec!["H", "H", "H", "H"],
356            vec![],
357        )?;
358        Ok(())
359    }
360
361    /// Get the advanced bias spectroscopy properties.
362    ///
363    /// # Returns
364    /// A [`BiasSpectrAdvProps`] struct with current advanced settings.
365    ///
366    /// # Errors
367    /// Returns `NanonisError` if communication fails.
368    ///
369    /// # Examples
370    /// ```no_run
371    /// use nanonis_rs::NanonisClient;
372    ///
373    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
374    /// let adv = client.bias_spectr_adv_props_get()?;
375    /// println!("Z-controller hold: {}", adv.z_controller_hold);
376    /// # Ok::<(), Box<dyn std::error::Error>>(())
377    /// ```
378    pub fn bias_spectr_adv_props_get(&mut self) -> Result<BiasSpectrAdvProps, NanonisError> {
379        let result = self.quick_send(
380            "BiasSpectr.AdvPropsGet",
381            vec![],
382            vec![],
383            vec!["H", "H", "H", "H"],
384        )?;
385
386        if result.len() >= 4 {
387            Ok(BiasSpectrAdvProps {
388                reset_bias: result[0].as_u16()? != 0,
389                z_controller_hold: result[1].as_u16()? != 0,
390                record_final_z: result[2].as_u16()? != 0,
391                lockin_run: result[3].as_u16()? != 0,
392            })
393        } else {
394            Err(NanonisError::Protocol(
395                "Invalid adv props response".to_string(),
396            ))
397        }
398    }
399
400    /// Set the bias spectroscopy sweep limits.
401    ///
402    /// # Arguments
403    /// * `start_value_v` - Starting bias voltage in volts
404    /// * `end_value_v` - Ending bias voltage in volts
405    ///
406    /// # Errors
407    /// Returns `NanonisError` if communication fails.
408    ///
409    /// # Examples
410    /// ```no_run
411    /// use nanonis_rs::NanonisClient;
412    ///
413    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
414    /// // Sweep from -2V to +2V
415    /// client.bias_spectr_limits_set(-2.0, 2.0)?;
416    /// # Ok::<(), Box<dyn std::error::Error>>(())
417    /// ```
418    pub fn bias_spectr_limits_set(
419        &mut self,
420        start_value_v: f32,
421        end_value_v: f32,
422    ) -> Result<(), NanonisError> {
423        self.quick_send(
424            "BiasSpectr.LimitsSet",
425            vec![
426                NanonisValue::F32(start_value_v),
427                NanonisValue::F32(end_value_v),
428            ],
429            vec!["f", "f"],
430            vec![],
431        )?;
432        Ok(())
433    }
434
435    /// Get the bias spectroscopy sweep limits.
436    ///
437    /// # Returns
438    /// A tuple `(start_v, end_v)` with the voltage limits.
439    ///
440    /// # Errors
441    /// Returns `NanonisError` if communication fails.
442    ///
443    /// # Examples
444    /// ```no_run
445    /// use nanonis_rs::NanonisClient;
446    ///
447    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
448    /// let (start, end) = client.bias_spectr_limits_get()?;
449    /// println!("Sweep range: {:.2}V to {:.2}V", start, end);
450    /// # Ok::<(), Box<dyn std::error::Error>>(())
451    /// ```
452    pub fn bias_spectr_limits_get(&mut self) -> Result<(f32, f32), NanonisError> {
453        let result = self.quick_send("BiasSpectr.LimitsGet", vec![], vec![], vec!["f", "f"])?;
454
455        if result.len() >= 2 {
456            Ok((result[0].as_f32()?, result[1].as_f32()?))
457        } else {
458            Err(NanonisError::Protocol(
459                "Invalid limits response".to_string(),
460            ))
461        }
462    }
463
464    /// Set the bias spectroscopy timing parameters.
465    ///
466    /// # Arguments
467    /// * `timing` - A [`BiasSpectrTiming`] struct with timing configuration
468    ///
469    /// # Errors
470    /// Returns `NanonisError` if communication fails.
471    ///
472    /// # Examples
473    /// ```no_run
474    /// use nanonis_rs::NanonisClient;
475    /// use nanonis_rs::bias_spectr::BiasSpectrTiming;
476    /// use std::time::Duration;
477    ///
478    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
479    /// let timing = BiasSpectrTiming {
480    ///     settling_time: Duration::from_millis(20),
481    ///     integration_time: Duration::from_millis(50),
482    ///     ..Default::default()
483    /// };
484    /// client.bias_spectr_timing_set(&timing)?;
485    /// # Ok::<(), Box<dyn std::error::Error>>(())
486    /// ```
487    pub fn bias_spectr_timing_set(&mut self, timing: &BiasSpectrTiming) -> Result<(), NanonisError> {
488        self.quick_send(
489            "BiasSpectr.TimingSet",
490            vec![
491                NanonisValue::F32(timing.z_averaging_time.as_secs_f32()),
492                NanonisValue::F32(timing.z_offset_m),
493                NanonisValue::F32(timing.initial_settling_time.as_secs_f32()),
494                NanonisValue::F32(timing.max_slew_rate),
495                NanonisValue::F32(timing.settling_time.as_secs_f32()),
496                NanonisValue::F32(timing.integration_time.as_secs_f32()),
497                NanonisValue::F32(timing.end_settling_time.as_secs_f32()),
498                NanonisValue::F32(timing.z_control_time.as_secs_f32()),
499            ],
500            vec!["f", "f", "f", "f", "f", "f", "f", "f"],
501            vec![],
502        )?;
503        Ok(())
504    }
505
506    /// Get the bias spectroscopy timing parameters.
507    ///
508    /// # Returns
509    /// A [`BiasSpectrTiming`] struct with current timing settings.
510    ///
511    /// # Errors
512    /// Returns `NanonisError` if communication fails.
513    ///
514    /// # Examples
515    /// ```no_run
516    /// use nanonis_rs::NanonisClient;
517    ///
518    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
519    /// let timing = client.bias_spectr_timing_get()?;
520    /// println!("Integration time: {:?}", timing.integration_time);
521    /// # Ok::<(), Box<dyn std::error::Error>>(())
522    /// ```
523    pub fn bias_spectr_timing_get(&mut self) -> Result<BiasSpectrTiming, NanonisError> {
524        let result = self.quick_send(
525            "BiasSpectr.TimingGet",
526            vec![],
527            vec![],
528            vec!["f", "f", "f", "f", "f", "f", "f", "f"],
529        )?;
530
531        if result.len() >= 8 {
532            Ok(BiasSpectrTiming {
533                z_averaging_time: Duration::from_secs_f32(result[0].as_f32()?),
534                z_offset_m: result[1].as_f32()?,
535                initial_settling_time: Duration::from_secs_f32(result[2].as_f32()?),
536                max_slew_rate: result[3].as_f32()?,
537                settling_time: Duration::from_secs_f32(result[4].as_f32()?),
538                integration_time: Duration::from_secs_f32(result[5].as_f32()?),
539                end_settling_time: Duration::from_secs_f32(result[6].as_f32()?),
540                z_control_time: Duration::from_secs_f32(result[7].as_f32()?),
541            })
542        } else {
543            Err(NanonisError::Protocol(
544                "Invalid timing response".to_string(),
545            ))
546        }
547    }
548
549    /// Set the digital synchronization mode.
550    ///
551    /// # Arguments
552    /// * `mode` - The [`DigitalSync`] mode to set
553    ///
554    /// # Errors
555    /// Returns `NanonisError` if communication fails.
556    ///
557    /// # Examples
558    /// ```no_run
559    /// use nanonis_rs::NanonisClient;
560    /// use nanonis_rs::bias_spectr::DigitalSync;
561    ///
562    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
563    /// client.bias_spectr_dig_sync_set(DigitalSync::TTLSync)?;
564    /// # Ok::<(), Box<dyn std::error::Error>>(())
565    /// ```
566    pub fn bias_spectr_dig_sync_set(&mut self, mode: DigitalSync) -> Result<(), NanonisError> {
567        self.quick_send(
568            "BiasSpectr.DigSyncSet",
569            vec![NanonisValue::U16(mode.into())],
570            vec!["H"],
571            vec![],
572        )?;
573        Ok(())
574    }
575
576    /// Get the digital synchronization mode.
577    ///
578    /// # Returns
579    /// The current [`DigitalSync`] mode.
580    ///
581    /// # Errors
582    /// Returns `NanonisError` if communication fails.
583    ///
584    /// # Examples
585    /// ```no_run
586    /// use nanonis_rs::NanonisClient;
587    ///
588    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
589    /// let mode = client.bias_spectr_dig_sync_get()?;
590    /// println!("Digital sync mode: {:?}", mode);
591    /// # Ok::<(), Box<dyn std::error::Error>>(())
592    /// ```
593    pub fn bias_spectr_dig_sync_get(&mut self) -> Result<DigitalSync, NanonisError> {
594        let result = self.quick_send("BiasSpectr.DigSyncGet", vec![], vec![], vec!["H"])?;
595
596        if let Some(val) = result.first() {
597            DigitalSync::try_from(val.as_u16()?)
598        } else {
599            Err(NanonisError::Protocol(
600                "Invalid dig sync response".to_string(),
601            ))
602        }
603    }
604
605    /// Set the TTL synchronization configuration.
606    ///
607    /// # Arguments
608    /// * `config` - A [`TTLSyncConfig`] struct with TTL settings
609    ///
610    /// # Errors
611    /// Returns `NanonisError` if communication fails.
612    ///
613    /// # Examples
614    /// ```no_run
615    /// use nanonis_rs::NanonisClient;
616    /// use nanonis_rs::bias_spectr::{TTLSyncConfig, TTLLine, TTLPolarity};
617    /// use std::time::Duration;
618    ///
619    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
620    /// let config = TTLSyncConfig {
621    ///     line: TTLLine::HSLine1,
622    ///     polarity: TTLPolarity::HighActive,
623    ///     time_to_on: Duration::from_millis(10),
624    ///     on_duration: Duration::from_millis(100),
625    /// };
626    /// client.bias_spectr_ttl_sync_set(&config)?;
627    /// # Ok::<(), Box<dyn std::error::Error>>(())
628    /// ```
629    pub fn bias_spectr_ttl_sync_set(&mut self, config: &TTLSyncConfig) -> Result<(), NanonisError> {
630        self.quick_send(
631            "BiasSpectr.TTLSyncSet",
632            vec![
633                NanonisValue::U16(config.line.into()),
634                NanonisValue::U16(config.polarity.into()),
635                NanonisValue::F32(config.time_to_on.as_secs_f32()),
636                NanonisValue::F32(config.on_duration.as_secs_f32()),
637            ],
638            vec!["H", "H", "f", "f"],
639            vec![],
640        )?;
641        Ok(())
642    }
643
644    /// Get the TTL synchronization configuration.
645    ///
646    /// # Returns
647    /// A [`TTLSyncConfig`] struct with current TTL settings.
648    ///
649    /// # Errors
650    /// Returns `NanonisError` if communication fails.
651    ///
652    /// # Examples
653    /// ```no_run
654    /// use nanonis_rs::NanonisClient;
655    ///
656    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
657    /// let config = client.bias_spectr_ttl_sync_get()?;
658    /// println!("TTL line: {:?}, on duration: {:?}", config.line, config.on_duration);
659    /// # Ok::<(), Box<dyn std::error::Error>>(())
660    /// ```
661    pub fn bias_spectr_ttl_sync_get(&mut self) -> Result<TTLSyncConfig, NanonisError> {
662        let result = self.quick_send(
663            "BiasSpectr.TTLSyncGet",
664            vec![],
665            vec![],
666            vec!["H", "H", "f", "f"],
667        )?;
668
669        if result.len() >= 4 {
670            Ok(TTLSyncConfig {
671                line: TTLLine::try_from(result[0].as_u16()?)?,
672                polarity: TTLPolarity::try_from(result[1].as_u16()?)?,
673                time_to_on: Duration::from_secs_f32(result[2].as_f32()?),
674                on_duration: Duration::from_secs_f32(result[3].as_f32()?),
675            })
676        } else {
677            Err(NanonisError::Protocol(
678                "Invalid TTL sync response".to_string(),
679            ))
680        }
681    }
682
683    /// Set the pulse sequence synchronization configuration.
684    ///
685    /// # Arguments
686    /// * `config` - A [`PulseSeqSyncConfig`] struct with pulse sequence settings
687    ///
688    /// # Errors
689    /// Returns `NanonisError` if communication fails.
690    ///
691    /// # Examples
692    /// ```no_run
693    /// use nanonis_rs::NanonisClient;
694    /// use nanonis_rs::bias_spectr::PulseSeqSyncConfig;
695    ///
696    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
697    /// let config = PulseSeqSyncConfig {
698    ///     sequence_nr: 1,
699    ///     num_periods: 10,
700    /// };
701    /// client.bias_spectr_pulse_seq_sync_set(&config)?;
702    /// # Ok::<(), Box<dyn std::error::Error>>(())
703    /// ```
704    pub fn bias_spectr_pulse_seq_sync_set(
705        &mut self,
706        config: &PulseSeqSyncConfig,
707    ) -> Result<(), NanonisError> {
708        self.quick_send(
709            "BiasSpectr.PulseSeqSyncSet",
710            vec![
711                NanonisValue::U16(config.sequence_nr),
712                NanonisValue::U32(config.num_periods),
713            ],
714            vec!["H", "I"],
715            vec![],
716        )?;
717        Ok(())
718    }
719
720    /// Get the pulse sequence synchronization configuration.
721    ///
722    /// # Returns
723    /// A [`PulseSeqSyncConfig`] struct with current settings.
724    ///
725    /// # Errors
726    /// Returns `NanonisError` if communication fails.
727    ///
728    /// # Examples
729    /// ```no_run
730    /// use nanonis_rs::NanonisClient;
731    ///
732    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
733    /// let config = client.bias_spectr_pulse_seq_sync_get()?;
734    /// println!("Sequence #{}, {} periods", config.sequence_nr, config.num_periods);
735    /// # Ok::<(), Box<dyn std::error::Error>>(())
736    /// ```
737    pub fn bias_spectr_pulse_seq_sync_get(&mut self) -> Result<PulseSeqSyncConfig, NanonisError> {
738        let result =
739            self.quick_send("BiasSpectr.PulseSeqSyncGet", vec![], vec![], vec!["H", "I"])?;
740
741        if result.len() >= 2 {
742            Ok(PulseSeqSyncConfig {
743                sequence_nr: result[0].as_u16()?,
744                num_periods: result[1].as_u32()?,
745            })
746        } else {
747            Err(NanonisError::Protocol(
748                "Invalid pulse seq sync response".to_string(),
749            ))
750        }
751    }
752
753    /// Set the alternate Z-controller setpoint configuration.
754    ///
755    /// # Arguments
756    /// * `config` - An [`AltZCtrlConfig`] struct with alternate setpoint settings
757    ///
758    /// # Errors
759    /// Returns `NanonisError` if communication fails.
760    ///
761    /// # Examples
762    /// ```no_run
763    /// use nanonis_rs::NanonisClient;
764    /// use nanonis_rs::bias_spectr::AltZCtrlConfig;
765    /// use std::time::Duration;
766    ///
767    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
768    /// let config = AltZCtrlConfig {
769    ///     enabled: true,
770    ///     setpoint: 1e-9,  // 1 nA
771    ///     settling_time: Duration::from_millis(200),
772    /// };
773    /// client.bias_spectr_alt_z_ctrl_set(&config)?;
774    /// # Ok::<(), Box<dyn std::error::Error>>(())
775    /// ```
776    pub fn bias_spectr_alt_z_ctrl_set(&mut self, config: &AltZCtrlConfig) -> Result<(), NanonisError> {
777        let enabled_flag = if config.enabled {
778            OptionalFlag::On
779        } else {
780            OptionalFlag::Off
781        };
782
783        self.quick_send(
784            "BiasSpectr.AltZCtrlSet",
785            vec![
786                NanonisValue::U16(enabled_flag.into()),
787                NanonisValue::F32(config.setpoint),
788                NanonisValue::F32(config.settling_time.as_secs_f32()),
789            ],
790            vec!["H", "f", "f"],
791            vec![],
792        )?;
793        Ok(())
794    }
795
796    /// Get the alternate Z-controller setpoint configuration.
797    ///
798    /// # Returns
799    /// An [`AltZCtrlConfig`] struct with current settings.
800    ///
801    /// # Errors
802    /// Returns `NanonisError` if communication fails.
803    ///
804    /// # Examples
805    /// ```no_run
806    /// use nanonis_rs::NanonisClient;
807    ///
808    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
809    /// let config = client.bias_spectr_alt_z_ctrl_get()?;
810    /// println!("Alt Z-ctrl enabled: {}, setpoint: {}", config.enabled, config.setpoint);
811    /// # Ok::<(), Box<dyn std::error::Error>>(())
812    /// ```
813    pub fn bias_spectr_alt_z_ctrl_get(&mut self) -> Result<AltZCtrlConfig, NanonisError> {
814        let result =
815            self.quick_send("BiasSpectr.AltZCtrlGet", vec![], vec![], vec!["H", "f", "f"])?;
816
817        if result.len() >= 3 {
818            Ok(AltZCtrlConfig {
819                enabled: result[0].as_u16()? != 0,
820                setpoint: result[1].as_f32()?,
821                settling_time: Duration::from_secs_f32(result[2].as_f32()?),
822            })
823        } else {
824            Err(NanonisError::Protocol(
825                "Invalid alt z ctrl response".to_string(),
826            ))
827        }
828    }
829
830    /// Set the Z offset revert flag.
831    ///
832    /// When enabled, the Z offset applied at the beginning is reverted at the end.
833    ///
834    /// # Arguments
835    /// * `revert` - Whether to revert Z offset: NoChange/On/Off
836    ///
837    /// # Errors
838    /// Returns `NanonisError` if communication fails.
839    ///
840    /// # Examples
841    /// ```no_run
842    /// use nanonis_rs::NanonisClient;
843    /// use nanonis_rs::bias_spectr::OptionalFlag;
844    ///
845    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
846    /// client.bias_spectr_z_off_revert_set(OptionalFlag::On)?;
847    /// # Ok::<(), Box<dyn std::error::Error>>(())
848    /// ```
849    pub fn bias_spectr_z_off_revert_set(&mut self, revert: OptionalFlag) -> Result<(), NanonisError> {
850        self.quick_send(
851            "BiasSpectr.ZOffRevertSet",
852            vec![NanonisValue::U16(revert.into())],
853            vec!["H"],
854            vec![],
855        )?;
856        Ok(())
857    }
858
859    /// Get the Z offset revert flag.
860    ///
861    /// # Returns
862    /// `true` if Z offset revert is enabled.
863    ///
864    /// # Errors
865    /// Returns `NanonisError` if communication fails.
866    ///
867    /// # Examples
868    /// ```no_run
869    /// use nanonis_rs::NanonisClient;
870    ///
871    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
872    /// let revert = client.bias_spectr_z_off_revert_get()?;
873    /// println!("Z offset revert: {}", revert);
874    /// # Ok::<(), Box<dyn std::error::Error>>(())
875    /// ```
876    pub fn bias_spectr_z_off_revert_get(&mut self) -> Result<bool, NanonisError> {
877        let result = self.quick_send("BiasSpectr.ZOffRevertGet", vec![], vec![], vec!["H"])?;
878
879        if let Some(val) = result.first() {
880            Ok(val.as_u16()? != 0)
881        } else {
882            Err(NanonisError::Protocol(
883                "Invalid z off revert response".to_string(),
884            ))
885        }
886    }
887
888    /// Set the MLS lock-in per segment flag.
889    ///
890    /// When enabled, lock-in can be configured per segment in the MLS editor.
891    ///
892    /// # Arguments
893    /// * `enabled` - Whether to enable per-segment lock-in configuration
894    ///
895    /// # Errors
896    /// Returns `NanonisError` if communication fails.
897    ///
898    /// # Examples
899    /// ```no_run
900    /// use nanonis_rs::NanonisClient;
901    ///
902    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
903    /// client.bias_spectr_mls_lockin_per_seg_set(true)?;
904    /// # Ok::<(), Box<dyn std::error::Error>>(())
905    /// ```
906    pub fn bias_spectr_mls_lockin_per_seg_set(&mut self, enabled: bool) -> Result<(), NanonisError> {
907        let flag = if enabled { 1u32 } else { 0u32 };
908        self.quick_send(
909            "BiasSpectr.MLSLockinPerSegSet",
910            vec![NanonisValue::U32(flag)],
911            vec!["I"],
912            vec![],
913        )?;
914        Ok(())
915    }
916
917    /// Get the MLS lock-in per segment flag.
918    ///
919    /// # Returns
920    /// `true` if per-segment lock-in configuration is enabled.
921    ///
922    /// # Errors
923    /// Returns `NanonisError` if communication fails.
924    ///
925    /// # Examples
926    /// ```no_run
927    /// use nanonis_rs::NanonisClient;
928    ///
929    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
930    /// let enabled = client.bias_spectr_mls_lockin_per_seg_get()?;
931    /// println!("MLS lock-in per segment: {}", enabled);
932    /// # Ok::<(), Box<dyn std::error::Error>>(())
933    /// ```
934    pub fn bias_spectr_mls_lockin_per_seg_get(&mut self) -> Result<bool, NanonisError> {
935        let result =
936            self.quick_send("BiasSpectr.MLSLockinPerSegGet", vec![], vec![], vec!["I"])?;
937
938        if let Some(val) = result.first() {
939            Ok(val.as_u32()? != 0)
940        } else {
941            Err(NanonisError::Protocol(
942                "Invalid MLS lockin per seg response".to_string(),
943            ))
944        }
945    }
946
947    /// Set the MLS sweep mode.
948    ///
949    /// # Arguments
950    /// * `mode` - The [`SweepMode`] to set
951    ///
952    /// # Errors
953    /// Returns `NanonisError` if communication fails.
954    ///
955    /// # Examples
956    /// ```no_run
957    /// use nanonis_rs::NanonisClient;
958    /// use nanonis_rs::bias_spectr::SweepMode;
959    ///
960    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
961    /// client.bias_spectr_mls_mode_set(SweepMode::MLS)?;
962    /// # Ok::<(), Box<dyn std::error::Error>>(())
963    /// ```
964    pub fn bias_spectr_mls_mode_set(&mut self, mode: SweepMode) -> Result<(), NanonisError> {
965        let mode_str: &str = mode.into();
966        self.quick_send(
967            "BiasSpectr.MLSModeSet",
968            vec![NanonisValue::String(mode_str.to_string())],
969            vec!["+*c"],
970            vec![],
971        )?;
972        Ok(())
973    }
974
975    /// Get the current MLS sweep mode.
976    ///
977    /// # Returns
978    /// The current [`SweepMode`].
979    ///
980    /// # Errors
981    /// Returns `NanonisError` if communication fails.
982    ///
983    /// # Examples
984    /// ```no_run
985    /// use nanonis_rs::NanonisClient;
986    ///
987    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
988    /// let mode = client.bias_spectr_mls_mode_get()?;
989    /// println!("Sweep mode: {:?}", mode);
990    /// # Ok::<(), Box<dyn std::error::Error>>(())
991    /// ```
992    pub fn bias_spectr_mls_mode_get(&mut self) -> Result<SweepMode, NanonisError> {
993        let result = self.quick_send(
994            "BiasSpectr.MLSModeGet",
995            vec![],
996            vec![],
997            vec!["i", "i", "*+c"],
998        )?;
999
1000        if result.len() >= 3 {
1001            let modes = result[2].as_string_array()?;
1002            if let Some(mode_str) = modes.first() {
1003                SweepMode::try_from(mode_str.as_str())
1004            } else {
1005                Ok(SweepMode::Linear)
1006            }
1007        } else {
1008            Err(NanonisError::Protocol(
1009                "Invalid MLS mode response".to_string(),
1010            ))
1011        }
1012    }
1013
1014    /// Set the MLS segment values.
1015    ///
1016    /// # Arguments
1017    /// * `segments` - Vector of [`MLSSegment`] configurations
1018    ///
1019    /// # Errors
1020    /// Returns `NanonisError` if communication fails.
1021    ///
1022    /// # Examples
1023    /// ```no_run
1024    /// use nanonis_rs::NanonisClient;
1025    /// use nanonis_rs::bias_spectr::MLSSegment;
1026    /// use std::time::Duration;
1027    ///
1028    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
1029    /// let segments = vec![
1030    ///     MLSSegment {
1031    ///         bias_start: -2.0,
1032    ///         bias_end: 0.0,
1033    ///         steps: 100,
1034    ///         ..Default::default()
1035    ///     },
1036    ///     MLSSegment {
1037    ///         bias_start: 0.0,
1038    ///         bias_end: 2.0,
1039    ///         steps: 100,
1040    ///         ..Default::default()
1041    ///     },
1042    /// ];
1043    /// client.bias_spectr_mls_vals_set(&segments)?;
1044    /// # Ok::<(), Box<dyn std::error::Error>>(())
1045    /// ```
1046    pub fn bias_spectr_mls_vals_set(&mut self, segments: &[MLSSegment]) -> Result<(), NanonisError> {
1047        let num_segments = segments.len() as i32;
1048        let bias_start: Vec<f32> = segments.iter().map(|s| s.bias_start).collect();
1049        let bias_end: Vec<f32> = segments.iter().map(|s| s.bias_end).collect();
1050        let initial_settling: Vec<f32> = segments
1051            .iter()
1052            .map(|s| s.initial_settling_time.as_secs_f32())
1053            .collect();
1054        let settling: Vec<f32> = segments
1055            .iter()
1056            .map(|s| s.settling_time.as_secs_f32())
1057            .collect();
1058        let integration: Vec<f32> = segments
1059            .iter()
1060            .map(|s| s.integration_time.as_secs_f32())
1061            .collect();
1062        let slew_rate: Vec<f32> = segments.iter().map(|s| s.max_slew_rate).collect();
1063        let steps: Vec<i32> = segments.iter().map(|s| s.steps).collect();
1064
1065        self.quick_send(
1066            "BiasSpectr.MLSValsSet",
1067            vec![
1068                NanonisValue::I32(num_segments),
1069                NanonisValue::ArrayF32(bias_start),
1070                NanonisValue::ArrayF32(bias_end),
1071                NanonisValue::ArrayF32(initial_settling),
1072                NanonisValue::ArrayF32(settling),
1073                NanonisValue::ArrayF32(integration),
1074                NanonisValue::ArrayF32(slew_rate),
1075                NanonisValue::ArrayI32(steps),
1076            ],
1077            vec!["i", "*f", "*f", "*f", "*f", "*f", "*f", "*i"],
1078            vec![],
1079        )?;
1080        Ok(())
1081    }
1082
1083    /// Get the MLS segment values.
1084    ///
1085    /// # Returns
1086    /// A vector of [`MLSSegment`] configurations.
1087    ///
1088    /// # Errors
1089    /// Returns `NanonisError` if communication fails.
1090    ///
1091    /// # Examples
1092    /// ```no_run
1093    /// use nanonis_rs::NanonisClient;
1094    ///
1095    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
1096    /// let segments = client.bias_spectr_mls_vals_get()?;
1097    /// for (i, seg) in segments.iter().enumerate() {
1098    ///     println!("Segment {}: {:.2}V to {:.2}V, {} steps",
1099    ///              i, seg.bias_start, seg.bias_end, seg.steps);
1100    /// }
1101    /// # Ok::<(), Box<dyn std::error::Error>>(())
1102    /// ```
1103    pub fn bias_spectr_mls_vals_get(&mut self) -> Result<Vec<MLSSegment>, NanonisError> {
1104        let result = self.quick_send(
1105            "BiasSpectr.MLSValsGet",
1106            vec![],
1107            vec![],
1108            vec!["i", "*f", "*f", "*f", "*f", "*f", "*f", "*i"],
1109        )?;
1110
1111        if result.len() >= 8 {
1112            let num_segments = result[0].as_i32()? as usize;
1113            let bias_start = result[1].as_f32_array()?;
1114            let bias_end = result[2].as_f32_array()?;
1115            let initial_settling = result[3].as_f32_array()?;
1116            let settling = result[4].as_f32_array()?;
1117            let integration = result[5].as_f32_array()?;
1118            let slew_rate = result[6].as_f32_array()?;
1119            let steps = result[7].as_i32_array()?;
1120
1121            let mut segments = Vec::with_capacity(num_segments);
1122            for i in 0..num_segments {
1123                segments.push(MLSSegment {
1124                    bias_start: *bias_start.get(i).unwrap_or(&0.0),
1125                    bias_end: *bias_end.get(i).unwrap_or(&0.0),
1126                    initial_settling_time: Duration::from_secs_f32(
1127                        *initial_settling.get(i).unwrap_or(&0.0),
1128                    ),
1129                    settling_time: Duration::from_secs_f32(*settling.get(i).unwrap_or(&0.0)),
1130                    integration_time: Duration::from_secs_f32(*integration.get(i).unwrap_or(&0.0)),
1131                    max_slew_rate: *slew_rate.get(i).unwrap_or(&1.0),
1132                    steps: *steps.get(i).unwrap_or(&100),
1133                });
1134            }
1135
1136            Ok(segments)
1137        } else {
1138            Err(NanonisError::Protocol(
1139                "Invalid MLS vals response".to_string(),
1140            ))
1141        }
1142    }
1143}