Added UsbDescriptor, added get_device_list() on native, updated deps

This commit is contained in:
G2-Games 2024-03-23 20:14:48 -05:00
parent 54154d54c3
commit 202d2d1fbd
4 changed files with 393 additions and 104 deletions

View file

@ -1,7 +1,13 @@
use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; use crate::usb::{
ControlIn, ControlOut, ControlType, Descriptor, Device, Interface, Recipient, UsbError,
};
pub struct UsbDescriptor {
device_info: nusb::DeviceInfo,
}
pub struct UsbDevice { pub struct UsbDevice {
device_info: nusb::DeviceInfo, device_info: UsbDescriptor,
device: nusb::Device, device: nusb::Device,
} }
@ -36,15 +42,13 @@ impl DeviceFilter {
} }
} }
pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDevice, UsbError> { pub async fn get_device(device_filters: Vec<DeviceFilter>) -> Result<UsbDescriptor, UsbError> {
let devices = nusb::list_devices().unwrap(); let devices = nusb::list_devices().unwrap();
let mut device_info = None; let mut device_info = None;
for prelim_dev_inf in devices { for prelim_dev_inf in devices {
// See if the device exists in the list // See if the device exists in the list
if device_filter if device_filters.iter().any(|info| {
.iter()
.any(|info| {
let mut result = false; let mut result = false;
if info.vendor_id.is_some() { if info.vendor_id.is_some() {
@ -68,10 +72,9 @@ pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDevice, U
} }
result result
}) }) {
{
device_info = Some(prelim_dev_inf); device_info = Some(prelim_dev_inf);
break break;
} }
} }
@ -80,39 +83,69 @@ pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDevice, U
None => return Err(UsbError::DeviceNotFound), None => return Err(UsbError::DeviceNotFound),
}; };
let device = match device_info.open() { Ok(UsbDescriptor { device_info })
Ok(dev) => dev,
Err(_) => return Err(UsbError::CommunicationError),
};
Ok(UsbDevice {
device_info,
device,
})
} }
impl Device for UsbDevice { pub async fn get_device_list(
type UsbDevice = UsbDevice; device_filters: Vec<DeviceFilter>,
type UsbInterface = UsbInterface; ) -> Result<Vec<UsbDescriptor>, UsbError> {
let devices_info = nusb::list_devices().unwrap();
async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError> { let mut devices = Vec::new();
let interface = match self.device.claim_interface(number) { for prelim_dev_inf in devices_info {
Ok(inter) => inter, // See if the device exists in the list
Err(_) => return Err(UsbError::CommunicationError), if device_filters.iter().any(|info| {
}; let mut result = false;
Ok(UsbInterface { interface }) if info.vendor_id.is_some() {
result = info.vendor_id.unwrap() == prelim_dev_inf.vendor_id();
} }
async fn reset(&self) -> Result<(), UsbError> { if info.product_id.is_some() {
match self.device.reset() { result = info.product_id.unwrap() == prelim_dev_inf.product_id();
Ok(_) => Ok(()), }
Err(_) => Err(UsbError::CommunicationError),
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();
}
result
}) {
devices.push(prelim_dev_inf);
} }
} }
async fn forget(&self) -> Result<(), UsbError> { if devices.is_empty() {
self.reset().await return Err(UsbError::DeviceNotFound);
}
let devices_opened: Vec<UsbDescriptor> = devices
.into_iter()
.map(|d| UsbDescriptor { device_info: d })
.collect();
Ok(devices_opened)
}
impl Descriptor for UsbDescriptor {
type Device = UsbDevice;
async fn open(self) -> Result<Self::Device, UsbError> {
match self.device_info.open() {
Ok(dev) => Ok(Self::Device {
device_info: self,
device: dev,
}),
Err(err) => Err(UsbError::CommunicationError(err.to_string())),
}
} }
async fn vendor_id(&self) -> u16 { async fn vendor_id(&self) -> u16 {
@ -140,6 +173,69 @@ impl Device for UsbDevice {
} }
} }
impl Device for UsbDevice {
type Interface = UsbInterface;
async fn open_interface(&self, number: u8) -> Result<Self::Interface, UsbError> {
let interface = match self.device.claim_interface(number) {
Ok(inter) => inter,
Err(err) => return Err(UsbError::CommunicationError(err.to_string())),
};
Ok(UsbInterface { interface })
}
async fn detach_and_open_interface(&self, number: u8) -> Result<Self::Interface, UsbError> {
let interface = match self.device.detach_and_claim_interface(number) {
Ok(inter) => inter,
Err(err) => return Err(UsbError::CommunicationError(err.to_string())),
};
Ok(UsbInterface { interface })
}
async fn reset(&self) -> Result<(), UsbError> {
match self.device.reset() {
Ok(_) => Ok(()),
Err(err) => Err(UsbError::CommunicationError(err.to_string())),
}
}
async fn forget(&self) -> Result<(), UsbError> {
self.reset().await
}
async fn product_id(&self) -> u16 {
self.device_info.product_id().await
}
async fn vendor_id(&self) -> u16 {
self.device_info.vendor_id().await
}
async fn class(&self) -> u8 {
self.device_info.class().await
}
async fn subclass(&self) -> u8 {
self.device_info.subclass().await
}
async fn manufacturer_string(&self) -> Option<String> {
self.device_info.manufacturer_string().await
}
async fn product_string(&self) -> Option<String> {
self.device_info.product_string().await
}
}
impl Drop for UsbDevice {
fn drop(&mut self) {
let _ = self.device.reset();
}
}
impl<'a> Interface<'a> for UsbInterface { impl<'a> Interface<'a> for UsbInterface {
async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, UsbError> { async fn control_in(&self, data: ControlIn) -> Result<Vec<u8>, UsbError> {
let result = match self.interface.control_in(data.into()).await.into_result() { let result = match self.interface.control_in(data.into()).await.into_result() {

View file

@ -1,4 +1,4 @@
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] //#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use js_sys::{Array, Object, Promise, Uint8Array}; use js_sys::{Array, Object, Promise, Uint8Array};
@ -9,7 +9,12 @@ use web_sys::{
}; };
// Crate stuff // Crate stuff
use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError, Descriptor};
#[wasm_bindgen]
pub struct UsbDescriptor {
device: WasmUsbDevice,
}
#[wasm_bindgen] #[wasm_bindgen]
pub struct UsbDevice { pub struct UsbDevice {
@ -51,9 +56,7 @@ impl DeviceFilter {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub async fn get_device( pub async fn get_device(device_filter: Vec<DeviceFilter>) -> Result<UsbDescriptor, js_sys::Error> {
device_filter: Vec<DeviceFilter>,
) -> Result<UsbDevice, js_sys::Error> {
let window = web_sys::window().unwrap(); let window = web_sys::window().unwrap();
let navigator = window.navigator(); let navigator = window.navigator();
@ -68,9 +71,7 @@ pub async fn get_device(
for js_device in device_list { for js_device in device_list {
let device: WasmUsbDevice = js_device.into(); let device: WasmUsbDevice = js_device.into();
if device_filter if device_filter.iter().any(|info| {
.iter()
.any(|info| {
let mut result = false; let mut result = false;
if info.vendor_id.is_some() { if info.vendor_id.is_some() {
@ -94,10 +95,9 @@ pub async fn get_device(
} }
result result
}) }) {
{
let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?;
return Ok(UsbDevice { device }); return Ok(UsbDescriptor { device });
} }
} }
@ -156,12 +156,154 @@ pub async fn get_device(
let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?; let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?;
Ok(UsbDevice { device }) Ok(UsbDescriptor { device })
}
/*
#[wasm_bindgen]
pub async fn get_device_list(device_filter: Vec<DeviceFilter>) -> Result<Vec<UsbDescriptor>, js_sys::Error> {
let window = web_sys::window().unwrap();
let navigator = window.navigator();
let usb = navigator.usb();
let device_list: Array = match JsFuture::from(Promise::resolve(&usb.get_devices())).await {
Ok(list) => list.into(),
Err(_) => Array::new(),
};
let mut devices = Vec::new();
// Check if the device is already paired, if so, we don't need to request it again
for js_device in device_list {
let device: WasmUsbDevice = js_device.into();
if device_filter.iter().any(|info| {
let mut result = false;
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();
}
result
}) {
let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?;
devices.push(UsbDescriptor { device });
}
}
let arr = Array::new();
for filter in device_filter {
let js_filter = js_sys::Object::new();
if let Some(vid) = filter.vendor_id {
js_sys::Reflect::set(
&js_filter,
&JsValue::from_str("vendorId"),
&JsValue::from(vid),
)
.unwrap();
}
if let Some(pid) = filter.product_id {
js_sys::Reflect::set(
&js_filter,
&JsValue::from_str("productId"),
&JsValue::from(pid),
)
.unwrap();
}
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();
}
arr.push(&js_filter);
}
let filters = JsValue::from(&arr);
let filters2 = UsbDeviceRequestOptions::new(&filters);
let device: WasmUsbDevice = JsFuture::from(Promise::resolve(&usb.request_device(&filters2)))
.await?
.into();
let _open_promise = JsFuture::from(Promise::resolve(&device.open())).await?;
devices.push(UsbDescriptor { device });
return Ok(devices);
}
*/
impl Descriptor for UsbDescriptor {
type Device = UsbDevice;
async fn open(self) -> Result<Self::Device, UsbError> {
Ok(Self::Device {
device: self.device
})
}
async fn product_id(&self) -> u16 {
self.device.vendor_id()
}
async fn vendor_id(&self) -> u16 {
self.device.product_id()
}
async fn class(&self) -> u8 {
self.device.device_class()
}
async fn subclass(&self) -> u8 {
self.device.device_subclass()
}
async fn manufacturer_string(&self) -> Option<String> {
self.device.manufacturer_name()
}
async fn product_string(&self) -> Option<String> {
self.device.product_name()
}
} }
impl Device for UsbDevice { impl Device for UsbDevice {
type UsbDevice = UsbDevice; type Interface = UsbInterface;
type UsbInterface = UsbInterface;
async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError> { async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError> {
let dev_promise = let dev_promise =
@ -170,8 +312,8 @@ impl Device for UsbDevice {
// 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) => {
return Err(UsbError::CommunicationError); return Err(UsbError::CommunicationError(err.as_string().unwrap_or_default()));
} }
}; };
@ -181,12 +323,16 @@ impl Device for UsbDevice {
}) })
} }
async fn detach_and_open_interface(&self, number: u8) -> Result<Self::Interface, UsbError> {
self.open_interface(number).await
}
async fn reset(&self) -> Result<(), UsbError> { async fn reset(&self) -> Result<(), UsbError> {
let result = JsFuture::from(Promise::resolve(&self.device.reset())).await; let result = JsFuture::from(Promise::resolve(&self.device.reset())).await;
match result { match result {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(_) => Err(UsbError::CommunicationError), Err(err) => Err(UsbError::CommunicationError(err.as_string().unwrap_or_default())),
} }
} }
@ -195,7 +341,7 @@ impl Device for UsbDevice {
match result { match result {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(_) => Err(UsbError::CommunicationError), Err(err) => Err(UsbError::CommunicationError(err.as_string().unwrap_or_default())),
} }
} }

View file

@ -9,24 +9,34 @@
//! //!
//! When a [UsbInterface] is dropped, it is automatically released. //! When a [UsbInterface] is dropped, it is automatically released.
//! //!
//! ### CURRENT LIMITATIONS:
//! * Hotplug support is not implemented. Waiting on [hotplug support in nusb](https://github.com/kevinmehall/nusb/pull/20).
//!
//! * Until [this pull request](https://github.com/rustwasm/wasm-bindgen/issues/3155)
//! is merged into wasm bindgen, getting a list of USB devices is not possible on WASM
//! targets. However, this isn't a huge deal as the user gets a list to select from anyway.
//!
//! ## Example: //! ## Example:
//! ```no_run //! ```no_run
//! # tokio_test::block_on(async { //! # tokio_test::block_on(async {
//! use cross_usb::usb::{Device, Interface, Recipient, ControlType, ControlIn}; //! use cross_usb::usb::{Descriptor, Device, Interface, Recipient, ControlType, ControlIn};
//! use cross_usb::device_filter; //! use cross_usb::device_filter;
//! //!
//! // Obtain a device using its VendorID and ProductID //! // Obtain a device descriptor (UsbDescriptor) using a DeviceFilter,
//! let filter = vec![ //! // in this case with its VendorID and ProductID
//! let filters = vec![
//! device_filter!{vendor_id: 0x054c, product_id: 0x00c9} //! device_filter!{vendor_id: 0x054c, product_id: 0x00c9}
//! ]; //! ];
//! let dev_descriptor = cross_usb::get_device(filters).await.expect("Failed to find device");
//! //!
//! let device = cross_usb::get_device(filter).await.expect("Failed to get device"); //! // Open the device that the descriptor is describing
//! let dev = dev_descriptor.open().await.expect("Failed to open device");
//! //!
//! // Obtain an interface of the device //! // Obtain an interface of the device
//! let interface = device.open_interface(0).await.expect("Failed to open interface"); //! let interface = dev.open_interface(0).await.expect("Failed to open interface");
//! //!
//! // Send a Control transfer to the device, obtaining //! // Send a Control transfer to the device, obtaining
//! // the result and storing it in `result`, and you're done! //! // the result and storing it in `result`
//! let result = interface.control_in(ControlIn { //! let result = interface.control_in(ControlIn {
//! control_type: ControlType::Vendor, //! control_type: ControlType::Vendor,
//! recipient: Recipient::Interface, //! recipient: Recipient::Interface,
@ -41,16 +51,19 @@
//! ``` //! ```
pub mod usb; pub mod usb;
/// The context contains the platform specific implementation of the USB transfers
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
#[path = "./backend/native.rs"] #[path = "./backend/native.rs"]
/// The context contains the platform specific implementation of the USB transfers
mod context; mod context;
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
#[path = "./backend/wasm.rs"] #[path = "./backend/wasm.rs"]
/// The context contains the platform specific implementation of the USB transfers
mod context; mod context;
#[doc(inline)]
/// An implementation of a USB device descriptor
pub use crate::context::UsbDescriptor;
#[doc(inline)] #[doc(inline)]
/// An implementation of a USB device /// An implementation of a USB device
pub use crate::context::UsbDevice; pub use crate::context::UsbDevice;
@ -64,14 +77,13 @@ pub use crate::context::UsbInterface;
#[doc(inline)] #[doc(inline)]
pub use crate::context::DeviceFilter; pub use crate::context::DeviceFilter;
/// Gets a single device from a list of VendorID and ProductIDs /// Gets a single device descriptor ([UsbDescriptor]) from a list of VendorID and ProductIDs
/// ///
/// ## Example /// ## Example
/// ```no_run /// ```no_run
/// # tokio_test::block_on(async { /// # tokio_test::block_on(async {
/// use cross_usb::{get_device, DeviceFilter, device_filter}; /// use cross_usb::{get_device, DeviceFilter, device_filter};
/// ///
///
/// let filter = vec![ /// let filter = vec![
/// device_filter!{vendor_id: 0x054c, product_id: 0x00c9}, /// device_filter!{vendor_id: 0x054c, product_id: 0x00c9},
/// device_filter!{vendor_id: 0x054c}, /// device_filter!{vendor_id: 0x054c},
@ -83,6 +95,11 @@ pub use crate::context::DeviceFilter;
#[doc(inline)] #[doc(inline)]
pub use crate::context::get_device; pub use crate::context::get_device;
/// Gets a list of devices from a list of VendorID and ProductIDs
#[cfg(not(target_family = "wasm"))]
#[doc(inline)]
pub use crate::context::get_device_list;
/// Macro to create a device filter more easily. /// Macro to create a device filter more easily.
/// ///
/// The only valid keys are fields of the [DeviceFilter] struct. /// The only valid keys are fields of the [DeviceFilter] struct.

View file

@ -2,19 +2,49 @@
//! This module contains the traits and associated functions and //! This module contains the traits and associated functions and
//! structs which allow for USB communication. //! structs which allow for USB communication.
use crate::context::UsbInterface;
use thiserror::Error; use thiserror::Error;
pub trait Descriptor {
/// A unique USB Device
type Device;
/// Opens the USB connection, returning a [Self::Device]
async fn open(self) -> Result<Self::Device, UsbError>;
/// 16 bit device Product ID
async fn product_id(&self) -> u16;
/// 16 bit device Vendor ID
async fn vendor_id(&self) -> u16;
/// Device standard class
async fn class(&self) -> u8;
/// Device standard subclass
async fn subclass(&self) -> u8;
/// Get the manufacturer string string of the device, if available without device IO
///
/// Not available on Windows
async fn manufacturer_string(&self) -> Option<String>;
/// Get the product string of the device, if available without device IO
async fn product_string(&self) -> Option<String>;
}
/// A unique USB device /// A unique USB device
pub trait Device { pub trait Device {
/// A unique USB Device
type UsbDevice;
/// A unique Interface on a USB Device /// A unique Interface on a USB Device
type UsbInterface; type Interface;
/// Open a specific interface of the device /// Open a specific interface of the device
async fn open_interface(&self, number: u8) -> Result<UsbInterface, UsbError>; async fn open_interface(&self, number: u8) -> Result<Self::Interface, UsbError>;
/// Open a specific interface of the device, detaching any
/// kernel drivers and claiming it.
///
/// **Note:** This only has an effect on Native, and only on Linux.
async fn detach_and_open_interface(&self, number: u8) -> Result<Self::Interface, UsbError>;
/// Reset the device, which causes it to no longer be usable. You must /// Reset the device, which causes it to no longer be usable. You must
/// request a new device with [crate::get_device] /// request a new device with [crate::get_device]
@ -22,7 +52,7 @@ pub trait Device {
/// Remove the device from the paired devices list, causing it to no longer be usable. You must request to reconnect using [crate::get_device] /// Remove the device from the paired devices list, causing it to no longer be usable. You must request to reconnect using [crate::get_device]
/// ///
/// **Note: on Native with `nusb` this simply resets the device** /// **Note:** On Native this simply resets the device.
async fn forget(&self) -> Result<(), UsbError>; async fn forget(&self) -> Result<(), UsbError>;
/// 16 bit device Product ID /// 16 bit device Product ID
@ -87,7 +117,7 @@ pub enum UsbError {
TransferError, TransferError,
#[error("device communication failed")] #[error("device communication failed")]
CommunicationError, CommunicationError(String),
#[error("device disconnected")] #[error("device disconnected")]
Disconnected, Disconnected,