Skip to main content

nanonis_rs/client/
tip_recovery.rs

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