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