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}