1use crate::classes::{
2 Counter, DcPowerSupply, Dmm, FunctionGenerator, Oscilloscope, PowerMeter, SpectrumAnalyzer,
3 Switch,
4};
5use crate::device::DeviceRef;
6use crate::mock_backend::MockSessionOpener;
7use instrument_core::address::ResourceAddress;
8use instrument_core::classifier::{classify_from_address, merge_classifications};
9use instrument_core::connect::ConnectOptions;
10use instrument_core::diagnostics::{CommsObserver, DeviceHealth};
11use instrument_core::error::Result;
12use instrument_core::identity::DeviceId;
13use instrument_core::kind::InstrumentKind;
14use instrument_core::mock::{mock_address, ScriptedFixture};
15use instrument_core::registry::ModelRegistry;
16use instrument_core::session::SessionOpener;
17use instrument_core::DiscoveredDevice;
18use std::collections::HashMap;
19use std::sync::{Arc, Mutex};
20
21pub struct DeviceCatalog {
23 opener: Arc<dyn SessionOpener>,
24 devices: Vec<DiscoveredDevice>,
25 by_address: HashMap<String, usize>,
26 by_device_id: HashMap<String, usize>,
27 connect_options: ConnectOptions,
28 health_registry: HashMap<String, Arc<Mutex<DeviceHealth>>>,
29 observer: Option<Arc<dyn CommsObserver>>,
30}
31
32impl DeviceCatalog {
33 pub fn from_devices(opener: Arc<dyn SessionOpener>, devices: Vec<DiscoveredDevice>) -> Self {
34 Self::from_devices_with_observer(opener, devices, None)
35 }
36
37 pub fn from_devices_with_observer(
38 opener: Arc<dyn SessionOpener>,
39 devices: Vec<DiscoveredDevice>,
40 observer: Option<Arc<dyn CommsObserver>>,
41 ) -> Self {
42 let mut by_address = HashMap::new();
43 let mut by_device_id = HashMap::new();
44 let mut health_registry = HashMap::new();
45
46 for (idx, dev) in devices.iter().enumerate() {
47 by_address.insert(dev.address.raw.clone(), idx);
48 by_device_id.insert(dev.device_id().0.clone(), idx);
49 health_registry.insert(
50 dev.address.raw.clone(),
51 Arc::new(Mutex::new(DeviceHealth::default())),
52 );
53 }
54
55 Self {
56 opener,
57 devices,
58 by_address,
59 by_device_id,
60 connect_options: ConnectOptions::default(),
61 health_registry,
62 observer,
63 }
64 }
65
66 pub fn with_connect_options(mut self, opts: ConnectOptions) -> Self {
68 self.connect_options = opts;
69 self
70 }
71
72 pub fn connect_options(&self) -> &ConnectOptions {
73 &self.connect_options
74 }
75
76 pub fn from_fixture(address: &str, fixture: ScriptedFixture) -> Result<Self> {
77 let addr = if address.starts_with("mock://") {
78 ResourceAddress::parse(address)?
79 } else {
80 mock_address(address)?
81 };
82
83 let idn = fixture.idn().clone();
84 let kinds: Vec<_> = fixture.kinds().to_vec();
85 let transport = fixture.into_transport();
86 let opener = MockSessionOpener::new();
87 opener.register(&addr.raw, transport);
88
89 let (identity, layer1) = classify_from_address(&addr, &ModelRegistry::embedded());
90 let mut id = identity;
91 id.manufacturer = Some(idn.manufacturer.clone());
92 id.model = Some(idn.model.clone());
93 id.serial = Some(idn.serial.clone());
94 id.firmware = Some(idn.firmware.clone());
95
96 let (supported_kinds, classification) = merge_classifications(
97 vec![layer1],
98 if kinds.is_empty() {
99 None
100 } else {
101 Some(kinds.as_slice())
102 },
103 );
104
105 let device = DiscoveredDevice {
106 address: addr.clone(),
107 identity: id,
108 supported_kinds,
109 classification,
110 reachable: true,
111 error: None,
112 };
113
114 Ok(Self::from_devices(Arc::new(opener), vec![device]))
115 }
116
117 pub fn devices(&self) -> &[DiscoveredDevice] {
118 &self.devices
119 }
120
121 pub fn devices_by_kind(&self, kind: InstrumentKind) -> Vec<&DiscoveredDevice> {
122 self.devices
123 .iter()
124 .filter(|d| d.supported_kinds.contains(&kind))
125 .collect()
126 }
127
128 pub fn device(&self, address: &str) -> Result<DeviceRef> {
129 let idx = self.by_address.get(address).copied().ok_or_else(|| {
130 instrument_core::Error::DeviceNotFound {
131 address: address.to_string(),
132 }
133 })?;
134 Ok(self.device_at(idx))
135 }
136
137 pub fn device_by_id(&self, id: &DeviceId) -> Result<DeviceRef> {
138 let idx = self.by_device_id.get(&id.0).copied().ok_or_else(|| {
139 instrument_core::Error::DeviceNotFound {
140 address: id.0.clone(),
141 }
142 })?;
143 Ok(self.device_at(idx))
144 }
145
146 pub fn reconnect_by_identity(&self, id: &DeviceId) -> Result<DeviceRef> {
148 self.device_by_id(id)
149 }
150
151 pub fn health(&self, address: &str) -> Result<DeviceHealth> {
152 let health = self.health_registry.get(address).ok_or_else(|| {
153 instrument_core::Error::DeviceNotFound {
154 address: address.to_string(),
155 }
156 })?;
157 Ok(health.lock().unwrap().clone())
158 }
159
160 fn device_at(&self, idx: usize) -> DeviceRef {
161 let dev = &self.devices[idx];
162 let health = self
163 .health_registry
164 .get(&dev.address.raw)
165 .cloned()
166 .unwrap_or_else(|| Arc::new(Mutex::new(DeviceHealth::default())));
167 DeviceRef {
168 device: dev.clone(),
169 opener: self.opener.clone(),
170 connect_options: self.connect_options.clone(),
171 health,
172 observer: self.observer.clone(),
173 }
174 }
175
176 pub fn open_dmm(&self, address: &str) -> Result<Dmm> {
177 self.device(address)?.open_dmm()
178 }
179
180 pub fn open_dc_power_supply(&self, address: &str) -> Result<DcPowerSupply> {
181 self.device(address)?.open_dc_power_supply()
182 }
183
184 pub fn open_function_generator(&self, address: &str) -> Result<FunctionGenerator> {
185 self.device(address)?.open_function_generator()
186 }
187
188 pub fn open_oscilloscope(&self, address: &str) -> Result<Oscilloscope> {
189 self.device(address)?.open_oscilloscope()
190 }
191
192 pub fn open_switch(&self, address: &str) -> Result<Switch> {
193 self.device(address)?.open_switch()
194 }
195
196 pub fn open_counter(&self, address: &str) -> Result<Counter> {
197 self.device(address)?.open_counter()
198 }
199
200 pub fn open_power_meter(&self, address: &str) -> Result<PowerMeter> {
201 self.device(address)?.open_power_meter()
202 }
203
204 pub fn open_spectrum_analyzer(&self, address: &str) -> Result<SpectrumAnalyzer> {
205 self.device(address)?.open_spectrum_analyzer()
206 }
207
208 pub fn print_summary(&self) {
209 for dev in &self.devices {
210 println!(
211 "{} ({}) @ {} — kinds: {:?} reachable: {}",
212 dev.identity.model.as_deref().unwrap_or("?"),
213 dev.device_id(),
214 dev.address.raw,
215 dev.supported_kinds,
216 dev.reachable
217 );
218 }
219 }
220}