Implemented error type, changed how get_device_filter() works to be more useful

This commit is contained in:
G2-Games 2024-02-01 12:38:52 -06:00
parent 319fae96ad
commit 2ea7fd68c8
5 changed files with 259 additions and 97 deletions

View file

@ -52,6 +52,9 @@ tokio-test = "0.4.3"
[profile.release] [profile.release]
opt-level = "s" opt-level = "s"
[dependencies]
thiserror = "1.0.56"
[package.metadata.wasm-pack.profile.dev.wasm-bindgen] [package.metadata.wasm-pack.profile.dev.wasm-bindgen]
dwarf-debug-info = true dwarf-debug-info = true

View file

@ -1,6 +1,4 @@
use std::error::Error; use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError};
use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient};
pub struct UsbDevice { pub struct UsbDevice {
device_info: nusb::DeviceInfo, device_info: nusb::DeviceInfo,
@ -11,7 +9,7 @@ pub struct UsbInterface {
interface: nusb::Interface, interface: nusb::Interface,
} }
pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, Box<dyn Error>> { pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, UsbError> {
let devices = nusb::list_devices().unwrap(); let devices = nusb::list_devices().unwrap();
let mut device_info = None; let mut device_info = None;
@ -24,10 +22,13 @@ pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, Bo
let device_info = match device_info { let device_info = match device_info {
Some(dev) => dev, Some(dev) => dev,
None => return Err("Device not found".into()), None => return Err(UsbError::DeviceNotFound),
}; };
let device = device_info.open()?; let device = match device_info.open() {
Ok(dev) => dev,
Err(_) => return Err(UsbError::CommunicationError)
};
Ok(UsbDevice { Ok(UsbDevice {
device_info, device_info,
@ -35,38 +36,87 @@ pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, Bo
}) })
} }
#[derive(PartialEq, Eq, Clone)] #[derive(PartialEq, Clone)]
pub struct FilterTuple(pub u16, pub u16); pub struct DeviceFilter {
pub vendor_id: Option<u16>,
pub product_id: Option<u16>,
pub class: Option<u8>,
pub subclass: Option<u8>,
pub protocol: Option<u8>,
serial_number: Option<String>,
}
impl DeviceFilter {
pub fn serial_number(&self) -> &Option<String> {
&self.serial_number
}
}
impl Default for DeviceFilter {
fn default() -> Self {
Self {
vendor_id: None,
product_id: None,
class: None,
subclass: None,
protocol: None,
serial_number: None,
}
}
}
pub async fn get_device_filter( pub async fn get_device_filter(
device_filter: Vec<FilterTuple>, device_filter: Vec<DeviceFilter>,
) -> Result<UsbDevice, Box<dyn Error>> { ) -> Result<UsbDevice, UsbError> {
let devices = nusb::list_devices().unwrap(); let devices = nusb::list_devices().unwrap();
let mut device_info = None; let mut device_info = None;
for device in devices { for prelim_dev_inf in devices {
match device_filter // See if the device exists in the list
.iter() if device_filter.iter().position(|info|
.position(|i| i == &FilterTuple(device.vendor_id(), device.product_id()))
{ {
Some(_) => { let mut result = false;
device_info = Some(device);
break; if info.vendor_id.is_some() {
} result = info.vendor_id.unwrap() == prelim_dev_inf.vendor_id();
None => device_info = None,
}
} }
if device_info.is_none() { if info.product_id.is_some() {
return Err("No devices from the list found".into()); result = info.product_id.unwrap() == prelim_dev_inf.product_id();
}
if info.class.is_some() {
result = info.class.unwrap() == prelim_dev_inf.class();
}
if info.subclass.is_some() {
result = info.subclass.unwrap() == prelim_dev_inf.subclass();
}
if info.protocol.is_some() {
result = info.protocol.unwrap() == prelim_dev_inf.protocol();
}
if info.serial_number().is_some() && prelim_dev_inf.serial_number().is_some() {
result = info.serial_number().as_ref().unwrap() == &prelim_dev_inf.serial_number().unwrap().to_owned();
}
result
}
).is_some() {
device_info = Some(prelim_dev_inf)
}
} }
let device_info = match device_info { let device_info = match device_info {
Some(dev) => dev, Some(dev) => dev,
None => return Err("Device not found".into()), None => return Err(UsbError::DeviceNotFound),
}; };
let device = device_info.open()?; let device = match device_info.open() {
Ok(dev) => dev,
Err(_) => return Err(UsbError::CommunicationError),
};
Ok(UsbDevice { Ok(UsbDevice {
device_info, device_info,
@ -78,19 +128,19 @@ impl Device for UsbDevice {
type UsbDevice = UsbDevice; type UsbDevice = UsbDevice;
type UsbInterface = UsbInterface; type UsbInterface = UsbInterface;
async fn open_interface(&self, number: u8) -> Result<UsbInterface, Box<dyn Error>> { async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError> {
let interface = match self.device.claim_interface(number) { let interface = match self.device.claim_interface(number) {
Ok(inter) => inter, Ok(inter) => inter,
Err(e) => return Err(e.into()), Err(_) => return Err(UsbError::CommunicationError),
}; };
Ok(UsbInterface { interface }) Ok(UsbInterface { interface })
} }
async fn reset(&self) -> Result<(), Box<dyn Error>> { async fn reset(&self) -> Result<(), UsbError> {
match self.device.reset() { match self.device.reset() {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(e.into()), Err(_) => Err(UsbError::CommunicationError),
} }
} }
@ -120,28 +170,36 @@ impl Device for UsbDevice {
} }
impl<'a> Interface<'a> for UsbInterface { impl<'a> Interface<'a> for UsbInterface {
async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, Box<dyn Error>> { async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, UsbError> {
Ok(self.interface.control_in(data.into()).await.into_result()?) let result = match self.interface.control_in(data.into()).await.into_result() {
Ok(res) => res,
Err(_) => return Err(UsbError::TransferError),
};
Ok(result)
} }
async fn control_out(&self, data: ControlOut<'a>) -> Result<(), Box<dyn Error>> { async fn control_out(&self, data: ControlOut<'a>) -> Result<usize, UsbError> {
match self.interface.control_out(data.into()).await.into_result() { match self.interface.control_out(data.into()).await.into_result() {
Ok(_) => Ok(()), Ok(bytes) => Ok(bytes.actual_length()),
Err(e) => Err(e.into()), Err(_) => Err(UsbError::TransferError),
} }
} }
async fn bulk_in(&self, endpoint: u8, length: usize) -> Result<Vec<u8>, Box<dyn Error>> { async fn bulk_in(&self, endpoint: u8, length: usize) -> Result<Vec<u8>, UsbError> {
let request_buffer = nusb::transfer::RequestBuffer::new(length); let request_buffer = nusb::transfer::RequestBuffer::new(length);
Ok(self match self
.interface .interface
.bulk_in(endpoint, request_buffer) .bulk_in(endpoint, request_buffer)
.await .await
.into_result()?) .into_result() {
Ok(res) => Ok(res),
Err(_) => Err(UsbError::TransferError),
}
} }
async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result<usize, Box<dyn Error>> { async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result<usize, UsbError> {
match self match self
.interface .interface
.bulk_out(endpoint, data.to_vec()) .bulk_out(endpoint, data.to_vec())
@ -149,7 +207,7 @@ impl<'a> Interface<'a> for UsbInterface {
.into_result() .into_result()
{ {
Ok(len) => Ok(len.actual_length()), Ok(len) => Ok(len.actual_length()),
Err(e) => Err(e.into()), Err(_) => Err(UsbError::TransferError),
} }
} }
} }

View file

@ -1,16 +1,15 @@
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] #![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
use std::error::Error;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use js_sys::{Array, Object, Promise, Uint8Array}; use js_sys::{Array, Object, Promise, Uint8Array};
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::{ use web_sys::{
console, UsbControlTransferParameters, UsbDevice as WasmUsbDevice, UsbDeviceRequestOptions, UsbControlTransferParameters, UsbDevice as WasmUsbDevice, UsbDeviceRequestOptions,
UsbInTransferResult, UsbOutTransferResult, UsbRecipient, UsbRequestType, UsbInTransferResult, UsbOutTransferResult, UsbRecipient, UsbRequestType,
}; };
// Crate stuff // Crate stuff
use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient}; use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError};
#[wasm_bindgen] #[wasm_bindgen]
pub struct UsbDevice { pub struct UsbDevice {
@ -22,6 +21,36 @@ pub struct UsbInterface {
device: WasmUsbDevice, device: WasmUsbDevice,
} }
#[wasm_bindgen]
#[derive(PartialEq, Clone)]
pub struct DeviceFilter {
pub vendor_id: Option<u16>,
pub product_id: Option<u16>,
pub class: Option<u8>,
pub subclass: Option<u8>,
pub protocol: Option<u8>,
serial_number: Option<String>,
}
impl DeviceFilter {
pub fn serial_number(&self) -> &Option<String> {
&self.serial_number
}
}
impl Default for DeviceFilter {
fn default() -> Self {
Self {
vendor_id: None,
product_id: None,
class: None,
subclass: None,
protocol: None,
serial_number: None,
}
}
}
#[wasm_bindgen] #[wasm_bindgen]
pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, js_sys::Error> { pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, js_sys::Error> {
let window = web_sys::window().unwrap(); let window = web_sys::window().unwrap();
@ -71,13 +100,9 @@ pub async fn get_device(vendor_id: u16, product_id: u16) -> Result<UsbDevice, js
Ok(UsbDevice { device }) Ok(UsbDevice { device })
} }
#[wasm_bindgen]
#[derive(PartialEq, Eq, Clone)]
pub struct FilterTuple(pub u16, pub u16);
#[wasm_bindgen] #[wasm_bindgen]
pub async fn get_device_filter( pub async fn get_device_filter(
device_filter: Vec<FilterTuple>, device_filter: Vec<DeviceFilter>,
) -> Result<UsbDevice, js_sys::Error> { ) -> Result<UsbDevice, js_sys::Error> {
let window = web_sys::window().unwrap(); let window = web_sys::window().unwrap();
@ -91,31 +116,93 @@ pub async fn get_device_filter(
for js_device in device_list { for js_device in device_list {
let device: WasmUsbDevice = js_device.into(); let device: WasmUsbDevice = js_device.into();
for filter_info in &device_filter { if device_filter.iter().position(|info|
if device.vendor_id() == filter_info.0 && device.product_id() == filter_info.1 { {
let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; let mut result = false;
return Ok(UsbDevice { device }); if info.vendor_id.is_some() {
result = info.vendor_id.unwrap() == device.vendor_id();
} }
if info.product_id.is_some() {
result = info.product_id.unwrap() == device.product_id();
}
if info.class.is_some() {
result = info.class.unwrap() == device.device_class();
}
if info.subclass.is_some() {
result = info.subclass.unwrap() == device.device_subclass();
}
if info.protocol.is_some() {
result = info.protocol.unwrap() == device.device_protocol();
}
if info.serial_number().is_some() && device.serial_number().is_some() {
result = info.serial_number().as_ref().unwrap() == &device.serial_number().unwrap().to_owned();
}
result
}
).is_some() {
return Ok(UsbDevice { device });
} }
} }
let arr = Array::new(); let arr = Array::new();
for device in device_filter { for filter in device_filter {
let filter1 = js_sys::Object::new(); let js_filter = js_sys::Object::new();
if let Some(vid) = filter.vendor_id {
js_sys::Reflect::set( js_sys::Reflect::set(
&filter1, &js_filter,
&JsValue::from_str("vendorId"), &JsValue::from_str("vendorId"),
&JsValue::from(device.0), &JsValue::from(vid),
) )
.unwrap(); .unwrap();
}
if let Some(pid) = filter.product_id {
js_sys::Reflect::set( js_sys::Reflect::set(
&filter1, &js_filter,
&JsValue::from_str("productId"), &JsValue::from_str("productId"),
&JsValue::from(device.1), &JsValue::from(pid),
) )
.unwrap(); .unwrap();
arr.push(&filter1); }
if let Some(class) = filter.class {
js_sys::Reflect::set(
&js_filter,
&JsValue::from_str("classCode"),
&JsValue::from(class),
)
.unwrap();
}
if let Some(subclass) = filter.subclass {
js_sys::Reflect::set(
&js_filter,
&JsValue::from_str("subclassCode"),
&JsValue::from(subclass),
)
.unwrap();
}
if let Some(pro) = filter.protocol {
js_sys::Reflect::set(
&js_filter,
&JsValue::from_str("protocolCode"),
&JsValue::from(pro),
)
.unwrap();
}
if let Some(serial) = filter.serial_number {
js_sys::Reflect::set(
&js_filter,
&JsValue::from_str("serialNumber"),
&JsValue::from(serial),
)
.unwrap();
}
arr.push(&js_filter);
} }
let filters = JsValue::from(&arr); let filters = JsValue::from(&arr);
@ -134,16 +221,15 @@ impl Device for UsbDevice {
type UsbDevice = UsbDevice; type UsbDevice = UsbDevice;
type UsbInterface = UsbInterface; type UsbInterface = UsbInterface;
async fn open_interface(&self, number: u8) -> Result<UsbInterface, Box<dyn Error>> { async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError> {
let dev_promise = let dev_promise =
JsFuture::from(Promise::resolve(&self.device.claim_interface(number))).await; JsFuture::from(Promise::resolve(&self.device.claim_interface(number))).await;
// Wait for the interface to be claimed // Wait for the interface to be claimed
let _device: WasmUsbDevice = match dev_promise { let _device: WasmUsbDevice = match dev_promise {
Ok(dev) => dev.into(), Ok(dev) => dev.into(),
Err(err) => { Err(_) => {
console::log_1(&err.clone()); return Err(UsbError::CommunicationError);
return Err(format!("{:?}", err).into());
} }
}; };
@ -152,14 +238,14 @@ impl Device for UsbDevice {
}) })
} }
async fn reset(&self) -> Result<(), Box<dyn Error>> { async fn reset(&self) -> Result<(), UsbError> {
let promise = Promise::resolve(&self.device.reset()); let promise = Promise::resolve(&self.device.reset());
let result = JsFuture::from(promise).await; let result = JsFuture::from(promise).await;
match result { match result {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(_) => Err("cancelled".into()), Err(_) => Err(UsbError::CommunicationError),
} }
} }
@ -189,7 +275,7 @@ impl Device for UsbDevice {
} }
impl<'a> Interface<'a> for UsbInterface { impl<'a> Interface<'a> for UsbInterface {
async fn control_in(&self, data: crate::usb::ControlIn) -> Result<Vec<u8>, Box<dyn Error>> { async fn control_in(&self, data: crate::usb::ControlIn) -> Result<Vec<u8>, UsbError> {
let length = data.length; let length = data.length;
let params: UsbControlTransferParameters = data.into(); let params: UsbControlTransferParameters = data.into();
@ -198,12 +284,12 @@ impl<'a> Interface<'a> for UsbInterface {
let transfer_result: UsbInTransferResult = match result { let transfer_result: UsbInTransferResult = match result {
Ok(res) => res.into(), Ok(res) => res.into(),
Err(err) => return Err(format!("Error {:?}", err).into()), Err(_) => return Err(UsbError::TransferError),
}; };
let data = match transfer_result.data() { let data = match transfer_result.data() {
Some(res) => res.buffer(), Some(res) => res.buffer(),
None => return Err("No data returned".into()), None => return Err(UsbError::TransferError),
}; };
let array = Uint8Array::new(&data); let array = Uint8Array::new(&data);
@ -211,37 +297,36 @@ impl<'a> Interface<'a> for UsbInterface {
Ok(array.to_vec()) Ok(array.to_vec())
} }
async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result<(), Box<dyn Error>> { async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result<usize, UsbError> {
let array = Uint8Array::from(data.data); let array = Uint8Array::from(data.data);
let array_obj = Object::try_from(&array).unwrap(); let array_obj = Object::try_from(&array).unwrap();
let params: UsbControlTransferParameters = data.into(); let params: UsbControlTransferParameters = data.into();
let promise = Promise::resolve( let result: UsbOutTransferResult = match JsFuture::from(Promise::resolve(
&self &self
.device .device
.control_transfer_out_with_buffer_source(&params, array_obj), .control_transfer_out_with_buffer_source(&params, array_obj),
); )).await {
let result = JsFuture::from(promise).await; Ok(res) => res.into(),
Err(_) => return Err(UsbError::TransferError)
};
match result { Ok(result.bytes_written() as usize)
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err).into()),
}
} }
async fn bulk_in(&self, endpoint: u8, length: usize) -> Result<Vec<u8>, Box<dyn Error>> { async fn bulk_in(&self, endpoint: u8, length: usize) -> Result<Vec<u8>, UsbError> {
let promise = Promise::resolve(&self.device.transfer_in(endpoint, length as u32)); let promise = Promise::resolve(&self.device.transfer_in(endpoint, length as u32));
let result = JsFuture::from(promise).await; let result = JsFuture::from(promise).await;
let transfer_result: UsbInTransferResult = match result { let transfer_result: UsbInTransferResult = match result {
Ok(res) => res.into(), Ok(res) => res.into(),
Err(err) => return Err(format!("Error {:?}", err).into()), Err(_) => return Err(UsbError::TransferError),
}; };
let data = match transfer_result.data() { let data = match transfer_result.data() {
Some(res) => res.buffer(), Some(res) => res.buffer(),
None => return Err("No data returned".into()), None => return Err(UsbError::TransferError),
}; };
let array = Uint8Array::new(&data); let array = Uint8Array::new(&data);
@ -249,7 +334,7 @@ impl<'a> Interface<'a> for UsbInterface {
Ok(array.to_vec()) Ok(array.to_vec())
} }
async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result<usize, Box<dyn Error>> { async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result<usize, UsbError> {
let array = Uint8Array::from(data); let array = Uint8Array::from(data);
let array_obj = Object::try_from(&array).unwrap(); let array_obj = Object::try_from(&array).unwrap();
@ -263,7 +348,7 @@ impl<'a> Interface<'a> for UsbInterface {
let transfer_result: UsbOutTransferResult = match result { let transfer_result: UsbOutTransferResult = match result {
Ok(res) => res.into(), Ok(res) => res.into(),
Err(err) => return Err(format!("Error {:?}", err).into()), Err(_) => return Err(UsbError::TransferError),
}; };
Ok(transfer_result.bytes_written() as usize) Ok(transfer_result.bytes_written() as usize)

View file

@ -51,7 +51,7 @@ pub use crate::context::UsbInterface;
/// A single Device ID and Product ID pair to find when looking /// A single Device ID and Product ID pair to find when looking
/// for new USB devices using [get_device_filter] /// for new USB devices using [get_device_filter]
#[doc(inline)] #[doc(inline)]
pub use crate::context::FilterTuple; pub use crate::context::DeviceFilter;
/// Gets a single device from the VendorID and ProductID /// Gets a single device from the VendorID and ProductID
#[doc(inline)] #[doc(inline)]

View file

@ -3,7 +3,7 @@
//! structs which allow for USB communication. //! structs which allow for USB communication.
use crate::context::UsbInterface; use crate::context::UsbInterface;
use std::error::Error; use thiserror::Error;
/// A unique USB device /// A unique USB device
pub trait Device { pub trait Device {
@ -14,10 +14,10 @@ pub trait Device {
type UsbInterface; type UsbInterface;
/// Open a specific interface of the device /// Open a specific interface of the device
async fn open_interface(&self, number: u8) -> Result<UsbInterface, Box<dyn Error>>; async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError>;
/// Reset the device, which causes it to no longer be usable /// Reset the device, which causes it to no longer be usable
async fn reset(&self) -> Result<(), Box<dyn Error>>; async fn reset(&self) -> Result<(), UsbError>;
/// 16 bit device Product ID /// 16 bit device Product ID
async fn product_id(&self) -> u16; async fn product_id(&self) -> u16;
@ -44,20 +44,20 @@ pub trait Device {
pub trait Interface<'a> { pub trait Interface<'a> {
/// A USB control in transfer (device to host) /// A USB control in transfer (device to host)
/// Returns a [Result] with the bytes in a `Vec<u8>` /// Returns a [Result] with the bytes in a `Vec<u8>`
async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, Box<dyn Error>>; async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, UsbError>;
/// A USB control out transfer (host to device) /// A USB control out transfer (host to device)
async fn control_out(&self, data: ControlOut<'a>) -> Result<(), Box<dyn Error>>; async fn control_out(&self, data: ControlOut<'a>) -> Result<usize, UsbError>;
/// A USB bulk in transfer (device to host) /// A USB bulk in transfer (device to host)
/// It takes in a bulk endpoint to send to along with the length of /// It takes in a bulk endpoint to send to along with the length of
/// data to read, and returns a [Result] with the bytes /// data to read, and returns a [Result] with the bytes
async fn bulk_in(&self, endpoint: u8, length: usize) -> Result<Vec<u8>, Box<dyn Error>>; async fn bulk_in(&self, endpoint: u8, length: usize) -> Result<Vec<u8>, UsbError>;
/// A USB bulk out transfer (host to device). /// A USB bulk out transfer (host to device).
/// It takes in a bulk endpoint to send to along with some data as /// It takes in a bulk endpoint to send to along with some data as
/// a slice, and returns a [Result] containing the number of bytes transferred /// a slice, and returns a [Result] containing the number of bytes transferred
async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result<usize, Box<dyn Error>>; async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result<usize, UsbError>;
/* Interrupt transfers are a work in progress /* Interrupt transfers are a work in progress
async fn interrupt_in(&self, _endpoint: u8, _buf: Vec<u8>) { async fn interrupt_in(&self, _endpoint: u8, _buf: Vec<u8>) {
@ -70,6 +70,22 @@ pub trait Interface<'a> {
*/ */
} }
/// An error from a USB interface
#[derive(Error, Debug)]
pub enum UsbError {
#[error("device not found")]
DeviceNotFound,
#[error("device transfer failed")]
TransferError,
#[error("device communication failed")]
CommunicationError,
#[error("device disconnected")]
Disconnected,
}
/// The type of USB transfer /// The type of USB transfer
pub enum ControlType { pub enum ControlType {
Standard = 0, Standard = 0,