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}