rusty_tip/nanonis/client/
signals.rs

1use super::NanonisClient;
2use crate::error::NanonisError;
3use crate::types::{NanonisValue, SignalIndex};
4
5impl NanonisClient {
6    /// Get available signal names
7    pub fn signal_names_get(
8        &mut self,
9        print: bool,
10    ) -> Result<Vec<String>, NanonisError> {
11        let result =
12            self.quick_send("Signals.NamesGet", vec![], vec![], vec!["+*c"])?;
13        match result.first() {
14            Some(value) => {
15                let signal_names = value.as_string_array()?.to_vec();
16
17                if print {
18                    Self::print_signal_names(&signal_names);
19                }
20
21                Ok(signal_names)
22            }
23            None => Err(NanonisError::Protocol(
24                "No signal names returned".to_string(),
25            )),
26        }
27    }
28
29    /// Helper function for printing signal names
30    fn print_signal_names(names: &[String]) {
31        log::info!("Available signal names ({} total):", names.len());
32        for (index, name) in names.iter().enumerate() {
33            log::info!("  {index}: {name}");
34        }
35    }
36
37    /// Get calibration and offset of a signal by index
38    pub fn signals_calibr_get(
39        &mut self,
40        signal_index: SignalIndex,
41    ) -> Result<(f32, f32), NanonisError> {
42        let result = self.quick_send(
43            "Signals.CalibrGet",
44            vec![NanonisValue::I32(signal_index.into())],
45            vec!["i"],
46            vec!["f", "f"],
47        )?;
48        if result.len() >= 2 {
49            Ok((result[0].as_f32()?, result[1].as_f32()?))
50        } else {
51            Err(NanonisError::Protocol(
52                "Invalid calibration response".to_string(),
53            ))
54        }
55    }
56
57    /// Get range limits of a signal by index
58    pub fn signals_range_get(
59        &mut self,
60        signal_index: SignalIndex,
61    ) -> Result<(f32, f32), NanonisError> {
62        let result = self.quick_send(
63            "Signals.RangeGet",
64            vec![NanonisValue::I32(signal_index.into())],
65            vec!["i"],
66            vec!["f", "f"],
67        )?;
68        if result.len() >= 2 {
69            Ok((result[0].as_f32()?, result[1].as_f32()?)) // (max, min)
70        } else {
71            Err(NanonisError::Protocol("Invalid range response".to_string()))
72        }
73    }
74
75    /// Get current values of signals by index(es)
76    pub fn signals_vals_get(
77        &mut self,
78        signal_indexes: Vec<i32>,
79        wait_for_newest_data: bool,
80    ) -> Result<Vec<f32>, NanonisError> {
81        let indexes = signal_indexes;
82        let wait_flag = if wait_for_newest_data { 1u32 } else { 0u32 };
83
84        let result = self.quick_send(
85            "Signals.ValsGet",
86            vec![
87                NanonisValue::ArrayI32(indexes),
88                NanonisValue::U32(wait_flag),
89            ],
90            vec!["+*i", "I"],
91            vec!["i", "*f"],
92        )?;
93
94        if result.len() >= 2 {
95            match &result[1] {
96                NanonisValue::ArrayF32(values) => Ok(values.clone()),
97                _ => Err(NanonisError::Protocol(
98                    "Invalid signal values response".to_string(),
99                )),
100            }
101        } else {
102            Err(NanonisError::Protocol(
103                "Incomplete signal values response".to_string(),
104            ))
105        }
106    }
107
108    /// Find signal index by name (case-insensitive)
109    pub fn find_signal_index(
110        &mut self,
111        signal_name: &str,
112    ) -> Result<Option<SignalIndex>, NanonisError> {
113        let signals = self.signal_names_get(false)?;
114        let signal_name_lower = signal_name.to_lowercase();
115
116        for (index, name) in signals.iter().enumerate() {
117            if name.to_lowercase().contains(&signal_name_lower) {
118                return Ok(Some(SignalIndex(index as i32)));
119            }
120        }
121        Ok(None)
122    }
123
124    /// Get the current value of a single selected signal.
125    ///
126    /// Returns the current value of the selected signal, oversampled during the
127    /// Acquisition Period time (Tap). The signal is continuously oversampled and published
128    /// every Tap seconds.
129    ///
130    /// # Signal Measurement Principle
131    /// This function waits for the next oversampled data to be published and returns its value.
132    /// It does not trigger a measurement but waits for data to be published. The function
133    /// returns a value 0 to Tap seconds after being called.
134    ///
135    /// **Important**: If you change a signal and immediately call this function, you might
136    /// get "old" data measured before the signal change. Set `wait_for_newest_data` to `true`
137    /// to ensure you get only fresh data.
138    ///
139    /// # Arguments
140    /// * `signal_index` - Signal index (0-127)
141    /// * `wait_for_newest_data` - If `true`, discards first value and waits for fresh data.
142    ///   Takes Tap to 2*Tap seconds. If `false`, returns next available value (0 to Tap seconds).
143    ///
144    /// # Returns
145    /// The signal value in physical units.
146    ///
147    /// # Errors
148    /// Returns `NanonisError` if:
149    /// - Invalid signal index provided
150    /// - Communication timeout or protocol error
151    ///
152    /// # Examples
153    /// ```no_run
154    /// use rusty_tip::{NanonisClient, SignalIndex};
155    ///
156    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
157    ///
158    /// // Read bias signal immediately
159    /// let bias_value = client.signal_val_get(SignalIndex(24), false)?;
160    ///
161    /// // Wait for fresh data after signal change
162    /// let fresh_value = client.signal_val_get(SignalIndex(24), true)?;
163    /// # Ok::<(), Box<dyn std::error::Error>>(())
164    /// ```
165    pub fn signal_val_get(
166        &mut self,
167        signal_index: impl Into<SignalIndex>,
168        wait_for_newest_data: bool,
169    ) -> Result<f32, NanonisError> {
170        let wait_flag = if wait_for_newest_data { 1u32 } else { 0u32 };
171
172        let result = self.quick_send(
173            "Signals.ValGet",
174            vec![
175                NanonisValue::I32(signal_index.into().into()),
176                NanonisValue::U32(wait_flag),
177            ],
178            vec!["i", "I"],
179            vec!["f"],
180        )?;
181
182        match result.first() {
183            Some(value) => Ok(value.as_f32()?),
184            None => Err(NanonisError::Protocol(
185                "No signal value returned".to_string(),
186            )),
187        }
188    }
189
190    /// Get the list of measurement channels names available in the software.
191    ///
192    /// Returns the names of measurement channels used in sweepers and other measurement modules.
193    ///
194    /// **Important Note**: Measurement channels are different from Signals. Measurement channels
195    /// are used in sweepers, while Signals are used by graphs and other modules. The indexes
196    /// returned here are used for sweeper channel configuration (e.g., `GenSwp.ChannelsGet/Set`).
197    ///
198    /// # Returns
199    /// A vector of measurement channel names where each name corresponds to an index
200    /// that can be used in sweeper functions.
201    ///
202    /// # Errors
203    /// Returns `NanonisError` if communication fails or protocol error occurs.
204    ///
205    /// # Examples
206    /// ```no_run
207    /// use rusty_tip::NanonisClient;
208    ///
209    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
210    ///
211    /// let meas_channels = client.signals_meas_names_get()?;
212    /// println!("Available measurement channels: {}", meas_channels.len());
213    ///
214    /// for (index, name) in meas_channels.iter().enumerate() {
215    ///     println!("  {}: {}", index, name);
216    /// }
217    /// # Ok::<(), Box<dyn std::error::Error>>(())
218    /// ```
219    pub fn signals_meas_names_get(&mut self) -> Result<Vec<String>, NanonisError> {
220        let result = self.quick_send(
221            "Signals.MeasNamesGet",
222            vec![],
223            vec![],
224            vec!["i", "i", "*+c"],
225        )?;
226
227        if result.len() >= 3 {
228            let meas_names = result[2].as_string_array()?.to_vec();
229            Ok(meas_names)
230        } else {
231            Err(NanonisError::Protocol(
232                "Invalid measurement names response".to_string(),
233            ))
234        }
235    }
236
237    /// Get the list of additional Real-Time (RT) signals and current assignments.
238    ///
239    /// Returns the list of additional RT signals available for assignment to Internal 23 and 24,
240    /// plus the names of signals currently assigned to these internal channels.
241    ///
242    /// **Note**: This assignment in the Signals Manager doesn't automatically make them available
243    /// in graphs and modules. Internal 23 and 24 must be assigned to one of the 24 display slots
244    /// using functions like `Signals.InSlotSet` to be visible in the software.
245    ///
246    /// # Returns
247    /// A tuple containing:
248    /// - `Vec<String>` - List of additional RT signals that can be assigned to Internal 23/24
249    /// - `String` - Name of RT signal currently assigned to Internal 23
250    /// - `String` - Name of RT signal currently assigned to Internal 24
251    ///
252    /// # Errors
253    /// Returns `NanonisError` if communication fails or protocol error occurs.
254    ///
255    /// # Examples
256    /// ```no_run
257    /// use rusty_tip::NanonisClient;
258    ///
259    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
260    ///
261    /// let (available_signals, internal_23, internal_24) = client.signals_add_rt_get()?;
262    ///
263    /// println!("Available additional RT signals: {}", available_signals.len());
264    /// for (i, signal) in available_signals.iter().enumerate() {
265    ///     println!("  {}: {}", i, signal);
266    /// }
267    ///
268    /// println!("Internal 23 assigned to: {}", internal_23);
269    /// println!("Internal 24 assigned to: {}", internal_24);
270    /// # Ok::<(), Box<dyn std::error::Error>>(())
271    /// ```
272    pub fn signals_add_rt_get(
273        &mut self,
274    ) -> Result<(Vec<String>, String, String), NanonisError> {
275        let result = self.quick_send(
276            "Signals.AddRTGet",
277            vec![],
278            vec![],
279            vec!["i", "i", "*+c", "i", "*-c", "i", "*-c"],
280        )?;
281
282        if result.len() >= 7 {
283            let available_signals = result[2].as_string_array()?.to_vec();
284            let internal_23 = result[4].as_string()?.to_string();
285            let internal_24 = result[6].as_string()?.to_string();
286            Ok((available_signals, internal_23, internal_24))
287        } else {
288            Err(NanonisError::Protocol(
289                "Invalid additional RT signals response".to_string(),
290            ))
291        }
292    }
293
294    /// Assign additional Real-Time (RT) signals to Internal 23 and 24 signals.
295    ///
296    /// Links advanced RT signals to Internal 23 and Internal 24 in the Signals Manager.
297    /// This enables routing of specialized real-time signals through the internal channel system.
298    ///
299    /// **Important Note**: This assignment only links the RT signals to Internal 23/24.
300    /// To make them visible in graphs and available for acquisition in modules, Internal 23 and 24
301    /// must be assigned to one of the 24 display slots using functions like `Signals.InSlotSet`.
302    ///
303    /// # Arguments
304    /// * `additional_rt_signal_1` - Index of the RT signal to assign to Internal 23 (from `signals_add_rt_get()`)
305    /// * `additional_rt_signal_2` - Index of the RT signal to assign to Internal 24 (from `signals_add_rt_get()`)
306    ///
307    /// # Errors
308    /// Returns `NanonisError` if:
309    /// - Invalid RT signal indices provided
310    /// - RT signals are not available or accessible
311    /// - Communication fails or protocol error occurs
312    ///
313    /// # Examples
314    /// ```no_run
315    /// use rusty_tip::NanonisClient;
316    ///
317    /// let mut client = NanonisClient::new("127.0.0.1", 6501)?;
318    ///
319    /// // First, get the available RT signals
320    /// let (available_signals, current_23, current_24) = client.signals_add_rt_get()?;
321    ///
322    /// println!("Available RT signals:");
323    /// for (i, signal) in available_signals.iter().enumerate() {
324    ///     println!("  {}: {}", i, signal);
325    /// }
326    ///
327    /// // Assign RT signal index 0 to Internal 23 and index 1 to Internal 24
328    /// client.signals_add_rt_set(0, 1)?;
329    ///
330    /// // Verify the assignment
331    /// let (_, new_23, new_24) = client.signals_add_rt_get()?;
332    /// println!("Internal 23 now assigned to: {}", new_23);
333    /// println!("Internal 24 now assigned to: {}", new_24);
334    /// # Ok::<(), Box<dyn std::error::Error>>(())
335    /// ```
336    pub fn signals_add_rt_set(
337        &mut self,
338        additional_rt_signal_1: i32,
339        additional_rt_signal_2: i32,
340    ) -> Result<(), NanonisError> {
341        self.quick_send(
342            "Signals.AddRTSet",
343            vec![
344                NanonisValue::I32(additional_rt_signal_1),
345                NanonisValue::I32(additional_rt_signal_2),
346            ],
347            vec!["i", "i"],
348            vec![],
349        )?;
350        Ok(())
351    }
352
353    /// Read a signal by name (finds index automatically)
354    pub fn read_signal_by_name(
355        &mut self,
356        signal_name: &str,
357        wait_for_newest: bool,
358    ) -> Result<f32, NanonisError> {
359        match self.find_signal_index(signal_name)? {
360            Some(index) => {
361                let values =
362                    self.signals_vals_get(vec![index.into()], wait_for_newest)?;
363                values.first().copied().ok_or_else(|| {
364                    NanonisError::Protocol("No signal value returned".to_string())
365                })
366            }
367            None => Err(NanonisError::InvalidCommand(format!(
368                "Signal '{signal_name}' not found"
369            ))),
370        }
371    }
372}