diff options
author | Nao Pross <np@0hm.ch> | 2021-04-18 17:12:20 +0200 |
---|---|---|
committer | Nao Pross <np@0hm.ch> | 2021-04-18 17:12:20 +0200 |
commit | 9bc323efb9b37887e1c43f0d3ac1fe70230eefac (patch) | |
tree | a6d2298f16fa2fa0f662524045d0ee53295dd1a8 /src | |
parent | Delete demo lua console, start usbtmc driver (diff) | |
download | testbench-ui-9bc323efb9b37887e1c43f0d3ac1fe70230eefac.tar.gz testbench-ui-9bc323efb9b37887e1c43f0d3ac1fe70230eefac.zip |
Diffstat (limited to 'src')
-rw-r--r-- | src/rusbtmc.rs | 81 | ||||
-rw-r--r-- | src/testbench.rs | 114 |
2 files changed, 157 insertions, 38 deletions
diff --git a/src/rusbtmc.rs b/src/rusbtmc.rs index 994ae31..124b9ff 100644 --- a/src/rusbtmc.rs +++ b/src/rusbtmc.rs @@ -80,6 +80,10 @@ pub enum Error { Request, #[error("device does not support the request type")] NotSupported, + #[error("decoding error (utf-8)")] + Decoding(#[from] std::string::FromUtf8Error), + #[error("not implemented")] + NotImplemented, } /// Get a list of connected instruments @@ -89,6 +93,7 @@ pub fn instruments() -> Result<Vec<Instrument<rusb::GlobalContext>>, Error> { Err(e) => return Err(Error::Rusb(e)), }; + let mut instruments = Vec::<Instrument<rusb::GlobalContext>>::new(); for dev in devices.iter() { @@ -103,16 +108,22 @@ pub fn instruments() -> Result<Vec<Instrument<rusb::GlobalContext>>, Error> { /// 'High level' Instrument wrapper around rusb::Device pub struct Instrument<C: rusb::UsbContext> { connected: bool, + // rusb objects pub device: rusb::Device<C>, pub handle: Option<rusb::DeviceHandle<C>>, + // usbtmc capabilites capabilities: Option<Capabilities>, + // for linux kernel has_kernel_driver: bool, + // addresses in the usb device config_num: Option<u8>, iface_num: Option<u8>, ep_bulk_in: Option<u8>, ep_bulk_out: Option<u8>, ep_interrupt_in: Option<u8>, + // btag number to keep track of packet parts btag: Wrapping<u8>, + // default timeout timeout: Duration, } @@ -161,6 +172,10 @@ impl<C: rusb::UsbContext> Instrument<C> { }) } + pub fn is_connected(&self) -> bool { + return self.connected; + } + /// Opens (searches for) the USBTMC interface of the device /// /// It loops through the available usb interfaces and uses the first that @@ -197,6 +212,7 @@ impl<C: rusb::UsbContext> Instrument<C> { // check if it is has USB488 if iface_desc.protocol_code() == USB488_BINTERFACE_PROTOCOL { // TODO + return Err(Error::NotImplemented); } self.config_num = Some(cfg_desc.number()); @@ -439,6 +455,7 @@ impl<C: rusb::UsbContext> Instrument<C> { Ok(capabilities) } + /// pub fn pulse(&mut self) -> Result<(), Error> { if !self.connected { return Err(Error::NotConnected); @@ -480,6 +497,11 @@ impl<C: rusb::UsbContext> Instrument<C> { Ok(()) } + /// Write a string to the instrument + pub fn write(&mut self, message: &str) -> Result<usize, Error> { + return self.write_raw(message.as_bytes()); + } + /// Write binary data to the instrument pub fn write_raw(&mut self, data: &[u8]) -> Result<usize, Error> { if !self.connected { @@ -512,8 +534,8 @@ impl<C: rusb::UsbContext> Instrument<C> { ); let mut packet: [u8; PACKET_SIZE] = [0; PACKET_SIZE]; - packet[..12].clone_from_slice(&header); - packet[13..].clone_from_slice(chunk); + packet[..HEADER_SIZE].clone_from_slice(&header); + packet[(HEADER_SIZE +1)..].clone_from_slice(chunk); sent_bytes += match handle.write_bulk(endpoint, &packet, self.timeout) { Ok(sent) => sent, @@ -533,8 +555,8 @@ impl<C: rusb::UsbContext> Instrument<C> { } // send remainder - let padding = (4 - (last_data.len() % 4)) % 4; - let last_data_size: usize = last_data.len() + padding; + let pad_size = (4 - (last_data.len() % 4)) % 4; + let last_data_size: usize = last_data.len() + pad_size; let header = make_bulk_header( MsgId::DeviceDependent, @@ -544,11 +566,29 @@ impl<C: rusb::UsbContext> Instrument<C> { true, ); - // TODO: pad and send last byte + let mut packet: [u8; PACKET_SIZE] = [0; PACKET_SIZE]; + packet[..HEADER_SIZE].clone_from_slice(&header); + for i in 0..last_data.len() { + packet[HEADER_SIZE + 1 + i] = last_data[i]; + } + + sent_bytes += match handle.write_bulk( + endpoint, + &packet[..(HEADER_SIZE + last_data_size)], + self.timeout + ) { + Ok(sent) => sent, + Err(e) => { + dbg!("failed to send chunk during bulk out"); + self.abort_bulk_out(); + return Err(Error::Rusb(e)); + } + }; Ok(sent_bytes) } + /// Abort a bulk-out operation fn abort_bulk_out(&mut self) -> Result<(), Error> { if !self.connected { return Err(Error::NotConnected); @@ -576,6 +616,20 @@ impl<C: rusb::UsbContext> Instrument<C> { Ok(()) } + + /// Read binary data from the device and decode into an utf-8 string + pub fn read(&mut self) -> Result<String, Error> { + let data = self.read_raw()?; + return match String::from_utf8(data) { + Ok(s) => Ok(s), + Err(e) => Err(Error::Decoding(e)), + }; + } + + /// Read binary data from the device + pub fn read_raw(&mut self) -> Result<Vec<u8>, Error> { + return Err(Error::NotImplemented); + } } /// helper function to create bulk headers @@ -610,7 +664,7 @@ fn make_bulk_header( ts_bytes[0], ts_bytes[1], ts_bytes[2], - ts_bytes[4], + ts_bytes[3], // whether this is the last chunk match is_last { true => 0x01, @@ -624,3 +678,18 @@ fn make_bulk_header( header } + +impl<C: rusb::UsbContext> std::fmt::Debug for Instrument<C> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + // TODO: complete + f.debug_struct("Instrument") + .field("connected", &self.connected) + .field("capabilities", &self.capabilities) + .field("has_kernel_driver", &self.has_kernel_driver) + .field("config_num", &self.config_num) + .field("iface_num", &self.iface_num) + .finish() + + } +} + diff --git a/src/testbench.rs b/src/testbench.rs index 5dd18fd..52f5287 100644 --- a/src/testbench.rs +++ b/src/testbench.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use imgui; use rlua::Lua; -// use serialport; +use serialport; use crate::rusbtmc; @@ -43,15 +43,16 @@ impl Test { #[derive(Default)] pub struct Bench { - // tests window + /* tests window */ lua: Lua, tests: Vec<Test>, tests_path: PathBuf, + /* devices window */ selected_test: Option<usize>, selected_usb_dev: Option<usize>, usb_devices: HashMap<imgui::ImString, rusbtmc::Instrument<rusb::GlobalContext>>, selected_serial_dev: Option<usize>, - // serial_devices: Vec<?> + // serial_devices: HashMap<imgui::ImString, (serialport::SerialPortBuilder, Option<serialport::SerialPort>>, lua_console: Vec<imgui::ImString>, instr_console: Vec<imgui::ImString>, instr_input: imgui::ImString, @@ -103,6 +104,7 @@ impl Bench { return b; } + /// Loads lua tests from tests_path pub fn load_tests(&mut self) -> io::Result<()> { let entries: Result<Vec<PathBuf>, io::Error> = fs::read_dir(&self.tests_path)? .into_iter() @@ -124,6 +126,25 @@ impl Bench { } } + fn get_selected_device<'a>( + index: Option<usize>, + list: &'a mut HashMap<imgui::ImString, rusbtmc::Instrument<rusb::GlobalContext>>, + ) -> Option<&'a mut rusbtmc::Instrument<rusb::GlobalContext>> { + let selected_index = match index { + Some(i) => i, + None => return None, + }; + + for (index, (_name, dev)) in list.iter_mut().enumerate() { + if matches!(selected_index, i if i == index) { + return Some(dev); + } + } + + return None; + } + + /// draws the entire interface pub fn draw(&mut self, _: &mut bool, ui: &mut imgui::Ui) { use imgui::*; @@ -179,7 +200,8 @@ impl Bench { .position([520., 20.], Condition::Appearing); dev_win.build(&ui, || { - // usb devices + /* usb devices */ + // buttons ui.text_wrapped(im_str!("USB Devices")); ui.same_line(0.); if ui.button(im_str!("Refresh"), [0., 0.]) { @@ -199,8 +221,8 @@ impl Bench { let handle = match instr.device.open() { Ok(handle) => handle, - Err(_) => { - // dbg!("failed to get handle"); + Err(e) => { + dbg!("failed to get handle", e); continue; } }; @@ -219,46 +241,74 @@ impl Bench { } } - 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) = Bench::get_selected_device( + self.selected_usb_dev.clone(), + &mut self.usb_devices + ) { + // TODO: handle errors + ui.same_line(0.); + if ui.button(im_str!("Open"), [0., 0.]) { + let _ = dbg!(instr.open()); } - 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!("Close"), [0., 0.]) { + let _ = dbg!(instr.close()); + } - ui.same_line(0.); - if ui.button(im_str!("Pulse"), [0., 0.]) { - let _ = dbg!(instr.pulse()); - } + 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() { + ui.separator(); + + // device list + for (index, (name, dev)) in self.usb_devices.iter().enumerate() { let selected = matches!(self.selected_usb_dev, Some(i) if i == index); + let mut label = name.clone(); + if dev.is_connected() { + label.push_str(" (connected)"); + } - if Selectable::new(&name).selected(selected).build(ui) { + if Selectable::new(&label).selected(selected).build(ui) { self.selected_usb_dev = Some(index); } + } - ui.same_line(0.); + ui.separator(); + + // serial devices + /* + ui.text_wrapped(im_str!("Serial Devices")); + ui.same_line(0.); + if ui.button(im_str!("Refresh"), [0., 0.]) { + if let Ok(ports) = serialport::available_ports() { + for port in ports { + + } + } } ui.separator(); - ui.input_text(im_str!("Device"), &mut self.instr_input).build(); + */ + + // interactive device console + ui.input_text(im_str!("Device"), &mut self.instr_input) + .build(); + ui.same_line(0.); + + if let Some(instr) = Bench::get_selected_device( + self.selected_usb_dev, + &mut self.usb_devices + ) { + if ui.button(im_str!("Send"), [0., 0.]) { + let _ = dbg!(instr.write(self.instr_input.to_str())); + self.instr_input.clear(); + } + } + ChildWindow::new("instrument console") .size([0., 0.]) .scrollable(true) |