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}