1use crate::kind::InstrumentKind;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
6pub struct DialectProfile {
7 pub id: &'static str,
8 pub kind: InstrumentKind,
9 pub manufacturer_glob: &'static str,
10 pub model_glob: &'static str,
11 pub channels: u32,
12 pub commands: &'static [(&'static str, &'static str)],
13}
14
15pub static DIALECT_PROFILES: &[DialectProfile] = &[
16 DialectProfile {
17 id: "ci_dmm_dialect_wins",
18 kind: InstrumentKind::Dmm,
19 manufacturer_glob: "TestDialect*",
20 model_glob: "*",
21 channels: 1,
22 commands: &[
23 ("measure_voltage_dc", ":MEAS:VOLT:DC:TEST?"),
24 ("configure_voltage_dc", ":CONF:VOLT:DC {range}"),
25 ("read", ":FETC{channel}?"),
26 ],
27 },
28 DialectProfile {
29 id: "ci_psu_dialect_wins",
30 kind: InstrumentKind::DcPowerSupply,
31 manufacturer_glob: "TestDialect*",
32 model_glob: "*",
33 channels: 1,
34 commands: &[("set_voltage", ":VOLT {volts},(@{channel})")],
35 },
36 DialectProfile {
37 id: "keysight_sensor",
38 kind: InstrumentKind::PowerMeter,
39 manufacturer_glob: "Keysight*",
40 model_glob: "*U20*",
41 channels: 1,
42 commands: &[
43 ("unit", ":UNIT:POW {unit}"),
44 ("auto_range", ":SENS:POW:RANG:AUTO {state}"),
45 ("auto_average", ":SENS:AVER:COUN:AUTO {state}"),
46 ("correction_frequency", ":SENS:FREQ {hz}"),
47 ("offset", ":SENS:CORR:GAIN2 {db}"),
48 ("initiate", "INIT"),
49 ("fetch", "FETC?"),
50 ("read", "READ?"),
51 ("configure", ":CONF"),
52 ],
53 },
54 DialectProfile {
55 id: "keysight_xseries",
56 kind: InstrumentKind::SpectrumAnalyzer,
57 manufacturer_glob: "Keysight*",
58 model_glob: "N90*",
59 channels: 1,
60 commands: &[
61 ("center_frequency", ":FREQ:CENT {hz}"),
62 ("span", ":FREQ:SPAN {hz}"),
63 ("rbw", ":BAND {hz}"),
64 ("vbw", ":BAND:VID {hz}"),
65 ("ref_level", ":DISP:WIND:TRAC:Y:RLEV {dbm}"),
66 ("trace_data", ":TRAC:DATA? TRACE1"),
67 ("marker_peak", ":CALC:MARK:MAX"),
68 ("marker_x", ":CALC:MARK:X?"),
69 ("marker_y", ":CALC:MARK:Y?"),
70 ("sweep_continuous", ":INIT:CONT {state}"),
71 ("single_sweep", ":INIT:IMM"),
72 ("wait_opc", "*OPC?"),
73 ],
74 },
75 DialectProfile {
76 id: "rigol_dsa",
77 kind: InstrumentKind::SpectrumAnalyzer,
78 manufacturer_glob: "Rigol*",
79 model_glob: "DSA*",
80 channels: 1,
81 commands: &[
82 ("center_frequency", ":FREQ:CENT {hz}"),
83 ("span", ":FREQ:SPAN {hz}"),
84 ("rbw", ":BAND {hz}"),
85 ("vbw", ":BAND:VID {hz}"),
86 ("ref_level", ":DISP:WIND:TRAC:Y:RLEV {dbm}"),
87 ("trace_data", ":TRAC? TRACE1"),
88 ("marker_peak", ":CALC:MARK:MAX"),
89 ("marker_x", ":CALC:MARK:X?"),
90 ("marker_y", ":CALC:MARK:Y?"),
91 ("sweep_continuous", ":INIT:CONT {state}"),
92 ("single_sweep", ":INIT"),
93 ("wait_opc", "*OPC?"),
94 ],
95 },
96 DialectProfile {
97 id: "keithley_dmm6500",
98 kind: InstrumentKind::Dmm,
99 manufacturer_glob: "Keithley*",
100 model_glob: "*DMM6500*",
101 channels: 1,
102 commands: &[
103 ("measure_voltage_dc", ":SENS:FUNC \"VOLT:DC\";:READ?"),
104 ("measure_voltage_ac", ":SENS:FUNC \"VOLT:AC\";:READ?"),
105 ("measure_current_dc", ":SENS:FUNC \"CURR:DC\";:READ?"),
106 ("measure_current_ac", ":SENS:FUNC \"CURR:AC\";:READ?"),
107 ("measure_resistance_2w", ":SENS:FUNC \"RES\";:READ?"),
108 ("measure_resistance_4w", ":SENS:FUNC \"FRES\";:READ?"),
109 ("measure_temperature", ":SENS:FUNC \"TEMP\";:READ?"),
110 ],
111 },
112 DialectProfile {
113 id: "keysight_n6705c",
114 kind: InstrumentKind::DcPowerSupply,
115 manufacturer_glob: "*",
116 model_glob: "N6705*",
117 channels: 4,
118 commands: &[
119 ("set_voltage", ":VOLT {volts}, (@{channel})"),
120 ("set_current_limit", ":CURR {amps}, (@{channel})"),
121 ("output_enable", ":OUTP {state}, (@{channel})"),
122 ("output_state_query", ":OUTP? (@{channel})"),
123 ("read_voltage", ":MEAS:VOLT? (@{channel})"),
124 ("read_current", ":MEAS:CURR? (@{channel})"),
125 ("ovp_level", ":VOLT:PROT {volts}, (@{channel})"),
126 ("ovp_enable", ":VOLT:PROT:STAT {state}, (@{channel})"),
127 ("ovp_query", ":VOLT:PROT:STAT? (@{channel})"),
128 ],
129 },
130 DialectProfile {
131 id: "generic_dmm",
132 kind: InstrumentKind::Dmm,
133 manufacturer_glob: "*",
134 model_glob: "*",
135 channels: 1,
136 commands: &[
137 ("measure_voltage_dc", ":MEAS:VOLT:DC?"),
138 ("measure_voltage_ac", ":MEAS:VOLT:AC?"),
139 ("measure_current_dc", ":MEAS:CURR:DC?"),
140 ("measure_current_ac", ":MEAS:CURR:AC?"),
141 ("measure_resistance_2w", ":MEAS:RES?"),
142 ("measure_resistance_4w", ":MEAS:FRES?"),
143 ("measure_temperature", ":MEAS:TEMP?"),
144 ("configure_voltage_dc", ":CONF:VOLT:DC"),
145 ("initiate", "INIT"),
146 ("fetch", "FETC?"),
147 ("read", "READ?"),
148 ("software_trigger", "*TRG"),
149 ],
150 },
151 DialectProfile {
152 id: "generic_dcpwr",
153 kind: InstrumentKind::DcPowerSupply,
154 manufacturer_glob: "*",
155 model_glob: "*",
156 channels: 1,
157 commands: &[
158 ("set_voltage", ":SOUR{channel}:VOLT {volts}"),
159 ("set_current_limit", ":SOUR{channel}:CURR {amps}"),
160 ("output_enable", ":OUTP{channel} {state}"),
161 ("output_state_query", ":OUTP{channel}?"),
162 ("read_voltage", ":MEAS:VOLT? {channel}"),
163 ("read_current", ":MEAS:CURR? {channel}"),
164 ("ovp_level", ":SOUR{channel}:VOLT:PROT {volts}"),
165 ("ovp_enable", ":SOUR{channel}:VOLT:PROT:STAT {state}"),
166 ("ovp_query", ":SOUR{channel}:VOLT:PROT:STAT?"),
167 ("sense_enable", ":OUTP{channel}:SENS {state}"),
168 ],
169 },
170 DialectProfile {
171 id: "generic_fgen",
172 kind: InstrumentKind::FunctionGenerator,
173 manufacturer_glob: "*",
174 model_glob: "*",
175 channels: 1,
176 commands: &[
177 ("set_waveform", ":SOUR:FUNC {scpi_name}"),
178 ("set_frequency", ":SOUR:FREQ {hz}"),
179 ("set_amplitude", ":SOUR:VOLT {vpp}"),
180 ("set_offset", ":SOUR:VOLT:OFFS {volts}"),
181 ("set_duty_cycle", ":SOUR:FUNC:SQU:DCYC {percent}"),
182 ("set_load", ":OUTP:LOAD {ohms}"),
183 ("output_enable", ":OUTP {state}"),
184 ("burst_count", ":SOUR:BURS:NCYC {count}"),
185 ("burst_state", ":SOUR:BURS:STAT {state}"),
186 ("burst_trigger", ":TRIG:SOUR {source}"),
187 ],
188 },
189 DialectProfile {
190 id: "generic_scope",
191 kind: InstrumentKind::Oscilloscope,
192 manufacturer_glob: "*",
193 model_glob: "*",
194 channels: 4,
195 commands: &[
196 ("set_timebase_scale", ":TIMebase:SCALe {seconds_per_div}"),
197 (
198 "set_channel_scale",
199 ":CHANnel{channel}:SCALe {volts_per_div}",
200 ),
201 ("channel_display", ":CHANnel{channel}:DISP {state}"),
202 ("channel_coupling", ":CHANnel{channel}:COUP {coupling}"),
203 ("trigger_source", ":TRIGger:EDGE:SOURce {source}"),
204 ("trigger_level", ":TRIGger:EDGE:LEVel {volts}"),
205 ("trigger_slope", ":TRIGger:EDGE:SLOPe {slope}"),
206 ("single", ":SINGle"),
207 ("run", ":RUN"),
208 ("stop", ":STOP"),
209 ("measure_vpp", ":MEASure:VPP? CHAN{channel}"),
210 ("measure_frequency", ":MEASure:FREQuency? CHAN{channel}"),
211 ("waveform_source", ":WAVeform:SOURce CHAN{channel}"),
212 ("waveform_format_ascii", ":WAVeform:FORMat ASCii"),
213 ("waveform_preamble", ":WAVeform:PREamble?"),
214 ("waveform_data", ":WAVeform:DATA?"),
215 ],
216 },
217 DialectProfile {
218 id: "generic_switch",
219 kind: InstrumentKind::Switch,
220 manufacturer_glob: "*",
221 model_glob: "*",
222 channels: 1,
223 commands: &[
224 ("close_route", ":ROUTe:CLOS (@({ch1},{ch2}))"),
225 ("open_route", ":ROUTe:OPEN (@({ch1},{ch2}))"),
226 ("is_closed", ":ROUTe:CLOS? (@({ch1},{ch2}))"),
227 ("open_all", ":ROUTe:OPEN:ALL"),
228 ],
229 },
230 DialectProfile {
231 id: "generic_counter",
232 kind: InstrumentKind::Counter,
233 manufacturer_glob: "*",
234 model_glob: "*",
235 channels: 1,
236 commands: &[
237 ("measure_frequency", ":MEASure:FREQuency?"),
238 ("measure_period", ":MEASure:PERiod?"),
239 ("reset_totalize", ":COUNter:CRESet"),
240 ("read_totalize", ":COUNter:DATA?"),
241 ("gate_time", ":SENSe:FREQuency:APERture {seconds}"),
242 ("channel_select", ":SENSe:FUNCtion:ON \"FREQ {channel}\""),
243 ],
244 },
245 DialectProfile {
246 id: "generic_pwrmeter",
247 kind: InstrumentKind::PowerMeter,
248 manufacturer_glob: "*",
249 model_glob: "*",
250 channels: 1,
251 commands: &[
252 ("unit", ":UNIT:POW {unit}"),
253 ("auto_range", ":SENS:POW:RANG:AUTO {state}"),
254 ("auto_average", ":SENS:AVER:COUN:AUTO {state}"),
255 ("correction_frequency", ":SENS:FREQ {hz}"),
256 ("offset", ":SENS:CORR:GAIN2 {db}"),
257 ("initiate", "INIT"),
258 ("fetch", "FETC?"),
259 ("read", "READ?"),
260 ("configure", ":CONF"),
261 ],
262 },
263 DialectProfile {
264 id: "generic_specan",
265 kind: InstrumentKind::SpectrumAnalyzer,
266 manufacturer_glob: "*",
267 model_glob: "*",
268 channels: 1,
269 commands: &[
270 ("center_frequency", ":FREQ:CENT {hz}"),
271 ("span", ":FREQ:SPAN {hz}"),
272 ("rbw", ":BAND {hz}"),
273 ("vbw", ":BAND:VID {hz}"),
274 ("ref_level", ":DISP:WIND:TRAC:Y:RLEV {dbm}"),
275 ("trace_data", ":TRAC:DATA? TRACE1"),
276 ("marker_peak", ":CALC:MARK:MAX"),
277 ("marker_x", ":CALC:MARK:X?"),
278 ("marker_y", ":CALC:MARK:Y?"),
279 ("sweep_continuous", ":INIT:CONT {state}"),
280 ("single_sweep", ":INIT:IMM"),
281 ("wait_opc", "*OPC?"),
282 ],
283 },
284];
285
286fn glob_match(pat: &str, value: &str) -> bool {
287 let value = value.to_ascii_lowercase();
288 let pat = pat.to_ascii_lowercase();
289 if pat == "*" {
290 return true;
291 }
292 let starts = pat.starts_with('*');
293 let ends = pat.ends_with('*');
294 match (starts, ends) {
295 (true, true) => {
296 let inner = &pat[1..pat.len() - 1];
297 inner.is_empty() || value.contains(inner)
298 }
299 (false, true) => value.starts_with(&pat[..pat.len() - 1]),
300 (true, false) => value.ends_with(&pat[1..]),
301 (false, false) => value == pat,
302 }
303}
304
305pub fn resolve_dialect(
307 kind: InstrumentKind,
308 manufacturer: Option<&str>,
309 model: Option<&str>,
310) -> &'static DialectProfile {
311 let mfr = manufacturer.unwrap_or("");
312 let model = model.unwrap_or("");
313 for profile in DIALECT_PROFILES {
314 if profile.kind != kind {
315 continue;
316 }
317 if glob_match(profile.manufacturer_glob, mfr) && glob_match(profile.model_glob, model) {
318 return profile;
319 }
320 }
321 DIALECT_PROFILES
322 .iter()
323 .find(|p| p.kind == kind && p.id.starts_with("generic_"))
324 .or_else(|| DIALECT_PROFILES.iter().find(|p| p.kind == kind))
325 .expect("no dialect profile for kind")
326}
327
328impl DialectProfile {
329 pub fn command(&self, key: &str) -> Option<&'static str> {
330 self.commands
331 .iter()
332 .find(|(k, _)| *k == key)
333 .map(|(_, v)| *v)
334 }
335
336 pub fn command_map(&self) -> HashMap<&'static str, &'static str> {
337 self.commands.iter().copied().collect()
338 }
339
340 pub fn format_command(&self, key: &str, vars: &[(&str, String)]) -> Option<String> {
342 let mut tmpl = self.command(key)?.to_string();
343 for (name, value) in vars {
344 tmpl = tmpl.replace(&format!("{{{name}}}"), value);
345 }
346 Some(tmpl)
347 }
348}