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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! # VISA API Crate
//!
//! `visa-api` is a collection of common VISA commands and a high-level
//! interface to create instrument libraries.

use std::io::{BufRead, Write};
use thiserror::Error;
use visa_rs::AsResourceManager;
pub use visa_rs::DefaultRM;

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

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

/// IEEE 488.2 common commands
#[derive(Debug, PartialEq, strum::AsRefStr, strum::Display)]
pub enum CommonCommands {
    #[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,
}

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

/// Instrument struct that holds the IDN data, the session and the instrument adress
#[derive(Debug)]
pub struct Instrument {
    pub idn: Idn,
    pub session: visa_rs::Instrument,
    pub address: visa_rs::VisaString,
}

impl Instrument {
    /// Create a new Instrument object
    pub fn new(idn: Idn, session: visa_rs::Instrument, address: visa_rs::VisaString) -> Instrument {
        Instrument {
            idn,
            session,
            address,
        }
    }

    /// Raw write to a VISA device
    pub fn write(&mut self, command: &str) -> Result<()> {
        self.session
            .write_all(command.as_bytes())
            .map_err(visa_rs::io_to_vs_err)?;
        Ok(())
    }

    fn private_write(session: &mut visa_rs::Instrument, command: &str) -> Result<()> {
        session
            .write_all(command.as_bytes())
            .map_err(visa_rs::io_to_vs_err)?;
        Ok(())
    }

    /// Raw read to a VISA device
    pub fn read(&self) -> Result<String> {
        let mut reader = std::io::BufReader::new(&self.session);
        let mut buffer = String::new();
        reader
            .read_line(&mut buffer)
            .map_err(visa_rs::io_to_vs_err)?;
        Ok(buffer)
    }

    pub fn private_read(session: &visa_rs::Instrument) -> Result<String> {
        let mut reader = std::io::BufReader::new(session);
        let mut buffer = String::new();
        reader
            .read_line(&mut buffer)
            .map_err(visa_rs::io_to_vs_err)?;
        Ok(buffer)
    }

    /// Query the device for Identify data, returns the Idn struct
    pub fn query_idn(&mut self) -> Result<Idn> {
        self.write(CommonCommands::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 private_query_idn(session: &mut visa_rs::Instrument) -> Result<Idn> {
        Self::private_write(session, CommonCommands::Identify.as_ref())?;
        let response = Self::private_read(session)?;
        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(),
        })
    }

    /// Query for a devide with a specific manufacturer and model, returns the VISA API Instrument struct
    pub fn get_instrument(
        resource_manager: &visa_rs::DefaultRM,
        manufacturer: &str,
        model: &str,
    ) -> Result<Option<Instrument>> {
        let addresses = Self::get_addresses(resource_manager)?;
        for address in addresses {
            let mut session = resource_manager.open(
                &address,
                visa_rs::flags::AccessMode::NO_LOCK,
                visa_rs::TIMEOUT_IMMEDIATE,
            )?;

            let idn = Self::private_query_idn(&mut session)?;
            let manufacturer_and_model = format!("{} {}", idn.manufacturer, idn.model);

            // Check that the Manufacturer and Model from the IDN matches (even partially if an instrument series is compatible)
            if manufacturer_and_model.contains(&format!("{} {}", manufacturer, model)) {
                return Ok(Some(Instrument {
                    idn,
                    session,
                    address,
                }));
            }
        }
        Ok(None)
    }

    /// Query for all instruments connected, returns a list of addresses
    pub fn get_addresses(
        resource_manager: &visa_rs::DefaultRM,
    ) -> Result<Vec<visa_rs::VisaString>> {
        let expression = visa_rs::VisaString::from(
            std::ffi::CString::new("?*INSTR").expect("Failed to create C compatible String."),
        );

        let mut resources = resource_manager.find_res_list(&expression)?;
        let mut resources_checked = false;
        let mut addresses: Vec<visa_rs::VisaString> = vec![];

        // Runs through all the VISA resources until find_next() returns a None
        while !resources_checked {
            let resource = resources.find_next()?;
            if resource.is_none() {
                resources_checked = true;
            } else {
                let resource = resource.expect("No VISA Resource Found");
                addresses.push(resource);
            }
        }
        Ok(addresses)
    }

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