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}