instrument-core 0.1.1

VISA-agnostic transport, SCPI session, discovery, and mock fixtures for instrument control
Documentation
// @generated by tools/gen-dialects.ts — do not edit by hand
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,
    }
}

/// Resolves the first dialect profile matching kind + optional IDN fields.
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()
    }

    /// Formats a command template replacing `{name}` placeholders.
    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)
    }
}