Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB Composite Device Support #58

Merged
merged 7 commits into from
Nov 27, 2024
87 changes: 85 additions & 2 deletions source/postcard-rpc/src/host_client/raw_nusb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::future::Future;

use nusb::{
transfer::{Queue, RequestBuffer, TransferError},
DeviceInfo,
DeviceInfo, InterfaceInfo,
};
use postcard_schema::Schema;
use serde::de::DeserializeOwned;
Expand Down Expand Up @@ -85,11 +85,94 @@ where
.map_err(|e| format!("Error listing devices: {e:?}"))?
.find(func)
.ok_or_else(|| String::from("Failed to find matching nusb device!"))?;
let interface_id = x
.interfaces()
.position(|i| i.class() == 0xFF)
.ok_or_else(|| String::from("Failed to find matching interface!!"))?;
let dev = x
.open()
.map_err(|e| format!("Failed opening device: {e:?}"))?;
let interface = dev
.claim_interface(0)
.claim_interface(interface_id as u8)
.map_err(|e| format!("Failed claiming interface: {e:?}"))?;

let boq = interface.bulk_out_queue(BULK_OUT_EP);
let biq = interface.bulk_in_queue(BULK_IN_EP);

Ok(HostClient::new_with_wire(
NusbWireTx { boq },
NusbWireRx {
biq,
consecutive_errs: 0,
},
NusbSpawn,
seq_no_kind,
err_uri_path,
outgoing_depth,
))
}
/// Try to create a new link using [`nusb`] for connectivity
///
/// The provided function will be used to find a matching device. The first
/// matching device will be connected to. `err_uri_path` is
/// the path associated with the `WireErr` message type.
///
/// Returns an error if no device or interface could be found, or if there was an error
/// connecting to the device or interface.
///
/// This constructor is available when the `raw-nusb` feature is enabled.
///
/// ## Example
///
/// ```rust,no_run
/// use postcard_rpc::host_client::HostClient;
/// use postcard_rpc::header::VarSeqKind;
/// use serde::{Serialize, Deserialize};
/// use postcard_schema::Schema;
///
/// /// A "wire error" type your server can use to respond to any
/// /// kind of request, for example if deserializing a request fails
/// #[derive(Debug, PartialEq, Schema, Serialize, Deserialize)]
/// pub enum Error {
/// SomethingBad
/// }
///
/// let client = HostClient::<Error>::try_new_raw_nusb_with_interface(
/// // Find the first device with the serial 12345678
/// |d| d.serial_number() == Some("12345678"),
/// // Find the "Vendor Specific" interface
/// |i| i.class() == 0xFF,
/// // the URI/path for `Error` messages
/// "error",
/// // Outgoing queue depth in messages
/// 8,
/// // Use one-byte sequence numbers
/// VarSeqKind::Seq1,
/// ).unwrap();
/// ```
pub fn try_new_raw_nusb_with_interface<
F1: FnMut(&DeviceInfo) -> bool,
F2: FnMut(&InterfaceInfo) -> bool,
>(
device_func: F1,
interface_func: F2,
err_uri_path: &str,
outgoing_depth: usize,
seq_no_kind: VarSeqKind,
) -> Result<Self, String> {
let x = nusb::list_devices()
.map_err(|e| format!("Error listing devices: {e:?}"))?
.find(device_func)
.ok_or_else(|| String::from("Failed to find matching nusb device!"))?;
let interface_id = x
.interfaces()
.position(interface_func)
.ok_or_else(|| String::from("Failed to find matching interface!!"))?;
let dev = x
.open()
.map_err(|e| format!("Failed opening device: {e:?}"))?;
let interface = dev
.claim_interface(interface_id as u8)
.map_err(|e| format!("Failed claiming interface: {e:?}"))?;

let boq = interface.bulk_out_queue(BULK_OUT_EP);
Expand Down
18 changes: 14 additions & 4 deletions source/postcard-rpc/src/server/impls/embassy_usb_v0_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ pub mod dispatch_impl {
config: Config<'static>,
tx_buf: &'static mut [u8],
) -> (UsbDevice<'static, D>, WireTxImpl<M, D>, WireRxImpl<D>) {
let (builder, wtx, wrx) = self.init_without_build(driver, config, tx_buf);
let usb = builder.build();
(usb, wtx, wrx)
}
/// Initialize the static storage, without building `Builder`
///
/// This must only be called once.
pub fn init_without_build(
&'static self,
driver: D,
config: Config<'static>,
tx_buf: &'static mut [u8],
) -> (Builder<'static, D>, WireTxImpl<M, D>, WireRxImpl<D>) {
let bufs = self.bufs_usb.take();

let mut builder = Builder::new(
Expand Down Expand Up @@ -204,10 +217,7 @@ pub mod dispatch_impl {
pending_frame: false,
}));

// Build the builder.
let usb = builder.build();

(usb, EUsbWireTx { inner: wtx }, EUsbWireRx { ep_out })
(builder, EUsbWireTx { inner: wtx }, EUsbWireRx { ep_out })
}
}
}
Expand Down