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}