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}