use std::time::Duration;
use nusb::transfer::{Control, ControlType, Queue, RequestBuffer};
use nusb::{DeviceInfo, Interface};
use super::record::Recorder;
use super::{Transport, CLASS_VENDOR_SPECIFIC, EP_IN, EP_OUT};
use crate::deadline::with_timeout;
use crate::error::{Error, Result};
pub use super::{PRODUCT_ID_ELECTRO5, VENDOR_ID};
pub use nusb::transfer::Recipient;
const REAP_LIMIT: Duration = Duration::from_secs(2);
fn map_err<E: std::fmt::Display>(what: &str) -> impl FnOnce(E) -> Error + '_ {
move |e| Error::Transport(format!("{what}: {e}"))
}
pub fn list() -> Result<Vec<DeviceInfo>> {
Ok(nusb::list_devices()
.map_err(map_err("listing usb devices"))?
.filter(|d| d.vendor_id() == VENDOR_ID)
.collect())
}
pub struct UsbTransport {
interface: Interface,
record: Option<Recorder>,
read_queue: Queue<RequestBuffer>,
}
impl UsbTransport {
pub fn open_first() -> Result<Self> {
let info = list()?
.into_iter()
.next()
.ok_or_else(|| Error::Transport("no Clavia device found".into()))?;
Self::open(&info)
}
pub fn open(info: &DeviceInfo) -> Result<Self> {
let iface_num = info
.interfaces()
.find(|i| i.class() == CLASS_VENDOR_SPECIFIC)
.map(|i| i.interface_number())
.ok_or_else(|| {
Error::Transport(
"device exposes no vendor-specific interface; is this a Nord?".into(),
)
})?;
let device = info.open().map_err(map_err("opening device"))?;
let interface = device.claim_interface(iface_num).map_err(map_err(
"claiming the vendor interface (on Linux this usually means a udev rule is missing)",
))?;
let read_queue = interface.bulk_in_queue(EP_IN);
Ok(Self {
interface,
record: None,
read_queue,
})
}
pub fn recording_to(mut self, path: &std::path::Path) -> Result<Self> {
self.record = Some(Recorder::create(path)?);
Ok(self)
}
pub fn mark(&mut self, what: &str) {
if let Some(r) = self.record.as_mut() {
r.comment(what);
}
}
pub fn recording_result(&mut self) -> Result<()> {
match self.record.as_mut() {
Some(r) => r.check(),
None => Ok(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Identity {
pub firmware: u16,
pub max_transfer: u32,
pub kind: u16,
pub build: u16,
}
impl UsbTransport {
pub fn identity(&self) -> Result<Identity> {
let limit = Duration::from_millis(500);
let word = |request: u8| -> Result<u16> {
let b = self.vendor_control_in(Recipient::Device, request, 0, 0, 2, limit)?;
if b.len() < 2 {
return Err(Error::Transport(format!(
"vendor request {request:#04x} returned {} bytes, expected 2",
b.len()
)));
}
Ok(u16::from_le_bytes([b[0], b[1]]))
};
let max = self.vendor_control_in(Recipient::Device, 0x08, 0, 0, 4, limit)?;
if max.len() < 4 {
return Err(Error::Transport(format!(
"vendor request 0x08 returned {} bytes, expected 4",
max.len()
)));
}
Ok(Identity {
kind: word(0x00)?,
firmware: word(0x04)?,
build: word(0x05)?,
max_transfer: u32::from_le_bytes([max[0], max[1], max[2], max[3]]),
})
}
pub async fn interrupt_read(
&mut self,
len: usize,
timeout: Duration,
) -> Result<Option<Vec<u8>>> {
use crate::deadline::with_timeout;
let buf = nusb::transfer::RequestBuffer::new(len);
match with_timeout(self.interface.interrupt_in(0x81, buf), timeout).await {
Some(completion) => {
completion.status.map_err(map_err("interrupt read"))?;
Ok(Some(completion.data))
}
None => Ok(None),
}
}
pub fn vendor_control_in(
&self,
recipient: Recipient,
request: u8,
value: u16,
index: u16,
len: usize,
timeout: Duration,
) -> Result<Vec<u8>> {
let mut buf = vec![0u8; len];
let control = Control {
control_type: ControlType::Vendor,
recipient,
request,
value,
index,
};
let n = self
.interface
.control_in_blocking(control, &mut buf, timeout)
.map_err(map_err("vendor control read"))?;
buf.truncate(n);
Ok(buf)
}
}
impl Transport for UsbTransport {
async fn write(&mut self, buf: &[u8]) -> Result<()> {
let completion = self.interface.bulk_out(EP_OUT, buf.to_vec()).await;
completion.status.map_err(map_err("bulk write"))?;
if let Some(r) = self.record.as_mut() {
r.out(buf);
}
Ok(())
}
async fn read(&mut self, max: usize) -> Result<Vec<u8>> {
self.read_queue.submit(RequestBuffer::new(max));
let completion = self.read_queue.next_complete().await;
completion.status.map_err(map_err("bulk read"))?;
if let Some(r) = self.record.as_mut() {
r.r#in(&completion.data);
}
Ok(completion.data)
}
async fn write_timeout(&mut self, buf: &[u8], limit: Duration) -> Result<bool> {
match with_timeout(self.interface.bulk_out(EP_OUT, buf.to_vec()), limit).await {
Some(completion) => {
completion.status.map_err(map_err("bulk write"))?;
if let Some(r) = self.record.as_mut() {
r.out(buf);
}
Ok(true)
}
None => Ok(false),
}
}
async fn read_timeout(&mut self, max: usize, limit: Duration) -> Result<Option<Vec<u8>>> {
self.read_queue.submit(RequestBuffer::new(max));
if let Some(completion) = with_timeout(self.read_queue.next_complete(), limit).await {
completion.status.map_err(map_err("bulk read"))?;
if let Some(r) = self.record.as_mut() {
r.r#in(&completion.data);
}
return Ok(Some(completion.data));
}
self.read_queue.cancel_all();
match with_timeout(self.read_queue.next_complete(), REAP_LIMIT).await {
Some(_) => Ok(None),
None => Err(Error::Transport(
"read timed out and the transfer could not be cancelled; \
the connection is out of step and the instrument needs a power cycle"
.into(),
)),
}
}
}