rusty_tip/nanonis/client/
z_ctrl.rs

1use std::time::Duration;
2
3use super::NanonisClient;
4use crate::error::NanonisError;
5use crate::types::NanonisValue;
6
7impl NanonisClient {
8    /// Switch the Z-Controller on or off.
9    ///
10    /// Controls the Z-Controller state. This is fundamental for enabling/disabling
11    /// tip-sample distance regulation during scanning and positioning operations.
12    ///
13    /// # Arguments
14    /// * `controller_on` - `true` to turn controller on, `false` to turn off
15    ///
16    /// # Errors
17    /// Returns `NanonisError` if communication fails or protocol error occurs.
18    ///
19    /// # Examples
20    /// ```no_run
21    /// use rusty_tip::NanonisClient;
22    ///
23    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
24    ///
25    /// // Turn Z-controller on for feedback control
26    /// client.z_ctrl_on_off_set(true)?;
27    ///
28    /// // Turn Z-controller off for manual positioning
29    /// client.z_ctrl_on_off_set(false)?;
30    /// # Ok::<(), Box<dyn std::error::Error>>(())
31    /// ```
32    pub fn z_ctrl_on_off_set(
33        &mut self,
34        controller_on: bool,
35    ) -> 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 rusty_tip::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 rusty_tip::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(
115        &mut self,
116        z_position_m: f32,
117    ) -> Result<(), NanonisError> {
118        self.quick_send(
119            "ZCtrl.ZPosSet",
120            vec![NanonisValue::F32(z_position_m)],
121            vec!["f"],
122            vec![],
123        )?;
124        Ok(())
125    }
126
127    /// Get the current Z position of the tip.
128    ///
129    /// Returns the current tip Z coordinate in meters. This works whether
130    /// the Z-controller is on or off.
131    ///
132    /// # Returns
133    /// Current Z position in meters.
134    ///
135    /// # Errors
136    /// Returns `NanonisError` if communication fails or protocol error occurs.
137    ///
138    /// # Examples
139    /// ```no_run
140    /// use rusty_tip::NanonisClient;
141    ///
142    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
143    ///
144    /// let z_pos = client.z_ctrl_z_pos_get()?;
145    /// println!("Current tip height: {:.2} nm", z_pos * 1e9);
146    ///
147    /// // Check if tip is at safe distance
148    /// if z_pos > 5e-9 {
149    ///     println!("Tip is safely withdrawn");
150    /// }
151    /// # Ok::<(), Box<dyn std::error::Error>>(())
152    /// ```
153    pub fn z_ctrl_z_pos_get(&mut self) -> Result<f32, NanonisError> {
154        let result = self.quick_send("ZCtrl.ZPosGet", vec![], vec![], vec!["f"])?;
155
156        match result.first() {
157            Some(value) => Ok(value.as_f32()?),
158            None => {
159                Err(NanonisError::Protocol("No Z position returned".to_string()))
160            }
161        }
162    }
163
164    /// Set the setpoint of the Z-Controller.
165    ///
166    /// The setpoint is the target value for the feedback signal that the Z-controller
167    /// tries to maintain by adjusting the tip-sample distance.
168    ///
169    /// # Arguments
170    /// * `setpoint` - Z-controller setpoint value (units depend on feedback signal)
171    ///
172    /// # Errors
173    /// Returns `NanonisError` if communication fails or protocol error occurs.
174    ///
175    /// # Examples
176    /// ```no_run
177    /// use rusty_tip::NanonisClient;
178    ///
179    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
180    ///
181    /// // Set tunneling current setpoint to 100 pA
182    /// client.z_ctrl_setpoint_set(100e-12)?;
183    ///
184    /// // Set force setpoint for AFM mode
185    /// client.z_ctrl_setpoint_set(1e-9)?;  // 1 nN
186    /// # Ok::<(), Box<dyn std::error::Error>>(())
187    /// ```
188    pub fn z_ctrl_setpoint_set(
189        &mut self,
190        setpoint: f32,
191    ) -> Result<(), NanonisError> {
192        self.quick_send(
193            "ZCtrl.SetpntSet",
194            vec![NanonisValue::F32(setpoint)],
195            vec!["f"],
196            vec![],
197        )?;
198        Ok(())
199    }
200
201    /// Get the current setpoint of the Z-Controller.
202    ///
203    /// Returns the target value that the Z-controller is trying to maintain.
204    ///
205    /// # Returns
206    /// Current Z-controller setpoint value.
207    ///
208    /// # Errors
209    /// Returns `NanonisError` if communication fails or protocol error occurs.
210    ///
211    /// # Examples
212    /// ```no_run
213    /// use rusty_tip::NanonisClient;
214    ///
215    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
216    ///
217    /// let setpoint = client.z_ctrl_setpoint_get()?;
218    /// println!("Current setpoint: {:.3e}", setpoint);
219    /// # Ok::<(), Box<dyn std::error::Error>>(())
220    /// ```
221    pub fn z_ctrl_setpoint_get(&mut self) -> Result<f32, NanonisError> {
222        let result =
223            self.quick_send("ZCtrl.SetpntGet", vec![], vec![], vec!["f"])?;
224
225        match result.first() {
226            Some(value) => Ok(value.as_f32()?),
227            None => Err(NanonisError::Protocol("No setpoint returned".to_string())),
228        }
229    }
230
231    /// Set the Z-Controller gains and time settings.
232    ///
233    /// Configures the PID controller parameters for Z-axis feedback control.
234    /// The integral gain is calculated as I = P/T where P is proportional gain
235    /// and T is the time constant.
236    ///
237    /// # Arguments
238    /// * `p_gain` - Proportional gain of the regulation loop
239    /// * `time_constant_s` - Time constant T in seconds
240    /// * `i_gain` - Integral gain of the regulation loop (calculated as P/T)
241    ///
242    /// # Errors
243    /// Returns `NanonisError` if communication fails or invalid gains provided.
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    /// // Set moderate feedback gains for stable operation
252    /// client.z_ctrl_gain_set(1.0, 0.1, 10.0)?;
253    ///
254    /// // Set aggressive gains for fast response
255    /// client.z_ctrl_gain_set(5.0, 0.05, 100.0)?;
256    /// # Ok::<(), Box<dyn std::error::Error>>(())
257    /// ```
258    pub fn z_ctrl_gain_set(
259        &mut self,
260        p_gain: f32,
261        time_constant_s: f32,
262        i_gain: f32,
263    ) -> Result<(), NanonisError> {
264        self.quick_send(
265            "ZCtrl.GainSet",
266            vec![
267                NanonisValue::F32(p_gain),
268                NanonisValue::F32(time_constant_s),
269                NanonisValue::F32(i_gain),
270            ],
271            vec!["f", "f", "f"],
272            vec![],
273        )?;
274        Ok(())
275    }
276
277    /// Get the current Z-Controller gains and time settings.
278    ///
279    /// Returns the PID controller parameters currently in use.
280    ///
281    /// # Returns
282    /// A tuple containing:
283    /// - `f32` - Proportional gain
284    /// - `f32` - Time constant in seconds
285    /// - `f32` - Integral gain (P/T)
286    ///
287    /// # Errors
288    /// Returns `NanonisError` if communication fails or protocol error occurs.
289    ///
290    /// # Examples
291    /// ```no_run
292    /// use rusty_tip::NanonisClient;
293    ///
294    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
295    ///
296    /// let (p_gain, time_const, i_gain) = client.z_ctrl_gain_get()?;
297    /// println!("P: {:.3}, T: {:.3}s, I: {:.3}", p_gain, time_const, i_gain);
298    ///
299    /// // Check if gains are in reasonable range
300    /// if p_gain > 10.0 {
301    ///     println!("Warning: High proportional gain may cause instability");
302    /// }
303    /// # Ok::<(), Box<dyn std::error::Error>>(())
304    /// ```
305    pub fn z_ctrl_gain_get(&mut self) -> Result<(f32, f32, f32), NanonisError> {
306        let result =
307            self.quick_send("ZCtrl.GainGet", vec![], vec![], vec!["f", "f", "f"])?;
308
309        if result.len() >= 3 {
310            Ok((
311                result[0].as_f32()?,
312                result[1].as_f32()?,
313                result[2].as_f32()?,
314            ))
315        } else {
316            Err(NanonisError::Protocol("Invalid gain response".to_string()))
317        }
318    }
319
320    /// Move the tip to its home position.
321    ///
322    /// Moves the tip to the predefined home position, which can be either absolute
323    /// (fixed position) or relative to the current position, depending on the
324    /// controller configuration.
325    ///
326    /// # Errors
327    /// Returns `NanonisError` if communication fails or protocol error occurs.
328    ///
329    /// # Examples
330    /// ```no_run
331    /// use rusty_tip::NanonisClient;
332    ///
333    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
334    ///
335    /// // Move tip to home position after experiment
336    /// client.z_ctrl_home()?;
337    ///
338    /// // Wait a moment for positioning to complete
339    /// std::thread::sleep(std::time::Duration::from_secs(1));
340    ///
341    /// // Check final position
342    /// let final_pos = client.z_ctrl_z_pos_get()?;
343    /// println!("Tip homed to: {:.2} nm", final_pos * 1e9);
344    /// # Ok::<(), Box<dyn std::error::Error>>(())
345    /// ```
346    pub fn z_ctrl_home(&mut self) -> Result<(), NanonisError> {
347        self.quick_send("ZCtrl.Home", vec![], vec![], vec![])?;
348        Ok(())
349    }
350
351    /// Withdraw the tip.
352    ///
353    /// Switches off the Z-Controller and fully withdraws the tip to the upper limit.
354    /// This is a safety function to prevent tip crashes during approach or when
355    /// moving to new scan areas.
356    ///
357    /// # Arguments
358    /// * `wait_until_finished` - If `true`, waits for withdrawal to complete
359    /// * `timeout_ms` - Timeout in milliseconds for the withdrawal operation
360    ///
361    /// # Errors
362    /// Returns `NanonisError` if communication fails or withdrawal times out.
363    ///
364    /// # Examples
365    /// ```no_run
366    /// use rusty_tip::NanonisClient;
367    /// use std::time::Duration;
368    ///
369    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
370    ///
371    /// // Emergency withdrawal - don't wait
372    /// client.z_ctrl_withdraw(false, Duration::from_secs(5))?;
373    ///
374    /// // Controlled withdrawal with waiting
375    /// client.z_ctrl_withdraw(true, Duration::from_secs(10))?;
376    /// println!("Tip safely withdrawn");
377    /// # Ok::<(), Box<dyn std::error::Error>>(())
378    /// ```
379    pub fn z_ctrl_withdraw(
380        &mut self,
381        wait_until_finished: bool,
382        timeout_ms: Duration,
383    ) -> Result<(), NanonisError> {
384        let wait_flag = if wait_until_finished { 1u32 } else { 0u32 };
385        self.quick_send(
386            "ZCtrl.Withdraw",
387            vec![
388                NanonisValue::U32(wait_flag),
389                NanonisValue::I32(timeout_ms.as_millis() as i32),
390            ],
391            vec!["I", "i"],
392            vec![],
393        )?;
394        Ok(())
395    }
396}