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}