From b47420a434910054c24d6010fdcfd8675c7578a5 Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Fri, 30 Aug 2024 10:05:32 -0400 Subject: [PATCH 01/10] Add kic-discovery-visa --- Cargo.lock | 29 ++ Cargo.toml | 1 + kic-discover-visa/Cargo.toml | 35 ++ kic-discover-visa/src/ethernet/mod.rs | 194 +++++++++ .../src/instrument_discovery/mod.rs | 97 +++++ kic-discover-visa/src/lib.rs | 131 ++++++ kic-discover-visa/src/main.rs | 379 ++++++++++++++++++ kic-discover-visa/src/usbtmc/mod.rs | 179 +++++++++ 8 files changed, 1045 insertions(+) create mode 100644 kic-discover-visa/Cargo.toml create mode 100644 kic-discover-visa/src/ethernet/mod.rs create mode 100644 kic-discover-visa/src/instrument_discovery/mod.rs create mode 100644 kic-discover-visa/src/lib.rs create mode 100644 kic-discover-visa/src/main.rs create mode 100644 kic-discover-visa/src/usbtmc/mod.rs diff --git a/Cargo.lock b/Cargo.lock index af8b24f..6a52aaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1303,6 +1303,35 @@ dependencies = [ "tsp-toolkit-kic-lib", ] +[[package]] +name = "kic-discover-visa" +version = "0.17.2" +dependencies = [ + "anyhow", + "async-std", + "async-trait", + "clap", + "colored", + "futures", + "futures-util", + "jsonrpsee", + "lazy_static", + "local-ip-address", + "mdns", + "minidom", + "reqwest", + "rpassword", + "rusb", + "serde", + "serde_json", + "thiserror", + "tmc", + "tokio", + "tracing", + "tracing-subscriber", + "tsp-toolkit-kic-lib", +] + [[package]] name = "kic-visa" version = "0.17.2" diff --git a/Cargo.toml b/Cargo.toml index ac057fd..dae00a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "kic", "kic-visa", "kic-discover", + "kic-discover-visa", "instrument-repl", ] resolver = "2" diff --git a/kic-discover-visa/Cargo.toml b/kic-discover-visa/Cargo.toml new file mode 100644 index 0000000..41e93b3 --- /dev/null +++ b/kic-discover-visa/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "kic-discover-visa" +description = "A tool to discover the instruments that are connect to your computer or are on your network" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +async-std = "1.12.0" +async-trait = "0.1.57" +clap = { workspace = true } +colored = { workspace = true } +futures = "0.3.30" +futures-util = "0.3.30" +jsonrpsee = { workspace = true } +lazy_static = "1.4.0" +local-ip-address = { workspace = true } +mdns = { workspace = true } # until https://github.com/dylanmckay/mdns/pull/27 is closed +minidom = { workspace = true } +reqwest = { workspace = true } +rpassword = { workspace = true } +rusb = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +tmc = { workspace = true } +tokio = { version = "1.36.0", features = ["full"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +tsp-toolkit-kic-lib = { workspace = true, features = ["visa"] } + diff --git a/kic-discover-visa/src/ethernet/mod.rs b/kic-discover-visa/src/ethernet/mod.rs new file mode 100644 index 0000000..a3b57e0 --- /dev/null +++ b/kic-discover-visa/src/ethernet/mod.rs @@ -0,0 +1,194 @@ +use futures::future::join_all; +use futures_util::{pin_mut, stream::StreamExt}; +use local_ip_address::list_afinet_netifas; +use mdns::{Record, RecordKind}; +use minidom::Element; +use serde::{Deserialize, Serialize}; +use std::hash::Hash; +use std::net::{IpAddr, Ipv4Addr}; +use std::{collections::HashSet, time::Duration}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; + +use crate::{insert_disc_device, model_check, IoType}; + +pub const COMM_PORT: u16 = 5025; +pub const DST_PORT: u16 = 5030; +pub const SERVICE_NAMES: [&str; 3] = [ + "_scpi-raw._tcp.local", + "_lxi._tcp.local", + "_vxi-11._tcp.local", + //"_scpi-telnet._tcp.local", +]; + +#[allow(clippy::unsafe_derive_deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Serialize, Deserialize)] +pub struct LxiDeviceInfo { + io_type: IoType, + pub instr_address: IpAddr, + pub manufacturer: String, + pub model: String, + pub serial_number: String, + pub firmware_revision: String, + socket_port: String, + instr_categ: String, +} + +impl LxiDeviceInfo { + async fn discover_devices( + service_name: &str, + interface_ip: Ipv4Addr, + device_tx: UnboundedSender, + ) -> Result<(), Box> { + // The hostname of the devices we are searching for. + // Every lxi compliant instrument will respond to this service name. + //Send out a broadcast every X milliseconds for LXI instruments over mDNS + + let stream = + mdns::discover::interface(service_name, Duration::from_millis(500), interface_ip)? + .listen(); + pin_mut!(stream); + + while let Some(Ok(response)) = stream.next().await { + #[cfg(debug_assertions)] + eprintln!("Found Instrument: {response:?}"); + let addr: Option = response.records().find_map(Self::to_ip_addr); + + if let Some(addr) = addr { + #[cfg(debug_assertions)] + eprintln!("Querying for LXI identification XML page for {addr}"); + if let Some(xmlstr) = Self::query_lxi_xml(addr).await { + if let Some(instr) = Self::parse_lxi_xml(&xmlstr, addr) { + if let Ok(out_str) = serde_json::to_string(&instr) { + insert_disc_device(out_str.as_str())?; + } + // Send devices back as we discover them + device_tx.send(instr)?; + } + } + } + } + + Ok(()) + } + + pub async fn query_lxi_xml(instr_addr: IpAddr) -> Option { + let uri = format!("http://{instr_addr}/lxi/identification"); + if let Ok(resp) = reqwest::get(uri).await { + if let Ok(resp_text) = resp.text().await { + return Some(resp_text); + } + } + None + } + + #[must_use] + pub fn parse_lxi_xml(xml_data: &str, instr_addr: IpAddr) -> Option { + const DEVICE_NS: &str = "http://www.lxistandard.org/InstrumentIdentification/1.0"; + if let Ok(root) = xml_data.parse::() { + if root.is("LXIDevice", DEVICE_NS) { + let manufacturer = root + .get_child("Manufacturer", DEVICE_NS) + .unwrap_or(&minidom::Element::bare("FirmwareRevision", DEVICE_NS)) + .text(); + let model = root + .get_child("Model", DEVICE_NS) + .unwrap_or(&minidom::Element::bare("FirmwareRevision", DEVICE_NS)) + .text(); + let serial_number = root + .get_child("SerialNumber", DEVICE_NS) + .unwrap_or(&minidom::Element::bare("FirmwareRevision", DEVICE_NS)) + .text(); + let firmware_revision = root + .get_child("FirmwareRevision", DEVICE_NS) + .unwrap_or(&minidom::Element::bare("FirmwareRevision", DEVICE_NS)) + .text(); + + let s1: Vec<&str> = xml_data.split("::SOCKET").collect(); + let port_split: Vec<&str> = s1[0].split("::").collect(); + let socket_port = if port_split.is_empty() { + COMM_PORT.to_string() + } else { + port_split[port_split.len().saturating_sub(1)].to_string() + }; + + //ToDo: test versatest when it's discoverable + let res = model_check(model.as_str()); + + if manufacturer.to_ascii_lowercase().contains("keithley") && res.0 { + let device = Self { + io_type: IoType::Lan, + instr_address: instr_addr, + manufacturer, + model, + serial_number, + firmware_revision, + socket_port, + instr_categ: res.1.to_string(), + }; + //println!("{:?}", device); + return Some(device); + } + } + } + None + } + + fn to_ip_addr(record: &Record) -> Option { + match record.kind { + //A refers to Ipv4 address and AAAA refers to Ipv6 address + RecordKind::A(addr) => Some(addr.into()), + RecordKind::AAAA(addr) => Some(addr.into()), + _ => None, + } + } +} + +impl LxiDeviceInfo { + ///Discover LXI devices + /// + ///# Errors + ///Possible errors include but are not limited to those generated by trying + ///to gather the network interface IPs to iterate over for our search. + pub async fn discover(timeout: Option) -> anyhow::Result> { + let timeout = timeout.unwrap_or(Duration::new(5, 0)); + + let mut discover_futures = Vec::new(); + + let interfaces = match list_afinet_netifas() { + Ok(ips) => ips, + Err(e) => return Err(Box::new(e).into()), + }; + + let (device_tx, mut device_rx) = unbounded_channel(); + + 'interface_loop: for (name, ip) in interfaces { + for service_name in SERVICE_NAMES { + #[cfg(debug_assertions)] + eprintln!("Looking for {service_name} on {name} ({ip})"); + if let IpAddr::V4(ip) = ip { + discover_futures.push(Self::discover_devices( + service_name, + ip, + device_tx.clone(), + )); + } else { + continue 'interface_loop; + } + } + } + + let mut devices: HashSet = HashSet::new(); + + // ignore the error from the timeout since that is our method of stopping execution of the futures + // This if statement prevents a must_use warning + let _ = tokio::time::timeout(timeout, join_all(discover_futures)).await; + // need to drop the last sender or else the while loop will spin forever + // The other Senders were cloned from this one + drop(device_tx); + while let Some(device) = device_rx.recv().await { + devices.insert(device); + } + + Ok(devices) + } +} diff --git a/kic-discover-visa/src/instrument_discovery/mod.rs b/kic-discover-visa/src/instrument_discovery/mod.rs new file mode 100644 index 0000000..4f59f5e --- /dev/null +++ b/kic-discover-visa/src/instrument_discovery/mod.rs @@ -0,0 +1,97 @@ +use std::net::SocketAddr; +use std::{collections::HashSet, time::Duration}; + +use tsp_toolkit_kic_lib::instrument::info::{ConnectionAddr, InstrumentInfo}; + +use crate::ethernet::{LxiDeviceInfo, COMM_PORT}; +use crate::usbtmc::Usbtmc; + +#[derive(Debug)] +pub struct InstrumentDiscovery { + timeout: Option, +} + +impl InstrumentDiscovery { + #[must_use] + pub const fn new(timeout: Duration) -> Self { + Self { + timeout: Some(timeout), + } + } + + // pub async fn discover(&self) -> anyhow::Result> + // where + // T: Discover, + // { + // let mut discovery_results: HashSet = HashSet::new(); + // match T::discover(self.timeout).await { + // Ok(instrs) => { + // for inst in instrs { + // discovery_results.insert(inst); + // } + // } + // Err(e) => { + // eprintln!("Unable to discover LXI devices: {e}"); //TODO add color + // return Err(e); + // } + // }; + // Ok(discovery_results) + // } + + /// Discover instruments on the network. + /// + /// # Errors + /// If [`LxiDeviceInfo::discover`] fails, an error will be returned + pub async fn lan_discover(&self) -> anyhow::Result> { + let mut discovery_results: HashSet = HashSet::new(); + + match LxiDeviceInfo::discover(self.timeout).await { + Ok(instrs) => { + for inst in instrs { + discovery_results.insert(inst.into()); + } + } + Err(e) => { + eprintln!("Unable to discover LXI devices: {e}"); //TODO add color + return Err(e); + } + }; + Ok(discovery_results) + } + + /// Discover instruments over USB + /// + /// # Errors + /// If [`Usbtmc::usb_discover`] fails, and error will be returned. + pub async fn usb_discover(&self) -> anyhow::Result> { + let mut discovery_results: HashSet = HashSet::new(); + + match Usbtmc::usb_discover(self.timeout).await { + Ok(instrs) => { + for inst in instrs { + discovery_results.insert(inst); + } + } + Err(e) => { + eprintln!("Unable to discover USB devices: {e}"); //TODO add color + return Err(e); + } + } + Ok(discovery_results) + } +} + +impl From for InstrumentInfo { + fn from(lxi_info: LxiDeviceInfo) -> Self { + Self { + vendor: Some(lxi_info.manufacturer), + model: Some(lxi_info.model), + serial_number: Some(lxi_info.serial_number), + firmware_rev: Some(lxi_info.firmware_revision), + address: Some(ConnectionAddr::Lan(SocketAddr::new( + lxi_info.instr_address, + COMM_PORT, + ))), + } + } +} diff --git a/kic-discover-visa/src/lib.rs b/kic-discover-visa/src/lib.rs new file mode 100644 index 0000000..bd4d176 --- /dev/null +++ b/kic-discover-visa/src/lib.rs @@ -0,0 +1,131 @@ +use std::{collections::HashSet, hash::Hash, io::Error, sync::Mutex}; + +pub mod ethernet; +pub mod instrument_discovery; +pub mod usbtmc; + +#[macro_use] +extern crate lazy_static; + +lazy_static! { + static ref SUPPORTED_SET: HashSet<&'static str> = { + HashSet::from([ + "2601", + "2602", + "2611", + "2612", + "2635", + "2636", + "2601A", + "2602A", + "2611A", + "2612A", + "2635A", + "2636A", + "2651A", + "2657A", + "2601B", + "2601B-PULSE", + "2602B", + "2606B", + "2611B", + "2612B", + "2635B", + "2636B", + "2604B", + "2614B", + "2634B", + "2601B-L", + "2602B-L", + "2611B-L", + "2612B-L", + "2635B-L", + "2636B-L", + "2604B-L", + "2614B-L", + "2634B-L", + "3706", + "3706-SNFP", + "3706-S", + "3706-NFP", + "3706A", + "3706A-SNFP", + "3706A-S", + "3706A-NFP", + "707B", + "708B", + ]) + }; + static ref SUPPORTED_NIMITZ_SET: HashSet<&'static str> = { + HashSet::from([ + "2450", "2470", "DMM7510", "2460", "2461", "2461-SYS", "DMM7512", "DMM6500", "DAQ6510", + ]) + }; + + //TODO : Remove the TSP entry when LXI page for versatest is available + static ref SUPPORTED_VERSATEST_SET: HashSet<&'static str> = { + HashSet::from([ + "VERSATEST-300", "VERSATEST-600", "TSP", + ]) + }; + pub static ref DISC_INSTRUMENTS: Mutex> = Mutex::new(HashSet::new()); +} + +#[must_use] +pub fn model_check(in_str: &str) -> (bool, &'static str) { + if let Some(model_split) = in_str + .to_ascii_uppercase() + .as_str() + .split_ascii_whitespace() + .last() + { + if SUPPORTED_SET.contains(model_split) { + return (true, "tti/26xx"); + } else if SUPPORTED_VERSATEST_SET.contains(in_str) { + return (true, "versatest"); + } + return (is_nimitz(in_str), "tti/26xx"); + } + (false, "") +} + +//Nimitz model is the set of instruments that were part of Nimitz program +//Nimitz is also known as the TTI platform +#[must_use] +pub fn is_nimitz(in_str: &str) -> bool { + if let Some(model_split) = in_str + .to_ascii_uppercase() + .as_str() + .split_ascii_whitespace() + .last() + { + if SUPPORTED_NIMITZ_SET.contains(model_split) { + return true; + } + } + false +} + +/// Insert a discovered device into our map of instruments +/// +/// # Errors +/// If we fail to lock the `DISC_INSTRUMENTS` variable, a [`std::io::Error`] +/// with [`std::io::ErrorKind::PermissionDenied`] will be returned. +pub fn insert_disc_device(device: &str) -> Result<(), Error> { + DISC_INSTRUMENTS + .lock() + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "failed to acquire".to_string(), + ) + })? + .insert(device.to_string()); + Ok(()) +} + +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, serde::Serialize, serde::Deserialize)] +enum IoType { + Lan, + Usb, +} diff --git a/kic-discover-visa/src/main.rs b/kic-discover-visa/src/main.rs new file mode 100644 index 0000000..0b3b2f8 --- /dev/null +++ b/kic-discover-visa/src/main.rs @@ -0,0 +1,379 @@ +use anyhow::Context; +use async_std::{path::PathBuf, task::sleep}; +use jsonrpsee::{ + server::{Server, ServerHandle}, + RpcModule, +}; +use kic_discover_visa::instrument_discovery::InstrumentDiscovery; +use tracing::{error, info, instrument, level_filters::LevelFilter, trace, warn}; +use tracing_subscriber::{layer::SubscriberExt, Layer, Registry}; +use tsp_toolkit_kic_lib::instrument::info::InstrumentInfo; + +use std::fs::OpenOptions; +use std::str; +use std::time::Duration; +use std::{ + collections::HashSet, + net::{SocketAddr, TcpStream}, + sync::Mutex, +}; + +use clap::{command, Args, Command, FromArgMatches, Parser, Subcommand}; + +use kic_discover_visa::DISC_INSTRUMENTS; + +#[derive(Debug, Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Enable logging to stderr. Can be used in conjunction with `--log-file` and `--verbose`. + #[arg(global = true, short = 'v', long = "verbose")] + verbose: bool, + + /// Log to the given log file path. Can be used in conjunction with `--log-socket` and `--verbose`. + #[arg(name = "log-file", global = true, short = 'l', long = "log-file")] + log_file: Option, + + /// Log to the given socket (in IPv4 or IPv6 format with port number). Can be used in conjunction with `--log-file` and `--verbose`. + #[arg(name = "log-socket", global = true, short = 's', long = "log-socket")] + log_socket: Option, + + #[command(subcommand)] + conn: SubCli, +} + +#[derive(Debug, Subcommand)] +enum SubCli { + /// Look for all devices connected on LAN + Lan(DiscoverCmd), + /// Look for all devices connected on USB + Usb(DiscoverCmd), + /// Look for all devices on all interface types. + All(DiscoverCmd), +} + +#[derive(Debug, Args, Clone, PartialEq)] +pub(crate) struct DiscoverCmd { + /// Enable logging to stderr. Can be used in conjunction with `--log-file` and `--verbose`. + #[arg(from_global)] + verbose: bool, + + /// Log to the given log file path. Can be used in conjunction with `--log-socket` and `--verbose`. + #[clap(name = "log-file", from_global)] + log_file: Option, + + /// Log to the given socket (in IPv4 or IPv6 format with port number). Can be used in conjunction with `--log-file` and `--verbose`. + #[clap(name = "log-socket", from_global)] + log_socket: Option, + + /// Print JSON-encoded instrument information. + #[clap(long)] + json: bool, + + /// The number of seconds to wait for instrument to be discovered. + /// If not specified, run continuously until the application is signalled. + #[clap(name = "seconds", long = "timeout", short = 't')] + timeout_secs: Option, + + /// This parameter specifies whether we need to wait for a few seconds before closing the json rpc connection. + /// If not specified, last few instruments discovered may not make it to the discovery pane UI. + #[clap(name = "exit", long, action)] + exit: bool, +} + +fn start_logger( + verbose: &bool, + log_file: &Option, + log_socket: &Option, +) -> anyhow::Result<()> { + match (verbose, log_file, log_socket) { + (true, Some(l), Some(s)) => { + let err = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_writer(std::io::stderr) + .with_filter(LevelFilter::INFO); + + let log = OpenOptions::new().append(true).create(true).open(l)?; + + let log = tracing_subscriber::fmt::layer() + .with_writer(log) + .fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new()) + .with_ansi(false); + + let sock = TcpStream::connect(s)?; + let sock = tracing_subscriber::fmt::layer() + .with_writer(Mutex::new(sock)) + .fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new()) + .json(); + + let logger = Registry::default() + .with(LevelFilter::TRACE) + .with(err) + .with(log) + .with(sock); + + tracing::subscriber::set_global_default(logger)?; + } + (true, Some(l), None) => { + let err = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_writer(std::io::stderr) + .with_filter(LevelFilter::INFO); + + let log = OpenOptions::new().append(true).create(true).open(l)?; + + let log = tracing_subscriber::fmt::layer() + .with_writer(log) + .fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new()) + .with_ansi(false); + + let logger = Registry::default() + .with(LevelFilter::TRACE) + .with(err) + .with(log); + + tracing::subscriber::set_global_default(logger)?; + } + (false, Some(l), Some(s)) => { + let log = OpenOptions::new().append(true).create(true).open(l)?; + + let log = tracing_subscriber::fmt::layer() + .with_writer(log) + .with_ansi(false); + + let sock = TcpStream::connect(s)?; + let sock = tracing_subscriber::fmt::layer() + .with_writer(Mutex::new(sock)) + .fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new()) + .json(); + + let logger = Registry::default() + .with(LevelFilter::TRACE) + .with(log) + .with(sock); + + tracing::subscriber::set_global_default(logger)?; + } + (false, Some(l), None) => { + let log = OpenOptions::new().append(true).create(true).open(l)?; + + let log = tracing_subscriber::fmt::layer() + .with_writer(log) + .with_ansi(false); + + let logger = Registry::default().with(LevelFilter::TRACE).with(log); + + tracing::subscriber::set_global_default(logger)?; + } + (true, None, Some(s)) => { + let err = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_writer(std::io::stderr); + + let sock = TcpStream::connect(s)?; + let sock = tracing_subscriber::fmt::layer() + .with_writer(Mutex::new(sock)) + .fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new()) + .json(); + + let logger = Registry::default() + .with(LevelFilter::TRACE) + .with(err) + .with(sock); + + tracing::subscriber::set_global_default(logger)?; + } + (true, None, None) => { + let err = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_writer(std::io::stderr); + + let logger = Registry::default().with(LevelFilter::TRACE).with(err); + + tracing::subscriber::set_global_default(logger)?; + } + (false, None, Some(s)) => { + let sock = TcpStream::connect(s)?; + let sock = tracing_subscriber::fmt::layer() + .with_writer(Mutex::new(sock)) + .fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new()) + .json(); + + let logger = Registry::default().with(LevelFilter::TRACE).with(sock); + + tracing::subscriber::set_global_default(logger)?; + } + (false, None, None) => {} + } + + info!("Application started"); + trace!( + "Application starting with the following args: {:?}", + std::env::args() + ); + Ok(()) +} + +#[tokio::main] +#[instrument] +async fn main() -> anyhow::Result<()> { + let cmd = command!() + .propagate_version(true) + .subcommand_required(true) + .allow_external_subcommands(true); + + let cmd = Cli::augment_args(cmd); + let cmd = cmd.subcommand(Command::new("print-description").hide(true)); + + let matches = cmd.clone().get_matches(); + + if let Some(("print-description", _)) = matches.subcommand() { + println!("{}", cmd.get_about().unwrap_or_default()); + return Ok(()); + } + + let sub = SubCli::from_arg_matches(&matches) + .map_err(|err| err.exit()) + .unwrap(); + + eprintln!("Keithley Instruments Discovery"); + let close_handle = init_rpc() + .await + .context("Unable to start JSON RPC server")?; + + let is_exit_timer = require_exit_timer(&sub); + + match sub { + SubCli::Lan(args) => { + start_logger(&args.verbose, &args.log_file, &args.log_socket)?; + info!("Discovering LAN instruments"); + #[allow(clippy::mutable_key_type)] + let lan_instruments = match discover_lan(args).await { + Ok(i) => i, + Err(e) => { + error!("Error in LAN discovery: {e}"); + return Err(e); + } + }; + info!("LAN Discovery complete"); + trace!("Discovered {} LAN instruments", lan_instruments.len()); + println!("Discovered {} LAN instruments", lan_instruments.len()); + trace!("Discovered instruments: {lan_instruments:?}"); + for instrument in lan_instruments { + println!("{instrument}"); + } + } + SubCli::Usb(args) => { + start_logger(&args.verbose, &args.log_file, &args.log_socket)?; + info!("Discovering USB instruments"); + #[allow(clippy::mutable_key_type)] + let usb_instruments = match discover_usb().await { + Ok(i) => i, + Err(e) => { + error!("Error in USB discovery: {e}"); + return Err(e); + } + }; + info!("USB Discovery complete"); + trace!("Discovered {} USB instruments", usb_instruments.len()); + trace!("Discovered instruments: {usb_instruments:?}"); + for instrument in usb_instruments { + println!("{instrument}"); + } + } + SubCli::All(args) => { + start_logger(&args.verbose, &args.log_file, &args.log_socket)?; + info!("Discovering USB instruments"); + #[allow(clippy::mutable_key_type)] + let usb_instruments = match discover_usb().await { + Ok(i) => i, + Err(e) => { + error!("Error in USB discovery: {e}"); + return Err(e); + } + }; + info!("USB Discovery complete"); + trace!("Discovered {} USB instruments", usb_instruments.len()); + println!("Discovered {} USB instruments", usb_instruments.len()); + trace!("Discovered USB instruments: {usb_instruments:?}"); + for instrument in usb_instruments { + println!("{instrument}"); + } + + info!("Discovering LAN instruments"); + #[allow(clippy::mutable_key_type)] + let lan_instruments = match discover_lan(args).await { + Ok(i) => i, + Err(e) => { + error!("Error in LAN discovery: {e}"); + return Err(e); + } + }; + info!("LAN Discovery complete"); + trace!("Discovered {} LAN instruments", lan_instruments.len()); + println!("Discovered {} LAN instruments", lan_instruments.len()); + trace!("Discovered LAN instruments: {lan_instruments:?}"); + for instrument in lan_instruments { + println!("{instrument}"); + } + } + } + + if is_exit_timer { + sleep(Duration::from_secs(5)).await; + } + close_handle.stop()?; + + info!("Discovery complete"); + + Ok(()) +} + +const fn require_exit_timer(sub: &SubCli) -> bool { + if let SubCli::All(args) = sub { + if args.exit { + return true; + } + } + false +} + +async fn init_rpc() -> anyhow::Result { + let server = Server::builder().build("127.0.0.1:3030").await?; + + let mut module = RpcModule::new(()); + module.register_method("get_instr_list", |_, ()| { + let mut new_out_str = String::new(); + + if let Ok(db) = DISC_INSTRUMENTS.lock() { + db.iter() + .for_each(|item| new_out_str = format!("{new_out_str}{item}\n")); + }; + + #[cfg(debug_assertions)] + eprintln!("newoutstr = {new_out_str}"); + + serde_json::Value::String(new_out_str) + })?; + + let handle = server.start(module); + + tokio::spawn(handle.clone().stopped()); + + Ok(handle) +} + +async fn discover_lan(args: DiscoverCmd) -> anyhow::Result> { + let dur = Duration::from_secs(args.timeout_secs.unwrap_or(20) as u64); + let discover_instance = InstrumentDiscovery::new(dur); + let instruments = discover_instance.lan_discover().await?; + + Ok(instruments) +} + +async fn discover_usb() -> anyhow::Result> { + let dur = Duration::from_secs(5); //Not used in USB + let discover_instance = InstrumentDiscovery::new(dur); + let instruments = discover_instance.usb_discover().await?; + + Ok(instruments) +} diff --git a/kic-discover-visa/src/usbtmc/mod.rs b/kic-discover-visa/src/usbtmc/mod.rs new file mode 100644 index 0000000..26516b6 --- /dev/null +++ b/kic-discover-visa/src/usbtmc/mod.rs @@ -0,0 +1,179 @@ +use serde::{Deserialize, Serialize}; +use std::{collections::HashSet, hash::Hash, time::Duration}; +use tmc::{list_instruments, Instrument, InstrumentHandle, TMCError, TMCResult}; +use tsp_toolkit_kic_lib::{ + instrument::info::{ConnectionAddr, InstrumentInfo}, + usbtmc::UsbtmcAddr, +}; + +use crate::{insert_disc_device, model_check, IoType}; + +pub struct Usbtmc { + device: rusb::Device, + #[allow(dead_code)] + handle: Option>, + pub unique_string: String, +} + +impl Usbtmc { + /// Construct a new [`Usbtmc`] device that holds the details of a USB + /// device. + /// + /// # Errors + /// This will return a [`TMCResult`] error if the details of the device + /// cannot be fetched from the USB subsystem. This can occur for platform + /// dependent reasons such as the device being assigned an unsupported + /// USB driver in Windows or the device not having proper udev rules created + /// in Linux. + pub fn new(device: rusb::Device) -> TMCResult { + let vendor = device.device_descriptor()?.vendor_id(); + let product = device.device_descriptor()?.product_id(); + let address = device.address(); + Ok(Self { + device, + handle: None, + unique_string: format!("{vendor:X}:{product:X}:{address}"), + }) + } + + /// Discover instruments connected via USB. + /// + /// # Errors + /// Errors can occur if + /// - [`rusb::Context`] has an error during construction + /// - [`list_instruments`] has an error + #[allow(clippy::unused_async)] // to keep API consistent + pub async fn usb_discover( + _timeout: Option, + ) -> anyhow::Result> { + let context = match rusb::Context::new() { + Ok(x) => x, + Err(e) => { + return Err(Box::new(e).into()); + } + }; + let instruments = match list_instruments(context) { + Ok(x) => x, + Err(e) => { + return Err(Box::new(e).into()); + } + }; + let mut discovered_instrs: HashSet = HashSet::new(); + + if instruments.is_empty() { + eprintln!("No instruments found"); + //return Ok(()); + } + + // We allow the unused mut here because it is only unused in release mode. + #[allow(unused_mut)] + for mut instrument in instruments { + #[cfg(debug_assertions)] + eprintln!( + "Found instrument: {}", + instrument + .read_resource_string() + .unwrap_or_else(|_| String::from("[UNKNOWN]")) + ); + let manufacturer = instrument + .read_manufacturer_string()? + .unwrap_or_else(|| String::from("NA")); + let firmware_revision = instrument + .read_device_version()? + .map_or_else(|| String::from("NA"), |version| version.to_string()); + let model = String::from(model_lut(instrument.device_desc.product_id())); + let serial_number = instrument + .read_serial_number()? + .unwrap_or_else(|| String::from("NA")) + .clone(); + + let tmc_instr: Result = instrument.try_into(); + + //ToDo: test versatest when it's discoverable + let res = model_check(model.as_str()); + if manufacturer.to_ascii_lowercase().contains("keithley") && res.0 { + if let Ok(mut instr) = tmc_instr { + let usb_info = UsbDeviceInfo { + io_type: IoType::Usb, + instr_address: instr.unique_string.clone(), + manufacturer, + model, + serial_number, + firmware_revision, + instr_categ: res.1.to_string(), + }; + if let Ok(out_str) = serde_json::to_string(&usb_info) { + insert_disc_device(out_str.as_str())?; + } + let usbtmc_addr = UsbtmcAddr { + device: instr.device, + model: usb_info.model.clone(), + serial: usb_info.serial_number.clone(), + }; + let disc_usb_inst = InstrumentInfo { + vendor: Some(usb_info.manufacturer), + model: Some(usb_info.model), + serial_number: Some(usb_info.serial_number.clone()), + firmware_rev: Some(usb_info.firmware_revision), + address: Some(ConnectionAddr::Usbtmc(usbtmc_addr.clone())), + }; + discovered_instrs.insert(disc_usb_inst); + } + } + } + Ok(discovered_instrs) + } +} + +impl TryFrom> for Usbtmc { + type Error = TMCError; + + fn try_from(value: Instrument) -> Result { + Self::new(value.device) + } +} + +const fn model_lut(pid: u16) -> &'static str { + match pid { + 0x3706 => "3706", + 0xCA7C => "4210-CVU-ACU", + 0x707A => "707A", + 0x707B => "707B", + 0x2100 => "2100", + 0x2110 => "2110", + 0x3390 => "3390", + 0x488B => "K-USB-488B", + 0x2450 => "2450", + 0x2460 => "2460", + 0x2461 => "2461", + 0x1642 => "2461-SYS", + 0x2470 => "2470", + 0x2601 => "2601", + 0x26F1 => "2601B-PULSE", + 0x2602 => "2602B", + 0x2604 => "2604B", + 0x2611 => "2611B", + 0x2612 => "2612B", + 0x2614 => "2614B", + 0x2634 => "2634B", + 0x2635 => "2635B", + 0x2636 => "2636B", + 0x426C => "4200A-CVIV", + 0x6500 => "DMM6500", + 0x6510 => "DAQ6510", + 0x7500 => "DMM7500", + 0x7512 => "DMM7512", + _ => "UNKNOWN", + } +} + +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Serialize, Deserialize)] +pub struct UsbDeviceInfo { + io_type: IoType, + instr_address: String, + manufacturer: String, + model: String, + serial_number: String, + firmware_revision: String, + instr_categ: String, +} From 8edebd8f6c95c8ccfe7007a2dee7a076beccc0e4 Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Fri, 30 Aug 2024 10:06:02 -0400 Subject: [PATCH 02/10] Inform GitHub What a TSP Is --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitattributes b/.gitattributes index 16c6b11..74812b8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,6 @@ # Tell GitLab that tsp files should be highlighted as Lua *.tsp gitlab-language=lua + +# Tell Github that tsp files should be highlighted as Lua +*.tsp linguist-language=Lua From cc2e202f3e29f1fb394ff8915b5363755a8b9770 Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 11:15:02 -0400 Subject: [PATCH 03/10] Create kic-discover-visa --- Cargo.lock | 93 ++++++++++--------- Cargo.toml | 11 +-- kic-discover-visa/Cargo.toml | 1 + .../src/instrument_discovery/mod.rs | 5 + kic-discover-visa/src/lib.rs | 1 + kic-discover-visa/src/main.rs | 28 ++++++ kic-discover-visa/src/visa.rs | 29 ++++++ 7 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 kic-discover-visa/src/visa.rs diff --git a/Cargo.lock b/Cargo.lock index 6a52aaa..f47bec2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ dependencies = [ "futures-lite 2.3.0", "parking", "polling 3.7.3", - "rustix 0.38.34", + "rustix 0.38.35", "slab", "tracing", "windows-sys 0.59.0", @@ -229,7 +229,7 @@ dependencies = [ "cfg-if 1.0.0", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.48.0", ] @@ -245,7 +245,7 @@ dependencies = [ "cfg-if 1.0.0", "futures-core", "futures-io", - "rustix 0.38.34", + "rustix 0.38.35", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -308,13 +308,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -419,9 +419,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.14" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" dependencies = [ "shlex", ] @@ -483,7 +483,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -779,7 +779,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1026,9 +1026,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", @@ -1112,9 +1112,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1330,6 +1330,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tsp-toolkit-kic-lib", + "visa-rs", ] [[package]] @@ -1398,9 +1399,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-ip-address" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136ef34e18462b17bf39a7826f8f3bbc223341f8e83822beb8b77db9a3d49696" +checksum = "b435d7dd476416a905f9634dff8c330cee8d3168fdd1fbd472a17d1a75c00c3e" dependencies = [ "libc", "neli", @@ -1574,14 +1575,14 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "object" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -1621,7 +1622,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1713,7 +1714,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1742,7 +1743,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1800,7 +1801,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.34", + "rustix 0.38.35", "tracing", "windows-sys 0.59.0", ] @@ -1816,9 +1817,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] @@ -2063,9 +2064,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.6.0", "errno", @@ -2105,9 +2106,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" dependencies = [ "ring", "rustls-pki-types", @@ -2198,7 +2199,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2367,9 +2368,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2427,7 +2428,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand 2.1.1", "once_cell", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.59.0", ] @@ -2448,7 +2449,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2488,9 +2489,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2512,7 +2513,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2570,9 +2571,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "toml_datetime", @@ -2627,7 +2628,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2687,7 +2688,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tsp-toolkit-kic-lib" version = "0.17.2" -source = "git+https://github.com/tektronix/tsp-toolkit-kic-lib.git?tag=v0.17.2-0#36cd744a5eb6f80dc176aff8504e6888d9dbb847" +source = "git+https://github.com/tektronix/tsp-toolkit-kic-lib.git?branch=task/discover-visa#b9b78cbb64b7f1a5f84832a027e63ca71ce0e7ed" dependencies = [ "bytes", "chrono", @@ -2858,7 +2859,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -2892,7 +2893,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3124,9 +3125,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -3149,7 +3150,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dae00a3..5f87dc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,13 +24,9 @@ clap = { version = "4.5.9", features = ["derive", "cargo", "string"] } colored = "2.1.0" exitcode = "1.1.2" instrument-repl = { path = "instrument-repl" } -jsonrpsee = { version = "0.22.3", features = [ - "tokio", - "tracing", - "server", -] } +jsonrpsee = { version = "0.22.3", features = ["tokio", "tracing", "server"] } local-ip-address = "0.6.1" -mdns = { git = "https://github.com/jaime-sense/mdns", rev = "498cf7cceaf7e2683a2e352b7e8a15dfc1c29037" } # until https://github.com/dylanmckay/mdns/pull/27 is closed +mdns = { git = "https://github.com/jaime-sense/mdns", rev = "498cf7cceaf7e2683a2e352b7e8a15dfc1c29037" } # until https://github.com/dylanmckay/mdns/pull/27 is closed minidom = "0.15.2" mockall = { version = "0.12.1", features = ["nightly"] } phf = { version = "0.11.2", features = ["macros"] } @@ -44,7 +40,7 @@ thiserror = "1.0.58" tmc = { git = "https://github.com/esarver/rusb-usbtmc" } tracing = { version = "0.1.40", features = ["async-await"] } tracing-subscriber = { version = "0.3.18", features = ["json"] } -tsp-toolkit-kic-lib = { git = "https://github.com/tektronix/tsp-toolkit-kic-lib.git", tag = "v0.17.2-0" } +tsp-toolkit-kic-lib = { git = "https://github.com/tektronix/tsp-toolkit-kic-lib.git", branch = "task/discover-visa" } [workspace.lints.rust] @@ -59,4 +55,3 @@ arithmetic_side_effects = "deny" [workspace.lints.rustdoc] all = "warn" missing_doc_code_examples = "warn" - diff --git a/kic-discover-visa/Cargo.toml b/kic-discover-visa/Cargo.toml index 41e93b3..463ce44 100644 --- a/kic-discover-visa/Cargo.toml +++ b/kic-discover-visa/Cargo.toml @@ -32,4 +32,5 @@ tokio = { version = "1.36.0", features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } tsp-toolkit-kic-lib = { workspace = true, features = ["visa"] } +visa-rs = "0.6.2" diff --git a/kic-discover-visa/src/instrument_discovery/mod.rs b/kic-discover-visa/src/instrument_discovery/mod.rs index 4f59f5e..d0cb375 100644 --- a/kic-discover-visa/src/instrument_discovery/mod.rs +++ b/kic-discover-visa/src/instrument_discovery/mod.rs @@ -5,6 +5,7 @@ use tsp_toolkit_kic_lib::instrument::info::{ConnectionAddr, InstrumentInfo}; use crate::ethernet::{LxiDeviceInfo, COMM_PORT}; use crate::usbtmc::Usbtmc; +use crate::visa::visa_discover; #[derive(Debug)] pub struct InstrumentDiscovery { @@ -79,6 +80,10 @@ impl InstrumentDiscovery { } Ok(discovery_results) } + + pub async fn visa_discover(&self) -> anyhow::Result> { + visa_discover(self.timeout).await + } } impl From for InstrumentInfo { diff --git a/kic-discover-visa/src/lib.rs b/kic-discover-visa/src/lib.rs index bd4d176..c70d6fa 100644 --- a/kic-discover-visa/src/lib.rs +++ b/kic-discover-visa/src/lib.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, hash::Hash, io::Error, sync::Mutex}; pub mod ethernet; pub mod instrument_discovery; pub mod usbtmc; +pub mod visa; #[macro_use] extern crate lazy_static; diff --git a/kic-discover-visa/src/main.rs b/kic-discover-visa/src/main.rs index 0b3b2f8..bf78d3c 100644 --- a/kic-discover-visa/src/main.rs +++ b/kic-discover-visa/src/main.rs @@ -47,6 +47,8 @@ enum SubCli { Lan(DiscoverCmd), /// Look for all devices connected on USB Usb(DiscoverCmd), + /// Look for all devices that can be connected to via the installed VISA driver + Visa(DiscoverCmd), /// Look for all devices on all interface types. All(DiscoverCmd), } @@ -280,6 +282,24 @@ async fn main() -> anyhow::Result<()> { println!("{instrument}"); } } + SubCli::Visa(args) => { + start_logger(&args.verbose, &args.log_file, &args.log_socket)?; + info!("Discovering VISA instruments"); + #[allow(clippy::mutable_key_type)] + let visa_instruments = match discover_visa(args).await { + Ok(i) => i, + Err(e) => { + error!("Error in VISA discovery: {e}"); + return Err(e); + } + }; + info!("VISA Discovery complete"); + trace!("Discovered {} VISA instruments", visa_instruments.len()); + trace!("Discovered instruments: {visa_instruments:?}"); + for instrument in visa_instruments { + println!("{instrument}"); + } + } SubCli::All(args) => { start_logger(&args.verbose, &args.log_file, &args.log_socket)?; info!("Discovering USB instruments"); @@ -370,6 +390,14 @@ async fn discover_lan(args: DiscoverCmd) -> anyhow::Result anyhow::Result> { + let dur = Duration::from_secs(args.timeout_secs.unwrap_or(20) as u64); + let discover_instance = InstrumentDiscovery::new(dur); + let instruments = discover_instance.visa_discover().await?; + + Ok(instruments) +} + async fn discover_usb() -> anyhow::Result> { let dur = Duration::from_secs(5); //Not used in USB let discover_instance = InstrumentDiscovery::new(dur); diff --git a/kic-discover-visa/src/visa.rs b/kic-discover-visa/src/visa.rs new file mode 100644 index 0000000..072d459 --- /dev/null +++ b/kic-discover-visa/src/visa.rs @@ -0,0 +1,29 @@ +use std::{collections::HashSet, ffi::CString, time::Duration}; + +use tracing::trace; +use tsp_toolkit_kic_lib::instrument::info::{get_info, InstrumentInfo, ConnectionAddr}; +use visa_rs::{flags::AccessMode, AsResourceManager}; + +#[tracing::instrument] +pub async fn visa_discover(timeout: Option) -> anyhow::Result> { + let mut discovered_instruments: HashSet = HashSet::new(); + + let rm = visa_rs::DefaultRM::new()?; + let instruments = rm.find_res_list(&CString::new("?*")?.into())?; + trace!("discovered: {instruments:?}"); + + for i in instruments { + let i = i?; + if i.to_string().contains("SOCKET") { + continue; + } + trace!("Connecting to {i:?} to get info"); + let mut connected = rm.open(&i, AccessMode::NO_LOCK, visa_rs::TIMEOUT_IMMEDIATE)?; + trace!("Getting info from {connected:?}"); + let mut info = get_info(&mut connected)?; + info.address = Some(ConnectionAddr::Visa(i)); + trace!("Got info: {info:?}"); + discovered_instruments.insert(info); + } + Ok(discovered_instruments) +} From 1f5f1fb346063c48ba29040d9da0fbd36130e0c2 Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 11:17:25 -0400 Subject: [PATCH 04/10] cargo fmt --- kic-discover-visa/src/visa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kic-discover-visa/src/visa.rs b/kic-discover-visa/src/visa.rs index 072d459..5ce7093 100644 --- a/kic-discover-visa/src/visa.rs +++ b/kic-discover-visa/src/visa.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, ffi::CString, time::Duration}; use tracing::trace; -use tsp_toolkit_kic_lib::instrument::info::{get_info, InstrumentInfo, ConnectionAddr}; +use tsp_toolkit_kic_lib::instrument::info::{get_info, ConnectionAddr, InstrumentInfo}; use visa_rs::{flags::AccessMode, AsResourceManager}; #[tracing::instrument] From b9a522f7646c0f4aadc9d91c625bc5fd8be58c84 Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 12:59:18 -0400 Subject: [PATCH 05/10] Call kic-discover-visa if conditions are right --- Cargo.lock | 1 + kic-discover/Cargo.toml | 4 ++ kic-discover/src/main.rs | 29 +++++++++++++ kic-discover/src/process.rs | 87 +++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 kic-discover/src/process.rs diff --git a/Cargo.lock b/Cargo.lock index f47bec2..bf257dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1301,6 +1301,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tsp-toolkit-kic-lib", + "windows-sys 0.52.0", ] [[package]] diff --git a/kic-discover/Cargo.toml b/kic-discover/Cargo.toml index 7d803a1..22606ad 100644 --- a/kic-discover/Cargo.toml +++ b/kic-discover/Cargo.toml @@ -32,4 +32,8 @@ tokio = { version = "1.36.0", features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } tsp-toolkit-kic-lib = { workspace = true } +windows-sys = { version = "0.52.0", features = [ + "Win32_System_Console", + "Win32_Foundation", +] } diff --git a/kic-discover/src/main.rs b/kic-discover/src/main.rs index aeba5e8..fd0bd56 100644 --- a/kic-discover/src/main.rs +++ b/kic-discover/src/main.rs @@ -22,6 +22,9 @@ use clap::{command, Args, Command, FromArgMatches, Parser, Subcommand}; use kic_discover::DISC_INSTRUMENTS; +mod process; +use crate::process::Process; + #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] struct Cli { @@ -216,6 +219,32 @@ fn start_logger( #[tokio::main] #[instrument] async fn main() -> anyhow::Result<()> { + let parent_dir: Option = std::env::current_exe().map_or(None, |path| { + path.canonicalize() + .expect("should have canonicalized path") + .parent() + .map(std::convert::Into::into) + }); + + if tsp_toolkit_kic_lib::is_visa_installed() { + #[cfg(target_os = "windows")] + let kic_discover_visa_exe: Option = parent_dir.clone().map(|d| d.join("kic-discover-visa.exe")); + + #[cfg(target_family = "unix")] + let kic_discover_visa_exe: Option = parent_dir.clone().map(|d| d.join("kic-discover-visa")); + + if let Some(kv) = kic_discover_visa_exe { + if kv.exists() { + Process::new(kv.clone(), std::env::args().skip(1)) + .exec_replace() + .context(format!( + "{} should have been launched because VISA was detected", + kv.display(), + ))?; + return Ok(()); + } + } + } let cmd = command!() .propagate_version(true) .subcommand_required(true) diff --git a/kic-discover/src/process.rs b/kic-discover/src/process.rs new file mode 100644 index 0000000..c3dbdce --- /dev/null +++ b/kic-discover/src/process.rs @@ -0,0 +1,87 @@ +use std::{io::ErrorKind, path::PathBuf}; + +#[derive(Debug)] +pub struct Process { + path: PathBuf, + args: Vec, +} +impl Process { + pub fn new(path: PathBuf, args: I) -> Self + where + I: IntoIterator, + I::Item: AsRef, + { + Self { + path, + args: args.into_iter().map(|s| s.as_ref().to_string()).collect(), + } + } + + pub fn exec_replace(self) -> anyhow::Result<()> { + imp::exec_replace(&self) + } + + #[cfg_attr(unix, allow(dead_code))] + pub fn exec(&self) -> anyhow::Result<()> { + let exit = std::process::Command::new(&self.path) + .args(&self.args) + .spawn()? + .wait()?; + if exit.success() { + Ok(()) + } else { + Err(std::io::Error::new( + ErrorKind::Other, + format!( + "child process did not exit successfully: {}", + self.path.display() + ), + ) + .into()) + } + } +} + +#[cfg(windows)] +mod imp { + use std::io::ErrorKind; + + use super::Process; + use windows_sys::Win32::{ + Foundation::{BOOL, FALSE, TRUE}, + System::Console::SetConsoleCtrlHandler, + }; + + #[allow(clippy::missing_const_for_fn)] // This lint seems to be broken for this specific case + unsafe extern "system" fn ctrlc_handler(_: u32) -> BOOL { + TRUE + } + + pub(super) fn exec_replace(process: &Process) -> anyhow::Result<()> { + //Safety: This is an external handler that calls into the windows API. It is + // expected to be safe. + unsafe { + if SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) == FALSE { + return Err(std::io::Error::new( + ErrorKind::Other, + "Unable to set Ctrl+C Handler".to_string(), + ) + .into()); + } + } + + process.exec() + } +} + +#[cfg(unix)] +mod imp { + use crate::Process; + use std::os::unix::process::CommandExt; + + pub(super) fn exec_replace(process: &Process) -> anyhow::Result<()> { + let mut command = std::process::Command::new(&process.path); + command.args(&process.args); + Err(command.exec().into()) // Exec replaces the current application's program memory, therefore execution will + } +} From 0c172f9786073c9c61667a4544a25bb7acaae3fb Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 13:00:53 -0400 Subject: [PATCH 06/10] cargo fmt --- kic-discover/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kic-discover/src/main.rs b/kic-discover/src/main.rs index fd0bd56..0086b93 100644 --- a/kic-discover/src/main.rs +++ b/kic-discover/src/main.rs @@ -228,10 +228,12 @@ async fn main() -> anyhow::Result<()> { if tsp_toolkit_kic_lib::is_visa_installed() { #[cfg(target_os = "windows")] - let kic_discover_visa_exe: Option = parent_dir.clone().map(|d| d.join("kic-discover-visa.exe")); + let kic_discover_visa_exe: Option = + parent_dir.clone().map(|d| d.join("kic-discover-visa.exe")); #[cfg(target_family = "unix")] - let kic_discover_visa_exe: Option = parent_dir.clone().map(|d| d.join("kic-discover-visa")); + let kic_discover_visa_exe: Option = + parent_dir.clone().map(|d| d.join("kic-discover-visa")); if let Some(kv) = kic_discover_visa_exe { if kv.exists() { From 01d644b0ecb41c7bf973e3ef65e4f29c72e0ef76 Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 15:01:02 -0400 Subject: [PATCH 07/10] Use deduplicated ConnectionAddr --- Cargo.lock | 2 +- .../src/instrument_discovery/mod.rs | 4 +- kic-discover-visa/src/main.rs | 125 +++++++++++------- kic-discover-visa/src/usbtmc/mod.rs | 2 +- kic-discover-visa/src/visa.rs | 5 +- kic-discover/src/instrument_discovery/mod.rs | 4 +- kic-discover/src/main.rs | 112 +++++++++------- kic-discover/src/usbtmc/mod.rs | 2 +- 8 files changed, 152 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf257dd..227e7bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2689,7 +2689,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tsp-toolkit-kic-lib" version = "0.17.2" -source = "git+https://github.com/tektronix/tsp-toolkit-kic-lib.git?branch=task/discover-visa#b9b78cbb64b7f1a5f84832a027e63ca71ce0e7ed" +source = "git+https://github.com/tektronix/tsp-toolkit-kic-lib.git?branch=task/discover-visa#97be4e66e8eeab45934232723cae084d02b28bda" dependencies = [ "bytes", "chrono", diff --git a/kic-discover-visa/src/instrument_discovery/mod.rs b/kic-discover-visa/src/instrument_discovery/mod.rs index d0cb375..50d86e6 100644 --- a/kic-discover-visa/src/instrument_discovery/mod.rs +++ b/kic-discover-visa/src/instrument_discovery/mod.rs @@ -1,7 +1,9 @@ use std::net::SocketAddr; use std::{collections::HashSet, time::Duration}; -use tsp_toolkit_kic_lib::instrument::info::{ConnectionAddr, InstrumentInfo}; +use tsp_toolkit_kic_lib::{ + instrument::info::InstrumentInfo, interface::connection_addr::ConnectionAddr, +}; use crate::ethernet::{LxiDeviceInfo, COMM_PORT}; use crate::usbtmc::Usbtmc; diff --git a/kic-discover-visa/src/main.rs b/kic-discover-visa/src/main.rs index bf78d3c..7e20611 100644 --- a/kic-discover-visa/src/main.rs +++ b/kic-discover-visa/src/main.rs @@ -45,8 +45,10 @@ struct Cli { enum SubCli { /// Look for all devices connected on LAN Lan(DiscoverCmd), + /// Look for all devices connected on USB - Usb(DiscoverCmd), + //Usb(DiscoverCmd), + /// Look for all devices that can be connected to via the installed VISA driver Visa(DiscoverCmd), /// Look for all devices on all interface types. @@ -244,12 +246,12 @@ async fn main() -> anyhow::Result<()> { let is_exit_timer = require_exit_timer(&sub); - match sub { + let instruments = match &sub { SubCli::Lan(args) => { start_logger(&args.verbose, &args.log_file, &args.log_socket)?; info!("Discovering LAN instruments"); #[allow(clippy::mutable_key_type)] - let lan_instruments = match discover_lan(args).await { + let lan_instruments = match discover_lan(args.clone()).await { Ok(i) => i, Err(e) => { error!("Error in LAN discovery: {e}"); @@ -260,33 +262,31 @@ async fn main() -> anyhow::Result<()> { trace!("Discovered {} LAN instruments", lan_instruments.len()); println!("Discovered {} LAN instruments", lan_instruments.len()); trace!("Discovered instruments: {lan_instruments:?}"); - for instrument in lan_instruments { - println!("{instrument}"); - } - } - SubCli::Usb(args) => { - start_logger(&args.verbose, &args.log_file, &args.log_socket)?; - info!("Discovering USB instruments"); - #[allow(clippy::mutable_key_type)] - let usb_instruments = match discover_usb().await { - Ok(i) => i, - Err(e) => { - error!("Error in USB discovery: {e}"); - return Err(e); - } - }; - info!("USB Discovery complete"); - trace!("Discovered {} USB instruments", usb_instruments.len()); - trace!("Discovered instruments: {usb_instruments:?}"); - for instrument in usb_instruments { - println!("{instrument}"); - } + lan_instruments } + //SubCli::Usb(args) => { + // start_logger(&args.verbose, &args.log_file, &args.log_socket)?; + // info!("Discovering USB instruments"); + // #[allow(clippy::mutable_key_type)] + // let usb_instruments = match discover_usb().await { + // Ok(i) => i, + // Err(e) => { + // error!("Error in USB discovery: {e}"); + // return Err(e); + // } + // }; + // info!("USB Discovery complete"); + // trace!("Discovered {} USB instruments", usb_instruments.len()); + // trace!("Discovered instruments: {usb_instruments:?}"); + // for instrument in usb_instruments { + // println!("{instrument}"); + // } + //} SubCli::Visa(args) => { start_logger(&args.verbose, &args.log_file, &args.log_socket)?; info!("Discovering VISA instruments"); #[allow(clippy::mutable_key_type)] - let visa_instruments = match discover_visa(args).await { + let visa_instruments = match discover_visa(args.clone()).await { Ok(i) => i, Err(e) => { error!("Error in VISA discovery: {e}"); @@ -296,32 +296,44 @@ async fn main() -> anyhow::Result<()> { info!("VISA Discovery complete"); trace!("Discovered {} VISA instruments", visa_instruments.len()); trace!("Discovered instruments: {visa_instruments:?}"); - for instrument in visa_instruments { - println!("{instrument}"); - } + visa_instruments } SubCli::All(args) => { start_logger(&args.verbose, &args.log_file, &args.log_socket)?; - info!("Discovering USB instruments"); + //info!("Discovering USB instruments"); + //#[allow(clippy::mutable_key_type)] + //let usb_instruments = match discover_usb().await { + // Ok(i) => i, + // Err(e) => { + // error!("Error in USB discovery: {e}"); + // return Err(e); + // } + //}; + //info!("USB Discovery complete"); + //trace!("Discovered {} USB instruments", usb_instruments.len()); + //println!("Discovered {} USB instruments", usb_instruments.len()); + //trace!("Discovered USB instruments: {usb_instruments:?}"); + //for instrument in usb_instruments { + // println!("{instrument}"); + //} + + info!("Discovering VISA instruments"); #[allow(clippy::mutable_key_type)] - let usb_instruments = match discover_usb().await { + let visa_instruments = match discover_visa(args.clone()).await { Ok(i) => i, Err(e) => { - error!("Error in USB discovery: {e}"); + error!("Error in VISA discovery: {e}"); return Err(e); } }; - info!("USB Discovery complete"); - trace!("Discovered {} USB instruments", usb_instruments.len()); - println!("Discovered {} USB instruments", usb_instruments.len()); - trace!("Discovered USB instruments: {usb_instruments:?}"); - for instrument in usb_instruments { - println!("{instrument}"); - } + info!("VISA Discovery complete"); + trace!("Discovered {} VISA instruments", visa_instruments.len()); + println!("Discovered {} VISA instruments", visa_instruments.len()); + trace!("Discovered VISA instruments: {visa_instruments:?}"); info!("Discovering LAN instruments"); #[allow(clippy::mutable_key_type)] - let lan_instruments = match discover_lan(args).await { + let mut lan_instruments = match discover_lan(args.clone()).await { Ok(i) => i, Err(e) => { error!("Error in LAN discovery: {e}"); @@ -332,10 +344,25 @@ async fn main() -> anyhow::Result<()> { trace!("Discovered {} LAN instruments", lan_instruments.len()); println!("Discovered {} LAN instruments", lan_instruments.len()); trace!("Discovered LAN instruments: {lan_instruments:?}"); - for instrument in lan_instruments { - println!("{instrument}"); - } + + lan_instruments.extend(visa_instruments); + lan_instruments } + }; + + for i in instruments { + println!( + "{}", + match sub { + SubCli::Lan(ref args) | SubCli::Visa(ref args) | SubCli::All(ref args) => { + if args.json { + serde_json::to_string(&i)? + } else { + i.to_string() + } + } + } + ); } if is_exit_timer { @@ -398,10 +425,10 @@ async fn discover_visa(args: DiscoverCmd) -> anyhow::Result anyhow::Result> { - let dur = Duration::from_secs(5); //Not used in USB - let discover_instance = InstrumentDiscovery::new(dur); - let instruments = discover_instance.usb_discover().await?; - - Ok(instruments) -} +//async fn discover_usb() -> anyhow::Result> { +// let dur = Duration::from_secs(5); //Not used in USB +// let discover_instance = InstrumentDiscovery::new(dur); +// let instruments = discover_instance.usb_discover().await?; +// +// Ok(instruments) +//} diff --git a/kic-discover-visa/src/usbtmc/mod.rs b/kic-discover-visa/src/usbtmc/mod.rs index 26516b6..743a10e 100644 --- a/kic-discover-visa/src/usbtmc/mod.rs +++ b/kic-discover-visa/src/usbtmc/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashSet, hash::Hash, time::Duration}; use tmc::{list_instruments, Instrument, InstrumentHandle, TMCError, TMCResult}; use tsp_toolkit_kic_lib::{ - instrument::info::{ConnectionAddr, InstrumentInfo}, + instrument::info::InstrumentInfo, interface::connection_addr::ConnectionAddr, usbtmc::UsbtmcAddr, }; diff --git a/kic-discover-visa/src/visa.rs b/kic-discover-visa/src/visa.rs index 5ce7093..9dd9a3c 100644 --- a/kic-discover-visa/src/visa.rs +++ b/kic-discover-visa/src/visa.rs @@ -1,7 +1,10 @@ use std::{collections::HashSet, ffi::CString, time::Duration}; use tracing::trace; -use tsp_toolkit_kic_lib::instrument::info::{get_info, ConnectionAddr, InstrumentInfo}; +use tsp_toolkit_kic_lib::{ + instrument::info::{get_info, InstrumentInfo}, + interface::connection_addr::ConnectionAddr, +}; use visa_rs::{flags::AccessMode, AsResourceManager}; #[tracing::instrument] diff --git a/kic-discover/src/instrument_discovery/mod.rs b/kic-discover/src/instrument_discovery/mod.rs index 4f59f5e..0cdc3cb 100644 --- a/kic-discover/src/instrument_discovery/mod.rs +++ b/kic-discover/src/instrument_discovery/mod.rs @@ -1,7 +1,9 @@ use std::net::SocketAddr; use std::{collections::HashSet, time::Duration}; -use tsp_toolkit_kic_lib::instrument::info::{ConnectionAddr, InstrumentInfo}; +use tsp_toolkit_kic_lib::{ + instrument::info::InstrumentInfo, interface::connection_addr::ConnectionAddr, +}; use crate::ethernet::{LxiDeviceInfo, COMM_PORT}; use crate::usbtmc::Usbtmc; diff --git a/kic-discover/src/main.rs b/kic-discover/src/main.rs index 0086b93..3830035 100644 --- a/kic-discover/src/main.rs +++ b/kic-discover/src/main.rs @@ -49,7 +49,7 @@ enum SubCli { /// Look for all devices connected on LAN Lan(DiscoverCmd), /// Look for all devices connected on USB - Usb(DiscoverCmd), + //Usb(DiscoverCmd), /// Look for all devices on all interface types. All(DiscoverCmd), } @@ -273,12 +273,12 @@ async fn main() -> anyhow::Result<()> { let is_exit_timer = require_exit_timer(&sub); - match sub { + let instruments: HashSet = match &sub { SubCli::Lan(args) => { start_logger(&args.verbose, &args.log_file, &args.log_socket)?; info!("Discovering LAN instruments"); #[allow(clippy::mutable_key_type)] - let lan_instruments = match discover_lan(args).await { + let lan_instruments = match discover_lan(args.clone()).await { Ok(i) => i, Err(e) => { error!("Error in LAN discovery: {e}"); @@ -289,50 +289,48 @@ async fn main() -> anyhow::Result<()> { trace!("Discovered {} LAN instruments", lan_instruments.len()); println!("Discovered {} LAN instruments", lan_instruments.len()); trace!("Discovered instruments: {lan_instruments:?}"); - for instrument in lan_instruments { - println!("{instrument}"); - } - } - SubCli::Usb(args) => { - start_logger(&args.verbose, &args.log_file, &args.log_socket)?; - info!("Discovering USB instruments"); - #[allow(clippy::mutable_key_type)] - let usb_instruments = match discover_usb().await { - Ok(i) => i, - Err(e) => { - error!("Error in USB discovery: {e}"); - return Err(e); - } - }; - info!("USB Discovery complete"); - trace!("Discovered {} USB instruments", usb_instruments.len()); - trace!("Discovered instruments: {usb_instruments:?}"); - for instrument in usb_instruments { - println!("{instrument}"); - } + lan_instruments } + //SubCli::Usb(args) => { + // start_logger(&args.verbose, &args.log_file, &args.log_socket)?; + // info!("Discovering USB instruments"); + // #[allow(clippy::mutable_key_type)] + // let usb_instruments = match discover_usb().await { + // Ok(i) => i, + // Err(e) => { + // error!("Error in USB discovery: {e}"); + // return Err(e); + // } + // }; + // info!("USB Discovery complete"); + // trace!("Discovered {} USB instruments", usb_instruments.len()); + // trace!("Discovered instruments: {usb_instruments:?}"); + // for instrument in usb_instruments { + // println!("{instrument}"); + // } + //} SubCli::All(args) => { start_logger(&args.verbose, &args.log_file, &args.log_socket)?; - info!("Discovering USB instruments"); - #[allow(clippy::mutable_key_type)] - let usb_instruments = match discover_usb().await { - Ok(i) => i, - Err(e) => { - error!("Error in USB discovery: {e}"); - return Err(e); - } - }; - info!("USB Discovery complete"); - trace!("Discovered {} USB instruments", usb_instruments.len()); - println!("Discovered {} USB instruments", usb_instruments.len()); - trace!("Discovered USB instruments: {usb_instruments:?}"); - for instrument in usb_instruments { - println!("{instrument}"); - } + //info!("Discovering USB instruments"); + //#[allow(clippy::mutable_key_type)] + //let usb_instruments = match discover_usb().await { + // Ok(i) => i, + // Err(e) => { + // error!("Error in USB discovery: {e}"); + // return Err(e); + // } + //}; + //info!("USB Discovery complete"); + //trace!("Discovered {} USB instruments", usb_instruments.len()); + //println!("Discovered {} USB instruments", usb_instruments.len()); + //trace!("Discovered USB instruments: {usb_instruments:?}"); + //for instrument in usb_instruments { + // println!("{instrument}"); + //} info!("Discovering LAN instruments"); #[allow(clippy::mutable_key_type)] - let lan_instruments = match discover_lan(args).await { + let lan_instruments = match discover_lan(args.clone()).await { Ok(i) => i, Err(e) => { error!("Error in LAN discovery: {e}"); @@ -343,10 +341,26 @@ async fn main() -> anyhow::Result<()> { trace!("Discovered {} LAN instruments", lan_instruments.len()); println!("Discovered {} LAN instruments", lan_instruments.len()); trace!("Discovered LAN instruments: {lan_instruments:?}"); - for instrument in lan_instruments { + for instrument in &lan_instruments { println!("{instrument}"); } + lan_instruments } + }; + + for i in instruments { + println!( + "{}", + match sub { + SubCli::Lan(ref args) | SubCli::All(ref args) => { + if args.json { + serde_json::to_string(&i)? + } else { + i.to_string() + } + } + } + ); } if is_exit_timer { @@ -401,10 +415,10 @@ async fn discover_lan(args: DiscoverCmd) -> anyhow::Result anyhow::Result> { - let dur = Duration::from_secs(5); //Not used in USB - let discover_instance = InstrumentDiscovery::new(dur); - let instruments = discover_instance.usb_discover().await?; - - Ok(instruments) -} +//async fn discover_usb() -> anyhow::Result> { +// let dur = Duration::from_secs(5); //Not used in USB +// let discover_instance = InstrumentDiscovery::new(dur); +// let instruments = discover_instance.usb_discover().await?; +// +// Ok(instruments) +//} diff --git a/kic-discover/src/usbtmc/mod.rs b/kic-discover/src/usbtmc/mod.rs index 26516b6..743a10e 100644 --- a/kic-discover/src/usbtmc/mod.rs +++ b/kic-discover/src/usbtmc/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashSet, hash::Hash, time::Duration}; use tmc::{list_instruments, Instrument, InstrumentHandle, TMCError, TMCResult}; use tsp_toolkit_kic_lib::{ - instrument::info::{ConnectionAddr, InstrumentInfo}, + instrument::info::InstrumentInfo, interface::connection_addr::ConnectionAddr, usbtmc::UsbtmcAddr, }; From fe451761228e3cea664c0a863f2778f60da94e5b Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 16:17:20 -0400 Subject: [PATCH 08/10] Support new VersaTest Model Number --- instrument-repl/src/resources/kic_common.tsp | 1 + 1 file changed, 1 insertion(+) diff --git a/instrument-repl/src/resources/kic_common.tsp b/instrument-repl/src/resources/kic_common.tsp index 9bc8a28..5acb4c7 100644 --- a/instrument-repl/src/resources/kic_common.tsp +++ b/instrument-repl/src/resources/kic_common.tsp @@ -66,6 +66,7 @@ local models = { ["VERSATEST-600"] = VERSATEST, ["TSPop"] = VERSATEST, ["TSP"] = VERSATEST, + ["MP5103"] = VERSATEST, } _KIC["is_tti"] = function() return models[localnode.model] == TTI end From f0fa9138f83cb26b78f83c2bc7e1225e215b31ce Mon Sep 17 00:00:00 2001 From: "Sarver, Edwin" Date: Wed, 4 Sep 2024 16:18:13 -0400 Subject: [PATCH 09/10] tsp-toolkit discover support --- kic-discover-visa/src/lib.rs | 1 + kic-discover-visa/src/visa.rs | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/kic-discover-visa/src/lib.rs b/kic-discover-visa/src/lib.rs index c70d6fa..7f8b06b 100644 --- a/kic-discover-visa/src/lib.rs +++ b/kic-discover-visa/src/lib.rs @@ -129,4 +129,5 @@ pub fn insert_disc_device(device: &str) -> Result<(), Error> { enum IoType { Lan, Usb, + Visa, } diff --git a/kic-discover-visa/src/visa.rs b/kic-discover-visa/src/visa.rs index 9dd9a3c..6f7c88f 100644 --- a/kic-discover-visa/src/visa.rs +++ b/kic-discover-visa/src/visa.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, ffi::CString, time::Duration}; +use serde::{Deserialize, Serialize}; use tracing::trace; use tsp_toolkit_kic_lib::{ instrument::info::{get_info, InstrumentInfo}, @@ -7,6 +8,8 @@ use tsp_toolkit_kic_lib::{ }; use visa_rs::{flags::AccessMode, AsResourceManager}; +use crate::{insert_disc_device, model_check, IoType}; + #[tracing::instrument] pub async fn visa_discover(timeout: Option) -> anyhow::Result> { let mut discovered_instruments: HashSet = HashSet::new(); @@ -24,9 +27,36 @@ pub async fn visa_discover(timeout: Option) -> anyhow::Result Date: Thu, 5 Sep 2024 08:31:49 -0400 Subject: [PATCH 10/10] Update to use latest kic-lib tag --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 227e7bd..8205f26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -464,9 +464,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -2205,9 +2205,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -2540,9 +2540,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -2552,9 +2552,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2689,7 +2689,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tsp-toolkit-kic-lib" version = "0.17.2" -source = "git+https://github.com/tektronix/tsp-toolkit-kic-lib.git?branch=task/discover-visa#97be4e66e8eeab45934232723cae084d02b28bda" +source = "git+https://github.com/tektronix/tsp-toolkit-kic-lib.git?tag=v0.17.2-2#46ca0c9bb4485115fc2ded0a932acff4a95b36a1" dependencies = [ "bytes", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 5f87dc3..37b49a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ thiserror = "1.0.58" tmc = { git = "https://github.com/esarver/rusb-usbtmc" } tracing = { version = "0.1.40", features = ["async-await"] } tracing-subscriber = { version = "0.3.18", features = ["json"] } -tsp-toolkit-kic-lib = { git = "https://github.com/tektronix/tsp-toolkit-kic-lib.git", branch = "task/discover-visa" } +tsp-toolkit-kic-lib = { git = "https://github.com/tektronix/tsp-toolkit-kic-lib.git", tag = "v0.17.2-2" } [workspace.lints.rust]