use crate::error::{Error, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Access {
ReadWrite,
ReadOnly,
WriteOnly,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ValueType {
U8,
U16,
U32,
I32,
Float,
Radians,
Char,
}
#[derive(Clone, Copy, Debug)]
pub struct Command {
pub name: &'static str,
pub resid: u16,
pub cmdid: u16,
pub length: usize,
pub access: Access,
pub value_type: ValueType,
pub info: &'static str,
}
#[derive(Clone, Debug, PartialEq)]
pub enum XvfValue {
U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
I32(Vec<i32>),
Float(Vec<f32>),
Text(String),
}
impl std::fmt::Display for XvfValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::U8(v) => write!(f, "{v:?}"),
Self::U16(v) => write!(f, "{v:?}"),
Self::U32(v) => {
let hex: Vec<_> = v.iter().map(|x| format!("0x{x:06X}")).collect();
write!(f, "[{}]", hex.join(", "))
}
Self::I32(v) => write!(f, "{v:?}"),
Self::Float(v) => {
let s: Vec<_> = v.iter().map(|x| format!("{x:.5}")).collect();
write!(f, "[{}]", s.join(", "))
}
Self::Text(v) => write!(f, "{v}"),
}
}
}
macro_rules! cmds {
($(($name:literal, $resid:expr, $cmdid:expr, $len:expr, $acc:ident, $ty:ident, $info:literal)),* $(,)?) => {
pub static COMMANDS: &[Command] = &[
$(
Command {
name: $name,
resid: $resid,
cmdid: $cmdid,
length: $len,
access: Access::$acc,
value_type: ValueType::$ty,
info: $info,
},
)*
];
};
}
cmds! {
("VERSION", 48, 0, 3, ReadOnly, U8, "Firmware version MAJOR MINOR PATCH"),
("SAVE_CONFIGURATION", 48, 9, 1, WriteOnly, U8, "Save current configuration to flash"),
("CLEAR_CONFIGURATION", 48, 10, 1, WriteOnly, U8, "Clear configuration / revert to defaults"),
("REBOOT", 48, 7, 1, WriteOnly, U8, "Reboot the chip"),
("AEC_AZIMUTH_VALUES", 33, 75, 4, ReadOnly, Radians, "Azimuth radians: beam1 beam2 free-running auto-select"),
("AEC_SPENERGY_VALUES", 33, 80, 4, ReadOnly, Float, "Speech energy per beam"),
("AEC_ASROUTGAIN", 33, 36, 1, ReadWrite, Float, "Fixed gain applied to ASR output"),
("AEC_HPFONOFF", 33, 1, 1, ReadWrite, I32, "Mic HPF: 0=off 1=70 2=125 3=150 4=180"),
("AEC_AECPATHCHANGE", 33, 0, 1, ReadOnly, I32, "AEC path change detection"),
("AEC_RT60", 33, 9, 1, ReadOnly, Float, "Current RT60 estimate (seconds)"),
("AUDIO_MGR_MIC_GAIN", 35, 0, 1, ReadWrite, Float, "Pre-SHF microphone gain"),
("AUDIO_MGR_REF_GAIN", 35, 1, 1, ReadWrite, Float, "Pre-SHF reference gain"),
("AUDIO_MGR_SYS_DELAY", 35, 26, 1, ReadWrite, I32, "Reference delay in samples"),
("AUDIO_MGR_OP_L", 35, 15, 2, ReadWrite, U8, "Left output category/source"),
("AUDIO_MGR_OP_R", 35, 19, 2, ReadWrite, U8, "Right output category/source"),
("GPO_READ_VALUES", 20, 0, 5, ReadOnly, U8, "GPO levels: X0D11 X0D30 X0D31 X0D33 X0D39"),
("GPO_WRITE_VALUE", 20, 1, 2, WriteOnly, U8, "Set GPO pin: pin_id level"),
("LED_EFFECT", 20, 12, 1, ReadWrite, U8, "0=off 1=breath 2=rainbow 3=mono 4=doa 5=ring"),
("LED_BRIGHTNESS", 20, 13, 1, ReadWrite, U8, "Brightness for breath/rainbow (0..=255)"),
("LED_GAMMIFY", 20, 14, 1, ReadWrite, U8, "Gamma correction 0/1"),
("LED_SPEED", 20, 15, 1, ReadWrite, U8, "Breath/rainbow speed"),
("LED_COLOR", 20, 16, 1, ReadWrite, U32, "Breath/mono color 0xRRGGBB"),
("LED_DOA_COLOR", 20, 17, 2, ReadWrite, U32, "DoA base + highlight colors"),
("DOA_VALUE", 20, 18, 2, ReadOnly, U16, "DOA degrees + speech detected flag"),
("LED_RING_COLOR", 20, 19, 12, WriteOnly, U32, "Per-LED colors for ring mode (write-only on current firmware)"),
("PP_AGCONOFF", 17, 10, 1, ReadWrite, I32, "Automatic Gain Control on/off"),
("PP_AGCMAXGAIN", 17, 11, 1, ReadWrite, Float, "Maximum AGC gain factor"),
("PP_AGCGAIN", 17, 13, 1, ReadWrite, Float, "Current AGC gain factor"),
("PP_AGCDESIREDLEVEL", 17, 12, 1, ReadWrite, Float, "AGC target power level"),
("PP_MIN_NS", 17, 21, 1, ReadWrite, Float, "Stationary noise gain floor"),
("PP_MIN_NN", 17, 22, 1, ReadWrite, Float, "Non-stationary noise gain floor"),
("PP_ECHOONOFF", 17, 23, 1, ReadWrite, I32, "Echo suppression on/off"),
("PP_FMIN_SPEINDEX", 17, 30, 1, ReadWrite, Float, "Double-talk SPEINDEX frequency"),
("PP_DTSENSITIVE", 17, 31, 1, ReadWrite, I32, "Echo vs double-talk tradeoff"),
}
pub fn find_command(name: &str) -> Option<&'static Command> {
COMMANDS
.iter()
.find(|c| c.name.eq_ignore_ascii_case(name))
}
impl Command {
pub(crate) fn payload_bytes(&self) -> usize {
match self.value_type {
ValueType::U8 | ValueType::Char => self.length,
ValueType::U16 => self.length * 2,
ValueType::U32 | ValueType::I32 | ValueType::Float | ValueType::Radians => {
self.length * 4
}
}
}
pub(crate) fn encode(&self, values: &[f64]) -> Result<Vec<u8>> {
if values.len() != self.length {
return Err(Error::InvalidValue(format!(
"{} expects {} value(s), got {}",
self.name,
self.length,
values.len()
)));
}
let mut out = Vec::with_capacity(self.payload_bytes());
match self.value_type {
ValueType::U8 => {
for v in values {
out.push(*v as u8);
}
}
ValueType::U16 => {
for v in values {
out.extend_from_slice(&(*v as u16).to_le_bytes());
}
}
ValueType::U32 => {
for v in values {
out.extend_from_slice(&(*v as u32).to_le_bytes());
}
}
ValueType::I32 => {
for v in values {
out.extend_from_slice(&(*v as i32).to_le_bytes());
}
}
ValueType::Float | ValueType::Radians => {
for v in values {
out.extend_from_slice(&(*v as f32).to_le_bytes());
}
}
ValueType::Char => {
return Err(Error::InvalidValue(
"char parameters are not writable via numeric values".into(),
));
}
}
Ok(out)
}
pub(crate) fn decode(&self, payload: &[u8]) -> Result<XvfValue> {
let need = self.payload_bytes();
if payload.len() < need {
return Err(Error::Message(format!(
"{}: short payload ({} < {})",
self.name,
payload.len(),
need
)));
}
let p = &payload[..need];
Ok(match self.value_type {
ValueType::U8 => XvfValue::U8(p.to_vec()),
ValueType::Char => {
let s = String::from_utf8_lossy(p)
.trim_end_matches('\0')
.to_string();
XvfValue::Text(s)
}
ValueType::U16 => {
let mut v = Vec::with_capacity(self.length);
for chunk in p.chunks_exact(2) {
v.push(u16::from_le_bytes([chunk[0], chunk[1]]));
}
XvfValue::U16(v)
}
ValueType::U32 => {
let mut v = Vec::with_capacity(self.length);
for chunk in p.chunks_exact(4) {
v.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
XvfValue::U32(v)
}
ValueType::I32 => {
let mut v = Vec::with_capacity(self.length);
for chunk in p.chunks_exact(4) {
v.push(i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
XvfValue::I32(v)
}
ValueType::Float | ValueType::Radians => {
let mut v = Vec::with_capacity(self.length);
for chunk in p.chunks_exact(4) {
v.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
XvfValue::Float(v)
}
})
}
}