use anyhow::Result;
use clap::{Parser, Subcommand};
use rust_patlite_beacon::{AsyncBeacon, Beacon, BuzzerCount, BuzzerPattern, BuzzerVolume, LedColor, LedPattern, Setting};
#[derive(Parser)]
#[command(name = "beacon")]
#[command(about = "Control USB beacon device", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
#[arg(long, value_name = "COLOR")]
color: Option<String>,
#[arg(long, value_name = "PATTERN")]
pattern: Option<String>,
#[arg(long, value_name = "BUZZER")]
buzzer: Option<String>,
#[arg(long, value_name = "VOLUME")]
volume: Option<u8>,
#[arg(long, value_name = "COUNT")]
count: Option<u8>,
}
#[derive(Subcommand)]
enum Commands {
Scan,
Light {
#[arg(value_name = "COLOR")]
color: String,
#[arg(value_name = "PATTERN", default_value = "on")]
pattern: String,
},
Buzzer {
#[arg(value_name = "PATTERN")]
pattern: String,
#[arg(value_name = "COUNT", default_value = "0")]
count: u8,
},
Volume {
#[arg(value_name = "LEVEL")]
level: u8,
},
BuzzerEx {
#[arg(value_name = "PATTERN")]
pattern: String,
#[arg(value_name = "COUNT")]
count: u8,
#[arg(value_name = "VOLUME")]
volume: u8,
},
Setting {
#[arg(value_name = "STATE")]
state: String,
},
TouchSensor,
WaitTouch {
#[arg(long, help = "Show touch sensor state while waiting")]
verbose: bool,
#[arg(long, help = "Exit with this code when pressed", default_value = "0")]
exit_code: i32,
},
Reset,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if let Some(Commands::Scan) = &cli.command {
let devices = Beacon::scan()?;
if devices.is_empty() {
println!("No beacon devices found");
} else {
println!("Found {} beacon device(s):", devices.len());
for (bus, addr, vid, pid) in devices {
println!(" Bus {:03} Device {:03}: ID {:04x}:{:04x}", bus, addr, vid, pid);
}
}
return Ok(());
}
let beacon = Beacon::open()?;
match cli.command {
Some(Commands::Light { color, pattern }) => {
let led_color = LedColor::from_str(&color)
.ok_or_else(|| anyhow::anyhow!("Invalid color: {}", color))?;
let led_pattern = LedPattern::from_str(&pattern)
.ok_or_else(|| anyhow::anyhow!("Invalid pattern: {}", pattern))?;
beacon.set_light(led_color, led_pattern)?;
println!("Set light to {} with pattern {}", color, pattern);
}
Some(Commands::Buzzer { pattern, count }) => {
let buzzer_pattern = BuzzerPattern::from_str(&pattern)
.ok_or_else(|| anyhow::anyhow!("Invalid buzzer pattern: {}", pattern))?;
let buzzer_count = if count == 0 {
BuzzerCount::CONTINUOUS
} else {
BuzzerCount::times(count)
.ok_or_else(|| anyhow::anyhow!("Invalid count: {} (must be 0 or 1-14)", count))?
};
beacon.set_buzzer(buzzer_pattern, buzzer_count)?;
println!("Set buzzer to {} {} times", pattern, if count == 0 { "continuous".to_string() } else { count.to_string() });
}
Some(Commands::Volume { level }) => {
let volume = BuzzerVolume::level(level)
.ok_or_else(|| anyhow::anyhow!("Invalid volume: {} (must be 0-10)", level))?;
beacon.set_volume(volume)?;
println!("Set volume to {}", level);
}
Some(Commands::BuzzerEx { pattern, count, volume }) => {
let buzzer_pattern = BuzzerPattern::from_str(&pattern)
.ok_or_else(|| anyhow::anyhow!("Invalid buzzer pattern: {}", pattern))?;
let buzzer_count = if count == 0 {
BuzzerCount::CONTINUOUS
} else {
BuzzerCount::times(count)
.ok_or_else(|| anyhow::anyhow!("Invalid count: {} (must be 0 or 1-14)", count))?
};
let buzzer_volume = BuzzerVolume::level(volume)
.ok_or_else(|| anyhow::anyhow!("Invalid volume: {} (must be 0-10)", volume))?;
beacon.set_buzzer_ex(buzzer_pattern, buzzer_count, buzzer_volume)?;
println!("Set buzzer to {} {} times at volume {}", pattern,
if count == 0 { "continuous".to_string() } else { count.to_string() }, volume);
}
Some(Commands::Setting { state }) => {
let setting = match state.to_lowercase().as_str() {
"on" | "1" => Setting::On,
"off" | "0" => Setting::Off,
_ => return Err(anyhow::anyhow!("Invalid setting state: {} (use 'on' or 'off')", state)),
};
beacon.set_setting(setting)?;
println!("Set connection display to {}", state);
}
Some(Commands::TouchSensor) => {
let state = beacon.get_touch_sensor_state()?;
println!("Touch sensor input = {}", if state { "ON" } else { "OFF" });
}
Some(Commands::WaitTouch { verbose, exit_code }) => {
let async_beacon = AsyncBeacon::new(beacon);
if verbose {
println!("Waiting for touch sensor press...");
async_beacon.wait_for_touch_with_callback(|state| {
println!("Touch sensor: {}", if state { "PRESSED" } else { "RELEASED" });
}).await?;
} else {
async_beacon.wait_for_touch().await?;
}
std::process::exit(exit_code);
}
Some(Commands::Reset) => {
beacon.reset()?;
println!("Device reset: LED off, buzzer stopped");
}
Some(Commands::Scan) => unreachable!(),
None => {
let mut action_taken = false;
if let Some(ref color) = cli.color {
let led_color = LedColor::from_str(color)
.ok_or_else(|| anyhow::anyhow!("Invalid color: {}", color))?;
let led_pattern = if let Some(ref pattern) = cli.pattern {
LedPattern::from_str(pattern)
.ok_or_else(|| anyhow::anyhow!("Invalid pattern: {}", pattern))?
} else {
LedPattern::On
};
beacon.set_light(led_color, led_pattern)?;
println!("Set light to {} with pattern {:?}", color, led_pattern);
action_taken = true;
}
if let Some(ref buzzer) = cli.buzzer {
let buzzer_pattern = BuzzerPattern::from_str(buzzer)
.ok_or_else(|| anyhow::anyhow!("Invalid buzzer pattern: {}", buzzer))?;
let buzzer_count = if let Some(count) = cli.count {
if count == 0 {
BuzzerCount::CONTINUOUS
} else {
BuzzerCount::times(count)
.ok_or_else(|| anyhow::anyhow!("Invalid count: {} (must be 0 or 1-14)", count))?
}
} else {
BuzzerCount::CONTINUOUS
};
if let Some(volume) = cli.volume {
let buzzer_volume = BuzzerVolume::level(volume)
.ok_or_else(|| anyhow::anyhow!("Invalid volume: {} (must be 0-10)", volume))?;
beacon.set_buzzer_ex(buzzer_pattern, buzzer_count, buzzer_volume)?;
println!("Set buzzer to {} at volume {}", buzzer, volume);
} else {
beacon.set_buzzer(buzzer_pattern, buzzer_count)?;
println!("Set buzzer to {}", buzzer);
}
action_taken = true;
}
if !action_taken && cli.volume.is_none() {
println!("No action specified. Use --help for usage information.");
}
}
}
Ok(())
}