diff options
author | Nao Pross <np@0hm.ch> | 2021-04-07 13:13:32 +0200 |
---|---|---|
committer | Nao Pross <np@0hm.ch> | 2021-04-07 13:13:32 +0200 |
commit | 4a6ddf107064c8b80b64346c15ae86f11e2fb0b7 (patch) | |
tree | 95a9364be5c561ec3dd7fe4f1404f6d9130f9b58 | |
parent | Add font (diff) | |
download | testbench-ui-4a6ddf107064c8b80b64346c15ae86f11e2fb0b7.tar.gz testbench-ui-4a6ddf107064c8b80b64346c15ae86f11e2fb0b7.zip |
Delete demo lua console, start usbtmc driver
Diffstat (limited to '')
-rw-r--r-- | Cargo.lock | 152 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | lua/demo.lua | 6 | ||||
-rw-r--r-- | src/main.rs | 69 | ||||
-rw-r--r-- | src/rusbtmc.rs | 626 | ||||
-rw-r--r-- | src/support/mod.rs | 36 | ||||
-rw-r--r-- | src/testbench.rs | 162 |
7 files changed, 945 insertions, 109 deletions
@@ -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" @@ -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 + } } |