instrument_ctl/
lib.rs

1//! # Instrument Interface
2//! 
3//! Connect to, command, and query intruments such as oscilloscopes. 
4//! 
5//! Currently only supports USBTMC connections.
6//! 
7//! Supported interfaces:
8//! - [x] USBTMC
9//! - [ ] VXI-11
10//! 
11//! Considered interfaces:
12//! - [ ] GPIB (reason not to: it is old)
13//! - [ ] VICP (reason not to: proprietary)
14//! - [ ] LSIB (reason not to: way too proprietary)
15//! 
16//! Eventually, and depending on my motivation, this project will become a pure Rust VISA driver. However, in my current position this seems to be a pipe dream.
17//! 
18//! ## Usage
19//! 
20//! To use, add the following line to your project's Cargo.toml dependencies:
21//! ```toml
22//! rs-instrument-ctl = "0.1"
23//! ```
24//! 
25//! ## Example
26//! 
27//! The example below demonstrates how to connect to, send commands to and query the device. 
28//! 
29//! ```rust
30//! use rs_instrument_ctl::Instrument;
31//! 
32//! const VISA_ADDRESS: &str = "USB::0x0000::0x0000::SERIALNUMBER::INSTR"
33//! 
34//! fn main() {
35//!     // connect to the instrument
36//!     let instrument = Instrument::connect(VISA_ADDRESS).expect("failed to connect to device");
37//! 
38//!     // send a command
39//!     instrument.command("*IDN").expect("failed to send command");
40//! 
41//!     // query the device and return the response as a string
42//!     let response: String = instrument.query("*IDN?").expect("failed to query");
43//! 
44//!     // query the device and return the response as a vector of bytes
45//!     let response: Vec<u8> = instrument.query("*IDN?").expect("failed to query");
46//! }
47//! ```
48//! 
49
50use std::time::Duration;
51use std::sync::Arc;
52
53mod interface;
54use interface::InstrumentClient;
55use rs_usbtmc::UsbtmcClient;
56
57use anyhow::{Result, anyhow};
58
59pub struct Instrument {
60    client: Arc<dyn InstrumentClient + 'static>,
61}
62
63unsafe impl Send for Instrument {}
64unsafe impl Sync for Instrument {}
65
66
67impl Instrument {
68    /// ## Connect
69    /// 
70    /// Connect to a compatible device with a VISA address
71    /// 
72    /// ### Arguments
73    /// - `address` -> a valid VISA address
74    /// 
75    pub fn connect(address: &str) -> Result<Instrument> {
76        // parse the address to figure out which interface to use
77        let addr: Vec<&str> = address.split("::").collect();
78
79        let interface = addr[0];
80
81        if interface.contains("USB") {
82            let vid = match addr[1].strip_prefix("0x") {
83                Some(s) => s,
84                None => addr[1],
85            };
86            let pid = match addr[2].strip_prefix("0x") {
87                Some(s) => s,
88                None => addr[2],
89            };
90            
91            // the device is USB
92            let vid = u16::from_str_radix(vid, 16)?;
93            let pid = u16::from_str_radix(pid, 16)?;
94            let client = Arc::new(UsbtmcClient::connect(vid, pid)?);
95
96            return Ok(Instrument { client })
97        } else {
98            return Err(anyhow!("unrecognized protocol"))
99        }
100    }
101
102    /// ### Set Timeout
103    ///
104    /// Set a new timeout for the device connection.
105    ///
106    /// #### Arguments
107    /// - `duration` -> the duration of the timeout
108    ///
109    pub fn set_timeout(&self, duration: Duration) {
110        self.client.set_timeout(duration);
111    }
112
113    /// ### Command
114    ///
115    /// Send a command to the device.
116    ///
117    /// #### Arguments
118    /// - `cmd` -> the command to send
119    ///
120    pub fn command(&self, cmd: &str) -> Result<()> {
121        let mut cmd = String::from(cmd);
122        if !cmd.ends_with("\n") {
123            cmd.push('\n');
124        }
125        self.client.command(&cmd)
126    }
127
128    /// ### Query
129    ///
130    /// Send a command and get a response from the device.
131    /// The response is a utf-8 string.
132    ///
133    /// #### Arguments
134    /// - `cmd` -> the command to send
135    ///
136    pub fn query(&self, cmd: &str) -> Result<String> {
137        let mut cmd = String::from(cmd);
138        if !cmd.ends_with("\n") {
139            cmd.push('\n');
140        }
141        self.client.query(&cmd)
142    }
143
144    /// ### Query Raw
145    ///
146    /// Send a command and get a response from the device.
147    /// The response is a vector of bytes.
148    ///
149    /// #### Arguments
150    /// - `cmd` -> the command to send
151    ///
152    pub fn query_raw(&self, cmd: &str) -> Result<Vec<u8>> {
153        let mut cmd = String::from(cmd);
154        if !cmd.ends_with("\n") {
155            cmd.push('\n');
156        }
157        self.client.query_raw(&cmd)
158    }
159}