use crate::kind::InstrumentKind;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct DialectProfile {
pub id: &'static str,
pub kind: InstrumentKind,
pub manufacturer_glob: &'static str,
pub model_glob: &'static str,
pub channels: u32,
pub commands: &'static [(&'static str, &'static str)],
}
pub static DIALECT_PROFILES: &[DialectProfile] = &[
DialectProfile {
id: "ci_dmm_dialect_wins",
kind: InstrumentKind::Dmm,
manufacturer_glob: "TestDialect*",
model_glob: "*",
channels: 1,
commands: &[
("measure_voltage_dc", ":MEAS:VOLT:DC:TEST?"),
("configure_voltage_dc", ":CONF:VOLT:DC {range}"),
("read", ":FETC{channel}?"),
],
},
DialectProfile {
id: "ci_psu_dialect_wins",
kind: InstrumentKind::DcPowerSupply,
manufacturer_glob: "TestDialect*",
model_glob: "*",
channels: 1,
commands: &[("set_voltage", ":VOLT {volts},(@{channel})")],
},
DialectProfile {
id: "keysight_sensor",
kind: InstrumentKind::PowerMeter,
manufacturer_glob: "Keysight*",
model_glob: "*U20*",
channels: 1,
commands: &[
("unit", ":UNIT:POW {unit}"),
("auto_range", ":SENS:POW:RANG:AUTO {state}"),
("auto_average", ":SENS:AVER:COUN:AUTO {state}"),
("correction_frequency", ":SENS:FREQ {hz}"),
("offset", ":SENS:CORR:GAIN2 {db}"),
("initiate", "INIT"),
("fetch", "FETC?"),
("read", "READ?"),
("configure", ":CONF"),
],
},
DialectProfile {
id: "keysight_xseries",
kind: InstrumentKind::SpectrumAnalyzer,
manufacturer_glob: "Keysight*",
model_glob: "N90*",
channels: 1,
commands: &[
("center_frequency", ":FREQ:CENT {hz}"),
("span", ":FREQ:SPAN {hz}"),
("rbw", ":BAND {hz}"),
("vbw", ":BAND:VID {hz}"),
("ref_level", ":DISP:WIND:TRAC:Y:RLEV {dbm}"),
("trace_data", ":TRAC:DATA? TRACE1"),
("marker_peak", ":CALC:MARK:MAX"),
("marker_x", ":CALC:MARK:X?"),
("marker_y", ":CALC:MARK:Y?"),
("sweep_continuous", ":INIT:CONT {state}"),
("single_sweep", ":INIT:IMM"),
("wait_opc", "*OPC?"),
],
},
DialectProfile {
id: "rigol_dsa",
kind: InstrumentKind::SpectrumAnalyzer,
manufacturer_glob: "Rigol*",
model_glob: "DSA*",
channels: 1,
commands: &[
("center_frequency", ":FREQ:CENT {hz}"),
("span", ":FREQ:SPAN {hz}"),
("rbw", ":BAND {hz}"),
("vbw", ":BAND:VID {hz}"),
("ref_level", ":DISP:WIND:TRAC:Y:RLEV {dbm}"),
("trace_data", ":TRAC? TRACE1"),
("marker_peak", ":CALC:MARK:MAX"),
("marker_x", ":CALC:MARK:X?"),
("marker_y", ":CALC:MARK:Y?"),
("sweep_continuous", ":INIT:CONT {state}"),
("single_sweep", ":INIT"),
("wait_opc", "*OPC?"),
],
},
DialectProfile {
id: "keithley_dmm6500",
kind: InstrumentKind::Dmm,
manufacturer_glob: "Keithley*",
model_glob: "*DMM6500*",
channels: 1,
commands: &[
("measure_voltage_dc", ":SENS:FUNC \"VOLT:DC\";:READ?"),
("measure_voltage_ac", ":SENS:FUNC \"VOLT:AC\";:READ?"),
("measure_current_dc", ":SENS:FUNC \"CURR:DC\";:READ?"),
("measure_current_ac", ":SENS:FUNC \"CURR:AC\";:READ?"),
("measure_resistance_2w", ":SENS:FUNC \"RES\";:READ?"),
("measure_resistance_4w", ":SENS:FUNC \"FRES\";:READ?"),
("measure_temperature", ":SENS:FUNC \"TEMP\";:READ?"),
],
},
DialectProfile {
id: "keysight_n6705c",
kind: InstrumentKind::DcPowerSupply,
manufacturer_glob: "*",
model_glob: "N6705*",
channels: 4,
commands: &[
("set_voltage", ":VOLT {volts}, (@{channel})"),
("set_current_limit", ":CURR {amps}, (@{channel})"),
("output_enable", ":OUTP {state}, (@{channel})"),
("output_state_query", ":OUTP? (@{channel})"),
("read_voltage", ":MEAS:VOLT? (@{channel})"),
("read_current", ":MEAS:CURR? (@{channel})"),
("ovp_level", ":VOLT:PROT {volts}, (@{channel})"),
("ovp_enable", ":VOLT:PROT:STAT {state}, (@{channel})"),
("ovp_query", ":VOLT:PROT:STAT? (@{channel})"),
],
},
DialectProfile {
id: "generic_dmm",
kind: InstrumentKind::Dmm,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("measure_voltage_dc", ":MEAS:VOLT:DC?"),
("measure_voltage_ac", ":MEAS:VOLT:AC?"),
("measure_current_dc", ":MEAS:CURR:DC?"),
("measure_current_ac", ":MEAS:CURR:AC?"),
("measure_resistance_2w", ":MEAS:RES?"),
("measure_resistance_4w", ":MEAS:FRES?"),
("measure_temperature", ":MEAS:TEMP?"),
("configure_voltage_dc", ":CONF:VOLT:DC"),
("initiate", "INIT"),
("fetch", "FETC?"),
("read", "READ?"),
("software_trigger", "*TRG"),
],
},
DialectProfile {
id: "generic_dcpwr",
kind: InstrumentKind::DcPowerSupply,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("set_voltage", ":SOUR{channel}:VOLT {volts}"),
("set_current_limit", ":SOUR{channel}:CURR {amps}"),
("output_enable", ":OUTP{channel} {state}"),
("output_state_query", ":OUTP{channel}?"),
("read_voltage", ":MEAS:VOLT? {channel}"),
("read_current", ":MEAS:CURR? {channel}"),
("ovp_level", ":SOUR{channel}:VOLT:PROT {volts}"),
("ovp_enable", ":SOUR{channel}:VOLT:PROT:STAT {state}"),
("ovp_query", ":SOUR{channel}:VOLT:PROT:STAT?"),
("sense_enable", ":OUTP{channel}:SENS {state}"),
],
},
DialectProfile {
id: "generic_fgen",
kind: InstrumentKind::FunctionGenerator,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("set_waveform", ":SOUR:FUNC {scpi_name}"),
("set_frequency", ":SOUR:FREQ {hz}"),
("set_amplitude", ":SOUR:VOLT {vpp}"),
("set_offset", ":SOUR:VOLT:OFFS {volts}"),
("set_duty_cycle", ":SOUR:FUNC:SQU:DCYC {percent}"),
("set_load", ":OUTP:LOAD {ohms}"),
("output_enable", ":OUTP {state}"),
("burst_count", ":SOUR:BURS:NCYC {count}"),
("burst_state", ":SOUR:BURS:STAT {state}"),
("burst_trigger", ":TRIG:SOUR {source}"),
],
},
DialectProfile {
id: "generic_scope",
kind: InstrumentKind::Oscilloscope,
manufacturer_glob: "*",
model_glob: "*",
channels: 4,
commands: &[
("set_timebase_scale", ":TIMebase:SCALe {seconds_per_div}"),
(
"set_channel_scale",
":CHANnel{channel}:SCALe {volts_per_div}",
),
("channel_display", ":CHANnel{channel}:DISP {state}"),
("channel_coupling", ":CHANnel{channel}:COUP {coupling}"),
("trigger_source", ":TRIGger:EDGE:SOURce {source}"),
("trigger_level", ":TRIGger:EDGE:LEVel {volts}"),
("trigger_slope", ":TRIGger:EDGE:SLOPe {slope}"),
("single", ":SINGle"),
("run", ":RUN"),
("stop", ":STOP"),
("measure_vpp", ":MEASure:VPP? CHAN{channel}"),
("measure_frequency", ":MEASure:FREQuency? CHAN{channel}"),
("waveform_source", ":WAVeform:SOURce CHAN{channel}"),
("waveform_format_ascii", ":WAVeform:FORMat ASCii"),
("waveform_preamble", ":WAVeform:PREamble?"),
("waveform_data", ":WAVeform:DATA?"),
],
},
DialectProfile {
id: "generic_switch",
kind: InstrumentKind::Switch,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("close_route", ":ROUTe:CLOS (@({ch1},{ch2}))"),
("open_route", ":ROUTe:OPEN (@({ch1},{ch2}))"),
("is_closed", ":ROUTe:CLOS? (@({ch1},{ch2}))"),
("open_all", ":ROUTe:OPEN:ALL"),
],
},
DialectProfile {
id: "generic_counter",
kind: InstrumentKind::Counter,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("measure_frequency", ":MEASure:FREQuency?"),
("measure_period", ":MEASure:PERiod?"),
("reset_totalize", ":COUNter:CRESet"),
("read_totalize", ":COUNter:DATA?"),
("gate_time", ":SENSe:FREQuency:APERture {seconds}"),
("channel_select", ":SENSe:FUNCtion:ON \"FREQ {channel}\""),
],
},
DialectProfile {
id: "generic_pwrmeter",
kind: InstrumentKind::PowerMeter,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("unit", ":UNIT:POW {unit}"),
("auto_range", ":SENS:POW:RANG:AUTO {state}"),
("auto_average", ":SENS:AVER:COUN:AUTO {state}"),
("correction_frequency", ":SENS:FREQ {hz}"),
("offset", ":SENS:CORR:GAIN2 {db}"),
("initiate", "INIT"),
("fetch", "FETC?"),
("read", "READ?"),
("configure", ":CONF"),
],
},
DialectProfile {
id: "generic_specan",
kind: InstrumentKind::SpectrumAnalyzer,
manufacturer_glob: "*",
model_glob: "*",
channels: 1,
commands: &[
("center_frequency", ":FREQ:CENT {hz}"),
("span", ":FREQ:SPAN {hz}"),
("rbw", ":BAND {hz}"),
("vbw", ":BAND:VID {hz}"),
("ref_level", ":DISP:WIND:TRAC:Y:RLEV {dbm}"),
("trace_data", ":TRAC:DATA? TRACE1"),
("marker_peak", ":CALC:MARK:MAX"),
("marker_x", ":CALC:MARK:X?"),
("marker_y", ":CALC:MARK:Y?"),
("sweep_continuous", ":INIT:CONT {state}"),
("single_sweep", ":INIT:IMM"),
("wait_opc", "*OPC?"),
],
},
];
fn glob_match(pat: &str, value: &str) -> bool {
let value = value.to_ascii_lowercase();
let pat = pat.to_ascii_lowercase();
if pat == "*" {
return true;
}
let starts = pat.starts_with('*');
let ends = pat.ends_with('*');
match (starts, ends) {
(true, true) => {
let inner = &pat[1..pat.len() - 1];
inner.is_empty() || value.contains(inner)
}
(false, true) => value.starts_with(&pat[..pat.len() - 1]),
(true, false) => value.ends_with(&pat[1..]),
(false, false) => value == pat,
}
}
pub fn resolve_dialect(
kind: InstrumentKind,
manufacturer: Option<&str>,
model: Option<&str>,
) -> &'static DialectProfile {
let mfr = manufacturer.unwrap_or("");
let model = model.unwrap_or("");
for profile in DIALECT_PROFILES {
if profile.kind != kind {
continue;
}
if glob_match(profile.manufacturer_glob, mfr) && glob_match(profile.model_glob, model) {
return profile;
}
}
DIALECT_PROFILES
.iter()
.find(|p| p.kind == kind && p.id.starts_with("generic_"))
.or_else(|| DIALECT_PROFILES.iter().find(|p| p.kind == kind))
.expect("no dialect profile for kind")
}
impl DialectProfile {
pub fn command(&self, key: &str) -> Option<&'static str> {
self.commands
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| *v)
}
pub fn command_map(&self) -> HashMap<&'static str, &'static str> {
self.commands.iter().copied().collect()
}
pub fn format_command(&self, key: &str, vars: &[(&str, String)]) -> Option<String> {
let mut tmpl = self.command(key)?.to_string();
for (name, value) in vars {
tmpl = tmpl.replace(&format!("{{{name}}}"), value);
}
Some(tmpl)
}
}