rusty_tip/nanonis/client/z_spectr.rs
1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4
5/// Return type for Z spectroscopy start operation (channel names, data, bias values)
6pub type ZSpectroscopyResult = (Vec<String>, Vec<Vec<f32>>, Vec<f32>);
7
8impl NanonisClient {
9 /// Open the Z Spectroscopy module.
10 ///
11 /// Opens and initializes the Z Spectroscopy module for distance-dependent
12 /// measurements. This must be called before performing spectroscopy operations.
13 ///
14 /// # Errors
15 /// Returns `NanonisError` if communication fails or module cannot be opened.
16 ///
17 /// # Examples
18 /// ```no_run
19 /// use rusty_tip::NanonisClient;
20 ///
21 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
22 ///
23 /// // Open Z spectroscopy module
24 /// client.z_spectr_open()?;
25 /// println!("Z Spectroscopy module opened");
26 /// # Ok::<(), Box<dyn std::error::Error>>(())
27 /// ```
28 pub fn z_spectr_open(&mut self) -> Result<(), NanonisError> {
29 self.quick_send("ZSpectr.Open", vec![], vec![], vec![])?;
30 Ok(())
31 }
32
33 /// Start a Z spectroscopy measurement.
34 ///
35 /// Initiates a Z spectroscopy measurement with the configured parameters.
36 /// The tip is moved through a range of Z positions while recording selected channels.
37 ///
38 /// # Arguments
39 /// * `get_data` - If `true`, returns measurement data; if `false`, only starts measurement
40 /// * `save_base_name` - Base filename for saving data (empty for no change)
41 ///
42 /// # Returns
43 /// If `get_data` is true, returns a tuple containing:
44 /// - `Vec<String>` - Channel names
45 /// - `Vec<Vec<f32>>` - 2D measurement data \[rows\]\[columns\]
46 /// - `Vec<f32>` - Fixed parameters and settings
47 ///
48 /// # Errors
49 /// Returns `NanonisError` if communication fails or measurement cannot start.
50 ///
51 /// # Examples
52 /// ```no_run
53 /// use rusty_tip::NanonisClient;
54 ///
55 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
56 ///
57 /// // Start measurement and get data
58 /// let (channels, data, params) = client.z_spectr_start(true, "approach_001")?;
59 /// println!("Recorded {} channels with {} points", channels.len(), data.len());
60 ///
61 /// // Just start measurement without getting data
62 /// let (_, _, _) = client.z_spectr_start(false, "")?;
63 /// # Ok::<(), Box<dyn std::error::Error>>(())
64 /// ```
65 pub fn z_spectr_start(
66 &mut self,
67 get_data: bool,
68 save_base_name: &str,
69 ) -> Result<ZSpectroscopyResult, NanonisError> {
70 let get_data_flag = if get_data { 1u32 } else { 0u32 };
71
72 let result = self.quick_send(
73 "ZSpectr.Start",
74 vec![
75 NanonisValue::U32(get_data_flag),
76 NanonisValue::String(save_base_name.to_string()),
77 ],
78 vec!["I", "+*c"],
79 vec!["i", "i", "*+c", "i", "i", "2f", "i", "*f"],
80 )?;
81
82 if result.len() >= 8 {
83 let channel_names = result[2].as_string_array()?.to_vec();
84 let rows = result[3].as_i32()? as usize;
85 let cols = result[4].as_i32()? as usize;
86
87 // Parse 2D data array
88 let flat_data = result[5].as_f32_array()?;
89 let mut data_2d = Vec::with_capacity(rows);
90 for row in 0..rows {
91 let start_idx = row * cols;
92 let end_idx = start_idx + cols;
93 data_2d.push(flat_data[start_idx..end_idx].to_vec());
94 }
95
96 let parameters = result[7].as_f32_array()?.to_vec();
97 Ok((channel_names, data_2d, parameters))
98 } else {
99 Err(NanonisError::Protocol(
100 "Invalid Z spectroscopy start response".to_string(),
101 ))
102 }
103 }
104
105 /// Stop the current Z spectroscopy measurement.
106 ///
107 /// Immediately stops any running Z spectroscopy measurement and returns
108 /// the tip to its original position.
109 ///
110 /// # Errors
111 /// Returns `NanonisError` if communication fails.
112 ///
113 /// # Examples
114 /// ```no_run
115 /// use rusty_tip::NanonisClient;
116 ///
117 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
118 ///
119 /// // Start a measurement
120 /// let (_, _, _) = client.z_spectr_start(false, "")?;
121 ///
122 /// // Stop it after some condition
123 /// client.z_spectr_stop()?;
124 /// println!("Z spectroscopy stopped");
125 /// # Ok::<(), Box<dyn std::error::Error>>(())
126 /// ```
127 pub fn z_spectr_stop(&mut self) -> Result<(), NanonisError> {
128 self.quick_send("ZSpectr.Stop", vec![], vec![], vec![])?;
129 Ok(())
130 }
131
132 /// Get the status of Z spectroscopy measurement.
133 ///
134 /// Returns whether a Z spectroscopy measurement is currently running.
135 ///
136 /// # Returns
137 /// `true` if measurement is running, `false` if stopped.
138 ///
139 /// # Errors
140 /// Returns `NanonisError` if communication fails.
141 ///
142 /// # Examples
143 /// ```no_run
144 /// use rusty_tip::NanonisClient;
145 ///
146 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
147 ///
148 /// if client.z_spectr_status_get()? {
149 /// println!("Z spectroscopy is running");
150 /// } else {
151 /// println!("Z spectroscopy is stopped");
152 /// }
153 /// # Ok::<(), Box<dyn std::error::Error>>(())
154 /// ```
155 pub fn z_spectr_status_get(&mut self) -> Result<bool, NanonisError> {
156 let result =
157 self.quick_send("ZSpectr.StatusGet", vec![], vec![], vec!["I"])?;
158
159 match result.first() {
160 Some(value) => Ok(value.as_u32()? == 1),
161 None => Err(NanonisError::Protocol(
162 "No Z spectroscopy status returned".to_string(),
163 )),
164 }
165 }
166
167 /// Set the channels to record during Z spectroscopy.
168 ///
169 /// Configures which signals will be recorded during the Z spectroscopy measurement.
170 /// Channel indexes correspond to the 24 signals assigned in the Signals Manager (0-23).
171 ///
172 /// # Arguments
173 /// * `channel_indexes` - Vector of channel indexes to record (0-23)
174 ///
175 /// # Errors
176 /// Returns `NanonisError` if communication fails or invalid channel indexes provided.
177 ///
178 /// # Examples
179 /// ```no_run
180 /// use rusty_tip::NanonisClient;
181 ///
182 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
183 ///
184 /// // Record current (0), Z position (1), and bias voltage (2)
185 /// client.z_spectr_chs_set(vec![0, 1, 2])?;
186 ///
187 /// // Record more comprehensive dataset
188 /// client.z_spectr_chs_set(vec![0, 1, 2, 3, 4, 5])?;
189 /// # Ok::<(), Box<dyn std::error::Error>>(())
190 /// ```
191 pub fn z_spectr_chs_set(
192 &mut self,
193 channel_indexes: Vec<i32>,
194 ) -> Result<(), NanonisError> {
195 self.quick_send(
196 "ZSpectr.ChsSet",
197 vec![NanonisValue::ArrayI32(channel_indexes)],
198 vec!["+*i"],
199 vec![],
200 )?;
201 Ok(())
202 }
203
204 /// Get the channels configured for Z spectroscopy recording.
205 ///
206 /// Returns the channel indexes and names that will be recorded during measurements.
207 ///
208 /// # Returns
209 /// A tuple containing:
210 /// - `Vec<i32>` - Channel indexes (0-23 for Signals Manager slots)
211 /// - `Vec<String>` - Channel names corresponding to the indexes
212 ///
213 /// # Errors
214 /// Returns `NanonisError` if communication fails.
215 ///
216 /// # Examples
217 /// ```no_run
218 /// use rusty_tip::NanonisClient;
219 ///
220 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
221 ///
222 /// let (indexes, names) = client.z_spectr_chs_get()?;
223 /// println!("Recording {} channels:", indexes.len());
224 /// for (idx, name) in indexes.iter().zip(names.iter()) {
225 /// println!(" Channel {}: {}", idx, name);
226 /// }
227 /// # Ok::<(), Box<dyn std::error::Error>>(())
228 /// ```
229 pub fn z_spectr_chs_get(
230 &mut self,
231 ) -> Result<(Vec<i32>, Vec<String>), NanonisError> {
232 let result = self.quick_send(
233 "ZSpectr.ChsGet",
234 vec![],
235 vec![],
236 vec!["i", "*i", "i", "i", "*+c"],
237 )?;
238
239 if result.len() >= 5 {
240 let channel_indexes = result[1].as_i32_array()?.to_vec();
241 let channel_names = result[4].as_string_array()?.to_vec();
242 Ok((channel_indexes, channel_names))
243 } else {
244 Err(NanonisError::Protocol(
245 "Invalid Z spectroscopy channels response".to_string(),
246 ))
247 }
248 }
249
250 /// Set the Z range for spectroscopy measurements.
251 ///
252 /// Configures the Z offset and sweep distance for the spectroscopy measurement.
253 /// The tip will move from (offset - distance/2) to (offset + distance/2).
254 ///
255 /// # Arguments
256 /// * `z_offset_m` - Z offset position in meters (center of sweep)
257 /// * `z_sweep_distance_m` - Total sweep distance in meters
258 ///
259 /// # Errors
260 /// Returns `NanonisError` if communication fails or invalid range parameters.
261 ///
262 /// # Examples
263 /// ```no_run
264 /// use rusty_tip::NanonisClient;
265 ///
266 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
267 ///
268 /// // Sweep ±5 nm around current position
269 /// client.z_spectr_range_set(0.0, 10e-9)?;
270 ///
271 /// // Sweep from current position up to +20 nm
272 /// client.z_spectr_range_set(10e-9, 20e-9)?;
273 /// # Ok::<(), Box<dyn std::error::Error>>(())
274 /// ```
275 pub fn z_spectr_range_set(
276 &mut self,
277 z_offset_m: f32,
278 z_sweep_distance_m: f32,
279 ) -> Result<(), NanonisError> {
280 self.quick_send(
281 "ZSpectr.RangeSet",
282 vec![
283 NanonisValue::F32(z_offset_m),
284 NanonisValue::F32(z_sweep_distance_m),
285 ],
286 vec!["f", "f"],
287 vec![],
288 )?;
289 Ok(())
290 }
291
292 /// Get the current Z range configuration for spectroscopy.
293 ///
294 /// Returns the configured Z offset and sweep distance.
295 ///
296 /// # Returns
297 /// A tuple containing:
298 /// - `f32` - Z offset in meters (center position)
299 /// - `f32` - Z sweep distance in meters (total range)
300 ///
301 /// # Errors
302 /// Returns `NanonisError` if communication fails.
303 ///
304 /// # Examples
305 /// ```no_run
306 /// use rusty_tip::NanonisClient;
307 ///
308 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
309 ///
310 /// let (offset, distance) = client.z_spectr_range_get()?;
311 /// println!("Z sweep: {:.1} nm ± {:.1} nm", offset * 1e9, distance * 1e9 / 2.0);
312 /// # Ok::<(), Box<dyn std::error::Error>>(())
313 /// ```
314 pub fn z_spectr_range_get(&mut self) -> Result<(f32, f32), NanonisError> {
315 let result =
316 self.quick_send("ZSpectr.RangeGet", vec![], vec![], vec!["f", "f"])?;
317
318 if result.len() >= 2 {
319 Ok((result[0].as_f32()?, result[1].as_f32()?))
320 } else {
321 Err(NanonisError::Protocol(
322 "Invalid Z spectroscopy range response".to_string(),
323 ))
324 }
325 }
326
327 /// Set the timing parameters for Z spectroscopy.
328 ///
329 /// Configures timing-related parameters that control the speed and quality
330 /// of the Z spectroscopy measurement.
331 ///
332 /// # Arguments
333 /// * `z_averaging_time_s` - Time to average signals at each Z position
334 /// * `initial_settling_time_s` - Initial settling time before measurement
335 /// * `maximum_slew_rate_vdivs` - Maximum slew rate in V/s
336 /// * `settling_time_s` - Settling time between measurement points
337 /// * `integration_time_s` - Integration time for each measurement point
338 /// * `end_settling_time_s` - Settling time at the end of sweep
339 ///
340 /// # Errors
341 /// Returns `NanonisError` if communication fails or invalid timing parameters.
342 ///
343 /// # Examples
344 /// ```no_run
345 /// use rusty_tip::NanonisClient;
346 ///
347 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
348 ///
349 /// // Fast spectroscopy settings
350 /// client.z_spectr_timing_set(0.01, 0.1, 1000.0, 0.01, 0.01, 0.1)?;
351 ///
352 /// // High-quality slow spectroscopy
353 /// client.z_spectr_timing_set(0.1, 0.5, 100.0, 0.05, 0.05, 0.2)?;
354 /// # Ok::<(), Box<dyn std::error::Error>>(())
355 /// ```
356 pub fn z_spectr_timing_set(
357 &mut self,
358 z_averaging_time_s: f32,
359 initial_settling_time_s: f32,
360 maximum_slew_rate_vdivs: f32,
361 settling_time_s: f32,
362 integration_time_s: f32,
363 end_settling_time_s: f32,
364 ) -> Result<(), NanonisError> {
365 self.quick_send(
366 "ZSpectr.TimingSet",
367 vec![
368 NanonisValue::F32(z_averaging_time_s),
369 NanonisValue::F32(initial_settling_time_s),
370 NanonisValue::F32(maximum_slew_rate_vdivs),
371 NanonisValue::F32(settling_time_s),
372 NanonisValue::F32(integration_time_s),
373 NanonisValue::F32(end_settling_time_s),
374 ],
375 vec!["f", "f", "f", "f", "f", "f"],
376 vec![],
377 )?;
378 Ok(())
379 }
380
381 /// Get the current timing parameters for Z spectroscopy.
382 ///
383 /// Returns all timing-related configuration parameters.
384 ///
385 /// # Returns
386 /// A tuple containing:
387 /// - `f32` - Z averaging time (s)
388 /// - `f32` - Initial settling time (s)
389 /// - `f32` - Maximum slew rate (V/s)
390 /// - `f32` - Settling time (s)
391 /// - `f32` - Integration time (s)
392 /// - `f32` - End settling time (s)
393 ///
394 /// # Errors
395 /// Returns `NanonisError` if communication fails.
396 ///
397 /// # Examples
398 /// ```no_run
399 /// use rusty_tip::NanonisClient;
400 ///
401 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
402 ///
403 /// let (z_avg, init_settle, slew_rate, settle, integrate, end_settle) =
404 /// client.z_spectr_timing_get()?;
405 /// println!("Integration time: {:.3} s, settling: {:.3} s", integrate, settle);
406 /// # Ok::<(), Box<dyn std::error::Error>>(())
407 /// ```
408 pub fn z_spectr_timing_get(
409 &mut self,
410 ) -> Result<(f32, f32, f32, f32, f32, f32), NanonisError> {
411 let result = self.quick_send(
412 "ZSpectr.TimingGet",
413 vec![],
414 vec![],
415 vec!["f", "f", "f", "f", "f", "f"],
416 )?;
417
418 if result.len() >= 6 {
419 Ok((
420 result[0].as_f32()?,
421 result[1].as_f32()?,
422 result[2].as_f32()?,
423 result[3].as_f32()?,
424 result[4].as_f32()?,
425 result[5].as_f32()?,
426 ))
427 } else {
428 Err(NanonisError::Protocol(
429 "Invalid Z spectroscopy timing response".to_string(),
430 ))
431 }
432 }
433
434 /// Set the retraction parameters for tip protection during Z spectroscopy.
435 ///
436 /// Configures automatic tip retraction based on signal thresholds to prevent
437 /// tip crashes during approach spectroscopy.
438 ///
439 /// # Arguments
440 /// * `enable` - Enable/disable automatic retraction
441 /// * `threshold` - Signal threshold value for retraction trigger
442 /// * `signal_index` - Index of signal to monitor (0-23)
443 /// * `comparison` - Comparison type: 0=greater than, 1=less than
444 ///
445 /// # Errors
446 /// Returns `NanonisError` if communication fails or invalid parameters.
447 ///
448 /// # Examples
449 /// ```no_run
450 /// use rusty_tip::NanonisClient;
451 ///
452 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
453 ///
454 /// // Enable retraction when current exceeds 1 nA (signal 0, greater than)
455 /// client.z_spectr_retract_set(true, 1e-9, 0, 0)?;
456 ///
457 /// // Disable retraction
458 /// client.z_spectr_retract_set(false, 0.0, 0, 0)?;
459 /// # Ok::<(), Box<dyn std::error::Error>>(())
460 /// ```
461 pub fn z_spectr_retract_set(
462 &mut self,
463 enable: bool,
464 threshold: f32,
465 signal_index: i32,
466 comparison: u16,
467 ) -> Result<(), NanonisError> {
468 let enable_flag = if enable { 1u16 } else { 0u16 };
469
470 self.quick_send(
471 "ZSpectr.RetractSet",
472 vec![
473 NanonisValue::U16(enable_flag),
474 NanonisValue::F32(threshold),
475 NanonisValue::I32(signal_index),
476 NanonisValue::U16(comparison),
477 ],
478 vec!["H", "f", "i", "H"],
479 vec![],
480 )?;
481 Ok(())
482 }
483
484 /// Get the current retraction configuration for Z spectroscopy.
485 ///
486 /// Returns the tip protection settings that prevent crashes during measurements.
487 ///
488 /// # Returns
489 /// A tuple containing:
490 /// - `bool` - Retraction enabled/disabled
491 /// - `f32` - Threshold value for retraction
492 /// - `i32` - Signal index being monitored
493 /// - `u16` - Comparison type (0=greater, 1=less than)
494 ///
495 /// # Errors
496 /// Returns `NanonisError` if communication fails.
497 ///
498 /// # Examples
499 /// ```no_run
500 /// use rusty_tip::NanonisClient;
501 ///
502 /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
503 ///
504 /// let (enabled, threshold, signal_idx, comparison) = client.z_spectr_retract_get()?;
505 /// if enabled {
506 /// let comp_str = if comparison == 0 { ">" } else { "<" };
507 /// println!("Retraction: signal[{}] {} {:.3e}", signal_idx, comp_str, threshold);
508 /// }
509 /// # Ok::<(), Box<dyn std::error::Error>>(())
510 /// ```
511 pub fn z_spectr_retract_get(
512 &mut self,
513 ) -> Result<(bool, f32, i32, u16), NanonisError> {
514 let result = self.quick_send(
515 "ZSpectr.RetractGet",
516 vec![],
517 vec![],
518 vec!["H", "f", "i", "H"],
519 )?;
520
521 if result.len() >= 4 {
522 let enabled = result[0].as_u16()? == 1;
523 let threshold = result[1].as_f32()?;
524 let signal_index = result[2].as_i32()?;
525 let comparison = result[3].as_u16()?;
526 Ok((enabled, threshold, signal_index, comparison))
527 } else {
528 Err(NanonisError::Protocol(
529 "Invalid Z spectroscopy retract response".to_string(),
530 ))
531 }
532 }
533}