use std::collections::HashSet;
use std::time::Duration;
use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand, ValueEnum};
use glow_control_lib::control_interface::{
CliColors, CliDeviceMode, ControlInterface, RtStdinErrorMode, RtStdinFormat, RGB,
};
use glow_control_lib::util::discovery::Discovery;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
let cli = Cli::parse();
handle_cli(cli).await
}
#[derive(Parser)]
#[clap(
name = "glow_control",
about = "Controls commercial LED devices",
version = "0.3.3"
)]
pub struct Cli {
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum OutputFormat {
Plaintext,
Json,
Yaml,
}
#[derive(Subcommand)]
pub enum Commands {
#[clap(name = "device-call")]
DeviceCall {
#[clap(long)]
ip: String,
#[clap(long)]
mac: String,
#[clap(subcommand)]
action: DeviceAction,
},
#[clap(name = "discover")]
Discover {
#[clap(short, long, value_enum, default_value_t = OutputFormat::Plaintext)]
output: OutputFormat,
#[clap(short = 't', long = "timeout", default_value_t = 5000)]
timeout: u64,
},
}
#[derive(Subcommand)]
pub enum RtEffect {
#[clap(name = "show-color")]
ShowColor {
#[clap(value_enum)]
color: Option<CliColors>,
#[clap(short = 'r', long = "red", value_parser = clap::value_parser!(u8))]
red: Option<u8>,
#[clap(short = 'g', long = "green", value_parser = clap::value_parser!(u8))]
green: Option<u8>,
#[clap(short = 'b', long = "blue", value_parser = clap::value_parser!(u8))]
blue: Option<u8>,
},
Shine {
#[clap(long)]
num_start_simultaneous: usize,
#[clap(long, use_value_delimiter = true)]
colors: Vec<CliColors>,
#[clap(long, value_parser = parse_duration)]
time_between_glow_start: Duration,
#[clap(long, value_parser = parse_duration)]
time_to_max_glow: Duration,
#[clap(long, value_parser = parse_duration)]
time_to_fade: Duration,
#[clap(long)]
frame_rate: f64,
},
}
fn parse_duration(s: &str) -> Result<Duration, &'static str> {
let millis = s
.parse::<u64>()
.map_err(|_| "could not parse duration in milliseconds")?;
Ok(Duration::from_millis(millis))
}
#[derive(Subcommand)]
pub enum DeviceAction {
#[clap(name = "get-mode")]
GetMode,
#[clap(name = "get-timer")]
GetTimer,
#[clap(name = "get-playlist")]
GetPlaylist,
#[clap(name = "set-mode")]
SetMode {
#[clap(value_enum)]
mode: CliDeviceMode,
},
#[clap(name = "fetch-layout")]
FetchLayout,
#[clap(name = "clear-movies")]
ClearMovies,
#[clap(name = "get-device-capacity")]
GetDeviceCapacity,
#[clap(name = "print-config")]
PrintConfig,
#[clap(name = "real-time-test")]
RealTimeTest,
#[clap(name = "rt-effect")]
RtEffect {
#[clap(subcommand)]
effect: RtEffect,
},
#[clap(name = "rt-stdin")]
RtStdin {
#[clap(long, value_enum)]
format: RtStdinFormat,
#[clap(long, value_enum)]
error_mode: RtStdinErrorMode,
#[clap(long, value_enum)]
leds_per_frame: u16,
#[clap(long, value_parser = parse_duration)]
min_frame_duration: Duration,
},
}
async fn handle_cli(cli: Cli) -> Result<()> {
match cli.command {
Commands::Discover { output, timeout } => {
let devices = Discovery::find_devices(Duration::from_millis(timeout)).await?;
match output {
OutputFormat::Plaintext => {
Discovery::pretty_print_devices(&devices);
}
OutputFormat::Json => {
let json = serde_json::to_string(&devices)?;
println!("{}", json);
}
OutputFormat::Yaml => {
let yaml = serde_yaml::to_string(&devices)?;
println!("{}", yaml);
}
}
}
Commands::DeviceCall { ip, mac, action } => {
let high_control_interface = ControlInterface::new(&ip, &mac, None).await?;
match action {
DeviceAction::GetMode => {
let mode_response = high_control_interface.get_mode().await?;
println!("Current mode: {}", mode_response);
}
DeviceAction::GetTimer => {
let timer_response = high_control_interface.get_timer().await?;
println!("Current timer settings:");
println!("Time now: {}", timer_response.time_now);
println!("Time to turn on: {}", timer_response.time_on);
println!("Time to turn off: {}", timer_response.time_off);
}
DeviceAction::GetPlaylist => {
let playlist_response = high_control_interface.get_playlist().await?;
println!("Current playlist:");
for entry in playlist_response.entries {
println!("ID: {}, Name: {}", entry.id, entry.name);
}
}
DeviceAction::SetMode { mode } => {
high_control_interface.set_mode(mode.into()).await?;
println!("Device mode set to {:?}", mode);
}
DeviceAction::FetchLayout => {
let layout = high_control_interface.fetch_layout().await?;
println!("LED layout fetched: {:?}", layout);
}
DeviceAction::ClearMovies => {
high_control_interface.clear_movies().await?;
println!("All movies have been cleared from the device.");
}
DeviceAction::GetDeviceCapacity => {
let capacity = high_control_interface.get_device_capacity().await?;
println!("Device capacity for movies: {}", capacity);
}
DeviceAction::PrintConfig => {
let device_info = high_control_interface.get_device_info();
println!("The device configuration:\n{:#?}", device_info);
}
DeviceAction::RealTimeTest => {
high_control_interface
.show_real_time_test_color_wheel(0.02, 20.0)
.await?;
println!("Real-time test color wheel displayed.");
}
DeviceAction::RtEffect { effect } => {
match effect {
RtEffect::ShowColor {
color,
red,
green,
blue,
} => {
let color_to_show = match (color, red, green, blue) {
(Some(color_name), None, None, None) => color_name.into(),
(None, Some(r), Some(g), Some(b)) => RGB {
red: r,
green: g,
blue: b,
},
_ => return Err(anyhow!("Invalid color specification")),
};
high_control_interface
.show_solid_color(color_to_show)
.await?;
println!("Displayed color: {:?}", color_to_show);
}
RtEffect::Shine {
num_start_simultaneous,
time_between_glow_start,
time_to_max_glow,
time_to_fade,
colors,
frame_rate,
} => {
let color_set: HashSet<RGB> =
colors.into_iter().map(Into::into).collect();
if color_set.is_empty() {
return Err(anyhow!("At least one color must be specified"));
}
high_control_interface
.shine_leds(
time_between_glow_start,
time_to_max_glow,
time_to_fade,
color_set,
frame_rate,
num_start_simultaneous,
)
.await?;
println!("Shine effect started.");
}
}
}
DeviceAction::RtStdin {
format,
error_mode,
leds_per_frame,
min_frame_duration: min_frame_time,
} => {
high_control_interface
.show_real_time_stdin_stream(
format,
error_mode,
leds_per_frame,
min_frame_time,
)
.await?;
}
}
}
}
Ok(())
}