#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] use wasm_bindgen::prelude::*; use js_sys::{Array, Object, Promise, Uint8Array}; use wasm_bindgen_futures::JsFuture; use web_sys::{ UsbControlTransferParameters, UsbDevice as WasmUsbDevice, UsbDeviceRequestOptions, UsbInTransferResult, UsbOutTransferResult, UsbRecipient, UsbRequestType, }; // Crate stuff use crate::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError}; #[wasm_bindgen] pub struct UsbDevice { device: WasmUsbDevice, } #[wasm_bindgen] pub struct UsbInterface { device: WasmUsbDevice, _number: u8, } #[wasm_bindgen] #[derive(PartialEq, Clone, Default)] pub struct DeviceFilter { pub vendor_id: Option, pub product_id: Option, pub class: Option, pub subclass: Option, pub protocol: Option, } impl DeviceFilter { pub fn new( vendor_id: Option, product_id: Option, class: Option, subclass: Option, protocol: Option, ) -> Self { Self { vendor_id, product_id, class, subclass, protocol, } } } #[wasm_bindgen] pub async fn get_device( device_filter: Vec, ) -> Result { 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(), }; // 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?; return Ok(UsbDevice { 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?; Ok(UsbDevice { device }) } impl Device for UsbDevice { type UsbDevice = UsbDevice; type UsbInterface = UsbInterface; async fn open_interface(&self, number: u8) -> Result { let dev_promise = JsFuture::from(Promise::resolve(&self.device.claim_interface(number))).await; // Wait for the interface to be claimed let _device: WasmUsbDevice = match dev_promise { Ok(dev) => dev.into(), Err(_) => { return Err(UsbError::CommunicationError); } }; Ok(UsbInterface { device: self.device.clone(), _number: number, }) } async fn reset(&self) -> Result<(), UsbError> { let result = JsFuture::from(Promise::resolve(&self.device.reset())).await; match result { Ok(_) => Ok(()), Err(_) => Err(UsbError::CommunicationError), } } async fn forget(&self) -> Result<(), UsbError> { let result = JsFuture::from(Promise::resolve(&self.device.forget())).await; match result { Ok(_) => Ok(()), Err(_) => Err(UsbError::CommunicationError), } } async fn vendor_id(&self) -> u16 { self.device.vendor_id() } async fn product_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 { self.device.manufacturer_name() } async fn product_string(&self) -> Option { self.device.product_name() } } impl<'a> Interface<'a> for UsbInterface { async fn control_in(&self, data: crate::usb::ControlIn) -> Result, UsbError> { let length = data.length; let params: UsbControlTransferParameters = data.into(); let promise = Promise::resolve(&self.device.control_transfer_in(¶ms, length)); let result = JsFuture::from(promise).await; let transfer_result: UsbInTransferResult = match result { Ok(res) => res.into(), Err(_) => return Err(UsbError::TransferError), }; let data = match transfer_result.data() { Some(res) => res.buffer(), None => return Err(UsbError::TransferError), }; let array = Uint8Array::new(&data); Ok(array.to_vec()) } async fn control_out(&self, data: crate::usb::ControlOut<'a>) -> Result { let array = Uint8Array::from(data.data); let array_obj = Object::try_from(&array).unwrap(); let params: UsbControlTransferParameters = data.into(); let result: UsbOutTransferResult = match JsFuture::from(Promise::resolve( &self .device .control_transfer_out_with_buffer_source(¶ms, array_obj), )) .await { Ok(res) => res.into(), Err(_) => return Err(UsbError::TransferError), }; Ok(result.bytes_written() as usize) } async fn bulk_in(&self, endpoint: u8, length: usize) -> Result, UsbError> { let promise = Promise::resolve(&self.device.transfer_in(endpoint, length as u32)); let result = JsFuture::from(promise).await; let transfer_result: UsbInTransferResult = match result { Ok(res) => res.into(), Err(_) => return Err(UsbError::TransferError), }; let data = match transfer_result.data() { Some(res) => res.buffer(), None => return Err(UsbError::TransferError), }; let array = Uint8Array::new(&data); Ok(array.to_vec()) } async fn bulk_out(&self, endpoint: u8, data: &[u8]) -> Result { let array = Uint8Array::from(data); let array_obj = Object::try_from(&array).unwrap(); let promise = Promise::resolve( &self .device .transfer_out_with_buffer_source(endpoint, array_obj), ); let result = JsFuture::from(promise).await; let transfer_result: UsbOutTransferResult = match result { Ok(res) => res.into(), Err(_) => return Err(UsbError::TransferError), }; Ok(transfer_result.bytes_written() as usize) } /* async fn interrupt_in(&self, endpoint: u8, length: usize) -> Result, UsbError> { let promise = Promise::resolve(&self.device.transfer_in(endpoint, length as u32)); let result = JsFuture::from(promise).await; let transfer_result: UsbInTransferResult = match result { Ok(res) => res.into(), Err(_) => return Err(UsbError::TransferError), }; if transfer_result. let data = match transfer_result.data() { Some(res) => res.buffer(), None => return Err(UsbError::TransferError), }; let array = Uint8Array::new(&data); Ok(array.to_vec()) } async fn interrupt_out(&self, endpoint: u8, buf: Vec) -> Result { todo!() } */ } impl From for UsbControlTransferParameters { fn from(value: ControlIn) -> Self { UsbControlTransferParameters::new( value.index, value.recipient.into(), value.request, value.control_type.into(), value.value, ) } } impl From> for UsbControlTransferParameters { fn from(value: ControlOut) -> Self { UsbControlTransferParameters::new( value.index, value.recipient.into(), value.request, value.control_type.into(), value.value, ) } } impl From for UsbRecipient { fn from(value: Recipient) -> Self { match value { Recipient::Device => UsbRecipient::Device, Recipient::Interface => UsbRecipient::Interface, Recipient::Endpoint => UsbRecipient::Endpoint, Recipient::Other => UsbRecipient::Other, } } } impl From for UsbRequestType { fn from(value: ControlType) -> Self { match value { ControlType::Standard => UsbRequestType::Standard, ControlType::Class => UsbRequestType::Class, ControlType::Vendor => UsbRequestType::Vendor, } } }