summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNao Pross <np@0hm.ch>2021-04-07 13:13:32 +0200
committerNao Pross <np@0hm.ch>2021-04-07 13:13:32 +0200
commit4a6ddf107064c8b80b64346c15ae86f11e2fb0b7 (patch)
tree95a9364be5c561ec3dd7fe4f1404f6d9130f9b58
parentAdd font (diff)
downloadtestbench-ui-4a6ddf107064c8b80b64346c15ae86f11e2fb0b7.tar.gz
testbench-ui-4a6ddf107064c8b80b64346c15ae86f11e2fb0b7.zip
Delete demo lua console, start usbtmc driver
-rw-r--r--Cargo.lock152
-rw-r--r--Cargo.toml3
-rw-r--r--lua/demo.lua6
-rw-r--r--src/main.rs69
-rw-r--r--src/rusbtmc.rs626
-rw-r--r--src/support/mod.rs36
-rw-r--r--src/testbench.rs162
7 files changed, 945 insertions, 109 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9b1c9c1..9741861 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,27 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
+name = "CoreFoundation-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
+dependencies = [
+ "libc",
+ "mach 0.1.2",
+]
+
+[[package]]
+name = "IOKit-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
+dependencies = [
+ "CoreFoundation-sys",
+ "libc",
+ "mach 0.1.2",
+]
+
+[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -22,6 +43,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "andrew"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -619,6 +649,38 @@ dependencies = [
]
[[package]]
+name = "libudev"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe"
+dependencies = [
+ "libc",
+ "libudev-sys",
+]
+
+[[package]]
+name = "libudev-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "libusb1-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22e89d08bbe6816c6c5d446203b859eba35b8fa94bf1b7edb2f6d25d43f023f"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -637,6 +699,24 @@ dependencies = [
]
[[package]]
+name = "mach"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "mach"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -786,6 +866,19 @@ dependencies = [
[[package]]
name = "nix"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 0.1.10",
+ "libc",
+ "void",
+]
+
+[[package]]
+name = "nix"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
@@ -991,6 +1084,23 @@ dependencies = [
]
[[package]]
+name = "regex"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
+
+[[package]]
name = "rlua"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1004,6 +1114,16 @@ dependencies = [
]
[[package]]
+name = "rusb"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f3264859095257507e4c011ab420ff9b2d9cc3349c6c08a1d3a019260bb437"
+dependencies = [
+ "libc",
+ "libusb1-sys",
+]
+
+[[package]]
name = "rustc-demangle"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1047,6 +1167,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f"
[[package]]
+name = "serialport"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22f37409d980045734250d679750bdf11bd875fec5bb5417dd21bb75d04d31a1"
+dependencies = [
+ "CoreFoundation-sys",
+ "IOKit-sys",
+ "bitflags",
+ "cfg-if 0.1.10",
+ "libudev",
+ "mach 0.2.3",
+ "nix 0.16.1",
+ "regex",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "shared_library"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1120,6 +1257,9 @@ dependencies = [
"imgui-glium-renderer",
"imgui-winit-support",
"rlua",
+ "rusb",
+ "serialport",
+ "thiserror",
]
[[package]]
@@ -1164,12 +1304,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
+name = "vcpkg"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
+
+[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
name = "walkdir"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index ee76fa9..a485172 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,3 +11,6 @@ imgui = "0.7.0"
imgui-glium-renderer = "0.7.0"
imgui-winit-support = "0.7.0"
rlua = "0.17.0"
+serialport = "4.0.0"
+rusb = "0.8.0"
+thiserror = "1.0"
diff --git a/lua/demo.lua b/lua/demo.lua
index 916eb2c..c5ee497 100644
--- a/lua/demo.lua
+++ b/lua/demo.lua
@@ -1,7 +1,11 @@
ui.print("hello world")
+table = {}
+table[1] = 1
+table["msg"] = "hello"
+
function do_something()
return 1
end
-return 10
+return table
diff --git a/src/main.rs b/src/main.rs
index 9e7d545..f997c06 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,80 +1,13 @@
-use imgui::*;
-use rlua::{Lua, MultiValue};
-
+mod rusbtmc;
mod support;
mod testbench;
-#[derive(Default)]
-struct State {
- lua: Lua,
- console: Vec<ImString>,
- repl_input: ImString,
-}
-
-impl State {
- fn new() -> State {
- State {
- lua: Lua::new(),
- console: Vec::new(),
- repl_input: ImString::with_capacity(256),
- }
- }
-}
-
-fn draw_console(_run: &mut bool, ui: &mut Ui, state: &mut State) {
- let win = Window::new(im_str!("Lua Console"))
- .size([400., 500.], Condition::Appearing)
- .position([440., 20.], Condition::Appearing);
-
- win.build(&ui, || {
- ChildWindow::new("console")
- .size([0., 400.])
- .scrollable(true)
- .build(&ui, || {
- for line in &state.console {
- ui.text(line);
- }
- });
-
- ui.separator();
- ui.input_text(im_str!("Lua"), &mut state.repl_input).build();
- ui.same_line(0.);
- if ui.button(im_str!("Eval"), [0., 0.]) {
- let input = state.repl_input.to_str().clone();
- let mut new_text = String::new();
-
- state.lua.context(|ctx| {
- match ctx.load(input).eval::<MultiValue>() {
- Ok(values) => {
- new_text.push_str(
- &values
- .iter()
- .map(|value| format!("{:?}", value))
- .collect::<Vec<_>>()
- .join("\t"),
- );
- }
- Err(e) => {
- new_text.push_str(&e.to_string());
- }
- };
- });
-
- state.console.push(ImString::new(format!("> {}", input)));
- state.console.push(ImString::new(new_text));
- state.repl_input.clear();
- }
- });
-}
-
fn main() {
let system = support::init(file!());
- let mut state = State::new();
let mut bench = testbench::Bench::new();
system.main_loop(move |run, ui| {
// ui.show_demo_window(run);
bench.draw(run, ui);
- draw_console(run, ui, &mut state);
});
}
diff --git a/src/rusbtmc.rs b/src/rusbtmc.rs
new file mode 100644
index 0000000..994ae31
--- /dev/null
+++ b/src/rusbtmc.rs
@@ -0,0 +1,626 @@
+use rusb;
+
+use std::num::Wrapping;
+use std::time::Duration;
+use thiserror::Error;
+
+const USBTMC_BINTERFACE_CLASS: u8 = 0xfe;
+const USBTMC_BINTERFACE_SUBCLASS: u8 = 3;
+// const USBTMC_BINTERFACE_PROTOCOL: u8 = 0;
+const USB488_BINTERFACE_PROTOCOL: u8 = 1;
+
+/* control values */
+
+/// USBTMC bRequest Values
+#[repr(u8)]
+#[allow(dead_code)]
+enum RequestType {
+ InitiateAbortBulkOut = 1,
+ CheckAbortBulkOutStatus = 2,
+ InitiateAbortBunkIn = 3,
+ CheckAbortBulkInStatus = 4,
+ InitiateClear = 5,
+ CheckClearStatus = 6,
+ GetCapabilities = 7,
+ IndicatorPulse = 64,
+}
+
+/// USBTMC Status values
+#[repr(u8)]
+#[allow(dead_code)]
+enum Status {
+ Success = 0x01,
+ Pending = 0x02,
+ Failed = 0x80,
+ TransferNotInProgress = 0x81,
+ SplitNotInProgress = 0x82,
+ SplitInProgress = 0x83,
+}
+
+/* bulk values */
+
+enum Direction {
+ In,
+ Out,
+}
+
+enum MsgId {
+ DeviceDependent,
+ VendorSpecific,
+}
+
+/* instruments */
+
+/// USBTMC Capabilities
+#[derive(Clone, Debug)]
+pub struct Capabilities {
+ // version number (in BCD)
+ bcd_usbtmc: u16,
+ // interface capabilities
+ pulse: bool,
+ talk_only: bool,
+ listen_only: bool,
+ // device capabilities
+ term_char: bool,
+}
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("error on low level USB")]
+ Rusb(#[from] rusb::Error),
+ #[error("device not found")]
+ DeviceNotFound,
+ #[error("not a usbtmc device")]
+ NotUsbtmcDevice,
+ #[error("no usb handle")]
+ NoHandle,
+ #[error("not connected")]
+ NotConnected,
+ #[error("request failed")]
+ Request,
+ #[error("device does not support the request type")]
+ NotSupported,
+}
+
+/// Get a list of connected instruments
+pub fn instruments() -> Result<Vec<Instrument<rusb::GlobalContext>>, Error> {
+ let devices = match rusb::devices() {
+ Ok(devices) => devices,
+ Err(e) => return Err(Error::Rusb(e)),
+ };
+
+ let mut instruments = Vec::<Instrument<rusb::GlobalContext>>::new();
+
+ for dev in devices.iter() {
+ if let Ok(instr) = Instrument::new(dev) {
+ instruments.push(instr);
+ }
+ }
+
+ Ok(instruments)
+}
+
+/// 'High level' Instrument wrapper around rusb::Device
+pub struct Instrument<C: rusb::UsbContext> {
+ connected: bool,
+ pub device: rusb::Device<C>,
+ pub handle: Option<rusb::DeviceHandle<C>>,
+ capabilities: Option<Capabilities>,
+ has_kernel_driver: bool,
+ config_num: Option<u8>,
+ iface_num: Option<u8>,
+ ep_bulk_in: Option<u8>,
+ ep_bulk_out: Option<u8>,
+ ep_interrupt_in: Option<u8>,
+ btag: Wrapping<u8>,
+ timeout: Duration,
+}
+
+impl<C: rusb::UsbContext> Instrument<C> {
+ /// Creates and Instrument from a rusb Device
+ pub fn new(device: rusb::Device<C>) -> Result<Instrument<C>, Error> {
+ Ok(Instrument {
+ connected: false,
+ device: device,
+ handle: None,
+ capabilities: None,
+ has_kernel_driver: false,
+ config_num: None,
+ iface_num: None,
+ ep_bulk_in: None,
+ ep_bulk_out: None,
+ ep_interrupt_in: None,
+ btag: Wrapping(0_u8),
+ timeout: Duration::from_millis(20),
+ })
+ }
+
+ /// Creates an Instrument from the idVendor and idProduct numbers
+ pub fn from_vid_pid(
+ id_vendor: u16,
+ id_product: u16,
+ ) -> Result<Instrument<rusb::GlobalContext>, Error> {
+ let handle = match rusb::open_device_with_vid_pid(id_vendor, id_product) {
+ Some(handle) => handle,
+ None => return Err(Error::DeviceNotFound),
+ };
+
+ Ok(Instrument {
+ connected: false,
+ device: handle.device(),
+ handle: Some(handle),
+ capabilities: None,
+ has_kernel_driver: false,
+ config_num: None,
+ iface_num: None,
+ ep_bulk_in: None,
+ ep_bulk_out: None,
+ ep_interrupt_in: None,
+ btag: Wrapping(0_u8),
+ timeout: Duration::from_millis(20),
+ })
+ }
+
+ /// Opens (searches for) the USBTMC interface of the device
+ ///
+ /// It loops through the available usb interfaces and uses the first that
+ /// matches the usbtmc spec class and subclass
+ pub fn open(&mut self) -> Result<bool, Error> {
+ if self.connected {
+ dbg!("device already connected");
+ return Ok(self.connected);
+ }
+
+ self.handle = match self.device.open() {
+ Ok(handle) => Some(handle),
+ Err(e) => {
+ dbg!("failed to get device handle");
+ return Err(Error::Rusb(e));
+ }
+ };
+
+ let desc = self.device.device_descriptor()?;
+
+ 'outer: for cfg_desc in (0..desc.num_configurations())
+ .map(|num| self.device.config_descriptor(num))
+ .filter_map(|cfg_desc| cfg_desc.ok())
+ {
+ for iface_desc in cfg_desc
+ .interfaces()
+ .map(|iface| iface.descriptors())
+ .flatten()
+ {
+ // check if it is an USBTMC device
+ if iface_desc.class_code() == USBTMC_BINTERFACE_CLASS
+ && iface_desc.sub_class_code() == USBTMC_BINTERFACE_SUBCLASS
+ {
+ // check if it is has USB488
+ if iface_desc.protocol_code() == USB488_BINTERFACE_PROTOCOL {
+ // TODO
+ }
+
+ self.config_num = Some(cfg_desc.number());
+ self.iface_num = Some(iface_desc.interface_number());
+
+ // find endpoints
+ for ep_desc in iface_desc.endpoint_descriptors() {
+ match ep_desc.transfer_type() {
+ rusb::TransferType::Bulk => match ep_desc.direction() {
+ rusb::Direction::Out => {
+ self.ep_bulk_out = Some(ep_desc.address());
+ }
+ rusb::Direction::In => {
+ self.ep_bulk_in = Some(ep_desc.address());
+ }
+ },
+ rusb::TransferType::Interrupt => {
+ if ep_desc.direction() == rusb::Direction::In {
+ self.ep_interrupt_in = Some(ep_desc.address());
+ }
+ }
+ // not interested in other cases
+ _ => {}
+ }
+ }
+
+ // found first interface = happy
+ break 'outer;
+ }
+ }
+ }
+
+ // check for valid addresse
+ if self.ep_bulk_out.is_none() || self.ep_bulk_in.is_none() || self.ep_interrupt_in.is_none()
+ {
+ return Err(Error::NotUsbtmcDevice);
+ }
+
+ let handle = self.handle.as_mut().unwrap(); // is this safe?
+
+ // detach kernel driver if necessary
+ let iface_num = match self.iface_num {
+ Some(num) => num,
+ None => {
+ dbg!("no interface number");
+ return Err(Error::NotUsbtmcDevice);
+ }
+ };
+ self.has_kernel_driver = match handle.kernel_driver_active(iface_num) {
+ Ok(true) => {
+ if let Err(e) = handle.detach_kernel_driver(iface_num) {
+ dbg!("failed to detach kernel driver");
+ return Err(Error::Rusb(e));
+ }
+ true
+ }
+ _ => false,
+ };
+
+ // set configuration if not correct
+ let config_num = match self.config_num {
+ Some(num) => num,
+ None => {
+ dbg!("no configuration number");
+ return Err(Error::NotUsbtmcDevice);
+ }
+ };
+
+ let active_conf = handle.active_configuration();
+ if active_conf != Ok(config_num) {
+ if let Err(e) = handle.set_active_configuration(config_num) {
+ dbg!("failed to set configuration");
+ return Err(Error::Rusb(e));
+ }
+ dbg!(format!("set configuration to {}", config_num));
+ }
+
+ // claim the interface
+ if let Err(e) = handle.claim_interface(iface_num) {
+ dbg!("failed to claim interface");
+ return Err(Error::Rusb(e));
+ }
+
+ self.connected = true;
+
+ if let Err(e) = self.clear() {
+ return Err(e);
+ }
+
+ Ok(self.connected)
+ }
+
+ /// Closes the devices
+ pub fn close(&mut self) {
+ if !self.connected {
+ return;
+ }
+
+ if let Some(handle) = &mut self.handle {
+ if let Some(iface_num) = self.iface_num {
+ if let Err(e) = handle.release_interface(iface_num) {
+ dbg!(e);
+ dbg!("failed to release interface");
+ }
+
+ if self.has_kernel_driver {
+ if let Err(e) = handle.attach_kernel_driver(iface_num) {
+ dbg!(e);
+ dbg!("failed to attach kernel driver");
+ }
+ }
+ }
+ }
+
+ // TODO: reset configuration
+
+ self.connected = false;
+
+ self.ep_bulk_out = None;
+ self.ep_bulk_in = None;
+ self.ep_interrupt_in = None;
+ }
+
+ /// Sends a clear request and waits for it to complete
+ pub fn clear(&mut self) -> Result<(), Error> {
+ if !self.connected {
+ return Err(Error::NotConnected);
+ }
+
+ let handle = self.handle.as_mut().unwrap();
+ let index = self.iface_num.unwrap();
+
+ // response buffer
+ let buf: &mut [u8] = &mut [0];
+
+ // send clear request
+ if let Err(e) = handle.read_control(
+ rusb::request_type(
+ rusb::Direction::In,
+ rusb::RequestType::Class,
+ rusb::Recipient::Interface,
+ ),
+ RequestType::InitiateClear as u8,
+ 0x0000,
+ index.into(),
+ buf,
+ self.timeout,
+ ) {
+ dbg!("failed to send clear request");
+ return Err(Error::Rusb(e));
+ }
+
+ if buf[0] != Status::Success as u8 {
+ return Err(Error::Request);
+ }
+
+ // wait for completion of clear
+ loop {
+ // response buffer
+ let mut buf: &mut [u8] = &mut [0, 0];
+
+ // send check status
+ if let Err(e) = handle.read_control(
+ rusb::request_type(
+ rusb::Direction::In,
+ rusb::RequestType::Class,
+ rusb::Recipient::Interface,
+ ),
+ RequestType::CheckClearStatus as u8,
+ 0x0000,
+ index.into(),
+ &mut buf,
+ self.timeout,
+ ) {
+ return Err(Error::Rusb(e));
+ }
+
+ if buf[0] != Status::Pending as u8 {
+ break;
+ }
+
+ std::thread::sleep(Duration::from_millis(100));
+ }
+
+ // clear halt condition
+ let bulk_out_ep = self.ep_bulk_out.unwrap();
+ if let Err(e) = handle.clear_halt(bulk_out_ep) {
+ dbg!("failed to clear halt");
+ return Err(Error::Rusb(e));
+ }
+
+ Ok(())
+ }
+
+ /// Ask to the device with features are supported
+ pub fn get_capabilities(&mut self) -> Result<Capabilities, Error> {
+ if !self.connected {
+ return Err(Error::NotConnected);
+ }
+
+ let handle = self.handle.as_mut().unwrap();
+ let index = self.iface_num.unwrap();
+
+ // response buffer
+ let buf: &mut [u8; 0x18] = &mut [0; 0x18];
+
+ // send request
+ if let Err(e) = handle.read_control(
+ rusb::request_type(
+ rusb::Direction::In,
+ rusb::RequestType::Class,
+ rusb::Recipient::Interface,
+ ),
+ RequestType::GetCapabilities as u8,
+ 0x0000,
+ index.into(),
+ buf,
+ self.timeout,
+ ) {
+ dbg!("failed to send get capabilities request");
+ return Err(Error::Rusb(e));
+ }
+
+ if buf[0] != Status::Success as u8 {
+ return Err(Error::Request);
+ }
+
+ // TODO: USB448 subclass
+
+ let bcd_usbtmc: u16 = (u16::from(buf[3]) << 8) + u16::from(buf[2]);
+ let capabilities = Capabilities {
+ bcd_usbtmc: bcd_usbtmc,
+ pulse: (buf[4] & 4) != 0,
+ talk_only: (buf[4] & 2) != 0,
+ listen_only: (buf[4] & 1) != 0,
+ term_char: (buf[5] & 1) != 0,
+ };
+
+ self.capabilities = Some(capabilities.clone());
+ Ok(capabilities)
+ }
+
+ pub fn pulse(&mut self) -> Result<(), Error> {
+ if !self.connected {
+ return Err(Error::NotConnected);
+ }
+
+ let can_pulse = match &self.capabilities {
+ Some(c) => c.pulse,
+ None => {
+ let c = self.get_capabilities()?;
+ c.pulse
+ }
+ };
+
+ if !can_pulse {
+ return Err(Error::NotSupported);
+ }
+
+ let handle = self.handle.as_ref().unwrap();
+ let index = self.iface_num.unwrap();
+
+ let buf: &mut [u8] = &mut [0];
+
+ // send request
+ if let Err(_) = handle.read_control(
+ rusb::request_type(
+ rusb::Direction::In,
+ rusb::RequestType::Class,
+ rusb::Recipient::Interface,
+ ),
+ RequestType::GetCapabilities as u8,
+ 0x0000,
+ index.into(),
+ buf,
+ self.timeout,
+ ) {
+ return Err(Error::NotSupported);
+ }
+
+ Ok(())
+ }
+
+ /// Write binary data to the instrument
+ pub fn write_raw(&mut self, data: &[u8]) -> Result<usize, Error> {
+ if !self.connected {
+ return Err(Error::NotConnected);
+ }
+
+ let mut sent_bytes = 0;
+
+ let handle = self.handle.as_ref().unwrap();
+ let endpoint = self.ep_bulk_out.unwrap();
+
+ const HEADER_SIZE: usize = 12;
+ const TRANSFER_SIZE: usize = 1024 * 1024;
+ const PACKET_SIZE: usize = HEADER_SIZE + TRANSFER_SIZE;
+
+ // reset btag counter
+ self.btag = Wrapping(1_u8);
+
+ // send chunks
+ let iter = data.chunks_exact(TRANSFER_SIZE);
+ let last_data = iter.remainder();
+
+ for chunk in iter {
+ let header = make_bulk_header(
+ MsgId::DeviceDependent,
+ Direction::Out,
+ self.btag.0,
+ TRANSFER_SIZE as u32,
+ false,
+ );
+
+ let mut packet: [u8; PACKET_SIZE] = [0; PACKET_SIZE];
+ packet[..12].clone_from_slice(&header);
+ packet[13..].clone_from_slice(chunk);
+
+ sent_bytes += match handle.write_bulk(endpoint, &packet, self.timeout) {
+ Ok(sent) => sent,
+ Err(e) => {
+ dbg!("failed to send chunk during bulk out");
+ self.abort_bulk_out();
+ return Err(Error::Rusb(e));
+ }
+ };
+
+ // increment btag
+ if self.btag.0 == 0 {
+ self.btag = Wrapping(1_u8);
+ } else {
+ self.btag += Wrapping(1_u8);
+ }
+ }
+
+ // send remainder
+ let padding = (4 - (last_data.len() % 4)) % 4;
+ let last_data_size: usize = last_data.len() + padding;
+
+ let header = make_bulk_header(
+ MsgId::DeviceDependent,
+ Direction::Out,
+ self.btag.0,
+ last_data_size as u32,
+ true,
+ );
+
+ // TODO: pad and send last byte
+
+ Ok(sent_bytes)
+ }
+
+ fn abort_bulk_out(&mut self) -> Result<(), Error> {
+ if !self.connected {
+ return Err(Error::NotConnected);
+ }
+
+ let handle = self.handle.as_ref().unwrap();
+ let endpoint = self.ep_bulk_out.unwrap();
+
+ let buf: &[u8; 2] = &[0, 0];
+ if let Err(e) = handle.write_control(
+ rusb::request_type(
+ rusb::Direction::In,
+ rusb::RequestType::Class,
+ rusb::Recipient::Endpoint,
+ ),
+ RequestType::InitiateAbortBulkOut as u8,
+ u16::from(self.btag.0),
+ endpoint.into(),
+ buf,
+ self.timeout,
+ ) {
+ dbg!("failed to initiate abort bulk out");
+ return Err(Error::Rusb(e));
+ }
+
+ Ok(())
+ }
+}
+
+/// helper function to create bulk headers
+fn make_bulk_header(
+ msgid: MsgId,
+ direction: Direction,
+ btag: u8,
+ transfer_size: u32,
+ is_last: bool,
+) -> [u8; 12] {
+ // table 2 in spec
+ let msgid_nr: u8 = match msgid {
+ MsgId::DeviceDependent => match direction {
+ Direction::Out => 1,
+ Direction::In => 2,
+ },
+ MsgId::VendorSpecific => match direction {
+ Direction::Out => 126,
+ Direction::In => 127,
+ },
+ };
+
+ let ts_bytes = transfer_size.to_le_bytes();
+ let header: [u8; 12] = [
+ // table 1 in spec
+ msgid_nr,
+ btag,
+ !btag,
+ 0x00,
+ // table 3 in spec
+ // size of the transfer, without header
+ ts_bytes[0],
+ ts_bytes[1],
+ ts_bytes[2],
+ ts_bytes[4],
+ // whether this is the last chunk
+ match is_last {
+ true => 0x01,
+ false => 0x00,
+ },
+ // reserved, must be zeroes
+ 0x00,
+ 0x00,
+ 0x00,
+ ];
+
+ header
+}
diff --git a/src/support/mod.rs b/src/support/mod.rs
index af894ba..aa27f78 100644
--- a/src/support/mod.rs
+++ b/src/support/mod.rs
@@ -50,25 +50,25 @@ pub fn init(title: &str) -> System {
}
let hidpi_factor = platform.hidpi_factor();
- let font_size = (13.0 * hidpi_factor) as f32;
+ let font_size = (15.0 * hidpi_factor) as f32;
- // imgui.fonts().add_font(&[
- // FontSource::DefaultFontData {
- // config: Some(FontConfig {
- // size_pixels: font_size,
- // ..FontConfig::default()
- // }),
- // },
- // FontSource::TtfData {
- // data: include_bytes!("../../../resources/mplus-1p-regular.ttf"),
- // size_pixels: font_size,
- // config: Some(FontConfig {
- // rasterizer_multiply: 1.75,
- // glyph_ranges: FontGlyphRanges::japanese(),
- // ..FontConfig::default()
- // }),
- // },
- // ]);
+ imgui.fonts().add_font(&[
+ // FontSource::DefaultFontData {
+ // config: Some(FontConfig {
+ // size_pixels: font_size,
+ // ..FontConfig::default()
+ // }),
+ // },
+ FontSource::TtfData {
+ data: include_bytes!("../../res/Hack-Regular.ttf"),
+ size_pixels: font_size,
+ config: Some(FontConfig {
+ rasterizer_multiply: 1.75,
+ glyph_ranges: FontGlyphRanges::default(),
+ ..FontConfig::default()
+ }),
+ },
+ ]);
imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32;
diff --git a/src/testbench.rs b/src/testbench.rs
index 6d9ea16..5dd18fd 100644
--- a/src/testbench.rs
+++ b/src/testbench.rs
@@ -1,11 +1,15 @@
+use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::option::Option;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use imgui;
use rlua::Lua;
+// use serialport;
+
+use crate::rusbtmc;
#[derive(Default, PartialEq)]
pub struct Test {
@@ -29,21 +33,28 @@ impl Test {
return self.text.as_ref().unwrap();
}
- pub fn text(&self) -> &str {
- return match &self.text {
- Some(text) => text,
- None => "",
- }
- }
+ // pub fn text(&self) -> &str {
+ // return match &self.text {
+ // Some(text) => text,
+ // None => "",
+ // }
+ // }
}
#[derive(Default)]
pub struct Bench {
+ // tests window
lua: Lua,
tests: Vec<Test>,
tests_path: PathBuf,
selected_test: Option<usize>,
- console: Vec<imgui::ImString>,
+ selected_usb_dev: Option<usize>,
+ usb_devices: HashMap<imgui::ImString, rusbtmc::Instrument<rusb::GlobalContext>>,
+ selected_serial_dev: Option<usize>,
+ // serial_devices: Vec<?>
+ lua_console: Vec<imgui::ImString>,
+ instr_console: Vec<imgui::ImString>,
+ instr_input: imgui::ImString,
}
impl Bench {
@@ -56,7 +67,9 @@ impl Bench {
let globals = context.globals();
let ui_table = context.create_table()?;
- let ui_print = context.create_function(|_, strings: Variadic<String>| {
+ let ui_print = context.create_function(|_, _strings: Variadic<String>| {
+ // TODO
+ // lua_console.push()
Ok(())
})?;
@@ -66,25 +79,31 @@ impl Bench {
Ok(())
});
- // TODO: handle errors
- // if let Err(e) = lua_setup {}
+ if let Err(_) = lua_setup {
+ // TODO: handle errors
+ }
let mut b = Bench {
lua: lua,
tests: Vec::new(),
tests_path: PathBuf::from("lua"),
selected_test: None,
- console: Vec::new(),
+ selected_usb_dev: None,
+ usb_devices: HashMap::new(),
+ selected_serial_dev: None,
+ lua_console: Vec::new(),
+ instr_console: Vec::new(),
+ instr_input: imgui::ImString::with_capacity(256),
+ // scope: None,
};
// TODO: set graphically and use RV
- b.load_tests();
+ let _ = b.load_tests();
return b;
}
- pub fn load_tests(&mut self) -> io::Result<()>
- {
+ pub fn load_tests(&mut self) -> io::Result<()> {
let entries: Result<Vec<PathBuf>, io::Error> = fs::read_dir(&self.tests_path)?
.into_iter()
.map(|r| r.map(|f| f.path()))
@@ -108,11 +127,12 @@ impl Bench {
pub fn draw(&mut self, _: &mut bool, ui: &mut imgui::Ui) {
use imgui::*;
- let win = Window::new(im_str!("Testbench"))
+ /* Testbench window */
+ let tb_win = Window::new(im_str!("Testbench"))
.size([400., 500.], Condition::Appearing)
.position([20., 20.], Condition::Appearing);
- win.build(&ui, || {
+ tb_win.build(&ui, || {
for (index, test) in self.tests.iter().enumerate() {
if let Some(test_name) = test.path.to_str() {
let test_name: ImString = test_name.to_string().into();
@@ -124,8 +144,8 @@ impl Bench {
}
}
- ui.separator();
if let Some(index) = self.selected_test {
+ ui.separator();
if let Some(test_name) = self.tests[index].path.to_str() {
let imstr: ImString =
format!("Selected Test: {}", test_name).to_string().into();
@@ -143,11 +163,107 @@ impl Bench {
}
ui.separator();
- ChildWindow::new("console")
+ ChildWindow::new("lua console")
+ .size([0., 0.])
+ .scrollable(true)
+ .build(&ui, || {
+ for line in &self.lua_console {
+ ui.text(line);
+ }
+ });
+ });
+
+ /* devices window */
+ let dev_win = Window::new(im_str!("Devices"))
+ .size([400., 500.], Condition::Appearing)
+ .position([520., 20.], Condition::Appearing);
+
+ dev_win.build(&ui, || {
+ // usb devices
+ ui.text_wrapped(im_str!("USB Devices"));
+ ui.same_line(0.);
+ if ui.button(im_str!("Refresh"), [0., 0.]) {
+ // TODO: do not remove open devices
+ self.usb_devices.clear();
+ self.selected_usb_dev = None;
+
+ if let Ok(instruments) = rusbtmc::instruments() {
+ for instr in instruments {
+ let desc = match instr.device.device_descriptor() {
+ Ok(desc) => desc,
+ Err(_) => {
+ dbg!("failed to get descriptor");
+ continue;
+ }
+ };
+
+ let handle = match instr.device.open() {
+ Ok(handle) => handle,
+ Err(_) => {
+ // dbg!("failed to get handle");
+ continue;
+ }
+ };
+
+ let prodstr = match handle.read_product_string_ascii(&desc) {
+ Ok(s) => s,
+ Err(_) => {
+ dbg!("failed to read product string");
+ continue;
+ }
+ };
+
+ let dev_name: ImString = prodstr.into();
+ self.usb_devices.insert(dev_name, instr);
+ }
+ }
+ }
+
+ if self.selected_usb_dev.is_some() {
+ // search dev
+ let mut instr = None;
+ for (index, (_name, dev)) in self.usb_devices.iter_mut().enumerate() {
+ if matches!(self.selected_usb_dev, Some(i) if i == index) {
+ instr = Some(dev);
+ break;
+ }
+ }
+
+ if let Some(instr) = instr {
+ ui.same_line(0.);
+ if ui.button(im_str!("Open"), [0., 0.]) {
+ let _ = dbg!(instr.open());
+ }
+
+ ui.same_line(0.);
+ if ui.button(im_str!("Close"), [0., 0.]) {
+ let _ = dbg!(instr.close());
+ }
+
+ ui.same_line(0.);
+ if ui.button(im_str!("Pulse"), [0., 0.]) {
+ let _ = dbg!(instr.pulse());
+ }
+ }
+ }
+
+ for (index, (name, _dev)) in self.usb_devices.iter().enumerate() {
+ let selected = matches!(self.selected_usb_dev, Some(i) if i == index);
+
+ if Selectable::new(&name).selected(selected).build(ui) {
+ self.selected_usb_dev = Some(index);
+ }
+
+ ui.same_line(0.);
+ }
+
+ ui.separator();
+ ui.input_text(im_str!("Device"), &mut self.instr_input).build();
+ ChildWindow::new("instrument console")
.size([0., 0.])
.scrollable(true)
.build(&ui, || {
- for line in &self.console {
+ for line in &self.instr_console {
ui.text(line);
}
});
@@ -176,9 +292,11 @@ impl Bench {
},
);
- self.console.push(output);
+ self.lua_console.push(output);
}
}
- fn show_test(&self, index: usize) {}
+ fn show_test(&self, _index: usize) {
+ // TODO
+ }
}