Skip to main content

instrument_core/
identity.rs

1use serde::{Deserialize, Serialize};
2
3/// Stable device identity for replacement and reconnection workflows.
4#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub struct DeviceId(pub String);
6
7impl DeviceId {
8    /// Builds an ID from manufacturer, model, and serial, falling back to the VISA address.
9    pub fn from_identity(identity: &DeviceIdentity, address: &str) -> Self {
10        if let (Some(m), Some(model), Some(serial)) = (
11            identity.manufacturer.as_deref(),
12            identity.model.as_deref(),
13            identity.serial.as_deref(),
14        ) {
15            if !serial.is_empty() {
16                return Self(format!("{m}|{model}|{serial}"));
17            }
18        }
19        Self(address.to_string())
20    }
21}
22
23impl std::fmt::Display for DeviceId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        f.write_str(&self.0)
26    }
27}
28
29/// Parsed `*IDN?` response.
30#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
31pub struct Idn {
32    pub manufacturer: String,
33    pub model: String,
34    pub serial: String,
35    pub firmware: String,
36}
37
38impl Idn {
39    /// Parses a comma-separated `*IDN?` response.
40    pub fn parse(response: &str) -> Self {
41        let trimmed = response.trim();
42        let parts: Vec<&str> = trimmed.splitn(4, ',').map(str::trim).collect();
43        Self {
44            manufacturer: parts.first().copied().unwrap_or("").to_string(),
45            model: parts.get(1).copied().unwrap_or("").to_string(),
46            serial: parts.get(2).copied().unwrap_or("").to_string(),
47            firmware: parts.get(3).copied().unwrap_or("").to_string(),
48        }
49    }
50
51    pub fn format_response(&self) -> String {
52        format!(
53            "{},{},{},{}",
54            self.manufacturer, self.model, self.serial, self.firmware
55        )
56    }
57}
58
59/// Merged device identity from all classification layers.
60#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
61pub struct DeviceIdentity {
62    pub manufacturer: Option<String>,
63    pub model: Option<String>,
64    pub serial: Option<String>,
65    pub firmware: Option<String>,
66    pub options: Option<String>,
67}
68
69impl DeviceIdentity {
70    pub fn from_idn(idn: &Idn) -> Self {
71        Self {
72            manufacturer: Some(idn.manufacturer.clone()),
73            model: Some(idn.model.clone()),
74            serial: Some(idn.serial.clone()),
75            firmware: Some(idn.firmware.clone()),
76            options: None,
77        }
78    }
79
80    pub fn merge(&mut self, other: &Self) {
81        if self.manufacturer.is_none() {
82            self.manufacturer = other.manufacturer.clone();
83        }
84        if self.model.is_none() {
85            self.model = other.model.clone();
86        }
87        if self.serial.is_none() {
88            self.serial = other.serial.clone();
89        }
90        if self.firmware.is_none() {
91            self.firmware = other.firmware.clone();
92        }
93        if self.options.is_none() {
94            self.options = other.options.clone();
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn parses_idn() {
105        let idn = Idn::parse("Keysight Technologies,34401A,MY123,1.0\n");
106        assert_eq!(idn.manufacturer, "Keysight Technologies");
107        assert_eq!(idn.model, "34401A");
108    }
109}