rs_usbtmc/
lib.rs

1//! # Rust USBTMC
2//!
3//! Pure Rust implementation of the USBTMC protocol to connect to instruments.
4//! 
5//! Thus far, this library implements the basic USBTMC control endpoint commands,
6//! writing DEVICE_DEPENDENT messages to the BULK OUT endpoint and reading DEVICE_DEPENDENT 
7//! messages to the BULK IN endpoint.
8//!
9//! ## Usage
10//! 
11//! To use, add the following line to your project's Cargo.toml dependencies:
12//! ```toml
13//! rs-usbtmc = "0.1"
14//! ```
15//! 
16//! ## Example
17//! 
18//! The example below demonstrates how to connect to, send commands to and query the device. 
19//! 
20//! ```rust
21//! use rs_usbtmc::UsbtmcClient;
22//! 
23//! const DEVICE_VID: u16 = 0x0000;
24//! const DEVICE_PID: u16 = 0x0000;
25//! 
26//! fn main() {
27//!     // connect to the device
28//!     let device = UsbtmcClient::connect(DEVICE_VID, DEVICE_PID).expect("failed to connect");
29//! 
30//!     // send a command to the device
31//!     device.command("*IDN?").expect("failed to send command");
32//! 
33//!     // query the device and get a string
34//!     let response: String = device.query("*IDN?").expect("failed to query device");
35//! 
36//!     // query the device and get a bytes
37//!     let response: Vec<u8> = device.query_raw("*IDN?").expect("failed to query device");
38//! }
39//! ```
40//! 
41//! ## Project Plans
42//! 
43//! I created this driver as part of a project to control an oscilloscope during a summer 
44//! research position. Alone, I do not have access to //! an oscilloscope. 
45//! If I do obtain one, the plan is to:
46//! 
47//! - Fully implement all possible requests
48//! - Implement the usb488 subclass requests
49//! 
50//! I'll reach out to my university for access to an instrument to complete this project, but I'm open to collaborating.
51//! 
52
53mod constants;
54mod error;
55mod init;
56mod types;
57mod communication {
58    pub mod bulk;
59    pub mod control;
60}
61
62use communication::control;
63use constants::misc::DEFAULT_TIMEOUT_DURATION;
64use error::Error;
65use types::{BTag, Capabilities, DeviceMode, Handle, Timeout, UsbtmcEndpoints};
66
67use anyhow::Result;
68
69/// ### UsbtmcClient
70/// 
71/// Client connected to a USBTMC device.
72/// 
73#[derive(Debug)]
74pub struct UsbtmcClient {
75    handle: Handle,
76    mode: DeviceMode,
77    timeout: Timeout,
78    capabilities: Capabilities,
79    btag: BTag,
80    endpoints: UsbtmcEndpoints,
81}
82
83impl UsbtmcClient {
84    /// ### Connect
85    ///
86    /// Connect a USB device and initialize it.
87    ///
88    /// #### Arguments
89    /// - `vid` -> the vendor ID
90    /// - `pid` -> the product ID
91    ///
92    pub fn connect(vid: u16, pid: u16) -> Result<UsbtmcClient> {
93        // OPEN THE DEVICE
94        // ==========
95
96        // setup context
97        let mut context = rusb::Context::new()?;
98        // attempt to open the device
99        let (device, mut handle) = match init::open_device(&mut context, vid, pid) {
100            Some(res) => res,
101            None => return Err(Error::DeviceNotFound.into()),
102        };
103
104        // GET THE DEVICE MODE
105        // ==========
106
107        // get the mode
108        let mut mode = init::get_usbtmc_mode(&device)?;
109        // detach kernel driver if it is used
110        init::detach_kernel_driver(&mut mode, &mut handle)?;
111
112        // GET ENDPOINTS
113        // ==========
114        let endpoints: UsbtmcEndpoints = init::get_endpoints(&mode, &device)?;
115
116        // CONFIGURE DEVICE
117        // ==========
118        handle.set_active_configuration(mode.config_number)?;
119        handle.claim_interface(mode.interface_number)?;
120        handle.set_alternate_setting(mode.interface_number, mode.setting_number)?;
121
122        // SETUP DATA FOR CLIENT
123        // ==========
124        let handle: Handle = Handle::new(handle);
125        let timeout: Timeout = Timeout::new(DEFAULT_TIMEOUT_DURATION);
126        let btag = BTag::new();
127
128        // GET CAPABILITIES
129        // ==========
130        let capabilities: Capabilities =
131            control::get_capabilities(&handle, mode.interface_number, &timeout)?;
132
133        // CLEAR THE BUFFERS AND FEATURES
134        // ==========
135        control::clear_buffers(&handle, mode.interface_number, &timeout)?;
136        control::clear_feature(&handle, &endpoints.bulk_out_ep)?;
137        control::clear_feature(&handle, &endpoints.bulk_in_ep)?;
138
139        // RETURN THE CLIENT
140        // ==========
141        Ok(UsbtmcClient {
142            handle,
143            mode,
144            timeout,
145            capabilities,
146            btag,
147            endpoints,
148        })
149    }
150
151    /// ### Set Timeout
152    ///
153    /// Set a new timeout for the device connection.
154    ///
155    /// #### Arguments
156    /// - `duration` -> the duration of the timeout
157    ///
158    pub fn set_timeout(&self, duration: std::time::Duration) {
159        *self.timeout.borrow() = duration;
160    }
161
162    /// ### Command
163    ///
164    /// Send a command to the device.
165    ///
166    /// #### Arguments
167    /// - `cmd` -> the command to send
168    ///
169    pub fn command(&self, cmd: &str) -> Result<()> {
170        use communication::bulk;
171
172        // Send the command
173        bulk::write(
174            &self.handle,
175            &self.btag,
176            cmd.into(),
177            &self.endpoints.bulk_out_ep,
178            &self.timeout,
179        )?;
180
181        Ok(())
182    }
183
184    /// ### Query Raw
185    ///
186    /// Send a command and get a response from the device.
187    /// The response is a vector of bytes.
188    ///
189    /// #### Arguments
190    /// - `cmd` -> the command to send
191    ///
192    pub fn query_raw(&self, cmd: &str) -> Result<Vec<u8>> {
193        use communication::bulk;
194
195        // Send a command
196        bulk::write(
197            &self.handle,
198            &self.btag,
199            cmd.into(),
200            &self.endpoints.bulk_out_ep,
201            &self.timeout,
202        )?;
203
204        // Read the response
205        let resp = bulk::read(
206            &self.handle,
207            &self.btag,
208            &self.endpoints.bulk_in_ep,
209            &self.endpoints.bulk_out_ep,
210            &self.capabilities,
211            &self.timeout,
212        )?;
213
214        Ok(resp)
215    }
216
217    /// ### Query
218    ///
219    /// Send a command and get a response from the device.
220    /// The response is a utf-8 string.
221    ///
222    /// #### Arguments
223    /// - `cmd` -> the command to send
224    ///
225    pub fn query(&self, cmd: &str) -> Result<String> {
226        use communication::bulk;
227
228        // Send a command
229        bulk::write(
230            &self.handle,
231            &self.btag,
232            cmd.into(),
233            &self.endpoints.bulk_out_ep,
234            &self.timeout,
235        )?;
236
237        // Read the response
238        let resp = bulk::read(
239            &self.handle,
240            &self.btag,
241            &self.endpoints.bulk_in_ep,
242            &self.endpoints.bulk_out_ep,
243            &self.capabilities,
244            &self.timeout,
245        )?;
246
247        // Convert response to string
248        let resp = std::str::from_utf8(&resp)?.trim();
249
250        Ok(String::from(resp))
251    }
252}
253
254impl Drop for UsbtmcClient {
255    fn drop(&mut self) {
256        // RESET THE CONFIGURATION
257        // Release the interface
258        self.handle
259            .borrow()
260            .release_interface(self.mode.interface_number)
261            .expect("failed to release device usb interface");
262        // Reattach the kernel driver if it was disconnected
263        if self.mode.has_kernel_driver {
264            self.handle
265                .borrow()
266                .attach_kernel_driver(self.mode.interface_number)
267                .expect("failed to attach kernel driver to usb device");
268        };
269    }
270}