Skip to main content

nanonis_rs/client/
pi_ctrl.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::NanonisValue;
4
5/// PI Controller slope direction.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum PISlope {
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<PISlope> for u16 {
18    fn from(slope: PISlope) -> Self {
19        slope as u16
20    }
21}
22
23impl TryFrom<u16> for PISlope {
24    type Error = NanonisError;
25
26    fn try_from(value: u16) -> Result<Self, Self::Error> {
27        match value {
28            0 => Ok(PISlope::Positive),
29            1 => Ok(PISlope::Negative),
30            _ => Err(NanonisError::Protocol(format!(
31                "Invalid PISlope value: {}",
32                value
33            ))),
34        }
35    }
36}
37
38/// PI Controller properties.
39#[derive(Debug, Clone, Copy, Default)]
40pub struct PICtrlProps {
41    /// Setpoint value
42    pub setpoint: f32,
43    /// Proportional gain
44    pub p_gain: f32,
45    /// Integral gain
46    pub i_gain: f32,
47    /// Slope direction
48    pub slope: PISlope,
49}
50
51/// PI Controller output limits.
52#[derive(Debug, Clone, Copy, Default)]
53pub struct PICtrlLimits {
54    /// Lower output limit
55    pub lower_limit: f32,
56    /// Upper output limit
57    pub upper_limit: f32,
58}
59
60/// Information about available control signals.
61#[derive(Debug, Clone)]
62pub struct ControlSignalInfo {
63    /// Currently selected signal index
64    pub current_index: i32,
65    /// Names of available signals
66    pub signal_names: Vec<String>,
67    /// Indexes of available signals
68    pub signal_indexes: Vec<i32>,
69}
70
71impl NanonisClient {
72    /// Enable or disable a PI controller.
73    ///
74    /// # Arguments
75    /// * `controller_index` - Controller index (1-8)
76    /// * `enabled` - True to enable, false to disable
77    ///
78    /// # Errors
79    /// Returns `NanonisError` if communication fails.
80    ///
81    /// # Examples
82    /// ```no_run
83    /// use nanonis_rs::NanonisClient;
84    ///
85    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
86    /// client.pi_ctrl_on_off_set(1, true)?;
87    /// # Ok::<(), Box<dyn std::error::Error>>(())
88    /// ```
89    pub fn pi_ctrl_on_off_set(
90        &mut self,
91        controller_index: i32,
92        enabled: bool,
93    ) -> Result<(), NanonisError> {
94        let status = if enabled { 1u32 } else { 0u32 };
95        self.quick_send(
96            "PICtrl.OnOffSet",
97            vec![
98                NanonisValue::I32(controller_index),
99                NanonisValue::U32(status),
100            ],
101            vec!["i", "I"],
102            vec![],
103        )?;
104        Ok(())
105    }
106
107    /// Get the on/off status of a PI controller.
108    ///
109    /// # Arguments
110    /// * `controller_index` - Controller index (1-8)
111    ///
112    /// # Returns
113    /// True if controller is enabled.
114    ///
115    /// # Errors
116    /// Returns `NanonisError` if communication fails.
117    pub fn pi_ctrl_on_off_get(&mut self, controller_index: i32) -> Result<bool, NanonisError> {
118        let result = self.quick_send(
119            "PICtrl.OnOffGet",
120            vec![NanonisValue::I32(controller_index)],
121            vec!["i"],
122            vec!["I"],
123        )?;
124
125        if !result.is_empty() {
126            Ok(result[0].as_u32()? != 0)
127        } else {
128            Err(NanonisError::Protocol("Invalid response".to_string()))
129        }
130    }
131
132    /// Set the control channel for a PI controller.
133    ///
134    /// # Arguments
135    /// * `controller_index` - Controller index (1-8)
136    /// * `signal_index` - Control signal index
137    ///
138    /// # Errors
139    /// Returns `NanonisError` if communication fails.
140    pub fn pi_ctrl_ctrl_ch_set(
141        &mut self,
142        controller_index: i32,
143        signal_index: i32,
144    ) -> Result<(), NanonisError> {
145        self.quick_send(
146            "PICtrl.CtrlChSet",
147            vec![
148                NanonisValue::I32(controller_index),
149                NanonisValue::I32(signal_index),
150            ],
151            vec!["i", "i"],
152            vec![],
153        )?;
154        Ok(())
155    }
156
157    /// Get the control channel information for a PI controller.
158    ///
159    /// # Arguments
160    /// * `controller_index` - Controller index (1-8)
161    ///
162    /// # Returns
163    /// A [`ControlSignalInfo`] struct with current and available signals.
164    ///
165    /// # Errors
166    /// Returns `NanonisError` if communication fails.
167    pub fn pi_ctrl_ctrl_ch_get(
168        &mut self,
169        controller_index: i32,
170    ) -> Result<ControlSignalInfo, NanonisError> {
171        let result = self.quick_send(
172            "PICtrl.CtrlChGet",
173            vec![NanonisValue::I32(controller_index)],
174            vec!["i"],
175            vec!["i", "i", "i", "*+c", "i", "*i"],
176        )?;
177
178        if result.len() >= 6 {
179            Ok(ControlSignalInfo {
180                current_index: result[0].as_i32()?,
181                signal_names: result[3].as_string_array()?.to_vec(),
182                signal_indexes: result[5].as_i32_array()?.to_vec(),
183            })
184        } else {
185            Err(NanonisError::Protocol("Invalid response".to_string()))
186        }
187    }
188
189    /// Set the input channel for a PI controller.
190    ///
191    /// # Arguments
192    /// * `controller_index` - Controller index (1-8)
193    /// * `input_index` - Input signal index
194    ///
195    /// # Errors
196    /// Returns `NanonisError` if communication fails.
197    pub fn pi_ctrl_input_ch_set(
198        &mut self,
199        controller_index: i32,
200        input_index: i32,
201    ) -> Result<(), NanonisError> {
202        self.quick_send(
203            "PICtrl.InputChSet",
204            vec![
205                NanonisValue::I32(controller_index),
206                NanonisValue::I32(input_index),
207            ],
208            vec!["i", "i"],
209            vec![],
210        )?;
211        Ok(())
212    }
213
214    /// Get the input channel information for a PI controller.
215    ///
216    /// # Arguments
217    /// * `controller_index` - Controller index (1-8)
218    ///
219    /// # Returns
220    /// A [`ControlSignalInfo`] struct with current and available input signals.
221    ///
222    /// # Errors
223    /// Returns `NanonisError` if communication fails.
224    pub fn pi_ctrl_input_ch_get(
225        &mut self,
226        controller_index: i32,
227    ) -> Result<ControlSignalInfo, NanonisError> {
228        let result = self.quick_send(
229            "PICtrl.InputChGet",
230            vec![NanonisValue::I32(controller_index)],
231            vec!["i"],
232            vec!["i", "i", "i", "*+c", "i", "*i"],
233        )?;
234
235        if result.len() >= 6 {
236            Ok(ControlSignalInfo {
237                current_index: result[0].as_i32()?,
238                signal_names: result[3].as_string_array()?.to_vec(),
239                signal_indexes: result[5].as_i32_array()?.to_vec(),
240            })
241        } else {
242            Err(NanonisError::Protocol("Invalid response".to_string()))
243        }
244    }
245
246    /// Set the properties of a PI controller.
247    ///
248    /// # Arguments
249    /// * `controller_index` - Controller index (1-8)
250    /// * `props` - A [`PICtrlProps`] struct with controller properties
251    ///
252    /// # Errors
253    /// Returns `NanonisError` if communication fails.
254    ///
255    /// # Examples
256    /// ```no_run
257    /// use nanonis_rs::NanonisClient;
258    /// use nanonis_rs::pi_ctrl::{PICtrlProps, PISlope};
259    ///
260    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
261    /// let props = PICtrlProps {
262    ///     setpoint: 0.0,
263    ///     p_gain: 1.0,
264    ///     i_gain: 100.0,
265    ///     slope: PISlope::Positive,
266    /// };
267    /// client.pi_ctrl_props_set(1, &props)?;
268    /// # Ok::<(), Box<dyn std::error::Error>>(())
269    /// ```
270    pub fn pi_ctrl_props_set(
271        &mut self,
272        controller_index: i32,
273        props: &PICtrlProps,
274    ) -> Result<(), NanonisError> {
275        self.quick_send(
276            "PICtrl.PropsSet",
277            vec![
278                NanonisValue::I32(controller_index),
279                NanonisValue::F32(props.setpoint),
280                NanonisValue::F32(props.p_gain),
281                NanonisValue::F32(props.i_gain),
282                NanonisValue::U16(props.slope.into()),
283            ],
284            vec!["i", "f", "f", "f", "H"],
285            vec![],
286        )?;
287        Ok(())
288    }
289
290    /// Get the properties of a PI controller.
291    ///
292    /// # Arguments
293    /// * `controller_index` - Controller index (1-8)
294    ///
295    /// # Returns
296    /// A [`PICtrlProps`] struct with current controller properties.
297    ///
298    /// # Errors
299    /// Returns `NanonisError` if communication fails.
300    pub fn pi_ctrl_props_get(&mut self, controller_index: i32) -> Result<PICtrlProps, NanonisError> {
301        let result = self.quick_send(
302            "PICtrl.PropsGet",
303            vec![NanonisValue::I32(controller_index)],
304            vec!["i"],
305            vec!["f", "f", "f", "H"],
306        )?;
307
308        if result.len() >= 4 {
309            Ok(PICtrlProps {
310                setpoint: result[0].as_f32()?,
311                p_gain: result[1].as_f32()?,
312                i_gain: result[2].as_f32()?,
313                slope: result[3].as_u16()?.try_into()?,
314            })
315        } else {
316            Err(NanonisError::Protocol("Invalid response".to_string()))
317        }
318    }
319
320    /// Set the control channel output limits for a PI controller.
321    ///
322    /// # Arguments
323    /// * `controller_index` - Controller index (1-8)
324    /// * `limits` - A [`PICtrlLimits`] struct with output limits
325    ///
326    /// # Errors
327    /// Returns `NanonisError` if communication fails.
328    pub fn pi_ctrl_ctrl_ch_props_set(
329        &mut self,
330        controller_index: i32,
331        limits: &PICtrlLimits,
332    ) -> Result<(), NanonisError> {
333        self.quick_send(
334            "PICtrl.CtrlChPropsSet",
335            vec![
336                NanonisValue::I32(controller_index),
337                NanonisValue::F32(limits.lower_limit),
338                NanonisValue::F32(limits.upper_limit),
339            ],
340            vec!["i", "f", "f"],
341            vec![],
342        )?;
343        Ok(())
344    }
345
346    /// Get the control channel output limits for a PI controller.
347    ///
348    /// # Arguments
349    /// * `controller_index` - Controller index (1-8)
350    ///
351    /// # Returns
352    /// A [`PICtrlLimits`] struct with current output limits.
353    ///
354    /// # Errors
355    /// Returns `NanonisError` if communication fails.
356    pub fn pi_ctrl_ctrl_ch_props_get(
357        &mut self,
358        controller_index: i32,
359    ) -> Result<PICtrlLimits, NanonisError> {
360        let result = self.quick_send(
361            "PICtrl.CtrlChPropsGet",
362            vec![NanonisValue::I32(controller_index)],
363            vec!["i"],
364            vec!["f", "f"],
365        )?;
366
367        if result.len() >= 2 {
368            Ok(PICtrlLimits {
369                lower_limit: result[0].as_f32()?,
370                upper_limit: result[1].as_f32()?,
371            })
372        } else {
373            Err(NanonisError::Protocol("Invalid response".to_string()))
374        }
375    }
376}