use clap::{Parser, Subcommand, ValueEnum};
use respeaker::{Access, ParamKind, ParamValue, ReSpeaker, Rgb, VadLed, PARAMETERS};
use std::process::ExitCode;
use std::thread;
use std::time::Duration;
#[derive(Parser, Debug)]
#[command(
name = "respeaker-cli",
about = "Exercise all ReSpeaker Mic Array v2.0 LED + DSP tuning APIs"
)]
struct Opts {
#[arg(long, global = true, default_value_t = 800)]
delay_ms: u64,
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand, Debug)]
enum Cmd {
TestAll,
Led {
#[command(subcommand)]
cmd: LedCmd,
},
Param {
#[command(subcommand)]
cmd: ParamCmd,
},
Doa,
Vad,
Version,
}
#[derive(Subcommand, Debug)]
enum LedCmd {
Off,
Trace,
Listen,
Speak,
Think,
Spin,
Mono {
#[arg(long)]
color: Option<String>,
#[arg(long, default_value_t = 0)]
r: u8,
#[arg(long, default_value_t = 0)]
g: u8,
#[arg(long, default_value_t = 0)]
b: u8,
},
Customize,
Brightness { level: u8 },
Volume { level: u8 },
VadLed {
#[arg(value_enum)]
state: VadLedCli,
},
Palette { a: String, b: String },
}
#[derive(Clone, Copy, Debug, ValueEnum)]
enum VadLedCli {
Off,
On,
Auto,
}
impl From<VadLedCli> for VadLed {
fn from(v: VadLedCli) -> Self {
match v {
VadLedCli::Off => VadLed::Off,
VadLedCli::On => VadLed::On,
VadLedCli::Auto => VadLed::Auto,
}
}
}
#[derive(Subcommand, Debug)]
enum ParamCmd {
List,
Get { name: String },
Set { name: String, value: String },
ReadAll,
}
fn main() -> ExitCode {
let opts = Opts::parse();
match run(opts) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}
fn run(opts: Opts) -> respeaker::Result<()> {
if matches!(opts.cmd, Cmd::Param { cmd: ParamCmd::List }) {
print_param_list();
return Ok(());
}
let dev = ReSpeaker::open()?;
let delay = Duration::from_millis(opts.delay_ms);
match opts.cmd {
Cmd::TestAll => test_all(&dev, delay)?,
Cmd::Led { cmd } => run_led(&dev, cmd)?,
Cmd::Param { cmd } => run_param(&dev, cmd)?,
Cmd::Doa => println!("DOA: {}°", dev.tuning().doa_angle()?),
Cmd::Vad => println!(
"VOICEACTIVITY: {}",
if dev.tuning().is_voice()? { 1 } else { 0 }
),
Cmd::Version => println!("version: {}", dev.tuning().version()?),
}
Ok(())
}
fn test_all(dev: &ReSpeaker, delay: Duration) -> respeaker::Result<()> {
let led = dev.led();
let tuning = dev.tuning();
println!("=== Device ===");
println!("protocol version: {}", tuning.version()?);
println!("DOA: {}°", tuning.doa_angle()?);
println!("voice: {}", tuning.is_voice()?);
println!();
println!("=== LED modes ===");
step("brightness 0x1F", delay, || led.set_brightness(0x1F))?;
step("palette red/green", delay, || {
led.set_color_palette(Rgb::RED, Rgb::GREEN)
})?;
step("vad-led auto", delay, || led.set_vad_led(VadLed::Auto))?;
step("trace", delay, || led.trace())?;
step("listen", delay, || led.listen())?;
step("speak", delay, || led.speak())?;
step("think", delay, || led.think())?;
step("spin", delay, || led.spin())?;
step("mono red", delay, || led.mono(Rgb::RED))?;
step("mono green", delay, || led.mono(Rgb::GREEN))?;
step("mono blue", delay, || led.mono(Rgb::BLUE))?;
step("mono white", delay, || led.mono(Rgb::WHITE))?;
step("customize rainbow", delay, || led.customize(&rainbow()))?;
for level in [0u8, 4, 8, 12] {
step(&format!("volume {level}"), delay, || led.set_volume(level))?;
}
for level in [0u8, 8, 16, 31] {
step(&format!("brightness {level}"), delay, || {
led.set_brightness(level)
})?;
}
step("vad-led off", delay, || led.set_vad_led(VadLed::Off))?;
step("vad-led on", delay, || led.set_vad_led(VadLed::On))?;
step("off", delay, || led.off())?;
println!();
println!("=== DSP parameters (read-all) ===");
println!("{:<24} {}", "name", "value");
println!("{}", "-".repeat(40));
for p in PARAMETERS {
match tuning.read(p.name) {
Ok(ParamValue::Int(v)) => println!("{:<24} {v}", p.name),
Ok(ParamValue::Float(v)) => println!("{:<24} {v}", p.name),
Err(e) => println!("{:<24} <error: {e}>", p.name),
}
}
println!();
println!("=== Convenience helpers ===");
println!("doa_angle() = {}°", tuning.doa_angle()?);
println!("is_voice() = {}", tuning.is_voice()?);
let agc = tuning.read("AGCONOFF")?;
println!("read(AGCONOFF) = {agc}");
let gamma = tuning.read("GAMMAVAD_SR")?;
println!("read(GAMMAVAD_SR) = {gamma}");
println!();
println!("test-all finished (LEDs left off)");
Ok(())
}
fn step(label: &str, delay: Duration, f: impl FnOnce() -> respeaker::Result<()>) -> respeaker::Result<()> {
print!(" {label} ... ");
f()?;
println!("ok");
thread::sleep(delay);
Ok(())
}
fn rainbow() -> [Rgb; 12] {
let mut colors = [Rgb::BLACK; 12];
for (i, c) in colors.iter_mut().enumerate() {
let hue = (i as f32) / 12.0;
*c = hsv_to_rgb(hue, 1.0, 1.0);
}
colors
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Rgb {
let i = (h * 6.0).floor();
let f = h * 6.0 - i;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
let (r, g, b) = match (i as i32).rem_euclid(6) {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q),
};
Rgb::new(
(r * 255.0).round() as u8,
(g * 255.0).round() as u8,
(b * 255.0).round() as u8,
)
}
fn run_led(dev: &ReSpeaker, cmd: LedCmd) -> respeaker::Result<()> {
let led = dev.led();
match cmd {
LedCmd::Off => {
led.off()?;
println!("LEDs off");
}
LedCmd::Trace => {
led.trace()?;
println!("LED mode: trace");
}
LedCmd::Listen => {
led.listen()?;
println!("LED mode: listen");
}
LedCmd::Speak => {
led.speak()?;
println!("LED mode: speak");
}
LedCmd::Think => {
led.think()?;
println!("LED mode: think");
}
LedCmd::Spin => {
led.spin()?;
println!("LED mode: spin");
}
LedCmd::Mono { color, r, g, b } => {
let rgb = if let Some(c) = color {
Rgb::from_hex(&c)?
} else {
Rgb::new(r, g, b)
};
led.mono(rgb)?;
println!("LED mono #{:02x}{:02x}{:02x}", rgb.r, rgb.g, rgb.b);
}
LedCmd::Customize => {
led.customize(&rainbow())?;
println!("LED customize: rainbow");
}
LedCmd::Brightness { level } => {
led.set_brightness(level)?;
println!("LED brightness {level}");
}
LedCmd::Volume { level } => {
led.set_volume(level)?;
println!("LED volume {level}");
}
LedCmd::VadLed { state } => {
led.set_vad_led(state.into())?;
println!("LED VAD center: {state:?}");
}
LedCmd::Palette { a, b } => {
let a = Rgb::from_hex(&a)?;
let b = Rgb::from_hex(&b)?;
led.set_color_palette(a, b)?;
println!(
"LED palette #{:02x}{:02x}{:02x} / #{:02x}{:02x}{:02x}",
a.r, a.g, a.b, b.r, b.g, b.b
);
}
}
Ok(())
}
fn run_param(dev: &ReSpeaker, cmd: ParamCmd) -> respeaker::Result<()> {
let tuning = dev.tuning();
match cmd {
ParamCmd::List => unreachable!("handled before open"),
ParamCmd::Get { name } => {
println!("{name}: {}", tuning.read(&name)?);
}
ParamCmd::Set { name, value } => {
tuning.write_str(&name, &value)?;
println!("{name}: {}", tuning.read(&name)?);
}
ParamCmd::ReadAll => {
println!("{:<24} {}", "name", "value");
println!("{}", "-".repeat(40));
for p in PARAMETERS {
match tuning.read(p.name) {
Ok(ParamValue::Int(v)) => println!("{:<24} {v}", p.name),
Ok(ParamValue::Float(v)) => println!("{:<24} {v}", p.name),
Err(e) => println!("{:<24} <error: {e}>", p.name),
}
}
}
}
Ok(())
}
fn print_param_list() {
println!(
"{:<22} {:<6} {:>8} {:>8} {:<4} {}",
"name", "type", "max", "min", "r/w", "info"
);
println!("{}", "-".repeat(80));
for p in PARAMETERS {
let kind = match p.kind {
ParamKind::Int => "int",
ParamKind::Float => "float",
};
let access = match p.access {
Access::ReadWrite => "rw",
Access::ReadOnly => "ro",
};
println!(
"{:<22} {:<6} {:>8} {:>8} {:<4} {}",
p.name, kind, p.max, p.min, access, p.info
);
}
}