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 =
205            self.quick_send("KelvinCtrl.GainGet", vec![], vec![], vec!["f", "f", "H"])?;
206
207        if result.len() >= 3 {
208            Ok(KelvinGain {
209                p_gain: result[0].as_f32()?,
210                time_constant_s: result[1].as_f32()?,
211                slope: result[2].as_u16()?.try_into()?,
212            })
213        } else {
214            Err(NanonisError::Protocol("Invalid response".to_string()))
215        }
216    }
217
218    /// Set the Kelvin controller modulation parameters.
219    ///
220    /// # Arguments
221    /// * `params` - A [`KelvinModParams`] struct with modulation parameters
222    ///
223    /// # Errors
224    /// Returns `NanonisError` if communication fails.
225    pub fn kelvin_ctrl_mod_params_set(
226        &mut self,
227        params: &KelvinModParams,
228    ) -> Result<(), NanonisError> {
229        self.quick_send(
230            "KelvinCtrl.ModParamsSet",
231            vec![
232                NanonisValue::F32(params.frequency_hz),
233                NanonisValue::F32(params.amplitude),
234                NanonisValue::F32(params.phase_deg),
235            ],
236            vec!["f", "f", "f"],
237            vec![],
238        )?;
239        Ok(())
240    }
241
242    /// Get the Kelvin controller modulation parameters.
243    ///
244    /// # Returns
245    /// A [`KelvinModParams`] struct with current modulation parameters.
246    ///
247    /// # Errors
248    /// Returns `NanonisError` if communication fails.
249    pub fn kelvin_ctrl_mod_params_get(&mut self) -> Result<KelvinModParams, NanonisError> {
250        let result =
251            self.quick_send("KelvinCtrl.ModParamsGet", vec![], vec![], vec!["f", "f", "f"])?;
252
253        if result.len() >= 3 {
254            Ok(KelvinModParams {
255                frequency_hz: result[0].as_f32()?,
256                amplitude: result[1].as_f32()?,
257                phase_deg: result[2].as_f32()?,
258            })
259        } else {
260            Err(NanonisError::Protocol("Invalid response".to_string()))
261        }
262    }
263
264    /// Set the Kelvin controller AC mode and modulation status.
265    ///
266    /// # Arguments
267    /// * `ac_mode` - AC mode setting
268    /// * `modulation` - True to enable modulation, false to disable
269    ///
270    /// # Errors
271    /// Returns `NanonisError` if communication fails.
272    pub fn kelvin_ctrl_mod_on_off_set(
273        &mut self,
274        ac_mode: KelvinACMode,
275        modulation: bool,
276    ) -> Result<(), NanonisError> {
277        let mod_flag = if modulation { 1u16 } else { 0u16 };
278        self.quick_send(
279            "KelvinCtrl.ModOnOffSet",
280            vec![
281                NanonisValue::U16(ac_mode.into()),
282                NanonisValue::U16(mod_flag),
283            ],
284            vec!["H", "H"],
285            vec![],
286        )?;
287        Ok(())
288    }
289
290    /// Get the Kelvin controller AC mode and modulation status.
291    ///
292    /// # Returns
293    /// A [`KelvinModStatus`] struct with current status.
294    ///
295    /// # Errors
296    /// Returns `NanonisError` if communication fails.
297    pub fn kelvin_ctrl_mod_on_off_get(&mut self) -> Result<KelvinModStatus, NanonisError> {
298        let result =
299            self.quick_send("KelvinCtrl.ModOnOffGet", vec![], vec![], vec!["H", "H"])?;
300
301        if result.len() >= 2 {
302            Ok(KelvinModStatus {
303                ac_mode: result[0].as_u16()? != 0,
304                modulation: result[1].as_u16()? != 0,
305            })
306        } else {
307            Err(NanonisError::Protocol("Invalid response".to_string()))
308        }
309    }
310
311    /// Set the Kelvin controller demodulated/control signal index.
312    ///
313    /// # Arguments
314    /// * `signal_index` - Signal index
315    ///
316    /// # Errors
317    /// Returns `NanonisError` if communication fails.
318    pub fn kelvin_ctrl_signal_set(&mut self, signal_index: i32) -> Result<(), NanonisError> {
319        self.quick_send(
320            "KelvinCtrl.CtrlSignalSet",
321            vec![NanonisValue::I32(signal_index)],
322            vec!["i"],
323            vec![],
324        )?;
325        Ok(())
326    }
327
328    /// Get the Kelvin controller demodulated/control signal index.
329    ///
330    /// # Returns
331    /// The signal index.
332    ///
333    /// # Errors
334    /// Returns `NanonisError` if communication fails.
335    pub fn kelvin_ctrl_signal_get(&mut self) -> Result<i32, NanonisError> {
336        let result = self.quick_send("KelvinCtrl.CtrlSignalGet", vec![], vec![], vec!["i"])?;
337
338        if !result.is_empty() {
339            Ok(result[0].as_i32()?)
340        } else {
341            Err(NanonisError::Protocol("Invalid response".to_string()))
342        }
343    }
344
345    /// Get the amplitude of the demodulated/control signal.
346    ///
347    /// # Returns
348    /// The amplitude value.
349    ///
350    /// # Errors
351    /// Returns `NanonisError` if communication fails.
352    pub fn kelvin_ctrl_amp_get(&mut self) -> Result<f32, NanonisError> {
353        let result = self.quick_send("KelvinCtrl.AmpGet", vec![], vec![], vec!["f"])?;
354
355        if !result.is_empty() {
356            Ok(result[0].as_f32()?)
357        } else {
358            Err(NanonisError::Protocol("Invalid response".to_string()))
359        }
360    }
361
362    /// Set the Kelvin controller bias limits.
363    ///
364    /// The bias voltage will be limited to these values as long as the controller is on.
365    ///
366    /// # Arguments
367    /// * `limits` - A [`KelvinBiasLimits`] struct with bias limits
368    ///
369    /// # Errors
370    /// Returns `NanonisError` if communication fails.
371    pub fn kelvin_ctrl_bias_limits_set(
372        &mut self,
373        limits: &KelvinBiasLimits,
374    ) -> Result<(), NanonisError> {
375        self.quick_send(
376            "KelvinCtrl.BiasLimitsSet",
377            vec![
378                NanonisValue::F32(limits.high_limit_v),
379                NanonisValue::F32(limits.low_limit_v),
380            ],
381            vec!["f", "f"],
382            vec![],
383        )?;
384        Ok(())
385    }
386
387    /// Get the Kelvin controller bias limits.
388    ///
389    /// # Returns
390    /// A [`KelvinBiasLimits`] struct with current bias limits.
391    ///
392    /// # Errors
393    /// Returns `NanonisError` if communication fails.
394    pub fn kelvin_ctrl_bias_limits_get(&mut self) -> Result<KelvinBiasLimits, NanonisError> {
395        let result =
396            self.quick_send("KelvinCtrl.BiasLimitsGet", vec![], vec![], vec!["f", "f"])?;
397
398        if result.len() >= 2 {
399            Ok(KelvinBiasLimits {
400                high_limit_v: result[0].as_f32()?,
401                low_limit_v: result[1].as_f32()?,
402            })
403        } else {
404            Err(NanonisError::Protocol("Invalid response".to_string()))
405        }
406    }
407}