use crate::scope::Nlab;
use std::{fmt, io};
use std::error::Error;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use rusb::Version;
use crate::firmware::{FIRMWARE, SUPPORTED_FIRMWARE_VERSION};
#[derive(Clone)]
pub(crate) struct HidDevice(hidapi::DeviceInfo);
pub(crate) enum NlabDevice {
HidApiDevice { device: HidDevice, api: Arc<RwLock<hidapi::HidApi>> },
RusbDevice(rusb::Device<rusb::GlobalContext>),
}
impl PartialEq<Self> for HidDevice {
fn eq(&self, other: &Self) -> bool {
if self.vendor_id() != other.vendor_id() {
return false;
}
if self.product_id() != other.product_id() {
return false;
}
if self.0.path() != other.0.path() {
return false;
}
true
}
}
impl HidDevice {
pub(crate) fn vendor_id(&self) -> u16 { self.0.vendor_id() }
pub(crate) fn product_id(&self) -> u16 { self.0.product_id() }
pub(crate) fn open_device(&self, api: &hidapi::HidApi) -> hidapi::HidResult<hidapi::HidDevice> { self.0.open_device(api) }
}
pub struct LabBench {
hid_api: Arc<RwLock<hidapi::HidApi>>,
hid_devices: Vec<HidDevice>,
rusb_devices: Vec<rusb::Device<rusb::GlobalContext>>,
}
pub struct NlabLink {
pub available: bool,
pub in_dfu: bool,
pub needs_update: bool,
device_version: Option<Version>,
device: NlabDevice,
}
impl LabBench {
pub fn new() -> Result<LabBench, Box<dyn Error>> {
let hid_api = hidapi::HidApi::new()?;
Ok(LabBench {
hid_devices: hid_api.device_list().cloned().map(HidDevice).collect(),
hid_api: Arc::new(RwLock::new(hid_api)),
rusb_devices: rusb::devices().unwrap().iter().collect(),
})
}
pub fn refresh(&mut self) {
let mut api = self.hid_api.write().unwrap();
api.refresh_devices().expect("failed to refresh");
self.hid_devices = api.device_list().cloned().map(HidDevice).collect();
self.rusb_devices = rusb::devices().unwrap().iter().collect();
}
pub fn list(&self) -> impl Iterator<Item=NlabLink> + '_ {
let v1_nlabs = self.hid_devices
.iter()
.filter_map(move |d| NlabLink::new(
NlabDevice::HidApiDevice {
device: d.clone(),
api: Arc::clone(&self.hid_api),
}
));
let v2_nlabs = self.rusb_devices
.iter()
.filter_map(move |d| NlabLink::new(
NlabDevice::RusbDevice(d.clone())
));
v2_nlabs.chain(v1_nlabs)
}
pub fn open_all_available(&self) -> Vec<Nlab> {
self.list().filter_map(|nsl| nsl.open(false).ok()).collect()
}
pub fn open_first_available(&self, power_on: bool) -> Result<Nlab, io::Error> {
let mut err = io::Error::new(io::ErrorKind::NotFound, "Cannot find any nLabs");
for nsl in self.list() {
err = io::Error::new(io::ErrorKind::ConnectionRefused, "Cannot connect to any nLabs");
if let Ok(nlab) = nsl.open(power_on) {
return Ok(nlab);
} else if nsl.needs_update && nsl.must_be_downgraded() {
err = io::Error::new(io::ErrorKind::ConnectionRefused, "Cannot connect to any nLabs: firmware downgrade needed")
} else if nsl.needs_update {
err = io::Error::new(io::ErrorKind::ConnectionRefused, "Cannot connect to any nLabs: firmware update needed")
}
}
Err(err)
}
pub fn get_first_in_dfu(&self) -> Option<NlabLink> {
self.list().find(|nsl| nsl.in_dfu)
}
pub fn get_first_needing_update(&self) -> Option<NlabLink> {
self.list().find(|nsl| nsl.needs_update && nsl.available)
}
}
impl NlabLink {
fn new(device: NlabDevice) -> Option<Self> {
match device {
NlabDevice::HidApiDevice { device: info, api } => { NlabLink::from_hid_device(info, api) }
NlabDevice::RusbDevice(device) => { NlabLink::from_rusb_device(device) }
}
}
fn from_hid_device(info: HidDevice, api: Arc<RwLock<hidapi::HidApi>>) -> Option<Self> {
if info.vendor_id() == 0x04D8 && info.product_id() == 0xF3F6 {
let hid_api = api.read().ok()?;
let available = info.open_device(&hid_api).is_ok();
return Some(NlabLink {
available,
in_dfu: false,
needs_update: false,
device_version: None,
device: NlabDevice::HidApiDevice { device: info.clone(), api: Arc::clone(&api) },
});
}
None
}
fn from_rusb_device(device: rusb::Device<rusb::GlobalContext>) -> Option<Self> {
if let Ok(device_desc) = device.device_descriptor() {
let vendor_id = device_desc.vendor_id();
let product_id = device_desc.product_id();
let firmware_version = device_desc.device_version();
if vendor_id == 0x0483 && product_id == 0xA4AA {
let mut available = false;
if let Ok(dev) = device.open() {
if let Ok(()) = dev.claim_interface(0) {
available = true;
}
}
return Some(NlabLink {
available,
in_dfu: false,
needs_update: firmware_version != Version::from_bcd(SUPPORTED_FIRMWARE_VERSION),
device_version: Some(firmware_version),
device: NlabDevice::RusbDevice(device),
});
} else if device_desc.vendor_id() == 0x0483 && device_desc.product_id() == 0xA4AB {
return Some(NlabLink {
available: false,
in_dfu: true,
needs_update: false,
device_version: None,
device: NlabDevice::RusbDevice(device),
});
}
}
None
}
pub(crate) fn must_be_downgraded(&self) -> bool {
self.needs_update && self.device_version.is_some_and(|v| v > Version::from_bcd(SUPPORTED_FIRMWARE_VERSION))
}
pub fn validate(self) -> Option<Self> {
match self.device {
NlabDevice::HidApiDevice { device: info, api } => {
let detected_devices: Option<Vec<HidDevice>> = match api.write() {
Ok(mut hid_api) => {
if hid_api.refresh_devices().is_ok() {
Some(hid_api.device_list().cloned().map(HidDevice).collect())
} else {
None
}
}
Err(_) => { None }
};
if let Some(device_list) = detected_devices {
for device in device_list {
if device == info {
return NlabLink::from_hid_device(device, api);
}
}
}
None
}
NlabDevice::RusbDevice(existing_device) => {
if let Ok(devices) = rusb::devices() {
for detected_device in devices.iter() {
if existing_device == detected_device {
return NlabLink::from_rusb_device(detected_device);
}
}
}
None
}
}
}
pub fn open(&self, power_on: bool) -> Result<Nlab, Box<dyn Error>> {
if self.in_dfu {
return Err("nLab is in DFU mode".into());
}
if self.needs_update {
return Err("nLab needs a firmware update".into());
}
Nlab::new(&self.device, power_on)
}
pub fn update(&self) -> Result<(), Box<dyn Error>> {
if !self.in_dfu {
return Err("nLab is not in DFU mode".into());
}
match &self.device {
NlabDevice::HidApiDevice { .. } => {
return Err("Cannot update nLab v1".into());
}
NlabDevice::RusbDevice(device) => {
let mut dfu = dfu_libusb::DfuLibusb::from_usb_device(
device.clone(),
device.open()?,
0, 0)?;
dfu.override_address(0x08010000);
dfu.download_from_slice(FIRMWARE)?;
}
};
Ok(())
}
pub fn request_dfu(&self) -> Result<(), Box<dyn Error>> {
if self.in_dfu {
return Err("nLab is already in DFU mode".into());
}
match &self.device {
NlabDevice::HidApiDevice { .. } => {
return Err("Unsupported for nLab v1".into());
}
NlabDevice::RusbDevice(device) => {
let out_buffer = [0u8, 6u8];
let device_handle = device.open()?;
device_handle.claim_interface(0)?;
device_handle.write_bulk(0x01, &out_buffer, Duration::from_millis(100))?;
}
};
Ok(())
}
}
impl fmt::Debug for LabBench {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#?}", self.list().collect::<Vec<NlabLink>>())
}
}
impl fmt::Debug for NlabLink {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let device_name = match &self.device {
NlabDevice::HidApiDevice { .. } => { "nLab v1" }
NlabDevice::RusbDevice(_) => { "nLab v2" }
};
if self.in_dfu {
return write!(f, "Link to {device_name} [ in DFU mode ]");
}
if self.needs_update {
return match self.device_version {
Some(v) if v < Version::from_bcd(SUPPORTED_FIRMWARE_VERSION) => {
write!(f, "Link to {device_name} [ Firmware Update Needed ]")
}
Some(v) if v > Version::from_bcd(SUPPORTED_FIRMWARE_VERSION) => {
write!(f, "Link to {device_name} [ Device is running newer firmware, software update needed ]")
}
Some(_) => { write!(f, "Link to {device_name} [ Unknown firmware mismatch ]") }
None => { write!(f, "Link to {device_name} [ Unknown firmware mismatch ]") }
};
}
write!(f, "Link to {device_name} [ available: {} ]", self.available)
}
}