instrument_core/
address.rs1use crate::error::{Error, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::hash_map::DefaultHasher;
4use std::hash::{Hash, Hasher};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
8#[non_exhaustive]
9pub enum InterfaceKind {
10 Usb,
11 Gpib,
12 Tcpip,
13 Serial,
14 Vxi,
15 Pxi,
16 #[default]
17 Unknown,
18}
19
20#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
22pub struct AddressParts {
23 pub board: Option<u32>,
24 pub primary_address: Option<u32>,
25 pub secondary_address: Option<u32>,
26 pub vid: Option<String>,
27 pub pid: Option<String>,
28 pub serial: Option<String>,
29 pub host: Option<String>,
30 pub port: Option<u16>,
31 pub lane: Option<String>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct ResourceAddress {
37 pub interface: InterfaceKind,
38 pub raw: String,
39 pub components: AddressParts,
40 dedup_key: u64,
41}
42
43impl ResourceAddress {
44 pub fn parse(raw: &str) -> Result<Self> {
46 let trimmed = raw.trim();
47 if trimmed.is_empty() {
48 return Err(Error::InvalidAddress("empty address".into()));
49 }
50
51 let upper = trimmed.to_uppercase();
52 let (interface, components) = if upper.starts_with("USB") {
53 parse_usb(trimmed)?
54 } else if upper.starts_with("GPIB") {
55 parse_gpib(trimmed)?
56 } else if upper.starts_with("TCPIP") || upper.starts_with("TCP") {
57 parse_tcpip(trimmed)?
58 } else if upper.starts_with("ASRL") {
59 parse_serial(trimmed)?
60 } else if upper.starts_with("VXI") {
61 (InterfaceKind::Vxi, AddressParts::default())
62 } else if upper.starts_with("PXI") {
63 (InterfaceKind::Pxi, AddressParts::default())
64 } else {
65 (InterfaceKind::Unknown, AddressParts::default())
66 };
67
68 let mut addr = Self {
69 interface,
70 raw: trimmed.to_string(),
71 components,
72 dedup_key: 0,
73 };
74 addr.dedup_key = addr.compute_dedup_key();
75 Ok(addr)
76 }
77
78 pub fn dedup_key(&self) -> u64 {
80 self.dedup_key
81 }
82
83 fn compute_dedup_key(&self) -> u64 {
84 let mut hasher = DefaultHasher::new();
85 self.interface.hash(&mut hasher);
86 self.raw.to_uppercase().hash(&mut hasher);
87 hasher.finish()
88 }
89}
90
91fn parse_usb(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
92 let parts: Vec<&str> = raw.split("::").collect();
94 let vid = parts.get(1).map(|s| normalize_hex_id(s));
95 let pid = parts.get(2).map(|s| normalize_hex_id(s));
96 let serial = parts.get(3).map(|s| s.to_string());
97 Ok((
98 InterfaceKind::Usb,
99 AddressParts {
100 vid,
101 pid,
102 serial,
103 ..Default::default()
104 },
105 ))
106}
107
108fn parse_gpib(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
109 let parts: Vec<&str> = raw.split("::").collect();
110 let board = parts
111 .first()
112 .and_then(|s| s.strip_prefix("GPIB"))
113 .and_then(|s| s.parse().ok());
114 let primary_address = parts.get(1).and_then(|s| s.parse().ok());
115 let secondary_address = parts.get(2).and_then(|s| s.parse().ok());
116 Ok((
117 InterfaceKind::Gpib,
118 AddressParts {
119 board,
120 primary_address,
121 secondary_address,
122 ..Default::default()
123 },
124 ))
125}
126
127fn parse_tcpip(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
128 let parts: Vec<&str> = raw.split("::").collect();
129 let host = parts.get(1).map(|s| s.to_string());
130 let port = parts.get(3).and_then(|s| s.parse().ok());
131 Ok((
132 InterfaceKind::Tcpip,
133 AddressParts {
134 host,
135 port,
136 ..Default::default()
137 },
138 ))
139}
140
141fn parse_serial(raw: &str) -> Result<(InterfaceKind, AddressParts)> {
142 let board = raw
143 .split("::")
144 .next()
145 .and_then(|s| s.strip_prefix("ASRL"))
146 .and_then(|s| s.parse().ok());
147 Ok((
148 InterfaceKind::Serial,
149 AddressParts {
150 board,
151 ..Default::default()
152 },
153 ))
154}
155
156fn normalize_hex_id(s: &str) -> String {
157 s.trim()
158 .trim_start_matches("0x")
159 .trim_start_matches("0X")
160 .to_uppercase()
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn parses_usb_address() {
169 let addr = ResourceAddress::parse("USB0::0x0957::0x0607::MY123::INSTR").unwrap();
170 assert_eq!(addr.interface, InterfaceKind::Usb);
171 assert_eq!(addr.components.vid.as_deref(), Some("0957"));
172 assert_eq!(addr.components.pid.as_deref(), Some("0607"));
173 }
174
175 #[test]
176 fn dedup_is_case_insensitive() {
177 let a = ResourceAddress::parse("usb0::0x0957::0x0607::SN::INSTR").unwrap();
178 let b = ResourceAddress::parse("USB0::0x0957::0x0607::SN::INSTR").unwrap();
179 assert_eq!(a.dedup_key(), b.dedup_key());
180 }
181}