minidisc-cli/minidisc-rs/src/netmd/base.rs

327 lines
12 KiB
Rust

#![cfg_attr(debug_assertions, allow(dead_code))]
use nofmt;
use once_cell::sync::Lazy;
use std::error::Error;
use std::time::Duration;
#[cfg(target_family = "wasm")]
use gloo::timers::future::TimeoutFuture;
// USB stuff
//use nusb::transfer::{Control, ControlIn, ControlOut, ControlType, Recipient, RequestBuffer};
use cross_usb::context::{UsbDevice, UsbInterface};
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient};
//use nusb::{Device, DeviceInfo, Interface};
const DEFAULT_TIMEOUT: Duration = Duration::new(10000, 0);
const BULK_WRITE_ENDPOINT: u8 = 0x02;
const BULK_READ_ENDPOINT: u8 = 0x81;
pub static DEVICE_IDS: Lazy<Box<[DeviceId]>> = Lazy::new(|| {
nofmt::pls! {
Box::new([
DeviceId {vendor_id: 0x04dd, product_id: 0x7202, name: Some(String::from("Sharp IM-MT899H"))},
DeviceId {vendor_id: 0x04dd, product_id: 0x9013, name: Some(String::from("Sharp IM-DR400"))},
DeviceId {vendor_id: 0x04dd, product_id: 0x9014, name: Some(String::from("Sharp IM-DR80"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0034, name: Some(String::from("Sony PCLK-XX"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0036, name: Some(String::from("Sony"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0075, name: Some(String::from("Sony MZ-N1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x007c, name: Some(String::from("Sony"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0080, name: Some(String::from("Sony LAM-1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0081, name: Some(String::from("Sony MDS-JB980/MDS-NT1/MDS-JE780"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0084, name: Some(String::from("Sony MZ-N505"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0085, name: Some(String::from("Sony MZ-S1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0086, name: Some(String::from("Sony MZ-N707"))},
DeviceId {vendor_id: 0x054c, product_id: 0x008e, name: Some(String::from("Sony CMT-C7NT"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0097, name: Some(String::from("Sony PCGA-MDN1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00ad, name: Some(String::from("Sony CMT-L7HD"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00c6, name: Some(String::from("Sony MZ-N10"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00c7, name: Some(String::from("Sony MZ-N910"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00c8, name: Some(String::from("Sony MZ-N710/NF810"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00c9, name: Some(String::from("Sony MZ-N510/N610"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00ca, name: Some(String::from("Sony MZ-NE410/NF520D"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00e7, name: Some(String::from("Sony CMT-M333NT/M373NT"))},
DeviceId {vendor_id: 0x054c, product_id: 0x00eb, name: Some(String::from("Sony MZ-NE810/NE910"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0101, name: Some(String::from("Sony LAM"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0113, name: Some(String::from("Aiwa AM-NX1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x013f, name: Some(String::from("Sony MDS-S500"))},
DeviceId {vendor_id: 0x054c, product_id: 0x014c, name: Some(String::from("Aiwa AM-NX9"))},
DeviceId {vendor_id: 0x054c, product_id: 0x017e, name: Some(String::from("Sony MZ-NH1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0180, name: Some(String::from("Sony MZ-NH3D"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0182, name: Some(String::from("Sony MZ-NH900"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0184, name: Some(String::from("Sony MZ-NH700/NH800"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0186, name: Some(String::from("Sony MZ-NH600"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0187, name: Some(String::from("Sony MZ-NH600D"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0188, name: Some(String::from("Sony MZ-N920"))},
DeviceId {vendor_id: 0x054c, product_id: 0x018a, name: Some(String::from("Sony LAM-3"))},
DeviceId {vendor_id: 0x054c, product_id: 0x01e9, name: Some(String::from("Sony MZ-DH10P"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0219, name: Some(String::from("Sony MZ-RH10"))},
DeviceId {vendor_id: 0x054c, product_id: 0x021b, name: Some(String::from("Sony MZ-RH710/MZ-RH910"))},
DeviceId {vendor_id: 0x054c, product_id: 0x021d, name: Some(String::from("Sony CMT-AH10"))},
DeviceId {vendor_id: 0x054c, product_id: 0x022c, name: Some(String::from("Sony CMT-AH10"))},
DeviceId {vendor_id: 0x054c, product_id: 0x023c, name: Some(String::from("Sony DS-HMD1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0286, name: Some(String::from("Sony MZ-RH1"))},
DeviceId {vendor_id: 0x054c, product_id: 0x011a, name: Some(String::from("Sony CMT-SE7"))},
DeviceId {vendor_id: 0x054c, product_id: 0x0148, name: Some(String::from("Sony MDS-A1"))},
DeviceId {vendor_id: 0x0b28, product_id: 0x1004, name: Some(String::from("Kenwood MDX-J9"))},
DeviceId {vendor_id: 0x04da, product_id: 0x23b3, name: Some(String::from("Panasonic SJ-MR250"))},
DeviceId {vendor_id: 0x04da, product_id: 0x23b6, name: Some(String::from("Panasonic SJ-MR270"))},
])
}
});
/// The current status of the Minidisc device
pub enum Status {
Ready,
Playing,
Paused,
FastForward,
Rewind,
ReadingTOC,
NoDisc,
DiscBlank,
}
/// The ID of a device, including the name
pub struct DeviceId {
vendor_id: u16,
product_id: u16,
name: Option<String>,
}
/// A connection to a NetMD device
pub struct NetMD {
usb_interface: UsbInterface,
model: DeviceId,
status: Option<Status>,
}
impl NetMD {
const READ_REPLY_RETRY_INTERVAL: u32 = 10;
/// Creates a new interface to a NetMD device
pub async fn new(usb_device: &UsbDevice) -> Result<Self, Box<dyn Error>> {
let mut model = DeviceId {
vendor_id: usb_device.vendor_id().await,
product_id: usb_device.product_id().await,
name: None,
};
for device_type in DEVICE_IDS.iter() {
if device_type.vendor_id == model.vendor_id
&& device_type.product_id == model.product_id
{
model.name = device_type.name.clone();
break;
}
}
match model.name {
None => return Err("Could not find device in list".into()),
Some(_) => (),
}
let usb_interface = usb_device.open_interface(0).await?;
Ok(Self {
usb_interface,
model,
status: None,
})
}
/// Gets the device name, this is limited to the devices in the list
pub async fn device_name(&self) -> &Option<String> {
&self.model.name
}
/// Gets the vendor id
pub async fn vendor_id(&self) -> &u16 {
&self.model.vendor_id
}
/// Gets the product id
pub async fn product_id(&self) -> &u16 {
&self.model.product_id
}
/// Poll the device to get either the result
/// of the previous command, or the status
pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), Box<dyn Error>> {
// Create an array to store the result of the poll
let poll_result = match self
.usb_interface
.control_in(ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: 0x01,
value: 0,
index: 0,
length: 4,
})
.await
{
Ok(size) => size,
Err(error) => return Err(error),
};
let length_bytes = u16::from_le_bytes([poll_result[2], poll_result[3]]);
let poll_result: [u8; 4] = match poll_result.try_into() {
Ok(val) => val,
Err(_) => return Err("could not convert result".into()),
};
Ok((length_bytes, poll_result))
}
pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> {
self._send_command(command, false).await
}
pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> {
self._send_command(command, true).await
}
/// Send a control message to the device
async fn _send_command(
&mut self,
command: Vec<u8>,
use_factory_command: bool,
) -> Result<(), Box<dyn Error>> {
// First poll to ensure the device is ready
match self.poll().await {
Ok(buffer) => match buffer.1[2] {
0 => 0,
_ => return Err("Device not ready!".into()),
},
Err(error) => return Err(error),
};
let request = match use_factory_command {
false => 0x80,
true => 0xff,
};
match self
.usb_interface
.control_out(ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request,
value: 0,
index: 0,
data: &command,
})
.await
{
Ok(_) => Ok(()),
Err(error) => Err(error),
}
}
pub async fn read_reply(
&mut self,
override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> {
self._read_reply(false, override_length).await
}
pub async fn read_factory_reply(
&mut self,
override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> {
self._read_reply(true, override_length).await
}
/// Poll to see if a message is ready, and once it is, retrieve it
async fn _read_reply(
&mut self,
use_factory_command: bool,
override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> {
let mut length = self.poll().await?.0;
let mut current_attempt = 0;
while length == 0 && current_attempt < 75 {
// Back off while trying again
let sleep_time = Self::READ_REPLY_RETRY_INTERVAL as u64
* (u64::pow(2, current_attempt as u32 / 10) - 1);
#[cfg(not(target_family = "wasm"))]
std::thread::sleep(std::time::Duration::from_millis(sleep_time));
#[cfg(target_family = "wasm")]
TimeoutFuture::new(sleep_time as u32).await;
length = self.poll().await?.0;
current_attempt += 1;
}
if let Some(value) = override_length {
length = value as u16
}
let request = match use_factory_command {
false => 0x81,
true => 0xff,
};
// Create a buffer to fill with the result
let reply = self
.usb_interface
.control_in(ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request,
value: 0,
index: 0,
length,
})
.await?;
Ok(reply)
}
// Default chunksize should be 0x10000
pub async fn read_bulk(
&mut self,
length: usize,
chunksize: usize,
) -> Result<Vec<u8>, Box<dyn Error>> {
let result = self.read_bulk_to_array(length, chunksize).await?;
Ok(result)
}
pub async fn read_bulk_to_array(
&mut self,
length: usize,
chunksize: usize,
) -> Result<Vec<u8>, Box<dyn Error>> {
let mut final_result: Vec<u8> = Vec::new();
let mut done = 0;
while done < length {
let to_read = std::cmp::min(chunksize, length - done);
done -= to_read;
let res = match self
.usb_interface
.bulk_in(BULK_READ_ENDPOINT, to_read)
.await
{
Ok(result) => result,
Err(error) => return Err(format!("USB error: {:?}", error).into()),
};
final_result.extend_from_slice(&res);
}
Ok(final_result)
}
pub async fn write_bulk(&mut self, data: Vec<u8>) -> Result<usize, Box<dyn Error>> {
self.usb_interface.bulk_out(BULK_WRITE_ENDPOINT, data).await
}
}