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(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
101pub struct Signal {
102 pub name: String,
104 pub index: u8,
106 pub tcp_channel: Option<u8>,
108}
109
110impl Signal {
111 pub fn new(name: String, index: u8, tcp_channel: Option<u8>) -> Result<Self, NanonisError> {
113 if index > 127 {
114 return Err(NanonisError::Protocol(format!(
115 "Nanonis index {index} out of range (0-127)"
116 )));
117 }
118
119 if let Some(ch) = tcp_channel {
120 if ch > 23 {
121 return Err(NanonisError::Protocol(format!(
122 "TCP Channel {ch} is out of range (0-23) "
123 )));
124 }
125 }
126
127 Ok(Self {
128 name,
129 index,
130 tcp_channel,
131 })
132 }
133
134 pub fn new_unchecked(name: &str, index: u8, tcp_channel: Option<u8>) -> Self {
135 let name = String::from(name);
136
137 Signal {
138 name,
139 index,
140 tcp_channel,
141 }
142 }
143
144 pub fn index_only(name: &str, index: u8) -> Self {
146 let name = String::from(name);
147 Signal {
148 name,
149 index,
150 tcp_channel: None,
151 }
152 }
153
154 pub fn with_tcp(name: &str, index: u8, tcp_channel: u8) -> Result<Self, NanonisError> {
156 let name = String::from(name);
157 Self::new(name, index, Some(tcp_channel))
158 }
159}
160
161#[derive(Default)]
162pub struct SignalRegistryBuilder {
163 signals: HashMap<String, Signal>,
164 nanonis_to_tcp: HashMap<u8, u8>,
165}
166
167impl SignalRegistryBuilder {
168 pub fn add_tcp_mapping(mut self, nanonis_index: u8, tcp_channel: u8) -> Self {
169 self.nanonis_to_tcp.insert(nanonis_index, tcp_channel);
170
171 self
172 }
173
174 pub fn add_tcp_map(mut self, nanonis_to_tcp: &[(u8, u8)]) -> Self {
175 nanonis_to_tcp.iter().for_each(|(n, t)| {
176 self.nanonis_to_tcp.insert(*n, *t);
177 });
178
179 self
180 }
181
182 pub fn with_standard_map(mut self) -> Self {
183 let standard_map: HashMap<u8, u8> = [
184 (0, 0),
185 (1, 1),
186 (2, 2),
187 (3, 3),
188 (4, 4),
189 (5, 5),
190 (6, 6),
191 (7, 7),
192 (24, 8),
193 (25, 9),
194 (26, 10),
195 (27, 11),
196 (28, 12),
197 (29, 13),
198 (30, 14),
199 (31, 15),
200 (74, 16),
201 (75, 17),
202 (76, 18),
203 (77, 19),
204 (78, 20),
205 (79, 21),
206 (80, 22),
207 (81, 23),
208 ]
209 .iter()
210 .cloned()
211 .collect();
212
213 self.nanonis_to_tcp.extend(standard_map);
214
215 self
216 }
217
218 pub fn from_signal_names(mut self, signal_names: &[String]) -> Self {
219 for (index, name) in signal_names.iter().enumerate() {
220 let clean_name = name.split('(').next().unwrap().trim();
221
222 let signal;
223
224 if let Some(tcp_channel) = self.nanonis_to_tcp.get(&(index as u8)).copied() {
225 signal = Signal {
226 name: name.clone(),
227 index: index as u8,
228 tcp_channel: Some(tcp_channel),
229 };
230 } else {
231 signal = Signal {
232 name: name.clone(),
233 index: index as u8,
234 tcp_channel: None,
235 };
236 }
237 self.signals.insert(name.to_lowercase(), signal.clone());
238
239 if clean_name != name {
240 self.signals
241 .insert(clean_name.to_lowercase(), signal.clone());
242 }
243 }
244 self
245 }
246
247 pub fn add_signal(mut self, name: String, index: u8) -> Self {
248 let tcp_channel = self.nanonis_to_tcp.get(&index).copied();
249 let clean_name = name.split('(').next().unwrap().trim();
250
251 let signal = Signal {
252 name: name.clone(),
253 index,
254 tcp_channel,
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 create_aliases(mut self) -> Self {
268 let mut new_aliases = Vec::new();
269
270 for (existing_key, signal) in &self.signals {
272 let name = &signal.name;
273 let clean_name = name.split('(').next().unwrap_or(name).trim();
274
275 let aliases = generate_signal_aliases(clean_name);
277
278 for alias in aliases {
279 let alias_key = alias.to_lowercase();
280 if !self.signals.contains_key(&alias_key) && alias_key != *existing_key {
282 new_aliases.push((alias_key, signal.clone()));
283 }
284 }
285 }
286
287 for (alias_key, signal) in new_aliases {
289 self.signals.insert(alias_key, signal);
290 }
291
292 self
293 }
294
295 pub fn build(self) -> SignalRegistry {
296 SignalRegistry(self.signals)
297 }
298}
299
300impl SignalRegistry {
301 pub fn builder() -> SignalRegistryBuilder {
302 SignalRegistryBuilder::default()
303 }
304
305 pub fn with_hardcoded_tcp_mapping(signal_names: &[String]) -> Self {
306 Self::builder()
307 .with_standard_map()
308 .from_signal_names(signal_names)
309 .create_aliases()
310 .build()
311 }
312
313 pub fn all_names(&self) -> Vec<String> {
314 self.0
315 .values()
316 .map(|s| s.name.clone())
317 .collect::<std::collections::HashSet<_>>()
318 .into_iter()
319 .collect()
320 }
321
322 pub fn tcp_signals(&self) -> Vec<&Signal> {
323 self.0
324 .values()
325 .filter(|s| s.tcp_channel.is_some())
326 .collect()
327 }
328
329 pub fn find_signals_like(&self, query: &str) -> Vec<&Signal> {
330 let query_lower = query.to_lowercase();
331 self.0
332 .values()
333 .filter(|s| s.name.to_lowercase().contains(&query_lower))
334 .collect()
335 }
336
337 pub fn get_by_name(&self, name: &str) -> Option<&Signal> {
339 self.get(name)
340 }
341
342 pub fn get_by_index(&self, index: u8) -> Option<&Signal> {
344 self.0.values().find(|signal| signal.index == index)
345 }
346}