rusty_tip/nanonis/client/
scan.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::{NanonisValue, Position, ScanAction, ScanDirection, ScanFrame};
4use std::time::Duration;
5
6impl NanonisClient {
7    /// Start, stop, pause or resume a scan
8    pub fn scan_action(
9        &mut self,
10        scan_action: ScanAction,
11        scan_direction: ScanDirection,
12    ) -> Result<(), NanonisError> {
13        self.quick_send(
14            "Scan.Action",
15            vec![
16                NanonisValue::U16(scan_action.into()),
17                NanonisValue::U32(scan_direction.into()),
18            ],
19            vec!["H", "I"],
20            vec![],
21        )?;
22        Ok(())
23    }
24
25    /// Configure the scan frame parameters
26    pub fn scan_frame_set(&mut self, frame: ScanFrame) -> Result<(), NanonisError> {
27        self.quick_send(
28            "Scan.FrameSet",
29            vec![
30                NanonisValue::F32(frame.center.x as f32),
31                NanonisValue::F32(frame.center.y as f32),
32                NanonisValue::F32(frame.width_m),
33                NanonisValue::F32(frame.height_m),
34                NanonisValue::F32(frame.angle_deg),
35            ],
36            vec!["f", "f", "f", "f", "f"],
37            vec![],
38        )?;
39        Ok(())
40    }
41
42    /// Get the scan frame parameters
43    pub fn scan_frame_get(&mut self) -> Result<ScanFrame, NanonisError> {
44        let result = self.quick_send(
45            "Scan.FrameGet",
46            vec![],
47            vec![],
48            vec!["f", "f", "f", "f", "f"],
49        )?;
50        if result.len() >= 5 {
51            let center_x = result[0].as_f64()?;
52            let center_y = result[1].as_f64()?;
53            let width = result[2].as_f32()?;
54            let height = result[3].as_f32()?;
55            let angle = result[4].as_f32()?;
56
57            Ok(ScanFrame::new(
58                Position::new(center_x, center_y),
59                width,
60                height,
61                angle,
62            ))
63        } else {
64            Err(NanonisError::Protocol(
65                "Invalid scan frame response".to_string(),
66            ))
67        }
68    }
69
70    /// Get the scan buffer parameters
71    /// Returns: (channel_indexes, pixels, lines)
72    pub fn scan_buffer_get(&mut self) -> Result<(Vec<i32>, i32, i32), NanonisError> {
73        let result = self.quick_send(
74            "Scan.BufferGet",
75            vec![],
76            vec![],
77            vec!["i", "*i", "i", "i"],
78        )?;
79        if result.len() >= 4 {
80            let channel_indexes = result[1].as_i32_array()?.to_vec();
81            let pixels = result[2].as_i32()?;
82            let lines = result[3].as_i32()?;
83            Ok((channel_indexes, pixels, lines))
84        } else {
85            Err(NanonisError::Protocol(
86                "Invalid scan buffer response".to_string(),
87            ))
88        }
89    }
90
91    /// Get the current scan status.
92    ///
93    /// Returns whether a scan is currently running or not.
94    ///
95    /// # Returns
96    /// `true` if scan is running, `false` if scan is not running.
97    ///
98    /// # Errors
99    /// Returns `NanonisError` if communication fails or protocol error occurs.
100    ///
101    /// # Examples
102    /// ```no_run
103    /// use rusty_tip::NanonisClient;
104    ///
105    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
106    ///
107    /// if client.scan_status_get()? {
108    ///     println!("Scan is currently running");
109    /// } else {
110    ///     println!("Scan is stopped");
111    /// }
112    /// # Ok::<(), Box<dyn std::error::Error>>(())
113    /// ```
114    pub fn scan_status_get(&mut self) -> Result<bool, NanonisError> {
115        let result = self.quick_send("Scan.StatusGet", vec![], vec![], vec!["I"])?;
116
117        match result.first() {
118            Some(value) => Ok(value.as_u32()? == 1),
119            None => Err(NanonisError::Protocol(
120                "No scan status returned".to_string(),
121            )),
122        }
123    }
124
125    /// Configure the scan buffer parameters.
126    ///
127    /// Sets which channels to record during scanning and the scan resolution.
128    /// The channel indexes refer to the 24 signals assigned in the Signals Manager (0-23).
129    ///
130    /// **Important**: The number of pixels is coerced to the closest multiple of 16
131    /// because scan data is sent in packages of 16 pixels.
132    ///
133    /// # Arguments
134    /// * `channel_indexes` - Indexes of channels to record (0-23 for signals in Signals Manager)
135    /// * `pixels` - Number of pixels per line (coerced to multiple of 16)
136    /// * `lines` - Number of scan lines
137    ///
138    /// # Errors
139    /// Returns `NanonisError` if communication fails or invalid parameters provided.
140    ///
141    /// # Examples
142    /// ```no_run
143    /// use rusty_tip::NanonisClient;
144    ///
145    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
146    ///
147    /// // Record channels 0, 1, and 2 with 512x512 resolution
148    /// client.scan_buffer_set(vec![0, 1, 2], 512, 512)?;
149    ///
150    /// // High resolution scan with multiple channels
151    /// client.scan_buffer_set(vec![0, 1, 2, 3, 4], 1024, 1024)?;
152    /// # Ok::<(), Box<dyn std::error::Error>>(())
153    /// ```
154    pub fn scan_buffer_set(
155        &mut self,
156        channel_indexes: Vec<i32>,
157        pixels: i32,
158        lines: i32,
159    ) -> Result<(), NanonisError> {
160        self.quick_send(
161            "Scan.BufferSet",
162            vec![
163                NanonisValue::ArrayI32(channel_indexes),
164                NanonisValue::I32(pixels),
165                NanonisValue::I32(lines),
166            ],
167            vec!["+*i", "i", "i"],
168            vec![],
169        )?;
170        Ok(())
171    }
172
173    /// Configure scan speed parameters.
174    ///
175    /// Sets the tip scanning speeds for both forward and backward scan directions.
176    /// You can specify either linear speed or time per line, and set speed ratios
177    /// between forward and backward scanning.
178    ///
179    /// # Arguments
180    /// * `forward_linear_speed_m_s` - Forward linear speed in m/s
181    /// * `backward_linear_speed_m_s` - Backward linear speed in m/s
182    /// * `forward_time_per_line_s` - Forward time per line in seconds
183    /// * `backward_time_per_line_s` - Backward time per line in seconds
184    /// * `keep_parameter_constant` - Which parameter to keep constant: 0=no change, 1=linear speed, 2=time per line
185    /// * `speed_ratio` - Backward tip speed relative to forward speed
186    ///
187    /// # Errors
188    /// Returns `NanonisError` if communication fails or invalid parameters provided.
189    ///
190    /// # Examples
191    /// ```no_run
192    /// use rusty_tip::NanonisClient;
193    ///
194    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
195    ///
196    /// // Set 1 μm/s forward, 2 μm/s backward, keep linear speed constant
197    /// client.scan_speed_set(1e-6, 2e-6, 0.1, 0.05, 1, 2.0)?;
198    ///
199    /// // Set based on time per line, equal forward/backward speed
200    /// client.scan_speed_set(1e-6, 1e-6, 0.1, 0.1, 2, 1.0)?;
201    /// # Ok::<(), Box<dyn std::error::Error>>(())
202    /// ```
203    pub fn scan_speed_set(
204        &mut self,
205        forward_linear_speed_m_s: f32,
206        backward_linear_speed_m_s: f32,
207        forward_time_per_line_s: f32,
208        backward_time_per_line_s: f32,
209        keep_parameter_constant: u16,
210        speed_ratio: f32,
211    ) -> Result<(), NanonisError> {
212        self.quick_send(
213            "Scan.SpeedSet",
214            vec![
215                NanonisValue::F32(forward_linear_speed_m_s),
216                NanonisValue::F32(backward_linear_speed_m_s),
217                NanonisValue::F32(forward_time_per_line_s),
218                NanonisValue::F32(backward_time_per_line_s),
219                NanonisValue::U16(keep_parameter_constant),
220                NanonisValue::F32(speed_ratio),
221            ],
222            vec!["f", "f", "f", "f", "H", "f"],
223            vec![],
224        )?;
225        Ok(())
226    }
227
228    /// Get the current scan speed parameters.
229    ///
230    /// Returns all scan speed configuration values including linear speeds,
231    /// time per line, and speed ratio settings.
232    ///
233    /// # Returns
234    /// A tuple containing:
235    /// - `f32` - Forward linear speed (m/s)
236    /// - `f32` - Backward linear speed (m/s)
237    /// - `f32` - Forward time per line (s)
238    /// - `f32` - Backward time per line (s)
239    /// - `u16` - Keep parameter constant (0=linear speed, 1=time per line)
240    /// - `f32` - Speed ratio (backward relative to forward)
241    ///
242    /// # Errors
243    /// Returns `NanonisError` if communication fails or protocol error occurs.
244    ///
245    /// # Examples
246    /// ```no_run
247    /// use rusty_tip::NanonisClient;
248    ///
249    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
250    ///
251    /// let (fwd_speed, bwd_speed, fwd_time, bwd_time, keep_param, speed_ratio) =
252    ///     client.scan_speed_get()?;
253    ///
254    /// println!("Forward speed: {:.2e} m/s", fwd_speed);
255    /// println!("Backward speed: {:.2e} m/s", bwd_speed);
256    /// println!("Speed ratio: {:.1}", speed_ratio);
257    /// # Ok::<(), Box<dyn std::error::Error>>(())
258    /// ```
259    pub fn scan_speed_get(
260        &mut self,
261    ) -> Result<(f32, f32, f32, f32, u16, f32), NanonisError> {
262        let result = self.quick_send(
263            "Scan.SpeedGet",
264            vec![],
265            vec![],
266            vec!["f", "f", "f", "f", "H", "f"],
267        )?;
268
269        if result.len() >= 6 {
270            Ok((
271                result[0].as_f32()?,
272                result[1].as_f32()?,
273                result[2].as_f32()?,
274                result[3].as_f32()?,
275                result[4].as_u16()?,
276                result[5].as_f32()?,
277            ))
278        } else {
279            Err(NanonisError::Protocol(
280                "Invalid scan speed response".to_string(),
281            ))
282        }
283    }
284
285    /// Get the current XY position during scanning.
286    ///
287    /// Returns the current values of the X and Y signals, useful for monitoring
288    /// tip position during scanning operations.
289    ///
290    /// # Arguments
291    /// * `wait_newest_data` - If `true`, discards first value and waits for fresh data
292    ///
293    /// # Returns
294    /// A tuple containing (X position in m, Y position in m)
295    ///
296    /// # Errors
297    /// Returns `NanonisError` if communication fails or protocol error occurs.
298    ///
299    /// # Examples
300    /// ```no_run
301    /// use rusty_tip::NanonisClient;
302    ///
303    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
304    ///
305    /// // Get current position immediately
306    /// let (x, y) = client.scan_xy_pos_get(false)?;
307    /// println!("Current position: ({:.6}, {:.6}) m", x, y);
308    ///
309    /// // Wait for fresh position data
310    /// let (x, y) = client.scan_xy_pos_get(true)?;
311    /// # Ok::<(), Box<dyn std::error::Error>>(())
312    /// ```
313    pub fn scan_xy_pos_get(
314        &mut self,
315        wait_newest_data: bool,
316    ) -> Result<(f32, f32), NanonisError> {
317        let wait_flag = if wait_newest_data { 1u32 } else { 0u32 };
318
319        let result = self.quick_send(
320            "Scan.XYPosGet",
321            vec![NanonisValue::U32(wait_flag)],
322            vec!["I"],
323            vec!["f", "f"],
324        )?;
325
326        if result.len() >= 2 {
327            Ok((result[0].as_f32()?, result[1].as_f32()?))
328        } else {
329            Err(NanonisError::Protocol(
330                "Invalid XY position response".to_string(),
331            ))
332        }
333    }
334
335    /// Save the current scan data buffer to file.
336    ///
337    /// Saves the current scan data into a file. If `wait_until_saved` is true,
338    /// the function waits for the save operation to complete before returning.
339    ///
340    /// # Arguments
341    /// * `wait_until_saved` - If `true`, waits for save completion before returning
342    /// * `timeout_ms` - Timeout in milliseconds (-1 for indefinite wait)
343    ///
344    /// # Returns
345    /// `true` if timeout occurred while waiting for save completion, `false` otherwise
346    ///
347    /// # Errors
348    /// Returns `NanonisError` if communication fails or protocol error occurs.
349    ///
350    /// # Examples
351    /// ```no_run
352    /// use rusty_tip::NanonisClient;
353    ///
354    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
355    ///
356    /// // Save immediately without waiting
357    /// let timed_out = client.scan_save(false, 5000)?;
358    ///
359    /// // Save and wait up to 30 seconds for completion
360    /// let timed_out = client.scan_save(true, 30000)?;
361    /// if timed_out {
362    ///     println!("Save operation timed out");
363    /// }
364    /// # Ok::<(), Box<dyn std::error::Error>>(())
365    /// ```
366    pub fn scan_save(
367        &mut self,
368        wait_until_saved: bool,
369        timeout_ms: i32,
370    ) -> Result<bool, NanonisError> {
371        let wait_flag = if wait_until_saved { 1u32 } else { 0u32 };
372
373        let result = self.quick_send(
374            "Scan.Save",
375            vec![NanonisValue::U32(wait_flag), NanonisValue::I32(timeout_ms)],
376            vec!["I", "i"],
377            vec!["I"],
378        )?;
379
380        match result.first() {
381            Some(value) => Ok(value.as_u32()? == 1),
382            None => Err(NanonisError::Protocol(
383                "No save status returned".to_string(),
384            )),
385        }
386    }
387
388    /// Get scan frame data for a specific channel and direction.
389    ///
390    /// Returns the complete 2D scan data array for the selected channel.
391    /// The channel must be one of the channels configured in the scan buffer.
392    ///
393    /// # Arguments
394    /// * `channel_index` - Index of channel to retrieve data from (must be in acquired channels)
395    /// * `data_direction` - Data direction: `true` for forward, `false` for backward
396    ///
397    /// # Returns
398    /// A tuple containing:
399    /// - `String` - Channel name
400    /// - `Vec<Vec<f32>>` - 2D scan data array \[rows\]\[columns\]
401    /// - `bool` - Scan direction: `true` for up, `false` for down
402    ///
403    /// # Errors
404    /// Returns `NanonisError` if:
405    /// - Invalid channel index (not in acquired channels)
406    /// - Communication fails or protocol error occurs
407    ///
408    /// # Examples
409    /// ```no_run
410    /// use rusty_tip::NanonisClient;
411    ///
412    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
413    ///
414    /// // Get forward scan data for channel 0
415    /// let (channel_name, data, scan_up) = client.scan_frame_data_grab(0, true)?;
416    /// println!("Channel: {}, Direction: {}", channel_name, if scan_up { "up" } else { "down" });
417    /// println!("Data size: {}x{}", data.len(), data[0].len());
418    ///
419    /// // Get backward scan data
420    /// let (_, back_data, _) = client.scan_frame_data_grab(0, false)?;
421    /// # Ok::<(), Box<dyn std::error::Error>>(())
422    /// ```
423    pub fn scan_frame_data_grab(
424        &mut self,
425        channel_index: u32,
426        data_direction: bool,
427    ) -> Result<(String, Vec<Vec<f32>>, bool), NanonisError> {
428        let direction_flag = if data_direction { 1u32 } else { 0u32 };
429
430        let result = self.quick_send(
431            "Scan.FrameDataGrab",
432            vec![
433                NanonisValue::U32(channel_index),
434                NanonisValue::U32(direction_flag),
435            ],
436            vec!["I", "I"],
437            vec!["i", "*-c", "i", "i", "2f", "I"],
438        )?;
439
440        if result.len() >= 6 {
441            let channel_name = result[1].as_string()?.to_string();
442            let rows = result[2].as_i32()? as usize;
443            let cols = result[3].as_i32()? as usize;
444
445            // Parse 2D array from flat f32 array
446            let flat_data = result[4].as_f32_array()?;
447            let mut data_2d = Vec::with_capacity(rows);
448
449            for row in 0..rows {
450                let start_idx = row * cols;
451                let end_idx = start_idx + cols;
452                data_2d.push(flat_data[start_idx..end_idx].to_vec());
453            }
454
455            let scan_direction = result[5].as_u32()? == 1;
456            Ok((channel_name, data_2d, scan_direction))
457        } else {
458            Err(NanonisError::Protocol(
459                "Invalid frame data response".to_string(),
460            ))
461        }
462    }
463
464    /// Wait for the End-of-Scan.
465    ///
466    /// Waits for the current scan to complete or timeout to occur, whichever comes first.
467    /// This is useful for synchronizing operations with scan completion.
468    ///
469    /// # Arguments
470    /// * `timeout` - Timeout duration (-1 for indefinite wait)
471    ///
472    /// # Returns
473    /// A tuple containing:
474    /// - `bool` - `true` if timeout occurred, `false` if scan completed normally
475    /// - `String` - File path where data was auto-saved (empty if no auto-save)
476    ///
477    /// # Errors
478    /// Returns `NanonisError` if communication fails or protocol error occurs.
479    ///
480    /// # Examples
481    /// ```no_run
482    /// use rusty_tip::{NanonisClient, ScanAction, ScanDirection};
483    /// use std::time::Duration;
484    ///
485    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
486    ///
487    /// // Start a scan
488    /// client.scan_action(ScanAction::Start, ScanDirection::Up)?;
489    ///
490    /// // Wait for scan to complete (up to 5 minutes)
491    /// let (timed_out, file_path) = client.scan_wait_end_of_scan(Duration::from_secs(300))?;
492    ///
493    /// if timed_out {
494    ///     println!("Scan timed out after 5 minutes");
495    /// } else {
496    ///     println!("Scan completed");
497    ///     if !file_path.is_empty() {
498    ///         println!("Data saved to: {}", file_path);
499    ///     }
500    /// }
501    /// # Ok::<(), Box<dyn std::error::Error>>(())
502    /// ```
503    pub fn scan_wait_end_of_scan(
504        &mut self,
505        timeout: Duration,
506    ) -> Result<(bool, String), NanonisError> {
507        let result = self.quick_send(
508            "Scan.WaitEndOfScan",
509            vec![NanonisValue::I32(timeout.as_millis() as i32)],
510            vec!["i"],
511            vec!["I", "I", "*-c"],
512        )?;
513
514        if result.len() >= 3 {
515            let timeout_occurred = result[0].as_u32()? == 1;
516            let file_path = result[2].as_string()?.to_string();
517            Ok((timeout_occurred, file_path))
518        } else {
519            Err(NanonisError::Protocol(
520                "Invalid scan wait response".to_string(),
521            ))
522        }
523    }
524}