Skip to main content

nanonis_rs/client/
lockin_freq_swp.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4
5/// Lock-In frequency sweep properties configuration.
6#[derive(Debug, Clone)]
7pub struct LockInFreqSwpProps {
8    /// Number of frequency steps (logarithmic distribution)
9    pub num_steps: u16,
10    /// Number of lock-in periods to average per measurement
11    pub integration_periods: u16,
12    /// Minimum integration time in seconds
13    pub min_integration_time_s: f32,
14    /// Number of lock-in periods to wait before acquiring
15    pub settling_periods: u16,
16    /// Minimum settling time in seconds
17    pub min_settling_time_s: f32,
18    /// Automatically save data at end of sweep
19    pub autosave: bool,
20    /// Show save dialog when saving
21    pub save_dialog: bool,
22    /// Base filename for saved files
23    pub basename: String,
24}
25
26impl Default for LockInFreqSwpProps {
27    fn default() -> Self {
28        Self {
29            num_steps: 100,
30            integration_periods: 10,
31            min_integration_time_s: 0.1,
32            settling_periods: 5,
33            min_settling_time_s: 0.05,
34            autosave: true,
35            save_dialog: false,
36            basename: String::new(),
37        }
38    }
39}
40
41/// Result data from a lock-in frequency sweep measurement.
42#[derive(Debug, Clone)]
43pub struct LockInFreqSwpResult {
44    /// Names of recorded channels
45    pub channel_names: Vec<String>,
46    /// 2D data array `[rows][columns]`
47    /// First row is swept frequency, additional rows are channel data
48    pub data: Vec<Vec<f32>>,
49}
50
51/// Sweep direction for lock-in frequency sweep.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53pub enum FreqSwpDirection {
54    /// Sweep down (from upper limit to lower limit)
55    Down = 0,
56    /// Sweep up (from lower limit to upper limit)
57    #[default]
58    Up = 1,
59}
60
61impl From<FreqSwpDirection> for u32 {
62    fn from(dir: FreqSwpDirection) -> Self {
63        dir as u32
64    }
65}
66
67impl NanonisClient {
68    /// Open the Lock-In Frequency Sweep (Transfer Function) module.
69    ///
70    /// The transfer function does not run when its front panel is closed.
71    /// To automate measurements it may be required to open the module first.
72    ///
73    /// # Errors
74    /// Returns `NanonisError` if communication fails.
75    ///
76    /// # Examples
77    /// ```no_run
78    /// use nanonis_rs::NanonisClient;
79    ///
80    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
81    /// client.lockin_freq_swp_open()?;
82    /// # Ok::<(), Box<dyn std::error::Error>>(())
83    /// ```
84    pub fn lockin_freq_swp_open(&mut self) -> Result<(), NanonisError> {
85        self.quick_send("LockInFreqSwp.Open", vec![], vec![], vec![])?;
86        Ok(())
87    }
88
89    /// Start a Lock-In frequency sweep.
90    ///
91    /// # Arguments
92    /// * `get_data` - If true, returns measurement data
93    /// * `direction` - Sweep direction (up or down)
94    ///
95    /// # Returns
96    /// A [`LockInFreqSwpResult`] with channel names and 2D data.
97    ///
98    /// # Errors
99    /// Returns `NanonisError` if communication fails.
100    ///
101    /// # Examples
102    /// ```no_run
103    /// use nanonis_rs::NanonisClient;
104    /// use nanonis_rs::lockin_freq_swp::FreqSwpDirection;
105    ///
106    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
107    /// let result = client.lockin_freq_swp_start(true, FreqSwpDirection::Up)?;
108    /// println!("Channels: {:?}", result.channel_names);
109    /// # Ok::<(), Box<dyn std::error::Error>>(())
110    /// ```
111    pub fn lockin_freq_swp_start(
112        &mut self,
113        get_data: bool,
114        direction: FreqSwpDirection,
115    ) -> Result<LockInFreqSwpResult, NanonisError> {
116        let get_data_flag = if get_data { 1u32 } else { 0u32 };
117
118        let result = self.quick_send(
119            "LockInFreqSwp.Start",
120            vec![
121                NanonisValue::U32(get_data_flag),
122                NanonisValue::U32(direction.into()),
123            ],
124            vec!["I", "I"],
125            vec!["i", "i", "*+c", "i", "i", "2f"],
126        )?;
127
128        if result.len() >= 6 {
129            let channel_names = result[2].as_string_array()?.to_vec();
130            let rows = result[3].as_i32()? as usize;
131            let cols = result[4].as_i32()? as usize;
132
133            let flat_data = result[5].as_f32_array()?;
134            let mut data_2d = Vec::with_capacity(rows);
135            for row in 0..rows {
136                let start_idx = row * cols;
137                let end_idx = start_idx + cols;
138                if end_idx <= flat_data.len() {
139                    data_2d.push(flat_data[start_idx..end_idx].to_vec());
140                }
141            }
142
143            Ok(LockInFreqSwpResult {
144                channel_names,
145                data: data_2d,
146            })
147        } else {
148            Ok(LockInFreqSwpResult {
149                channel_names: vec![],
150                data: vec![],
151            })
152        }
153    }
154
155    /// Set the sweep signal for the Lock-In frequency sweep module.
156    ///
157    /// # Arguments
158    /// * `signal_index` - Sweep signal index, or -1 for no signal
159    ///
160    /// # Errors
161    /// Returns `NanonisError` if communication fails.
162    pub fn lockin_freq_swp_signal_set(&mut self, signal_index: i32) -> Result<(), NanonisError> {
163        self.quick_send(
164            "LockInFreqSwp.SignalSet",
165            vec![NanonisValue::I32(signal_index)],
166            vec!["i"],
167            vec![],
168        )?;
169        Ok(())
170    }
171
172    /// Get the sweep signal for the Lock-In frequency sweep module.
173    ///
174    /// # Returns
175    /// The sweep signal index, or -1 if no signal is selected.
176    ///
177    /// # Errors
178    /// Returns `NanonisError` if communication fails.
179    pub fn lockin_freq_swp_signal_get(&mut self) -> Result<i32, NanonisError> {
180        let result = self.quick_send("LockInFreqSwp.SignalGet", vec![], vec![], vec!["i"])?;
181
182        if !result.is_empty() {
183            Ok(result[0].as_i32()?)
184        } else {
185            Err(NanonisError::Protocol("Invalid response".to_string()))
186        }
187    }
188
189    /// Set the frequency limits for the Lock-In frequency sweep.
190    ///
191    /// # Arguments
192    /// * `lower_limit_hz` - Lower frequency limit in Hz
193    /// * `upper_limit_hz` - Upper frequency limit in Hz
194    ///
195    /// # Errors
196    /// Returns `NanonisError` if communication fails.
197    pub fn lockin_freq_swp_limits_set(
198        &mut self,
199        lower_limit_hz: f32,
200        upper_limit_hz: f32,
201    ) -> Result<(), NanonisError> {
202        self.quick_send(
203            "LockInFreqSwp.LimitsSet",
204            vec![
205                NanonisValue::F32(lower_limit_hz),
206                NanonisValue::F32(upper_limit_hz),
207            ],
208            vec!["f", "f"],
209            vec![],
210        )?;
211        Ok(())
212    }
213
214    /// Get the frequency limits for the Lock-In frequency sweep.
215    ///
216    /// # Returns
217    /// A tuple of (lower_limit_hz, upper_limit_hz).
218    ///
219    /// # Errors
220    /// Returns `NanonisError` if communication fails.
221    pub fn lockin_freq_swp_limits_get(&mut self) -> Result<(f32, f32), NanonisError> {
222        let result = self.quick_send("LockInFreqSwp.LimitsGet", vec![], vec![], vec!["f", "f"])?;
223
224        if result.len() >= 2 {
225            Ok((result[0].as_f32()?, result[1].as_f32()?))
226        } else {
227            Err(NanonisError::Protocol("Invalid response".to_string()))
228        }
229    }
230
231    /// Set the Lock-In frequency sweep properties.
232    ///
233    /// # Arguments
234    /// * `props` - A [`LockInFreqSwpProps`] struct with configuration
235    ///
236    /// # Errors
237    /// Returns `NanonisError` if communication fails.
238    ///
239    /// # Examples
240    /// ```no_run
241    /// use nanonis_rs::NanonisClient;
242    /// use nanonis_rs::lockin_freq_swp::LockInFreqSwpProps;
243    ///
244    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
245    /// let props = LockInFreqSwpProps {
246    ///     num_steps: 200,
247    ///     integration_periods: 20,
248    ///     ..Default::default()
249    /// };
250    /// client.lockin_freq_swp_props_set(&props)?;
251    /// # Ok::<(), Box<dyn std::error::Error>>(())
252    /// ```
253    pub fn lockin_freq_swp_props_set(
254        &mut self,
255        props: &LockInFreqSwpProps,
256    ) -> Result<(), NanonisError> {
257        let autosave_flag = if props.autosave { 1u32 } else { 0u32 };
258        let dialog_flag = if props.save_dialog { 1u32 } else { 0u32 };
259
260        self.quick_send(
261            "LockInFreqSwp.PropsSet",
262            vec![
263                NanonisValue::U16(props.num_steps),
264                NanonisValue::U16(props.integration_periods),
265                NanonisValue::F32(props.min_integration_time_s),
266                NanonisValue::U16(props.settling_periods),
267                NanonisValue::F32(props.min_settling_time_s),
268                NanonisValue::U32(autosave_flag),
269                NanonisValue::U32(dialog_flag),
270                NanonisValue::String(props.basename.clone()),
271            ],
272            vec!["H", "H", "f", "H", "f", "I", "I", "+*c"],
273            vec![],
274        )?;
275        Ok(())
276    }
277
278    /// Get the Lock-In frequency sweep properties.
279    ///
280    /// # Returns
281    /// A [`LockInFreqSwpProps`] struct with current configuration.
282    ///
283    /// # Errors
284    /// Returns `NanonisError` if communication fails.
285    pub fn lockin_freq_swp_props_get(&mut self) -> Result<LockInFreqSwpProps, NanonisError> {
286        let result = self.quick_send(
287            "LockInFreqSwp.PropsGet",
288            vec![],
289            vec![],
290            vec!["H", "H", "f", "H", "f", "I", "I", "i", "*-c"],
291        )?;
292
293        if result.len() >= 9 {
294            Ok(LockInFreqSwpProps {
295                num_steps: result[0].as_u16()?,
296                integration_periods: result[1].as_u16()?,
297                min_integration_time_s: result[2].as_f32()?,
298                settling_periods: result[3].as_u16()?,
299                min_settling_time_s: result[4].as_f32()?,
300                autosave: result[5].as_u32()? != 0,
301                save_dialog: result[6].as_u32()? != 0,
302                basename: result[8].as_string()?.to_string(),
303            })
304        } else {
305            Err(NanonisError::Protocol("Invalid response".to_string()))
306        }
307    }
308}