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