Skip to main content

nanonis_rs/client/
pattern.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4
5/// Pattern type for grid experiments.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum PatternType {
8    /// No change to current pattern
9    #[default]
10    NoChange = 0,
11    /// Grid pattern
12    Grid = 1,
13    /// Line pattern
14    Line = 2,
15    /// Cloud pattern
16    Cloud = 3,
17}
18
19impl From<PatternType> for u16 {
20    fn from(p: PatternType) -> Self {
21        p as u16
22    }
23}
24
25/// Grid pattern configuration.
26#[derive(Debug, Clone, Copy, Default)]
27pub struct GridConfig {
28    /// Number of points in X direction
29    pub num_points_x: i32,
30    /// Number of points in Y direction
31    pub num_points_y: i32,
32    /// X coordinate of grid center (meters)
33    pub center_x_m: f32,
34    /// Y coordinate of grid center (meters)
35    pub center_y_m: f32,
36    /// Grid width (meters)
37    pub width_m: f32,
38    /// Grid height (meters)
39    pub height_m: f32,
40    /// Grid rotation angle (degrees)
41    pub angle_deg: f32,
42}
43
44/// Line pattern configuration.
45#[derive(Debug, Clone, Copy, Default)]
46pub struct LineConfig {
47    /// Number of points on line
48    pub num_points: i32,
49    /// X coordinate of point 1 (meters)
50    pub point1_x_m: f32,
51    /// Y coordinate of point 1 (meters)
52    pub point1_y_m: f32,
53    /// X coordinate of point 2 (meters)
54    pub point2_x_m: f32,
55    /// Y coordinate of point 2 (meters)
56    pub point2_y_m: f32,
57}
58
59/// Cloud pattern configuration.
60#[derive(Debug, Clone, Default)]
61pub struct CloudConfig {
62    /// X coordinates of points (meters)
63    pub x_coords_m: Vec<f32>,
64    /// Y coordinates of points (meters)
65    pub y_coords_m: Vec<f32>,
66}
67
68/// Pattern experiment properties.
69#[derive(Debug, Clone)]
70pub struct PatternProps {
71    /// List of available experiments
72    pub available_experiments: Vec<String>,
73    /// Currently selected experiment
74    pub selected_experiment: String,
75    /// Path to external VI
76    pub external_vi_path: String,
77    /// Pre-measure delay in seconds
78    pub pre_measure_delay_s: f32,
79    /// Save scan channels to file
80    pub save_scan_channels: bool,
81}
82
83impl Default for PatternProps {
84    fn default() -> Self {
85        Self {
86            available_experiments: vec![],
87            selected_experiment: String::new(),
88            external_vi_path: String::new(),
89            pre_measure_delay_s: 0.0,
90            save_scan_channels: false,
91        }
92    }
93}
94
95impl NanonisClient {
96    /// Open the selected grid experiment.
97    ///
98    /// Required to configure the experiment and be able to start it.
99    ///
100    /// # Errors
101    /// Returns `NanonisError` if communication fails.
102    ///
103    /// # Examples
104    /// ```no_run
105    /// use nanonis_rs::NanonisClient;
106    ///
107    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
108    /// client.pattern_exp_open()?;
109    /// # Ok::<(), Box<dyn std::error::Error>>(())
110    /// ```
111    pub fn pattern_exp_open(&mut self) -> Result<(), NanonisError> {
112        self.quick_send("Pattern.ExpOpen", vec![], vec![], vec![])?;
113        Ok(())
114    }
115
116    /// Start the selected grid experiment.
117    ///
118    /// Before using this function, select the experiment through `pattern_props_set`,
119    /// and be sure to have it open.
120    ///
121    /// # Arguments
122    /// * `pattern` - Pattern type to switch to before starting
123    ///
124    /// # Errors
125    /// Returns `NanonisError` if communication fails.
126    pub fn pattern_exp_start(&mut self, pattern: PatternType) -> Result<(), NanonisError> {
127        self.quick_send(
128            "Pattern.ExpStart",
129            vec![NanonisValue::U16(pattern.into())],
130            vec!["H"],
131            vec![],
132        )?;
133        Ok(())
134    }
135
136    /// Pause or resume the selected grid experiment.
137    ///
138    /// # Arguments
139    /// * `pause` - True to pause, false to resume
140    ///
141    /// # Errors
142    /// Returns `NanonisError` if communication fails.
143    pub fn pattern_exp_pause(&mut self, pause: bool) -> Result<(), NanonisError> {
144        let flag = if pause { 1u32 } else { 0u32 };
145        self.quick_send(
146            "Pattern.ExpPause",
147            vec![NanonisValue::U32(flag)],
148            vec!["I"],
149            vec![],
150        )?;
151        Ok(())
152    }
153
154    /// Stop the selected grid experiment.
155    ///
156    /// # Errors
157    /// Returns `NanonisError` if communication fails.
158    pub fn pattern_exp_stop(&mut self) -> Result<(), NanonisError> {
159        self.quick_send("Pattern.ExpStop", vec![], vec![], vec![])?;
160        Ok(())
161    }
162
163    /// Get the status of the selected grid experiment.
164    ///
165    /// # Returns
166    /// True if the experiment is running.
167    ///
168    /// # Errors
169    /// Returns `NanonisError` if communication fails.
170    pub fn pattern_exp_status_get(&mut self) -> Result<bool, NanonisError> {
171        let result = self.quick_send("Pattern.ExpStatusGet", vec![], vec![], vec!["I"])?;
172
173        if !result.is_empty() {
174            Ok(result[0].as_u32()? != 0)
175        } else {
176            Err(NanonisError::Protocol("Invalid response".to_string()))
177        }
178    }
179
180    /// Set the grid pattern parameters.
181    ///
182    /// # Arguments
183    /// * `set_active` - If true, switch to grid pattern
184    /// * `config` - Grid configuration
185    /// * `use_scan_frame` - If true, use scan frame size
186    ///
187    /// # Errors
188    /// Returns `NanonisError` if communication fails.
189    pub fn pattern_grid_set(
190        &mut self,
191        set_active: bool,
192        config: &GridConfig,
193        use_scan_frame: bool,
194    ) -> Result<(), NanonisError> {
195        let active_flag = if set_active { 1u32 } else { 0u32 };
196        let frame_flag = if use_scan_frame { 1u32 } else { 0u32 };
197
198        self.quick_send(
199            "Pattern.GridSet",
200            vec![
201                NanonisValue::U32(active_flag),
202                NanonisValue::I32(config.num_points_x),
203                NanonisValue::I32(config.num_points_y),
204                NanonisValue::U32(frame_flag),
205                NanonisValue::F32(config.center_x_m),
206                NanonisValue::F32(config.center_y_m),
207                NanonisValue::F32(config.width_m),
208                NanonisValue::F32(config.height_m),
209                NanonisValue::F32(config.angle_deg),
210            ],
211            vec!["I", "i", "i", "I", "f", "f", "f", "f", "f"],
212            vec![],
213        )?;
214        Ok(())
215    }
216
217    /// Get the grid pattern parameters.
218    ///
219    /// # Returns
220    /// A [`GridConfig`] struct with current grid parameters.
221    ///
222    /// # Errors
223    /// Returns `NanonisError` if communication fails.
224    pub fn pattern_grid_get(&mut self) -> Result<GridConfig, NanonisError> {
225        let result = self.quick_send(
226            "Pattern.GridGet",
227            vec![],
228            vec![],
229            vec!["i", "i", "f", "f", "f", "f", "f"],
230        )?;
231
232        if result.len() >= 7 {
233            Ok(GridConfig {
234                num_points_x: result[0].as_i32()?,
235                num_points_y: result[1].as_i32()?,
236                center_x_m: result[2].as_f32()?,
237                center_y_m: result[3].as_f32()?,
238                width_m: result[4].as_f32()?,
239                height_m: result[5].as_f32()?,
240                angle_deg: result[6].as_f32()?,
241            })
242        } else {
243            Err(NanonisError::Protocol("Invalid response".to_string()))
244        }
245    }
246
247    /// Set the line pattern parameters.
248    ///
249    /// # Arguments
250    /// * `set_active` - If true, switch to line pattern
251    /// * `config` - Line configuration
252    /// * `use_scan_frame` - If true, use scan frame diagonal
253    ///
254    /// # Errors
255    /// Returns `NanonisError` if communication fails.
256    pub fn pattern_line_set(
257        &mut self,
258        set_active: bool,
259        config: &LineConfig,
260        use_scan_frame: bool,
261    ) -> Result<(), NanonisError> {
262        let active_flag = if set_active { 1u32 } else { 0u32 };
263        let frame_flag = if use_scan_frame { 1u32 } else { 0u32 };
264
265        self.quick_send(
266            "Pattern.LineSet",
267            vec![
268                NanonisValue::U32(active_flag),
269                NanonisValue::I32(config.num_points),
270                NanonisValue::U32(frame_flag),
271                NanonisValue::F32(config.point1_x_m),
272                NanonisValue::F32(config.point1_y_m),
273                NanonisValue::F32(config.point2_x_m),
274                NanonisValue::F32(config.point2_y_m),
275            ],
276            vec!["I", "i", "I", "f", "f", "f", "f"],
277            vec![],
278        )?;
279        Ok(())
280    }
281
282    /// Get the line pattern parameters.
283    ///
284    /// # Returns
285    /// A [`LineConfig`] struct with current line parameters.
286    ///
287    /// # Errors
288    /// Returns `NanonisError` if communication fails.
289    pub fn pattern_line_get(&mut self) -> Result<LineConfig, NanonisError> {
290        let result = self.quick_send(
291            "Pattern.LineGet",
292            vec![],
293            vec![],
294            vec!["i", "f", "f", "f", "f"],
295        )?;
296
297        if result.len() >= 5 {
298            Ok(LineConfig {
299                num_points: result[0].as_i32()?,
300                point1_x_m: result[1].as_f32()?,
301                point1_y_m: result[2].as_f32()?,
302                point2_x_m: result[3].as_f32()?,
303                point2_y_m: result[4].as_f32()?,
304            })
305        } else {
306            Err(NanonisError::Protocol("Invalid response".to_string()))
307        }
308    }
309
310    /// Set the cloud pattern parameters.
311    ///
312    /// # Arguments
313    /// * `set_active` - If true, switch to cloud pattern
314    /// * `config` - Cloud configuration with point coordinates
315    ///
316    /// # Errors
317    /// Returns `NanonisError` if communication fails.
318    pub fn pattern_cloud_set(
319        &mut self,
320        set_active: bool,
321        config: &CloudConfig,
322    ) -> Result<(), NanonisError> {
323        let active_flag = if set_active { 1u32 } else { 0u32 };
324        let num_points = config.x_coords_m.len() as i32;
325
326        self.quick_send(
327            "Pattern.CloudSet",
328            vec![
329                NanonisValue::U32(active_flag),
330                NanonisValue::I32(num_points),
331                NanonisValue::ArrayF32(config.x_coords_m.clone()),
332                NanonisValue::ArrayF32(config.y_coords_m.clone()),
333            ],
334            vec!["I", "i", "*f", "*f"],
335            vec![],
336        )?;
337        Ok(())
338    }
339
340    /// Get the cloud pattern parameters.
341    ///
342    /// # Returns
343    /// A [`CloudConfig`] struct with current cloud point coordinates.
344    ///
345    /// # Errors
346    /// Returns `NanonisError` if communication fails.
347    pub fn pattern_cloud_get(&mut self) -> Result<CloudConfig, NanonisError> {
348        let result = self.quick_send(
349            "Pattern.CloudGet",
350            vec![],
351            vec![],
352            vec!["i", "*f", "*f"],
353        )?;
354
355        if result.len() >= 3 {
356            Ok(CloudConfig {
357                x_coords_m: result[1].as_f32_array()?.to_vec(),
358                y_coords_m: result[2].as_f32_array()?.to_vec(),
359            })
360        } else {
361            Err(NanonisError::Protocol("Invalid response".to_string()))
362        }
363    }
364
365    /// Set the pattern experiment properties.
366    ///
367    /// # Arguments
368    /// * `selected_experiment` - Name of experiment to select
369    /// * `basename` - Base filename for saved files
370    /// * `external_vi_path` - Path to external VI
371    /// * `pre_measure_delay_s` - Delay before measurement on each point
372    /// * `save_scan_channels` - Save scan channels to file
373    ///
374    /// # Errors
375    /// Returns `NanonisError` if communication fails.
376    pub fn pattern_props_set(
377        &mut self,
378        selected_experiment: &str,
379        basename: &str,
380        external_vi_path: &str,
381        pre_measure_delay_s: f32,
382        save_scan_channels: bool,
383    ) -> Result<(), NanonisError> {
384        let save_flag = if save_scan_channels { 1u32 } else { 0u32 };
385
386        self.quick_send(
387            "Pattern.PropsSet",
388            vec![
389                NanonisValue::String(selected_experiment.to_string()),
390                NanonisValue::String(basename.to_string()),
391                NanonisValue::String(external_vi_path.to_string()),
392                NanonisValue::F32(pre_measure_delay_s),
393                NanonisValue::U32(save_flag),
394            ],
395            vec!["+*c", "+*c", "+*c", "f", "I"],
396            vec![],
397        )?;
398        Ok(())
399    }
400
401    /// Get the pattern experiment properties.
402    ///
403    /// # Returns
404    /// A [`PatternProps`] struct with current experiment properties.
405    ///
406    /// # Errors
407    /// Returns `NanonisError` if communication fails.
408    pub fn pattern_props_get(&mut self) -> Result<PatternProps, NanonisError> {
409        let result = self.quick_send(
410            "Pattern.PropsGet",
411            vec![],
412            vec![],
413            vec!["i", "i", "*+c", "i", "*-c", "i", "*-c", "f", "I"],
414        )?;
415
416        if result.len() >= 9 {
417            Ok(PatternProps {
418                available_experiments: result[2].as_string_array()?.to_vec(),
419                selected_experiment: result[4].as_string()?.to_string(),
420                external_vi_path: result[6].as_string()?.to_string(),
421                pre_measure_delay_s: result[7].as_f32()?,
422                save_scan_channels: result[8].as_u32()? != 0,
423            })
424        } else {
425            Err(NanonisError::Protocol("Invalid response".to_string()))
426        }
427    }
428}