Skip to main content

instrument_core/
dialect.rs

1// @generated by tools/gen-dialects.ts — do not edit by hand
2use 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
305/// Resolves the first dialect profile matching kind + optional IDN fields.
306pub 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    /// Formats a command template replacing `{name}` placeholders.
341    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}