Skip to main content

nanonis_rs/client/
kelvin_ctrl.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4
5/// Kelvin controller slope direction.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum KelvinSlope {
8    /// No change to current setting
9    #[default]
10    NoChange = 0,
11    /// Positive slope
12    Positive = 1,
13    /// Negative slope
14    Negative = 2,
15}
16
17impl From<KelvinSlope> for u16 {
18    fn from(slope: KelvinSlope) -> Self {
19        slope as u16
20    }
21}
22
23impl TryFrom<u16> for KelvinSlope {
24    type Error = NanonisError;
25
26    fn try_from(value: u16) -> Result<Self, Self::Error> {
27        match value {
28            0 => Ok(KelvinSlope::Negative),
29            1 => Ok(KelvinSlope::Positive),
30            _ => Err(NanonisError::Protocol(format!(
31                "Invalid KelvinSlope value: {}",
32                value
33            ))),
34        }
35    }
36}
37
38/// AC mode toggle for Kelvin controller.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum KelvinACMode {
41    /// No change to current setting
42    #[default]
43    NoChange = 0,
44    /// AC mode on
45    On = 1,
46    /// AC mode off
47    Off = 2,
48}
49
50impl From<KelvinACMode> for u16 {
51    fn from(mode: KelvinACMode) -> Self {
52        mode as u16
53    }
54}
55
56/// Kelvin controller gain parameters.
57#[derive(Debug, Clone, Copy, Default)]
58pub struct KelvinGain {
59    /// Proportional gain
60    pub p_gain: f32,
61    /// Time constant in seconds
62    pub time_constant_s: f32,
63    /// Slope direction
64    pub slope: KelvinSlope,
65}
66
67/// Kelvin controller modulation parameters.
68#[derive(Debug, Clone, Copy, Default)]
69pub struct KelvinModParams {
70    /// Modulation frequency in Hz
71    pub frequency_hz: f32,
72    /// Modulation amplitude
73    pub amplitude: f32,
74    /// Modulation phase in degrees
75    pub phase_deg: f32,
76}
77
78/// Kelvin controller modulation status.
79#[derive(Debug, Clone, Copy, Default)]
80pub struct KelvinModStatus {
81    /// AC mode enabled
82    pub ac_mode: bool,
83    /// Modulation enabled
84    pub modulation: bool,
85}
86
87/// Kelvin controller bias limits.
88#[derive(Debug, Clone, Copy, Default)]
89pub struct KelvinBiasLimits {
90    /// High bias limit in volts
91    pub high_limit_v: f32,
92    /// Low bias limit in volts
93    pub low_limit_v: f32,
94}
95
96impl NanonisClient {
97    /// Enable or disable the Kelvin controller.
98    ///
99    /// # Arguments
100    /// * `enabled` - True to enable, false to disable
101    ///
102    /// # Errors
103    /// Returns `NanonisError` if communication fails.
104    ///
105    /// # Examples
106    /// ```no_run
107    /// use nanonis_rs::NanonisClient;
108    ///
109    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
110    /// client.kelvin_ctrl_on_off_set(true)?;
111    /// # Ok::<(), Box<dyn std::error::Error>>(())
112    /// ```
113    pub fn kelvin_ctrl_on_off_set(&mut self, enabled: bool) -> Result<(), NanonisError> {
114        let flag = if enabled { 1u32 } else { 0u32 };
115        self.quick_send(
116            "KelvinCtrl.CtrlOnOffSet",
117            vec![NanonisValue::U32(flag)],
118            vec!["I"],
119            vec![],
120        )?;
121        Ok(())
122    }
123
124    /// Get the Kelvin controller on/off status.
125    ///
126    /// # Returns
127    /// True if controller is enabled.
128    ///
129    /// # Errors
130    /// Returns `NanonisError` if communication fails.
131    pub fn kelvin_ctrl_on_off_get(&mut self) -> Result<bool, NanonisError> {
132        let result = self.quick_send("KelvinCtrl.CtrlOnOffGet", vec![], vec![], vec!["I"])?;
133
134        if !result.is_empty() {
135            Ok(result[0].as_u32()? != 0)
136        } else {
137            Err(NanonisError::Protocol("Invalid response".to_string()))
138        }
139    }
140
141    /// Set the Kelvin controller setpoint.
142    ///
143    /// # Arguments
144    /// * `setpoint` - Setpoint value
145    ///
146    /// # Errors
147    /// Returns `NanonisError` if communication fails.
148    pub fn kelvin_ctrl_setpnt_set(&mut self, setpoint: f32) -> Result<(), NanonisError> {
149        self.quick_send(
150            "KelvinCtrl.SetpntSet",
151            vec![NanonisValue::F32(setpoint)],
152            vec!["f"],
153            vec![],
154        )?;
155        Ok(())
156    }
157
158    /// Get the Kelvin controller setpoint.
159    ///
160    /// # Returns
161    /// The current setpoint value.
162    ///
163    /// # Errors
164    /// Returns `NanonisError` if communication fails.
165    pub fn kelvin_ctrl_setpnt_get(&mut self) -> Result<f32, NanonisError> {
166        let result = self.quick_send("KelvinCtrl.SetpntGet", vec![], vec![], vec!["f"])?;
167
168        if !result.is_empty() {
169            Ok(result[0].as_f32()?)
170        } else {
171            Err(NanonisError::Protocol("Invalid response".to_string()))
172        }
173    }
174
175    /// Set the Kelvin controller gain parameters.
176    ///
177    /// # Arguments
178    /// * `gain` - A [`KelvinGain`] struct with gain parameters
179    ///
180    /// # Errors
181    /// Returns `NanonisError` if communication fails.
182    pub fn kelvin_ctrl_gain_set(&mut self, gain: &KelvinGain) -> Result<(), NanonisError> {
183        self.quick_send(
184            "KelvinCtrl.GainSet",
185            vec![
186                NanonisValue::F32(gain.p_gain),
187                NanonisValue::F32(gain.time_constant_s),
188                NanonisValue::U16(gain.slope.into()),
189            ],
190            vec!["f", "f", "H"],
191            vec![],
192        )?;
193        Ok(())
194    }
195
196    /// Get the Kelvin controller gain parameters.
197    ///
198    /// # Returns
199    /// A [`KelvinGain`] struct with current gain parameters.
200    ///
201    /// # Errors
202    /// Returns `NanonisError` if communication fails.
203    pub fn kelvin_ctrl_gain_get(&mut self) -> Result<KelvinGain, NanonisError> {
204        let result = self.quick_send("KelvinCtrl.GainGet", vec![], vec![], vec!["f", "f", "H"])?;
205
206        if result.len() >= 3 {
207            Ok(KelvinGain {
208                p_gain: result[0].as_f32()?,
209                time_constant_s: result[1].as_f32()?,
210                slope: result[2].as_u16()?.try_into()?,
211            })
212        } else {
213            Err(NanonisError::Protocol("Invalid response".to_string()))
214        }
215    }
216
217    /// Set the Kelvin controller modulation parameters.
218    ///
219    /// # Arguments
220    /// * `params` - A [`KelvinModParams`] struct with modulation parameters
221    ///
222    /// # Errors
223    /// Returns `NanonisError` if communication fails.
224    pub fn kelvin_ctrl_mod_params_set(
225        &mut self,
226        params: &KelvinModParams,
227    ) -> Result<(), NanonisError> {
228        self.quick_send(
229            "KelvinCtrl.ModParamsSet",
230            vec![
231                NanonisValue::F32(params.frequency_hz),
232                NanonisValue::F32(params.amplitude),
233                NanonisValue::F32(params.phase_deg),
234            ],
235            vec!["f", "f", "f"],
236            vec![],
237        )?;
238        Ok(())
239    }
240
241    /// Get the Kelvin controller modulation parameters.
242    ///
243    /// # Returns
244    /// A [`KelvinModParams`] struct with current modulation parameters.
245    ///
246    /// # Errors
247    /// Returns `NanonisError` if communication fails.
248    pub fn kelvin_ctrl_mod_params_get(&mut self) -> Result<KelvinModParams, NanonisError> {
249        let result = self.quick_send(
250            "KelvinCtrl.ModParamsGet",
251            vec![],
252            vec![],
253            vec!["f", "f", "f"],
254        )?;
255
256        if result.len() >= 3 {
257            Ok(KelvinModParams {
258                frequency_hz: result[0].as_f32()?,
259                amplitude: result[1].as_f32()?,
260                phase_deg: result[2].as_f32()?,
261            })
262        } else {
263            Err(NanonisError::Protocol("Invalid response".to_string()))
264        }
265    }
266
267    /// Set the Kelvin controller AC mode and modulation status.
268    ///
269    /// # Arguments
270    /// * `ac_mode` - AC mode setting
271    /// * `modulation` - True to enable modulation, false to disable
272    ///
273    /// # Errors
274    /// Returns `NanonisError` if communication fails.
275    pub fn kelvin_ctrl_mod_on_off_set(
276        &mut self,
277        ac_mode: KelvinACMode,
278        modulation: bool,
279    ) -> Result<(), NanonisError> {
280        let mod_flag = if modulation { 1u16 } else { 0u16 };
281        self.quick_send(
282            "KelvinCtrl.ModOnOffSet",
283            vec![
284                NanonisValue::U16(ac_mode.into()),
285                NanonisValue::U16(mod_flag),
286            ],
287            vec!["H", "H"],
288            vec![],
289        )?;
290        Ok(())
291    }
292
293    /// Get the Kelvin controller AC mode and modulation status.
294    ///
295    /// # Returns
296    /// A [`KelvinModStatus`] struct with current status.
297    ///
298    /// # Errors
299    /// Returns `NanonisError` if communication fails.
300    pub fn kelvin_ctrl_mod_on_off_get(&mut self) -> Result<KelvinModStatus, NanonisError> {
301        let result = self.quick_send("KelvinCtrl.ModOnOffGet", vec![], vec![], vec!["H", "H"])?;
302
303        if result.len() >= 2 {
304            Ok(KelvinModStatus {
305                ac_mode: result[0].as_u16()? != 0,
306                modulation: result[1].as_u16()? != 0,
307            })
308        } else {
309            Err(NanonisError::Protocol("Invalid response".to_string()))
310        }
311    }
312
313    /// Set the Kelvin controller demodulated/control signal index.
314    ///
315    /// # Arguments
316    /// * `signal_index` - Signal index
317    ///
318    /// # Errors
319    /// Returns `NanonisError` if communication fails.
320    pub fn kelvin_ctrl_signal_set(&mut self, signal_index: i32) -> Result<(), NanonisError> {
321        self.quick_send(
322            "KelvinCtrl.CtrlSignalSet",
323            vec![NanonisValue::I32(signal_index)],
324            vec!["i"],
325            vec![],
326        )?;
327        Ok(())
328    }
329
330    /// Get the Kelvin controller demodulated/control signal index.
331    ///
332    /// # Returns
333    /// The signal index.
334    ///
335    /// # Errors
336    /// Returns `NanonisError` if communication fails.
337    pub fn kelvin_ctrl_signal_get(&mut self) -> Result<i32, NanonisError> {
338        let result = self.quick_send("KelvinCtrl.CtrlSignalGet", vec![], vec![], vec!["i"])?;
339
340        if !result.is_empty() {
341            Ok(result[0].as_i32()?)
342        } else {
343            Err(NanonisError::Protocol("Invalid response".to_string()))
344        }
345    }
346
347    /// Get the amplitude of the demodulated/control signal.
348    ///
349    /// # Returns
350    /// The amplitude value.
351    ///
352    /// # Errors
353    /// Returns `NanonisError` if communication fails.
354    pub fn kelvin_ctrl_amp_get(&mut self) -> Result<f32, NanonisError> {
355        let result = self.quick_send("KelvinCtrl.AmpGet", vec![], vec![], vec!["f"])?;
356
357        if !result.is_empty() {
358            Ok(result[0].as_f32()?)
359        } else {
360            Err(NanonisError::Protocol("Invalid response".to_string()))
361        }
362    }
363
364    /// Set the Kelvin controller bias limits.
365    ///
366    /// The bias voltage will be limited to these values as long as the controller is on.
367    ///
368    /// # Arguments
369    /// * `limits` - A [`KelvinBiasLimits`] struct with bias limits
370    ///
371    /// # Errors
372    /// Returns `NanonisError` if communication fails.
373    pub fn kelvin_ctrl_bias_limits_set(
374        &mut self,
375        limits: &KelvinBiasLimits,
376    ) -> Result<(), NanonisError> {
377        self.quick_send(
378            "KelvinCtrl.BiasLimitsSet",
379            vec![
380                NanonisValue::F32(limits.high_limit_v),
381                NanonisValue::F32(limits.low_limit_v),
382            ],
383            vec!["f", "f"],
384            vec![],
385        )?;
386        Ok(())
387    }
388
389    /// Get the Kelvin controller bias limits.
390    ///
391    /// # Returns
392    /// A [`KelvinBiasLimits`] struct with current bias limits.
393    ///
394    /// # Errors
395    /// Returns `NanonisError` if communication fails.
396    pub fn kelvin_ctrl_bias_limits_get(&mut self) -> Result<KelvinBiasLimits, NanonisError> {
397        let result = self.quick_send("KelvinCtrl.BiasLimitsGet", vec![], vec![], vec!["f", "f"])?;
398
399        if result.len() >= 2 {
400            Ok(KelvinBiasLimits {
401                high_limit_v: result[0].as_f32()?,
402                low_limit_v: result[1].as_f32()?,
403            })
404        } else {
405            Err(NanonisError::Protocol("Invalid response".to_string()))
406        }
407    }
408}