Skip to main content

nanonis_rs/client/z_ctrl/
mod.rs

1mod types;
2pub use types::*;
3
4use std::time::Duration;
5
6use super::NanonisClient;
7use crate::error::NanonisError;
8use crate::types::NanonisValue;
9
10impl NanonisClient {
11    /// Switch the Z-Controller on or off.
12    ///
13    /// Controls the Z-Controller state. This is fundamental for enabling/disabling
14    /// tip-sample distance regulation during scanning and positioning operations.
15    ///
16    /// # Arguments
17    /// * `controller_on` - `true` to turn controller on, `false` to turn off
18    ///
19    /// # Errors
20    /// Returns `NanonisError` if communication fails or protocol error occurs.
21    ///
22    /// # Examples
23    /// ```no_run
24    /// use nanonis_rs::NanonisClient;
25    ///
26    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
27    ///
28    /// // Turn Z-controller on for feedback control
29    /// client.z_ctrl_on_off_set(true)?;
30    ///
31    /// // Turn Z-controller off for manual positioning
32    /// client.z_ctrl_on_off_set(false)?;
33    /// # Ok::<(), Box<dyn std::error::Error>>(())
34    /// ```
35    pub fn z_ctrl_on_off_set(
36        &mut self,
37        controller_on: bool,
38    ) -> Result<(), NanonisError> {
39        let status_flag = if controller_on { 1u32 } else { 0u32 };
40
41        self.quick_send(
42            "ZCtrl.OnOffSet",
43            vec![NanonisValue::U32(status_flag)],
44            vec!["I"],
45            vec![],
46        )?;
47        Ok(())
48    }
49
50    /// Get the current status of the Z-Controller.
51    ///
52    /// Returns the real-time status from the controller (not from the Z-Controller module).
53    /// This is useful to ensure the controller is truly off before starting experiments,
54    /// as there can be communication delays and switch-off delays.
55    ///
56    /// # Returns
57    /// `true` if controller is on, `false` if controller is off.
58    ///
59    /// # Errors
60    /// Returns `NanonisError` if communication fails or protocol error occurs.
61    ///
62    /// # Examples
63    /// ```no_run
64    /// use nanonis_rs::NanonisClient;
65    ///
66    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
67    ///
68    /// // Check controller status before experiment
69    /// if client.z_ctrl_on_off_get()? {
70    ///     println!("Z-controller is active");
71    /// } else {
72    ///     println!("Z-controller is off - safe to move tip manually");
73    /// }
74    /// # Ok::<(), Box<dyn std::error::Error>>(())
75    /// ```
76    pub fn z_ctrl_on_off_get(&mut self) -> Result<bool, NanonisError> {
77        let result = self.quick_send("ZCtrl.OnOffGet", vec![], vec![], vec!["I"])?;
78
79        match result.first() {
80            Some(value) => Ok(value.as_u32()? == 1),
81            None => Err(NanonisError::Protocol(
82                "No Z-controller status returned".to_string(),
83            )),
84        }
85    }
86
87    /// Set the Z position of the tip.
88    ///
89    /// **Important**: The Z-controller must be switched OFF to change the tip position.
90    /// This function directly sets the tip's Z coordinate for manual positioning.
91    ///
92    /// # Arguments
93    /// * `z_position_m` - Z position in meters
94    ///
95    /// # Errors
96    /// Returns `NanonisError` if:
97    /// - Z-controller is still active (must be turned off first)
98    /// - Position is outside safe limits
99    /// - Communication fails or protocol error occurs
100    ///
101    /// # Examples
102    /// ```no_run
103    /// use nanonis_rs::NanonisClient;
104    ///
105    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
106    ///
107    /// // Ensure Z-controller is off
108    /// client.z_ctrl_on_off_set(false)?;
109    ///
110    /// // Move tip to specific Z position (10 nm above surface)
111    /// client.z_ctrl_z_pos_set(10e-9)?;
112    ///
113    /// // Move tip closer to surface (2 nm)
114    /// client.z_ctrl_z_pos_set(2e-9)?;
115    /// # Ok::<(), Box<dyn std::error::Error>>(())
116    /// ```
117    pub fn z_ctrl_z_pos_set(
118        &mut self,
119        z_position_m: f32,
120    ) -> Result<(), NanonisError> {
121        self.quick_send(
122            "ZCtrl.ZPosSet",
123            vec![NanonisValue::F32(z_position_m)],
124            vec!["f"],
125            vec![],
126        )?;
127        Ok(())
128    }
129
130    /// Get the current Z position of the tip.
131    ///
132    /// Returns the current tip Z coordinate in meters. This works whether
133    /// the Z-controller is on or off.
134    ///
135    /// # Returns
136    /// Current Z position in meters.
137    ///
138    /// # Errors
139    /// Returns `NanonisError` if communication fails or protocol error occurs.
140    ///
141    /// # Examples
142    /// ```no_run
143    /// use nanonis_rs::NanonisClient;
144    ///
145    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
146    ///
147    /// let z_pos = client.z_ctrl_z_pos_get()?;
148    /// println!("Current tip height: {:.2} nm", z_pos * 1e9);
149    ///
150    /// // Check if tip is at safe distance
151    /// if z_pos > 5e-9 {
152    ///     println!("Tip is safely withdrawn");
153    /// }
154    /// # Ok::<(), Box<dyn std::error::Error>>(())
155    /// ```
156    pub fn z_ctrl_z_pos_get(&mut self) -> Result<f32, NanonisError> {
157        let result = self.quick_send("ZCtrl.ZPosGet", vec![], vec![], vec!["f"])?;
158
159        match result.first() {
160            Some(value) => Ok(value.as_f32()?),
161            None => {
162                Err(NanonisError::Protocol("No Z position returned".to_string()))
163            }
164        }
165    }
166
167    /// Set the setpoint of the Z-Controller.
168    ///
169    /// The setpoint is the target value for the feedback signal that the Z-controller
170    /// tries to maintain by adjusting the tip-sample distance.
171    ///
172    /// # Arguments
173    /// * `setpoint` - Z-controller setpoint value (units depend on feedback signal)
174    ///
175    /// # Errors
176    /// Returns `NanonisError` if communication fails or protocol error occurs.
177    ///
178    /// # Examples
179    /// ```no_run
180    /// use nanonis_rs::NanonisClient;
181    ///
182    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
183    ///
184    /// // Set tunneling current setpoint to 100 pA
185    /// client.z_ctrl_setpoint_set(100e-12)?;
186    ///
187    /// // Set force setpoint for AFM mode
188    /// client.z_ctrl_setpoint_set(1e-9)?;  // 1 nN
189    /// # Ok::<(), Box<dyn std::error::Error>>(())
190    /// ```
191    pub fn z_ctrl_setpoint_set(
192        &mut self,
193        setpoint: f32,
194    ) -> Result<(), NanonisError> {
195        self.quick_send(
196            "ZCtrl.SetpntSet",
197            vec![NanonisValue::F32(setpoint)],
198            vec!["f"],
199            vec![],
200        )?;
201        Ok(())
202    }
203
204    /// Get the current setpoint of the Z-Controller.
205    ///
206    /// Returns the target value that the Z-controller is trying to maintain.
207    ///
208    /// # Returns
209    /// Current Z-controller setpoint value.
210    ///
211    /// # Errors
212    /// Returns `NanonisError` if communication fails or protocol error occurs.
213    ///
214    /// # Examples
215    /// ```no_run
216    /// use nanonis_rs::NanonisClient;
217    ///
218    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
219    ///
220    /// let setpoint = client.z_ctrl_setpoint_get()?;
221    /// println!("Current setpoint: {:.3e}", setpoint);
222    /// # Ok::<(), Box<dyn std::error::Error>>(())
223    /// ```
224    pub fn z_ctrl_setpoint_get(&mut self) -> Result<f32, NanonisError> {
225        let result =
226            self.quick_send("ZCtrl.SetpntGet", vec![], vec![], vec!["f"])?;
227
228        match result.first() {
229            Some(value) => Ok(value.as_f32()?),
230            None => Err(NanonisError::Protocol("No setpoint returned".to_string())),
231        }
232    }
233
234    /// Set the Z-Controller gains and time settings.
235    ///
236    /// Configures the PID controller parameters for Z-axis feedback control.
237    /// The integral gain is calculated as I = P/T where P is proportional gain
238    /// and T is the time constant.
239    ///
240    /// # Arguments
241    /// * `p_gain` - Proportional gain of the regulation loop
242    /// * `time_constant_s` - Time constant T in seconds
243    /// * `i_gain` - Integral gain of the regulation loop (calculated as P/T)
244    ///
245    /// # Errors
246    /// Returns `NanonisError` if communication fails or invalid gains provided.
247    ///
248    /// # Examples
249    /// ```no_run
250    /// use nanonis_rs::NanonisClient;
251    ///
252    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
253    ///
254    /// // Set moderate feedback gains for stable operation
255    /// client.z_ctrl_gain_set(1.0, 0.1, 10.0)?;
256    ///
257    /// // Set aggressive gains for fast response
258    /// client.z_ctrl_gain_set(5.0, 0.05, 100.0)?;
259    /// # Ok::<(), Box<dyn std::error::Error>>(())
260    /// ```
261    pub fn z_ctrl_gain_set(
262        &mut self,
263        p_gain: f32,
264        time_constant_s: f32,
265        i_gain: f32,
266    ) -> Result<(), NanonisError> {
267        self.quick_send(
268            "ZCtrl.GainSet",
269            vec![
270                NanonisValue::F32(p_gain),
271                NanonisValue::F32(time_constant_s),
272                NanonisValue::F32(i_gain),
273            ],
274            vec!["f", "f", "f"],
275            vec![],
276        )?;
277        Ok(())
278    }
279
280    /// Get the current Z-Controller gains and time settings.
281    ///
282    /// Returns the PID controller parameters currently in use.
283    ///
284    /// # Returns
285    /// A tuple containing:
286    /// - `f32` - Proportional gain
287    /// - `f32` - Time constant in seconds
288    /// - `f32` - Integral gain (P/T)
289    ///
290    /// # Errors
291    /// Returns `NanonisError` if communication fails or protocol error occurs.
292    ///
293    /// # Examples
294    /// ```no_run
295    /// use nanonis_rs::NanonisClient;
296    ///
297    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
298    ///
299    /// let (p_gain, time_const, i_gain) = client.z_ctrl_gain_get()?;
300    /// println!("P: {:.3}, T: {:.3}s, I: {:.3}", p_gain, time_const, i_gain);
301    ///
302    /// // Check if gains are in reasonable range
303    /// if p_gain > 10.0 {
304    ///     println!("Warning: High proportional gain may cause instability");
305    /// }
306    /// # Ok::<(), Box<dyn std::error::Error>>(())
307    /// ```
308    pub fn z_ctrl_gain_get(&mut self) -> Result<(f32, f32, f32), NanonisError> {
309        let result =
310            self.quick_send("ZCtrl.GainGet", vec![], vec![], vec!["f", "f", "f"])?;
311
312        if result.len() >= 3 {
313            Ok((
314                result[0].as_f32()?,
315                result[1].as_f32()?,
316                result[2].as_f32()?,
317            ))
318        } else {
319            Err(NanonisError::Protocol("Invalid gain response".to_string()))
320        }
321    }
322
323    /// Move the tip to its home position.
324    ///
325    /// Moves the tip to the predefined home position, which can be either absolute
326    /// (fixed position) or relative to the current position, depending on the
327    /// controller configuration.
328    ///
329    /// # Errors
330    /// Returns `NanonisError` if communication fails or protocol error occurs.
331    ///
332    /// # Examples
333    /// ```no_run
334    /// use nanonis_rs::NanonisClient;
335    ///
336    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
337    ///
338    /// // Move tip to home position after experiment
339    /// client.z_ctrl_home()?;
340    ///
341    /// // Wait a moment for positioning to complete
342    /// std::thread::sleep(std::time::Duration::from_secs(1));
343    ///
344    /// // Check final position
345    /// let final_pos = client.z_ctrl_z_pos_get()?;
346    /// println!("Tip homed to: {:.2} nm", final_pos * 1e9);
347    /// # Ok::<(), Box<dyn std::error::Error>>(())
348    /// ```
349    pub fn z_ctrl_home(&mut self) -> Result<(), NanonisError> {
350        self.quick_send("ZCtrl.Home", vec![], vec![], vec![])?;
351        Ok(())
352    }
353
354    /// Withdraw the tip.
355    ///
356    /// Switches off the Z-Controller and fully withdraws the tip to the upper limit.
357    /// This is a safety function to prevent tip crashes during approach or when
358    /// moving to new scan areas.
359    ///
360    /// # Arguments
361    /// * `wait_until_finished` - If `true`, waits for withdrawal to complete
362    /// * `timeout_ms` - Timeout in milliseconds for the withdrawal operation
363    ///
364    /// # Errors
365    /// Returns `NanonisError` if communication fails or withdrawal times out.
366    ///
367    /// # Examples
368    /// ```no_run
369    /// use nanonis_rs::NanonisClient;
370    /// use std::time::Duration;
371    ///
372    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
373    ///
374    /// // Emergency withdrawal - don't wait
375    /// client.z_ctrl_withdraw(false, Duration::from_secs(5))?;
376    ///
377    /// // Controlled withdrawal with waiting
378    /// client.z_ctrl_withdraw(true, Duration::from_secs(10))?;
379    /// println!("Tip safely withdrawn");
380    /// # Ok::<(), Box<dyn std::error::Error>>(())
381    /// ```
382    pub fn z_ctrl_withdraw(
383        &mut self,
384        wait_until_finished: bool,
385        timeout_ms: Duration,
386    ) -> Result<(), NanonisError> {
387        let wait_flag = if wait_until_finished { 1u32 } else { 0u32 };
388        self.quick_send(
389            "ZCtrl.Withdraw",
390            vec![
391                NanonisValue::U32(wait_flag),
392                NanonisValue::I32(timeout_ms.as_millis() as i32),
393            ],
394            vec!["I", "i"],
395            vec![],
396        )?;
397        Ok(())
398    }
399
400    /// Set the Z-Controller switch-off delay.
401    ///
402    /// Before turning off the controller, the Z position is averaged over this delay.
403    /// This leads to reproducible Z positions when switching off the controller.
404    ///
405    /// # Arguments
406    /// * `delay_s` - Switch-off delay in seconds
407    ///
408    /// # Errors
409    /// Returns `NanonisError` if communication fails.
410    pub fn z_ctrl_switch_off_delay_set(&mut self, delay_s: f32) -> Result<(), NanonisError> {
411        self.quick_send(
412            "ZCtrl.SwitchOffDelaySet",
413            vec![NanonisValue::F32(delay_s)],
414            vec!["f"],
415            vec![],
416        )?;
417        Ok(())
418    }
419
420    /// Get the Z-Controller switch-off delay.
421    ///
422    /// # Returns
423    /// Switch-off delay in seconds.
424    ///
425    /// # Errors
426    /// Returns `NanonisError` if communication fails.
427    pub fn z_ctrl_switch_off_delay_get(&mut self) -> Result<f32, NanonisError> {
428        let result = self.quick_send("ZCtrl.SwitchOffDelayGet", vec![], vec![], vec!["f"])?;
429        result[0].as_f32()
430    }
431
432    // NOTE: z_ctrl_tip_lift_set/get are implemented in safe_tip.rs
433
434    /// Set the home position properties.
435    ///
436    /// # Arguments
437    /// * `home_mode` - Home position mode (0=no change, 1=absolute, 2=relative)
438    /// * `home_position_m` - Home position in meters
439    ///
440    /// # Errors
441    /// Returns `NanonisError` if communication fails.
442    pub fn z_ctrl_home_props_set(
443        &mut self,
444        home_mode: u16,
445        home_position_m: f32,
446    ) -> Result<(), NanonisError> {
447        self.quick_send(
448            "ZCtrl.HomePropsSet",
449            vec![
450                NanonisValue::U16(home_mode),
451                NanonisValue::F32(home_position_m),
452            ],
453            vec!["H", "f"],
454            vec![],
455        )?;
456        Ok(())
457    }
458
459    /// Get the home position properties.
460    ///
461    /// # Returns
462    /// Tuple of (is_relative, home_position_m).
463    ///
464    /// # Errors
465    /// Returns `NanonisError` if communication fails.
466    pub fn z_ctrl_home_props_get(&mut self) -> Result<(bool, f32), NanonisError> {
467        let result = self.quick_send("ZCtrl.HomePropsGet", vec![], vec![], vec!["H", "f"])?;
468
469        Ok((result[0].as_u16()? != 0, result[1].as_f32()?))
470    }
471
472    /// Set the active Z-Controller.
473    ///
474    /// # Arguments
475    /// * `controller_index` - Controller index from the list
476    ///
477    /// # Errors
478    /// Returns `NanonisError` if communication fails.
479    pub fn z_ctrl_active_ctrl_set(&mut self, controller_index: i32) -> Result<(), NanonisError> {
480        self.quick_send(
481            "ZCtrl.ActiveCtrlSet",
482            vec![NanonisValue::I32(controller_index)],
483            vec!["i"],
484            vec![],
485        )?;
486        Ok(())
487    }
488
489    /// Get the list of Z-Controllers and active controller index.
490    ///
491    /// # Returns
492    /// Tuple of (list of controller names, active controller index).
493    ///
494    /// # Errors
495    /// Returns `NanonisError` if communication fails.
496    pub fn z_ctrl_ctrl_list_get(&mut self) -> Result<(Vec<String>, i32), NanonisError> {
497        let result = self.quick_send(
498            "ZCtrl.CtrlListGet",
499            vec![],
500            vec![],
501            vec!["i", "i", "*+c", "i"],
502        )?;
503
504        Ok((result[2].as_string_array()?.to_vec(), result[3].as_i32()?))
505    }
506
507    /// Set the withdraw slew rate.
508    ///
509    /// # Arguments
510    /// * `rate_m_s` - Withdraw rate in m/s
511    ///
512    /// # Errors
513    /// Returns `NanonisError` if communication fails.
514    pub fn z_ctrl_withdraw_rate_set(&mut self, rate_m_s: f32) -> Result<(), NanonisError> {
515        self.quick_send(
516            "ZCtrl.WithdrawRateSet",
517            vec![NanonisValue::F32(rate_m_s)],
518            vec!["f"],
519            vec![],
520        )?;
521        Ok(())
522    }
523
524    /// Get the withdraw slew rate.
525    ///
526    /// # Returns
527    /// Withdraw rate in m/s.
528    ///
529    /// # Errors
530    /// Returns `NanonisError` if communication fails.
531    pub fn z_ctrl_withdraw_rate_get(&mut self) -> Result<f32, NanonisError> {
532        let result = self.quick_send("ZCtrl.WithdrawRateGet", vec![], vec![], vec!["f"])?;
533        result[0].as_f32()
534    }
535
536    /// Enable or disable Z position limits.
537    ///
538    /// # Arguments
539    /// * `enabled` - True to enable limits
540    ///
541    /// # Errors
542    /// Returns `NanonisError` if communication fails.
543    pub fn z_ctrl_limits_enabled_set(&mut self, enabled: bool) -> Result<(), NanonisError> {
544        self.quick_send(
545            "ZCtrl.LimitsEnabledSet",
546            vec![NanonisValue::U32(if enabled { 1 } else { 0 })],
547            vec!["I"],
548            vec![],
549        )?;
550        Ok(())
551    }
552
553    /// Get the Z position limits enabled status.
554    ///
555    /// # Returns
556    /// True if limits are enabled.
557    ///
558    /// # Errors
559    /// Returns `NanonisError` if communication fails.
560    pub fn z_ctrl_limits_enabled_get(&mut self) -> Result<bool, NanonisError> {
561        let result = self.quick_send("ZCtrl.LimitsEnabledGet", vec![], vec![], vec!["I"])?;
562        Ok(result[0].as_u32()? != 0)
563    }
564
565    /// Set the Z position limits.
566    ///
567    /// # Arguments
568    /// * `high_limit_m` - High Z limit in meters
569    /// * `low_limit_m` - Low Z limit in meters
570    ///
571    /// # Errors
572    /// Returns `NanonisError` if communication fails.
573    pub fn z_ctrl_limits_set(
574        &mut self,
575        high_limit_m: f32,
576        low_limit_m: f32,
577    ) -> Result<(), NanonisError> {
578        self.quick_send(
579            "ZCtrl.LimitsSet",
580            vec![
581                NanonisValue::F32(high_limit_m),
582                NanonisValue::F32(low_limit_m),
583            ],
584            vec!["f", "f"],
585            vec![],
586        )?;
587        Ok(())
588    }
589
590    /// Get the Z position limits.
591    ///
592    /// # Returns
593    /// Tuple of (high_limit_m, low_limit_m).
594    ///
595    /// # Errors
596    /// Returns `NanonisError` if communication fails.
597    pub fn z_ctrl_limits_get(&mut self) -> Result<(f32, f32), NanonisError> {
598        let result = self.quick_send("ZCtrl.LimitsGet", vec![], vec![], vec!["f", "f"])?;
599
600        Ok((result[0].as_f32()?, result[1].as_f32()?))
601    }
602
603    /// Get the current Z-Controller status.
604    ///
605    /// # Returns
606    /// Controller status (1=Off, 2=On, 3=Hold, 4=Switching Off, 5=Safe Tip, 6=Withdrawing).
607    ///
608    /// # Errors
609    /// Returns `NanonisError` if communication fails.
610    pub fn z_ctrl_status_get(&mut self) -> Result<ZControllerStatus, NanonisError> {
611        let result = self.quick_send("ZCtrl.StatusGet", vec![], vec![], vec!["H"])?;
612        ZControllerStatus::try_from(result[0].as_u16()?)
613    }
614}