visa-api 0.3.2

A collection of common VISA commands and a high-level interface to create instrument libraries.
Documentation
use std::fmt::Display;

use thiserror::Error;
use visa_rs::{flags::AccessMode, AsResourceManager, DefaultRM, TIMEOUT_IMMEDIATE};
pub mod prelude;

#[derive(Debug, Error)]
pub enum Error {
    #[error(transparent)]
    StdIo(#[from] std::io::Error),
    #[error(transparent)]
    VisaRs(#[from] visa_rs::Error),
    #[error("No instrument was found")]
    NoInstrumentFound(),
}

pub type Result<T> = core::result::Result<T, Error>;

#[derive(Debug, PartialEq, strum::AsRefStr, strum::Display)]
pub enum Commands {
    #[strum(serialize = "CLS")]
    ClearStatus,
    #[strum(serialize = "*ESE")]
    EventStatusEnable,
    #[strum(serialize = "*ESE?")]
    EventStatusEnableQuery,
    #[strum(serialize = "*ESR?")]
    EventStatusEnableRegister,
    #[strum(serialize = "*IDN?")]
    Identify,
    #[strum(serialize = "*OPC")]
    OperationCompleteCommand,
    #[strum(serialize = "*OPC?")]
    OperationCompleteQuery,
    #[strum(serialize = "*OPT?")]
    IdentifyOptionsQuery,
    #[strum(serialize = "*RST")]
    Reset,
    #[strum(serialize = "*SRE")]
    ServiceRequestEnable,
    #[strum(serialize = "*SRE?")]
    ServiceRequestEnableQuery,
    #[strum(serialize = "*STB?")]
    StatusByteQuery,
    #[strum(serialize = "*TST?")]
    ResultOfSelfTestQuery,
    #[strum(serialize = "*WAI")]
    Wait,
}

#[derive(Clone, Debug)]
pub struct Idn {
    pub manufacturer: String,
    pub model: String,
    pub serial_number: String,
    pub software_version: String,
}

pub fn find_resources(rm: &visa_rs::DefaultRM) -> Result<Vec<visa_rs::VisaString>> {
    let expr = visa_rs::VisaString::from(
        std::ffi::CString::new("?*INSTR").expect("Failed to create C compatible String."),
    );
    let mut visa_rs_reslist = rm.find_res_list(&expr)?;
    let mut res_exhausted = false;
    let mut res: Vec<visa_rs::VisaString> = vec![];
    while !res_exhausted {
        let visa_rs_res = visa_rs_reslist.find_next()?;
        if let Some(visa_rs_res) = visa_rs_res {
            res.push(visa_rs_res);
        } else {
            res_exhausted = true;
        };
    }
    Ok(res)
}

fn get_idn(instrument: &mut visa_rs::Instrument) -> Result<Idn> {
    std::io::Write::write_all(instrument, Commands::Identify.as_ref().as_bytes())?;
    let mut r = std::io::BufReader::new(instrument);
    let mut buf = String::new();
    std::io::BufRead::read_line(&mut r, &mut buf)?;

    let buf = buf
        .split(',')
        .map(|s| s.trim().to_string())
        .collect::<Vec<String>>();
    Ok(Idn {
        manufacturer: buf[0].to_owned(),
        model: buf[1].to_owned(),
        serial_number: buf[2].to_owned(),
        software_version: buf[3].to_owned(),
    })
}

pub trait Visa {
    fn into_inner(&self) -> &visa_rs::Instrument;

    fn find_instrument<T>(rm: &DefaultRM, manufacturer: T, model: T) -> Result<visa_rs::Instrument>
    where
        T: Into<String> + Display,
    {
        let resources = find_resources(rm)?;
        for resource in resources {
            let mut instrument = rm.open(&resource, AccessMode::NO_LOCK, TIMEOUT_IMMEDIATE)?;
            let idn = get_idn(&mut instrument).unwrap();
            let manufacturer_and_model = format!("{} {}", idn.manufacturer, idn.model);
            if manufacturer_and_model.contains(&format!("{} {}", manufacturer, model)) {
                return Ok(instrument);
            }
        }
        Err(Error::NoInstrumentFound())
    }

    fn write<T>(&mut self, cmd: T) -> Result<()>
    where
        T: Into<String>,
    {
        let cmd: String = cmd.into();
        std::io::Write::write_all(&mut self.into_inner(), cmd.as_bytes())?;
        Ok(())
    }

    fn read(&self) -> Result<String> {
        let mut buf = String::new();
        std::io::Read::read_to_string(&mut self.into_inner(), &mut buf)?;
        Ok(buf)
    }

    fn get_idn(&mut self) -> Result<Idn> {
        self.write(Commands::Identify.as_ref())?;
        let buf = self.read()?;
        let buf = buf
            .split(',')
            .map(|s| s.trim().to_string())
            .collect::<Vec<String>>();
        Ok(Idn {
            manufacturer: buf[0].to_owned(),
            model: buf[1].to_owned(),
            serial_number: buf[2].to_owned(),
            software_version: buf[3].to_owned(),
        })
    }

    fn reset(&mut self) -> Result<()> {
        self.write(Commands::Reset.as_ref())?;
        Ok(())
    }
}