mirror of
https://github.com/G2-Games/minidisc-cli.git
synced 2025-04-19 11:42:53 -05:00
Initial code commit
This commit is contained in:
parent
ad3a4330d7
commit
397a886693
6 changed files with 598 additions and 0 deletions
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "minidisc-rs"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nofmt = "1.0.0"
|
||||||
|
once_cell = "1.18.0"
|
||||||
|
rusb = "0.9.3"
|
||||||
|
|
49
src/main.rs
Normal file
49
src/main.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
mod netmd;
|
||||||
|
|
||||||
|
use crate::netmd::base::NetMD;
|
||||||
|
use crate::netmd::interface::NetMDInterface;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
for device in rusb::devices().unwrap().iter() {
|
||||||
|
let device_desc = device.device_descriptor().unwrap();
|
||||||
|
|
||||||
|
let new_device = match device.open() {
|
||||||
|
Ok(device) => device,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Connected to Bus {:03} Device {:03} VID: {:04x}, PID: {:04x}, {:?}",
|
||||||
|
device.bus_number(),
|
||||||
|
device.address(),
|
||||||
|
device_desc.vendor_id(),
|
||||||
|
device_desc.product_id(),
|
||||||
|
new_device.read_product_string_ascii(&device_desc));
|
||||||
|
|
||||||
|
let player = NetMD::new(new_device, device_desc).unwrap();
|
||||||
|
let player_controller = NetMDInterface::new(player);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Player Model: {}",
|
||||||
|
player_controller.net_md_device.device_name().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
player_controller.get_disc_subunit_identifier();
|
||||||
|
|
||||||
|
/*
|
||||||
|
let mut request: [u8; 19] = [0x00, 0x18, 0x06, 0x02, 0x20, 0x18,
|
||||||
|
0x01, 0x00, 0x00, 0x30, 0x00, 0xa,
|
||||||
|
0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00];
|
||||||
|
request[4] = 0x75;
|
||||||
|
let test_result = player.send_command(&request);
|
||||||
|
|
||||||
|
match test_result {
|
||||||
|
Ok(_) => println!("Successfully sent command! Response: {:?}", test_result),
|
||||||
|
Err(error) => println!("Command failed! Error: {}", error)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
228
src/netmd/base.rs
Normal file
228
src/netmd/base.rs
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
use nofmt;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use rusb::{DeviceDescriptor, DeviceHandle, Direction, GlobalContext, Recipient, RequestType};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::new(1, 0);
|
||||||
|
|
||||||
|
const STANDARD_SEND: u8 =
|
||||||
|
rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface);
|
||||||
|
const STANDARD_RECV: u8 =
|
||||||
|
rusb::request_type(Direction::In, RequestType::Vendor, Recipient::Interface);
|
||||||
|
|
||||||
|
// TODO: I think this sucks, figure out a better way
|
||||||
|
pub static DEVICE_IDS: Lazy<Vec<DeviceId>> = Lazy::new(|| nofmt::pls!{
|
||||||
|
Vec::from([
|
||||||
|
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"))},
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
pub enum Status {
|
||||||
|
Ready,
|
||||||
|
Playing,
|
||||||
|
Paused,
|
||||||
|
FastForward,
|
||||||
|
Rewind,
|
||||||
|
ReadingTOC,
|
||||||
|
NoDisc,
|
||||||
|
DiscBlank,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceId {
|
||||||
|
vendor_id: u16,
|
||||||
|
product_id: u16,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetMD {
|
||||||
|
device_connection: DeviceHandle<GlobalContext>,
|
||||||
|
model: DeviceId,
|
||||||
|
status: Option<Status>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetMD {
|
||||||
|
/// Creates a new `NetMD` struct
|
||||||
|
pub fn new(
|
||||||
|
device: DeviceHandle<GlobalContext>,
|
||||||
|
device_desc: DeviceDescriptor,
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let mut model = DeviceId {
|
||||||
|
vendor_id: device_desc.vendor_id(),
|
||||||
|
product_id: device_desc.product_id(),
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match model.name {
|
||||||
|
None => return Err("Could not find device in list".into()),
|
||||||
|
Some(_) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device_connection: device,
|
||||||
|
model,
|
||||||
|
status: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the device name from the struct
|
||||||
|
pub fn device_name(&self) -> Option<String> {
|
||||||
|
self.model.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the vendor id from the struct
|
||||||
|
pub fn vendor_id(&self) -> u16 {
|
||||||
|
self.model.vendor_id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the product id from the struct
|
||||||
|
pub fn product_id(&self) -> u16 {
|
||||||
|
self.model.product_id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll the device to get either the result
|
||||||
|
/// of the previous command, or the status
|
||||||
|
fn poll(&self, tries: usize) -> Result<(usize, [u8; 4]), Box<dyn Error>> {
|
||||||
|
// Create an array to store the result of the poll
|
||||||
|
let mut poll_result = [0u8; 4];
|
||||||
|
|
||||||
|
// Try until failure or `tries` reached
|
||||||
|
for i in 0..tries {
|
||||||
|
let _status = match self.device_connection.read_control(
|
||||||
|
STANDARD_RECV,
|
||||||
|
0x01,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut poll_result,
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
) {
|
||||||
|
Ok(size) => size,
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if poll_result[0] != 0 {
|
||||||
|
return Ok((poll_result[2] as usize, poll_result));
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((poll_result[2] as usize, poll_result))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a control message to the device
|
||||||
|
pub fn send_command(
|
||||||
|
&self,
|
||||||
|
command: Vec<u8>,
|
||||||
|
use_factory_command: bool,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
//First poll to ensure the device is ready
|
||||||
|
match self.poll(1) {
|
||||||
|
Ok(buffer) => match buffer.1[2] {
|
||||||
|
0 => 0,
|
||||||
|
_ => return Err("Device not ready!".into()),
|
||||||
|
},
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = match use_factory_command {
|
||||||
|
false => 0x80,
|
||||||
|
true => 0xff,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.device_connection.write_control(
|
||||||
|
STANDARD_SEND,
|
||||||
|
0x80,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&command,
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(error) => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll to see if a message is ready,
|
||||||
|
/// and if so, recieve it
|
||||||
|
pub fn read_reply(&self, use_factory_command: bool) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let poll_result = match self.poll(30) {
|
||||||
|
Ok(buffer) => buffer,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = match use_factory_command {
|
||||||
|
false => 0x81,
|
||||||
|
true => 0xff,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a buffer to fill with the result
|
||||||
|
let mut buf: [u8; 255] = [0; 255];
|
||||||
|
|
||||||
|
match self.device_connection.read_control(
|
||||||
|
STANDARD_RECV,
|
||||||
|
request,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut buf,
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(buf[0..poll_result.0].to_vec()),
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
298
src/netmd/interface.rs
Normal file
298
src/netmd/interface.rs
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
use crate::netmd::utils;
|
||||||
|
use crate::NetMD;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum Action {
|
||||||
|
Play = 0x75,
|
||||||
|
Pause = 0x7d,
|
||||||
|
FastForward = 0x39,
|
||||||
|
Rewind = 0x49,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Track {
|
||||||
|
Previous = 0x0002,
|
||||||
|
Next = 0x8001,
|
||||||
|
Restart = 0x0001,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DiscFormat {
|
||||||
|
LP4 = 0,
|
||||||
|
LP2 = 2,
|
||||||
|
SPMono = 4,
|
||||||
|
SPStereo = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WireFormat {
|
||||||
|
PCM = 0x00,
|
||||||
|
L105kbps = 0x90,
|
||||||
|
LP2 = 0x94,
|
||||||
|
LP4 = 0xA8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WireFormat {
|
||||||
|
fn frame_size(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
WireFormat::PCM => 2048,
|
||||||
|
WireFormat::L105kbps => 192,
|
||||||
|
WireFormat::LP2 => 152,
|
||||||
|
WireFormat::LP4 => 96,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Encoding {
|
||||||
|
SP = 0x90,
|
||||||
|
LP2 = 0x92,
|
||||||
|
LP4 = 0x93,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Channels {
|
||||||
|
Mono = 0x01,
|
||||||
|
Stereo = 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChannelCount {
|
||||||
|
Mono = 1,
|
||||||
|
Stereo = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TrackFlag {
|
||||||
|
Protected = 0x03,
|
||||||
|
Unprotected = 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DiscFlag {
|
||||||
|
Writable = 0x10,
|
||||||
|
WriteProtected = 0x40,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NetMDLevel {
|
||||||
|
Level1 = 0x20, // Network MD
|
||||||
|
Level2 = 0x50, // Program play MD
|
||||||
|
Level3 = 0x70, // Editing MD
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Descriptor {
|
||||||
|
DiscTitleTD,
|
||||||
|
AudioUTOC1TD,
|
||||||
|
AudioUTOC4TD,
|
||||||
|
DSITD,
|
||||||
|
AudioContentsTD,
|
||||||
|
RootTD,
|
||||||
|
|
||||||
|
DiscSubunitIdentifier,
|
||||||
|
OperatingStatusBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Descriptor {
|
||||||
|
fn get_array(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
Descriptor::DiscTitleTD => vec![0x10, 0x18, 0x01],
|
||||||
|
Descriptor::AudioUTOC1TD => vec![0x10, 0x18, 0x02],
|
||||||
|
Descriptor::AudioUTOC4TD => vec![0x10, 0x18, 0x03],
|
||||||
|
Descriptor::DSITD => vec![0x10, 0x18, 0x04],
|
||||||
|
Descriptor::AudioContentsTD => vec![0x10, 0x10, 0x01],
|
||||||
|
Descriptor::RootTD => vec![0x10, 0x10, 0x00],
|
||||||
|
Descriptor::DiscSubunitIdentifier => vec![0x00],
|
||||||
|
Descriptor::OperatingStatusBlock => vec![0x80, 0x00],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DescriptorAction {
|
||||||
|
OpenRead = 1,
|
||||||
|
OpenWrite = 3,
|
||||||
|
Close = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
enum Status {
|
||||||
|
// NetMD Protocol return status (first byte of request)
|
||||||
|
Control = 0x00,
|
||||||
|
Status = 0x01,
|
||||||
|
SpecificInquiry = 0x02,
|
||||||
|
Notify = 0x03,
|
||||||
|
GeneralInquiry = 0x04,
|
||||||
|
// ... (first byte of response)
|
||||||
|
NotImplemented = 0x08,
|
||||||
|
Accepted = 0x09,
|
||||||
|
Rejected = 0x0a,
|
||||||
|
InTransition = 0x0b,
|
||||||
|
Implemented = 0x0c,
|
||||||
|
Changed = 0x0d,
|
||||||
|
Interim = 0x0f,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::TryFrom<u8> for Status {
|
||||||
|
type Error = Box<dyn Error>;
|
||||||
|
|
||||||
|
fn try_from(item: u8) -> Result<Self, Box<dyn Error>> {
|
||||||
|
match item {
|
||||||
|
0x00 => Ok(Status::Control),
|
||||||
|
0x01 => Ok(Status::Status),
|
||||||
|
0x02 => Ok(Status::SpecificInquiry),
|
||||||
|
0x03 => Ok(Status::Notify),
|
||||||
|
0x04 => Ok(Status::GeneralInquiry),
|
||||||
|
0x08 => Ok(Status::NotImplemented),
|
||||||
|
0x09 => Ok(Status::Accepted),
|
||||||
|
0x0a => Ok(Status::Rejected),
|
||||||
|
0x0b => Ok(Status::InTransition),
|
||||||
|
0x0c => Ok(Status::Implemented),
|
||||||
|
0x0d => Ok(Status::Changed),
|
||||||
|
0x0f => Ok(Status::Interim),
|
||||||
|
_ => Err("Not a valid value".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetMDInterface {
|
||||||
|
pub net_md_device: NetMD,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetMDInterface {
|
||||||
|
const MAX_INTERIM_READ_ATTEMPTS: u8 = 4;
|
||||||
|
const INTERIM_RESPONSE_RETRY_INTERVAL: u32 = 100;
|
||||||
|
|
||||||
|
pub fn new(net_md_device: NetMD) -> Self {
|
||||||
|
NetMDInterface { net_md_device }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_disc_subunit_identifier(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
self.change_descriptor_state(
|
||||||
|
Descriptor::DiscSubunitIdentifier,
|
||||||
|
DescriptorAction::OpenRead,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut query = vec![0x18, 0x09, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
|
||||||
|
let reply = self.send_query(&mut query, false, false);
|
||||||
|
|
||||||
|
println!("{:X?}", reply);
|
||||||
|
|
||||||
|
/*
|
||||||
|
let descriptor_length = i16::from_le_bytes(reply[0..1].try_into()?);
|
||||||
|
let generation_id = i16::from_le_bytes(reply[0..1].try_into()?);
|
||||||
|
*/
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn playback_control(&self, action: Action) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut query = vec![0x18, 0xc3, 0xff, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
|
||||||
|
query[3] = action as u8;
|
||||||
|
|
||||||
|
let result = self.send_query(&mut query, false, false)?;
|
||||||
|
|
||||||
|
utils::check_result(result, &[0x18, 0xc5, 0x00, action as u8, 0x00, 0x00, 0x00])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&self) {
|
||||||
|
let _ = self.playback_control(Action::Play);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fast_forward(&self) {
|
||||||
|
let _ = self.playback_control(Action::FastForward);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewind(&self) {
|
||||||
|
let _ = self.playback_control(Action::Rewind);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&self) {
|
||||||
|
let _ = self.playback_control(Action::Pause);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut query = vec![0x18, 0xc5, 0xff, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
|
||||||
|
let result = self.send_query(&mut query, false, false)?;
|
||||||
|
|
||||||
|
utils::check_result(result, &[0x18, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_descriptor_state(&self, descriptor: Descriptor, action: DescriptorAction) {
|
||||||
|
let mut query = vec![0x18, 0x08];
|
||||||
|
|
||||||
|
query.append(&mut descriptor.get_array());
|
||||||
|
|
||||||
|
query.push(action as u8);
|
||||||
|
|
||||||
|
query.push(0x00);
|
||||||
|
|
||||||
|
let _ = self.send_query(&mut query, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_query(
|
||||||
|
&self,
|
||||||
|
query: &mut Vec<u8>,
|
||||||
|
test: bool,
|
||||||
|
accept_interim: bool,
|
||||||
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
self.send_command(query, test)?;
|
||||||
|
|
||||||
|
let result = self.read_reply(accept_interim)?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_command(&self, query: &mut Vec<u8>, test: bool) -> Result<(), Box<dyn Error>> {
|
||||||
|
let status_byte = match test {
|
||||||
|
true => Status::GeneralInquiry,
|
||||||
|
false => Status::Control,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_query = Vec::new();
|
||||||
|
|
||||||
|
new_query.push(status_byte as u8);
|
||||||
|
new_query.append(query);
|
||||||
|
|
||||||
|
self.net_md_device.send_command(new_query, false)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_reply(&self, accept_interim: bool) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let mut current_attempt = 0;
|
||||||
|
let mut data;
|
||||||
|
|
||||||
|
while current_attempt < Self::MAX_INTERIM_READ_ATTEMPTS {
|
||||||
|
data = match self.net_md_device.read_reply(false) {
|
||||||
|
Ok(reply) => reply,
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = match Status::try_from(data[0]) {
|
||||||
|
Ok(status) => status,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
Status::NotImplemented => return Err("Not implemented".into()),
|
||||||
|
Status::Rejected => return Err("Rejected".into()),
|
||||||
|
Status::Interim if !accept_interim => {
|
||||||
|
let sleep_time = Self::INTERIM_RESPONSE_RETRY_INTERVAL as u64 * (u64::pow(2, current_attempt as u32) - 1);
|
||||||
|
let sleep_dur = std::time::Duration::from_millis(sleep_time);
|
||||||
|
std::thread::sleep(sleep_dur); // Sleep to wait before retrying
|
||||||
|
current_attempt += 1;
|
||||||
|
continue; // Retry!
|
||||||
|
}
|
||||||
|
Status::Accepted | Status::Implemented | Status::Interim => {
|
||||||
|
if current_attempt >= Self::MAX_INTERIM_READ_ATTEMPTS {
|
||||||
|
return Err("Max interim retry attempts reached".into());
|
||||||
|
}
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
_ => return Err("Unknown return status".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should NEVER happen unless the code is changed wrongly
|
||||||
|
Err("The max retries is set to 0".into())
|
||||||
|
}
|
||||||
|
}
|
3
src/netmd/mod.rs
Normal file
3
src/netmd/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod base;
|
||||||
|
pub mod interface;
|
||||||
|
pub mod utils;
|
8
src/netmd/utils.rs
Normal file
8
src/netmd/utils.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
pub fn check_result(result: Vec<u8>, expected: &[u8]) -> Result<(), Box<dyn Error>> {
|
||||||
|
match result.as_slice().eq(expected) {
|
||||||
|
true => Ok(()),
|
||||||
|
false => Err("Response was not expected!".into()),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue