1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! # VISA API Crate
//!
//! `visa-api` is a collection of common VISA commands and a high-level
//! interface to create instrument libraries.

use thiserror::Error;
use visa_rs::{io_to_vs_err, AsResourceManager};

pub use visa_rs::{DefaultRM, Instrument};

#[derive(Debug, Clone, Copy, Error)]
pub enum Error {
    #[error(transparent)]
    VisaRs(#[from] visa_rs::Error),
}

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

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

#[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,
}

pub trait Visa {
    fn write(&mut self, command: &str) -> Result<()>;
    fn read(&self) -> Result<String>;
    fn read_idn(&mut self) -> Result<Idn>;
    fn reset(&mut self) -> Result<()>;
    fn read_resources(rm: &visa_rs::DefaultRM) -> Result<Vec<visa_rs::VisaString>>;
    fn new_session(
        rm: &visa_rs::DefaultRM,
        manufacturer: &str,
        model: &str,
    ) -> Result<Option<Self>>
    where
        Self: Sized;
    fn wait_operation_complete(&mut self) -> Result<()>;
}

impl Visa for Instrument {
    fn write(&mut self, command: &str) -> Result<()> {
        std::io::Write::write_all(self, command.as_bytes()).map_err(io_to_vs_err)?;
        Ok(())
    }

    fn read(&self) -> Result<String> {
        let mut r = std::io::BufReader::new(self);
        let mut buf = String::new();

        std::io::BufRead::read_line(&mut r, &mut buf).map_err(io_to_vs_err)?;
        Ok(buf)
    }

    fn read_idn(&mut self) -> Result<Idn> {
        self.write(Commands::Identify.as_ref())?;
        let response = self.read()?;
        let idn = response
            .split(',')
            .map(|x| x.trim().to_string())
            .collect::<Vec<String>>();

        Ok(Idn {
            manufacturer: idn[0].to_owned(),
            model: idn[1].to_owned(),
            serial_number: idn[2].to_owned(),
            software_version: idn[3].to_owned(),
        })
    }

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

    fn read_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."),
        );

        // Get the resources from visa-rs
        let mut visa_rs_reslist = rm.find_res_list(&expr)?;
        let mut res_exhausted = false;
        let mut res: Vec<visa_rs::VisaString> = vec![];

        // Check the list until it's exhausted
        while !res_exhausted {
            // Get next resource
            let visa_rs_res = visa_rs_reslist.find_next()?;

            // Check if it has a value or if we have exhausted the list
            if let Some(visa_rs_res) = visa_rs_res {
                // Push it into the vec
                res.push(visa_rs_res);
            } else {
                res_exhausted = true;
            };
        }

        Ok(res)
    }

    fn new_session(rm: &visa_rs::DefaultRM, manufacturer: &str, model: &str) -> Result<Option<Self>>
    where
        Self: Sized,
    {
        let resources = Self::read_resources(rm)?;
        for resource in resources {
            let mut session = rm.open(
                &resource,
                visa_rs::flags::AccessMode::NO_LOCK,
                visa_rs::TIMEOUT_IMMEDIATE,
            )?;

            let idn = Self::read_idn(&mut session)?;

            let manufacturer_and_model = format!("{} {}", idn.manufacturer, idn.model);

            if manufacturer_and_model.contains(&format!("{} {}", manufacturer, model)) {
                return Ok(Some(session));
            }
        }
        Ok(None)
    }

    fn wait_operation_complete(&mut self) -> Result<()> {
        // Blocks until operations are complete, then sends a 1
        self.write(Commands::OperationCompleteQuery.as_ref())?;
        self.read()?;
        Ok(())
    }
}