From 4a6ddf107064c8b80b64346c15ae86f11e2fb0b7 Mon Sep 17 00:00:00 2001 From: Nao Pross Date: Wed, 7 Apr 2021 13:13:32 +0200 Subject: Delete demo lua console, start usbtmc driver --- src/main.rs | 69 +----- src/rusbtmc.rs | 626 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/support/mod.rs | 36 +-- src/testbench.rs | 162 ++++++++++++-- 4 files changed, 785 insertions(+), 108 deletions(-) create mode 100644 src/rusbtmc.rs (limited to 'src') 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, - 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::() { - Ok(values) => { - new_text.push_str( - &values - .iter() - .map(|value| format!("{:?}", value)) - .collect::>() - .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>, Error> { + let devices = match rusb::devices() { + Ok(devices) => devices, + Err(e) => return Err(Error::Rusb(e)), + }; + + let mut instruments = Vec::>::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 { + connected: bool, + pub device: rusb::Device, + pub handle: Option>, + capabilities: Option, + has_kernel_driver: bool, + config_num: Option, + iface_num: Option, + ep_bulk_in: Option, + ep_bulk_out: Option, + ep_interrupt_in: Option, + btag: Wrapping, + timeout: Duration, +} + +impl Instrument { + /// Creates and Instrument from a rusb Device + pub fn new(device: rusb::Device) -> Result, 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, 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 { + 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 { + 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 { + 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, tests_path: PathBuf, selected_test: Option, - console: Vec, + selected_usb_dev: Option, + usb_devices: HashMap>, + selected_serial_dev: Option, + // serial_devices: Vec + lua_console: Vec, + instr_console: Vec, + 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| { + let ui_print = context.create_function(|_, _strings: Variadic| { + // 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, 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 + } } -- cgit v1.2.1