use crate::error::{Error, Result};
use crate::xvf3800::params::{find_command, Access, Command, XvfValue};
use crate::{TUNING_TIMEOUT, VENDOR_IN, VENDOR_OUT};
use rusb::{DeviceHandle, UsbContext};
use std::thread;
use std::time::Duration;
const CONTROL_SUCCESS: u8 = 0;
const SERVICER_COMMAND_RETRY: u8 = 64;
const READ_RETRIES: usize = 100;
const RETRY_SLEEP: Duration = Duration::from_millis(2);
fn control_status_name(code: u8) -> &'static str {
match code {
0 => "CONTROL_SUCCESS",
1 => "CONTROL_REGISTRATION_FAILED",
2 => "CONTROL_BAD_COMMAND",
3 => "CONTROL_DATA_LENGTH_ERROR",
4 => "CONTROL_OTHER_TRANSPORT_ERROR",
5 => "CONTROL_BAD_RESOURCE",
6 => "CONTROL_MALFORMED_PACKET",
7 => "CONTROL_COMMAND_IGNORED_IN_DEVICE",
8 => "CONTROL_ERROR",
64 => "SERVICER_COMMAND_RETRY",
65 => "SERVICER_WRONG_COMMAND_ID",
66 => "SERVICER_WRONG_COMMAND_LEN",
67 => "SERVICER_WRONG_PAYLOAD",
68 => "SERVICER_QUEUE_FULL",
69 => "SERVICER_SPECIAL_COMMAND_ALREADY_ONGOING",
70 => "SERVICER_SPECIAL_COMMAND_BUFFER_OVERFLOW",
71 => "SERVICER_RESOURCE_ERROR",
72 => "SERVICER_SPECIAL_COMMAND_WRONG_ORDER",
73 => "SERVICER_SPECIAL_COMMAND_BUF_SIZE_ERROR",
_ => "unknown",
}
}
pub struct Xvf3800<'a, T: UsbContext> {
handle: &'a DeviceHandle<T>,
}
impl<'a, T: UsbContext> Xvf3800<'a, T> {
pub(crate) fn new(handle: &'a DeviceHandle<T>) -> Self {
Self { handle }
}
pub fn commands(&self) -> &'static [Command] {
crate::xvf3800::params::COMMANDS
}
pub fn read_cmd(&self, name: &str) -> Result<XvfValue> {
let cmd = find_command(name)
.ok_or_else(|| Error::UnknownParameter(name.to_string()))?;
if cmd.access == Access::WriteOnly {
return Err(Error::WriteOnly(cmd.name.to_string()));
}
let payload = self.read_raw(cmd)?;
cmd.decode(&payload)
}
pub fn write_cmd(&self, name: &str, values: &[f64]) -> Result<()> {
let cmd = find_command(name)
.ok_or_else(|| Error::UnknownParameter(name.to_string()))?;
if cmd.access == Access::ReadOnly {
return Err(Error::ReadOnly(cmd.name.to_string()));
}
let payload = cmd.encode(values)?;
self.write_raw(cmd, &payload)
}
pub fn write_str(&self, name: &str, texts: &[&str]) -> Result<()> {
let mut values = Vec::with_capacity(texts.len());
for t in texts {
values.push(parse_number(t)?);
}
self.write_cmd(name, &values)
}
pub fn version_string(&self) -> Result<String> {
match self.read_cmd("VERSION")? {
XvfValue::U8(v) => Ok(v
.iter()
.map(|b| b.to_string())
.collect::<Vec<_>>()
.join(".")),
other => Ok(other.to_string()),
}
}
pub fn doa_angle(&self) -> Result<i32> {
match self.read_cmd("DOA_VALUE")? {
XvfValue::U16(v) if !v.is_empty() => Ok(i32::from(v[0])),
other => Err(Error::Message(format!("unexpected DOA_VALUE: {other}"))),
}
}
pub fn is_voice(&self) -> Result<bool> {
match self.read_cmd("DOA_VALUE")? {
XvfValue::U16(v) if v.len() >= 2 => Ok(v[1] != 0),
other => Err(Error::Message(format!("unexpected DOA_VALUE: {other}"))),
}
}
pub fn save_configuration(&self) -> Result<()> {
self.write_cmd("SAVE_CONFIGURATION", &[1.0])
}
pub fn clear_configuration(&self) -> Result<()> {
self.write_cmd("CLEAR_CONFIGURATION", &[1.0])
}
fn read_raw(&self, cmd: &Command) -> Result<Vec<u8>> {
let wvalue = 0x80 | cmd.cmdid;
let length = cmd.payload_bytes() + 1; let mut last_status = 0xffu8;
for _ in 0..READ_RETRIES {
let mut buf = vec![0u8; length];
let n = self.handle.read_control(
VENDOR_IN,
0,
wvalue,
cmd.resid,
&mut buf,
TUNING_TIMEOUT,
)?;
if n == 0 {
return Err(Error::Message("empty USB control response".into()));
}
last_status = buf[0];
if last_status == CONTROL_SUCCESS {
return Ok(buf[1..n].to_vec());
}
if last_status != SERVICER_COMMAND_RETRY {
return Err(Error::ControlStatus {
code: last_status,
name: control_status_name(last_status),
});
}
thread::sleep(RETRY_SLEEP);
}
Err(Error::ControlStatus {
code: last_status,
name: "SERVICER_COMMAND_RETRY exhausted",
})
}
fn write_raw(&self, cmd: &Command, payload: &[u8]) -> Result<()> {
self.handle.write_control(
VENDOR_OUT,
0,
cmd.cmdid,
cmd.resid,
payload,
TUNING_TIMEOUT,
)?;
Ok(())
}
}
fn parse_number(text: &str) -> Result<f64> {
let t = text.trim();
if let Some(hex) = t
.strip_prefix("0x")
.or_else(|| t.strip_prefix("0X"))
.or_else(|| t.strip_prefix('$'))
{
let v = u64::from_str_radix(hex, 16)
.map_err(|e| Error::InvalidValue(format!("invalid hex `{t}`: {e}")))?;
return Ok(v as f64);
}
t.parse::<f64>()
.map_err(|e| Error::InvalidValue(format!("expected number, got `{t}`: {e}")))
}