1use serde::{Deserialize, Serialize};
2
3use crate::NanonisError;
4use std::{collections::HashMap, ops::Deref};
5
6fn generate_signal_aliases(name: &str) -> Vec<String> {
8 let mut aliases = Vec::new();
9 let name_lower = name.to_lowercase();
10
11 let alias_patterns = [
13 ("current", vec!["i", "cur", "amp"]),
15 ("bias", vec!["u", "voltage", "v"]),
16 ("x", vec!["x pos", "x position", "xpos"]),
18 ("y", vec!["y pos", "y position", "ypos"]),
19 ("z", vec!["z pos", "z position", "zpos", "height"]),
20 (
22 "oc m1 freq. shift",
23 vec!["freq shift", "frequency shift", "df", "oc freq shift"],
24 ),
25 ("oc m1 amplitude", vec!["amplitude", "amp", "oc amp"]),
26 ("oc m1 phase", vec!["phase", "oc phase"]),
27 ("li demod 1 x", vec!["li1x", "demod1x", "x1"]),
29 ("li demod 1 y", vec!["li1y", "demod1y", "y1"]),
30 ("li demod 2 x", vec!["li2x", "demod2x", "x2"]),
31 ("li demod 2 y", vec!["li2y", "demod2y", "y2"]),
32 ("z ctrl shift", vec!["z shift", "zshift"]),
34 ("frequency", vec!["freq", "f"]),
36 ("amplitude", vec!["amp", "a"]),
37 ("phase", vec!["ph"]),
38 ("shift", vec!["sh"]),
39 ];
40
41 for (pattern, pattern_aliases) in &alias_patterns {
43 if name_lower == *pattern {
44 aliases.extend(pattern_aliases.iter().map(|s| s.to_string()));
45 }
46 }
47
48 for (pattern, pattern_aliases) in &alias_patterns {
50 if name_lower.contains(pattern) {
51 aliases.extend(pattern_aliases.iter().map(|s| s.to_string()));
52 }
53 }
54
55 let words_to_remove = ["the", "signal", "channel", "ch", "ctrl", "control"];
57 let mut abbreviated = name_lower.clone();
58 for word in &words_to_remove {
59 abbreviated = abbreviated.replace(word, "").trim().to_string();
60 }
61 if abbreviated != name_lower && !abbreviated.is_empty() {
62 aliases.push(abbreviated);
63 }
64
65 let words: Vec<&str> = name_lower.split_whitespace().collect();
67 if words.len() > 1 {
68 let initials: String = words
69 .iter()
70 .map(|w| w.chars().next().unwrap_or('_'))
71 .collect();
72 if initials.len() > 1 {
73 aliases.push(initials);
74 }
75 }
76
77 aliases.retain(|s| !s.is_empty());
79 aliases.sort();
80 aliases.dedup();
81
82 aliases
83}
84
85#[derive(Debug, Clone, Default)]
87pub struct SignalRegistry(HashMap<String, Signal>);
88
89impl Deref for SignalRegistry {
90 type Target = HashMap<String, Signal>;
91 fn deref(&self) -> &Self::Target {
92 &self.0
93 }
94}
95#[derive(
101 Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default,
102)]
103pub struct Signal {
104 pub name: String,
106 pub index: u8,
108 pub tcp_channel: Option<u8>,
110}
111
112impl Signal {
113 pub fn new(
115 name: String,
116 index: u8,
117 tcp_channel: Option<u8>,
118 ) -> Result<Self, NanonisError> {
119 if index > 127 {
120 return Err(NanonisError::Protocol(format!(
121 "Nanonis index {index} out of range (0-127)"
122 )));
123 }
124
125 if let Some(ch) = tcp_channel {
126 if ch > 23 {
127 return Err(NanonisError::Protocol(format!(
128 "TCP Channel {ch} is out of range (0-23) "
129 )));
130 }
131 }
132
133 Ok(Self {
134 name,
135 index,
136 tcp_channel,
137 })
138 }
139
140 pub fn new_unchecked(
141 name: &str,
142 index: u8,
143 tcp_channel: Option<u8>,
144 ) -> Self {
145 let name = String::from(name);
146
147 Signal {
148 name,
149 index,
150 tcp_channel,
151 }
152 }
153
154 pub fn index_only(name: &str, index: u8) -> Self {
156 let name = String::from(name);
157 Signal {
158 name,
159 index,
160 tcp_channel: None,
161 }
162 }
163
164 pub fn with_tcp(
166 name: &str,
167 index: u8,
168 tcp_channel: u8,
169 ) -> Result<Self, NanonisError> {
170 let name = String::from(name);
171 Self::new(name, index, Some(tcp_channel))
172 }
173}
174
175#[derive(Default)]
176pub struct SignalRegistryBuilder {
177 signals: HashMap<String, Signal>,
178 nanonis_to_tcp: HashMap<u8, u8>,
179}
180
181impl SignalRegistryBuilder {
182 pub fn add_tcp_mapping(
183 mut self,
184 nanonis_index: u8,
185 tcp_channel: u8,
186 ) -> Self {
187 self.nanonis_to_tcp.insert(nanonis_index, tcp_channel);
188
189 self
190 }
191
192 pub fn add_tcp_map(mut self, nanonis_to_tcp: &[(u8, u8)]) -> Self {
193 nanonis_to_tcp.iter().for_each(|(n, t)| {
194 self.nanonis_to_tcp.insert(*n, *t);
195 });
196
197 self
198 }
199
200 pub fn with_standard_map(mut self) -> Self {
201 let standard_map: HashMap<u8, u8> = [
202 (0, 0),
203 (1, 1),
204 (2, 2),
205 (3, 3),
206 (4, 4),
207 (5, 5),
208 (6, 6),
209 (7, 7),
210 (24, 8),
211 (25, 9),
212 (26, 10),
213 (27, 11),
214 (28, 12),
215 (29, 13),
216 (30, 14),
217 (31, 15),
218 (74, 16),
219 (75, 17),
220 (76, 18),
221 (77, 19),
222 (78, 20),
223 (79, 21),
224 (80, 22),
225 (81, 23),
226 ]
227 .iter()
228 .cloned()
229 .collect();
230
231 self.nanonis_to_tcp.extend(standard_map);
232
233 self
234 }
235
236 pub fn from_signal_names(mut self, signal_names: &[String]) -> Self {
237 for (index, name) in signal_names.iter().enumerate() {
238 let clean_name = name.split('(').next().unwrap().trim();
239
240 let signal;
241
242 if let Some(tcp_channel) =
243 self.nanonis_to_tcp.get(&(index as u8)).copied()
244 {
245 signal = Signal {
246 name: name.clone(),
247 index: index as u8,
248 tcp_channel: Some(tcp_channel),
249 };
250 } else {
251 signal = Signal {
252 name: name.clone(),
253 index: index as u8,
254 tcp_channel: None,
255 };
256 }
257 self.signals.insert(name.to_lowercase(), signal.clone());
258
259 if clean_name != name {
260 self.signals
261 .insert(clean_name.to_lowercase(), signal.clone());
262 }
263 }
264 self
265 }
266
267 pub fn add_signal(mut self, name: String, index: u8) -> Self {
268 let tcp_channel = self.nanonis_to_tcp.get(&index).copied();
269 let clean_name = name.split('(').next().unwrap().trim();
270
271 let signal = Signal {
272 name: name.clone(),
273 index,
274 tcp_channel,
275 };
276
277 self.signals.insert(name.to_lowercase(), signal.clone());
278
279 if clean_name != name {
280 self.signals
281 .insert(clean_name.to_lowercase(), signal.clone());
282 };
283
284 self
285 }
286
287 pub fn create_aliases(mut self) -> Self {
288 let mut new_aliases = Vec::new();
289
290 for (existing_key, signal) in &self.signals {
292 let name = &signal.name;
293 let clean_name = name.split('(').next().unwrap_or(name).trim();
294
295 let aliases = generate_signal_aliases(clean_name);
297
298 for alias in aliases {
299 let alias_key = alias.to_lowercase();
300 if !self.signals.contains_key(&alias_key)
302 && alias_key != *existing_key
303 {
304 new_aliases.push((alias_key, signal.clone()));
305 }
306 }
307 }
308
309 for (alias_key, signal) in new_aliases {
311 self.signals.insert(alias_key, signal);
312 }
313
314 self
315 }
316
317 pub fn build(self) -> SignalRegistry {
318 SignalRegistry(self.signals)
319 }
320}
321
322impl SignalRegistry {
323 pub fn builder() -> SignalRegistryBuilder {
324 SignalRegistryBuilder::default()
325 }
326
327 pub fn with_hardcoded_tcp_mapping(signal_names: &[String]) -> Self {
328 Self::builder()
329 .with_standard_map()
330 .from_signal_names(signal_names)
331 .create_aliases()
332 .build()
333 }
334
335 pub fn all_names(&self) -> Vec<String> {
336 self.0
337 .values()
338 .map(|s| s.name.clone())
339 .collect::<std::collections::HashSet<_>>()
340 .into_iter()
341 .collect()
342 }
343
344 pub fn tcp_signals(&self) -> Vec<&Signal> {
345 self.0
346 .values()
347 .filter(|s| s.tcp_channel.is_some())
348 .collect()
349 }
350
351 pub fn find_signals_like(&self, query: &str) -> Vec<&Signal> {
352 let query_lower = query.to_lowercase();
353 self.0
354 .values()
355 .filter(|s| s.name.to_lowercase().contains(&query_lower))
356 .collect()
357 }
358
359 pub fn get_by_name(&self, name: &str) -> Option<&Signal> {
361 self.get(name)
362 }
363
364 pub fn get_by_index(&self, index: u8) -> Option<&Signal> {
366 self.0.values().find(|signal| signal.index == index)
367 }
368}