Skip to main content

nanonis_rs/client/
tip_recovery.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4use std::time::Duration;
5
6/// Configuration parameters for tip shaper
7#[derive(Debug, Clone)]
8pub struct TipShaperConfig {
9    pub switch_off_delay: Duration,
10    pub change_bias: bool,
11    pub bias_v: f32,
12    pub tip_lift_m: f32,
13    pub lift_time_1: Duration,
14    pub bias_lift_v: f32,
15    pub bias_settling_time: Duration,
16    pub lift_height_m: f32,
17    pub lift_time_2: Duration,
18    pub end_wait_time: Duration,
19    pub restore_feedback: bool,
20}
21
22/// Return type for tip shaper properties
23pub type TipShaperProps = (f32, u32, f32, f32, f32, f32, f32, f32, f32, f32, u32);
24
25impl NanonisClient {
26    /// Set the buffer size of the Tip Move Recorder.
27    ///
28    /// Sets the number of data elements that can be stored in the Tip Move Recorder
29    /// buffer. This recorder tracks signal values while the tip is moving in Follow Me mode.
30    /// **Note**: This function clears the existing graph data.
31    ///
32    /// # Arguments
33    /// * `buffer_size` - Number of data elements to store in the recorder buffer
34    ///
35    /// # Errors
36    /// Returns `NanonisError` if communication fails or invalid buffer size.
37    ///
38    /// # Examples
39    /// ```no_run
40    /// use nanonis_rs::NanonisClient;
41    ///
42    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
43    ///
44    /// // Set buffer for 10,000 data points
45    /// client.tip_rec_buffer_size_set(10000)?;
46    ///
47    /// // Set smaller buffer for quick tests
48    /// client.tip_rec_buffer_size_set(1000)?;
49    /// # Ok::<(), Box<dyn std::error::Error>>(())
50    /// ```
51    pub fn tip_rec_buffer_size_set(&mut self, buffer_size: i32) -> Result<(), NanonisError> {
52        self.quick_send(
53            "TipRec.BufferSizeSet",
54            vec![NanonisValue::I32(buffer_size)],
55            vec!["i"],
56            vec![],
57        )?;
58        Ok(())
59    }
60
61    /// Get the current buffer size of the Tip Move Recorder.
62    ///
63    /// Returns the number of data elements that can be stored in the recorder buffer.
64    ///
65    /// # Returns
66    /// Current buffer size (number of data elements).
67    ///
68    /// # Errors
69    /// Returns `NanonisError` if communication fails.
70    ///
71    /// # Examples
72    /// ```no_run
73    /// use nanonis_rs::NanonisClient;
74    ///
75    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
76    ///
77    /// let buffer_size = client.tip_rec_buffer_size_get()?;
78    /// println!("Tip recorder buffer size: {} points", buffer_size);
79    /// # Ok::<(), Box<dyn std::error::Error>>(())
80    /// ```
81    pub fn tip_rec_buffer_size_get(&mut self) -> Result<i32, NanonisError> {
82        let result = self.quick_send("TipRec.BufferSizeGet", vec![], vec![], vec!["i"])?;
83
84        match result.first() {
85            Some(value) => Ok(value.as_i32()?),
86            None => Err(NanonisError::Protocol(
87                "No buffer size returned".to_string(),
88            )),
89        }
90    }
91
92    /// Clear the buffer of the Tip Move Recorder.
93    ///
94    /// Removes all recorded data from the Tip Move Recorder buffer, resetting
95    /// it to an empty state. This is useful before starting a new recording session.
96    ///
97    /// # Errors
98    /// Returns `NanonisError` if communication fails.
99    ///
100    /// # Examples
101    /// ```no_run
102    /// use nanonis_rs::NanonisClient;
103    ///
104    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
105    ///
106    /// // Clear buffer before starting new measurement
107    /// client.tip_rec_buffer_clear()?;
108    /// println!("Tip recorder buffer cleared");
109    /// # Ok::<(), Box<dyn std::error::Error>>(())
110    /// ```
111    pub fn tip_rec_buffer_clear(&mut self) -> Result<(), NanonisError> {
112        self.quick_send("TipRec.BufferClear", vec![], vec![], vec![])?;
113        Ok(())
114    }
115
116    /// Get the recorded data from the Tip Move Recorder.
117    ///
118    /// Returns all data recorded while the tip was moving in Follow Me mode.
119    /// This includes channel indexes, names, and the complete 2D data array
120    /// with measurements taken during tip movement.
121    ///
122    /// # Returns
123    /// A tuple containing:
124    /// - `Vec<i32>` - Channel indexes (0-23 for Signals Manager slots)
125    /// - `Vec<Vec<f32>>` - 2D data array \[rows\]\[columns\] with recorded measurements
126    ///
127    /// # Errors
128    /// Returns `NanonisError` if communication fails or no data available.
129    ///
130    /// # Examples
131    /// ```no_run
132    /// use nanonis_rs::NanonisClient;
133    ///
134    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
135    ///
136    /// // Get recorded tip movement data
137    /// let (channel_indexes, data) = client.tip_rec_data_get()?;
138    ///
139    /// println!("Recorded {} channels with {} data points",
140    ///          channel_indexes.len(), data.len());
141    ///
142    /// // Analyze data for each channel
143    /// for (i, &channel_idx) in channel_indexes.iter().enumerate() {
144    ///     if i < data[0].len() {
145    ///         println!("Channel {}: {} values", channel_idx, data.len());
146    ///     }
147    /// }
148    /// # Ok::<(), Box<dyn std::error::Error>>(())
149    /// ```
150    pub fn tip_rec_data_get(&mut self) -> Result<(Vec<i32>, Vec<Vec<f32>>), NanonisError> {
151        let result = self.quick_send(
152            "TipRec.DataGet",
153            vec![],
154            vec![],
155            vec!["i", "*i", "i", "i", "2f"],
156        )?;
157
158        if result.len() >= 5 {
159            let channel_indexes = result[1].as_i32_array()?.to_vec();
160            let rows = result[2].as_i32()? as usize;
161            let cols = result[3].as_i32()? as usize;
162
163            // Parse 2D data array
164            let flat_data = result[4].as_f32_array()?;
165            let mut data_2d = Vec::with_capacity(rows);
166            for row in 0..rows {
167                let start_idx = row * cols;
168                let end_idx = start_idx + cols;
169                data_2d.push(flat_data[start_idx..end_idx].to_vec());
170            }
171
172            Ok((channel_indexes, data_2d))
173        } else {
174            Err(NanonisError::Protocol(
175                "Invalid tip recorder data response".to_string(),
176            ))
177        }
178    }
179
180    /// Save the tip movement data to a file.
181    ///
182    /// Saves all data recorded in Follow Me mode to a file with the specified basename.
183    /// Optionally clears the buffer after saving to prepare for new recordings.
184    ///
185    /// # Arguments
186    /// * `clear_buffer` - If `true`, clears buffer after saving
187    /// * `basename` - Base filename for saved data (empty to use last basename)
188    ///
189    /// # Errors
190    /// Returns `NanonisError` if communication fails or file save error occurs.
191    ///
192    /// # Examples
193    /// ```no_run
194    /// use nanonis_rs::NanonisClient;
195    ///
196    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
197    ///
198    /// // Save data and clear buffer for next measurement
199    /// client.tip_rec_data_save(true, "tip_approach_001")?;
200    ///
201    /// // Save without clearing buffer (keep data for analysis)
202    /// client.tip_rec_data_save(false, "tip_movement_log")?;
203    /// # Ok::<(), Box<dyn std::error::Error>>(())
204    /// ```
205    pub fn tip_rec_data_save(
206        &mut self,
207        clear_buffer: bool,
208        basename: &str,
209    ) -> Result<(), NanonisError> {
210        let clear_flag = if clear_buffer { 1u32 } else { 0u32 };
211
212        self.quick_send(
213            "TipRec.DataSave",
214            vec![
215                NanonisValue::U32(clear_flag),
216                NanonisValue::String(basename.to_string()),
217            ],
218            vec!["I", "+*c"],
219            vec![],
220        )?;
221        Ok(())
222    }
223
224    /// Start the tip shaper procedure for tip conditioning.
225    ///
226    /// Initiates the tip shaper procedure which performs controlled tip conditioning
227    /// by applying specific voltage sequences and mechanical movements. This is used
228    /// to improve tip sharpness and stability after crashes or contamination.
229    ///
230    /// # Arguments
231    /// * `wait_until_finished` - If `true`, waits for procedure completion
232    /// * `timeout_ms` - Timeout in milliseconds (-1 for infinite wait)
233    ///
234    /// # Errors
235    /// Returns `NanonisError` if communication fails or procedure cannot start.
236    ///
237    /// # Examples
238    /// ```no_run
239    /// use nanonis_rs::NanonisClient;
240    /// use std::time::Duration;
241    ///
242    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
243    ///
244    /// // Start tip shaping and wait for completion (30 second timeout)
245    /// client.tip_shaper_start(true, Duration::from_secs(30))?;
246    /// println!("Tip shaping completed");
247    ///
248    /// // Start tip shaping without waiting
249    /// client.tip_shaper_start(false, Duration::ZERO)?;
250    /// # Ok::<(), Box<dyn std::error::Error>>(())
251    /// ```
252    pub fn tip_shaper_start(
253        &mut self,
254        wait_until_finished: bool,
255        timeout: Duration,
256    ) -> Result<(), NanonisError> {
257        let wait_flag = if wait_until_finished { 1u32 } else { 0u32 };
258        let timeout = timeout.as_millis().min(u32::MAX as u128) as i32;
259
260        self.quick_send(
261            "TipShaper.Start",
262            vec![NanonisValue::U32(wait_flag), NanonisValue::I32(timeout)],
263            vec!["I", "i"],
264            vec![],
265        )?;
266        Ok(())
267    }
268
269    /// Set the tip shaper procedure configuration.
270    ///
271    /// Configures all parameters for the tip conditioning procedure including
272    /// timing, voltages, and mechanical movements. This is a complex procedure
273    /// with multiple stages of tip treatment.
274    ///
275    /// # Arguments
276    /// * `config` - Tip shaper configuration parameters
277    ///
278    /// # Errors
279    /// Returns `NanonisError` if communication fails or invalid parameters.
280    ///
281    /// # Examples
282    /// ```no_run
283    /// use nanonis_rs::NanonisClient;
284    /// use nanonis_rs::tip_recovery::TipShaperConfig;
285    /// use std::time::Duration;
286    ///
287    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
288    ///
289    /// // Conservative tip conditioning parameters
290    /// let config = TipShaperConfig {
291    ///     switch_off_delay: Duration::from_millis(100),
292    ///     change_bias: true,
293    ///     bias_v: -2.0,
294    ///     tip_lift_m: 50e-9,   // 50 nm
295    ///     lift_time_1: Duration::from_secs(1),
296    ///     bias_lift_v: 5.0,
297    ///     bias_settling_time: Duration::from_millis(500),
298    ///     lift_height_m: 100e-9, // 100 nm
299    ///     lift_time_2: Duration::from_millis(500),
300    ///     end_wait_time: Duration::from_millis(200),
301    ///     restore_feedback: true,
302    /// };
303    /// client.tip_shaper_props_set(config)?;
304    /// # Ok::<(), Box<dyn std::error::Error>>(())
305    /// ```
306    pub fn tip_shaper_props_set(&mut self, config: TipShaperConfig) -> Result<(), NanonisError> {
307        self.quick_send(
308            "TipShaper.PropsSet",
309            vec![
310                NanonisValue::F32(config.switch_off_delay.as_secs_f32()),
311                NanonisValue::U32(config.change_bias.into()),
312                NanonisValue::F32(config.bias_v),
313                NanonisValue::F32(config.tip_lift_m),
314                NanonisValue::F32(config.lift_time_1.as_secs_f32()),
315                NanonisValue::F32(config.bias_lift_v),
316                NanonisValue::F32(config.bias_settling_time.as_secs_f32()),
317                NanonisValue::F32(config.lift_height_m),
318                NanonisValue::F32(config.lift_time_2.as_secs_f32()),
319                NanonisValue::F32(config.end_wait_time.as_secs_f32()),
320                NanonisValue::U32(config.restore_feedback.into()),
321            ],
322            vec!["f", "I", "f", "f", "f", "f", "f", "f", "f", "f", "I"],
323            vec![],
324        )?;
325        Ok(())
326    }
327
328    /// Get the current tip shaper procedure configuration.
329    ///
330    /// Returns all parameters currently configured for the tip conditioning procedure.
331    /// Use this to verify settings before starting the procedure.
332    ///
333    /// # Returns
334    /// A tuple containing all tip shaper parameters:
335    /// - `f32` - Switch off delay (s)
336    /// - `u32` - Change bias flag (0=no change, 1=true, 2=false)
337    /// - `f32` - Bias voltage (V)
338    /// - `f32` - Tip lift distance (m)
339    /// - `f32` - First lift time (s)
340    /// - `f32` - Bias lift voltage (V)
341    /// - `f32` - Bias settling time (s)
342    /// - `f32` - Second lift height (m)
343    /// - `f32` - Second lift time (s)
344    /// - `f32` - End wait time (s)
345    /// - `u32` - Restore feedback flag (0=no change, 1=true, 2=false)
346    ///
347    /// # Errors
348    /// Returns `NanonisError` if communication fails.
349    ///
350    /// # Examples
351    /// ```no_run
352    /// use nanonis_rs::NanonisClient;
353    ///
354    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
355    ///
356    /// let (switch_delay, change_bias, bias_v, tip_lift, lift_time1,
357    ///      bias_lift, settling, lift_height, lift_time2, end_wait, restore) =
358    ///      client.tip_shaper_props_get()?;
359    ///
360    /// println!("Tip lift: {:.1} nm, Bias: {:.1} V", tip_lift * 1e9, bias_v);
361    /// println!("Total procedure time: ~{:.1} s",
362    ///          switch_delay + lift_time1 + settling + lift_time2 + end_wait);
363    /// # Ok::<(), Box<dyn std::error::Error>>(())
364    /// ```
365    pub fn tip_shaper_props_get(&mut self) -> Result<TipShaperProps, NanonisError> {
366        let result = self.quick_send(
367            "TipShaper.PropsGet",
368            vec![],
369            vec![],
370            vec!["f", "I", "f", "f", "f", "f", "f", "f", "f", "f", "I"],
371        )?;
372
373        if result.len() >= 11 {
374            Ok((
375                result[0].as_f32()?,  // switch_off_delay
376                result[1].as_u32()?,  // change_bias
377                result[2].as_f32()?,  // bias_v
378                result[3].as_f32()?,  // tip_lift_m
379                result[4].as_f32()?,  // lift_time_1_s
380                result[5].as_f32()?,  // bias_lift_v
381                result[6].as_f32()?,  // bias_settling_time_s
382                result[7].as_f32()?,  // lift_height_m
383                result[8].as_f32()?,  // lift_time_2_s
384                result[9].as_f32()?,  // end_wait_time_s
385                result[10].as_u32()?, // restore_feedback
386            ))
387        } else {
388            Err(NanonisError::Protocol(
389                "Invalid tip shaper properties response".to_string(),
390            ))
391        }
392    }
393
394    /// Get the Tip Shaper properties as a type-safe TipShaperConfig struct
395    ///
396    /// This method returns the same information as `tip_shaper_props_get()` but
397    /// with Duration types for time fields instead of raw f32 seconds.
398    ///
399    /// # Returns
400    /// Returns a `TipShaperConfig` struct with type-safe Duration fields for all time parameters.
401    ///
402    /// # Errors
403    /// Returns `NanonisError` if communication fails or if the response format is invalid.
404    ///
405    /// # Examples
406    /// ```no_run
407    /// use nanonis_rs::NanonisClient;
408    /// use std::time::Duration;
409    ///
410    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
411    /// let config = client.tip_shaper_config_get()?;
412    ///
413    /// println!("Tip lift: {:.1} nm, Bias: {:.1} V",
414    ///          config.tip_lift_m * 1e9, config.bias_v);
415    /// println!("Total procedure time: {:.1} s",
416    ///          (config.switch_off_delay + config.lift_time_1 +
417    ///           config.bias_settling_time + config.lift_time_2 +
418    ///           config.end_wait_time).as_secs_f32());
419    /// # Ok::<(), Box<dyn std::error::Error>>(())
420    /// ```
421    pub fn tip_shaper_config_get(&mut self) -> Result<TipShaperConfig, NanonisError> {
422        let result = self.quick_send(
423            "TipShaper.PropsGet",
424            vec![],
425            vec![],
426            vec!["f", "I", "f", "f", "f", "f", "f", "f", "f", "f", "I"],
427        )?;
428
429        if result.len() >= 11 {
430            let restore_feedback = match result[10].as_u32()? {
431                0 => true,
432                1 => false,
433                _ => panic!("Wrong return value for restore_feedback"),
434            };
435
436            let change_bias = match result[1].as_u32()? {
437                0 => true,
438                1 => false,
439                _ => panic!("Wrong return value for change_bias"),
440            };
441
442            Ok(TipShaperConfig {
443                switch_off_delay: Duration::from_secs_f32(result[0].as_f32()?),
444                change_bias,
445                bias_v: result[2].as_f32()?,
446                tip_lift_m: result[3].as_f32()?,
447                lift_time_1: Duration::from_secs_f32(result[4].as_f32()?),
448                bias_lift_v: result[5].as_f32()?,
449                bias_settling_time: Duration::from_secs_f32(result[6].as_f32()?),
450                lift_height_m: result[7].as_f32()?,
451                lift_time_2: Duration::from_secs_f32(result[8].as_f32()?),
452                end_wait_time: Duration::from_secs_f32(result[9].as_f32()?),
453                restore_feedback,
454            })
455        } else {
456            Err(NanonisError::Protocol(
457                "Invalid tip shaper properties response".to_string(),
458            ))
459        }
460    }
461}