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}