use anyhow::Result;
use std::time::Duration;
use crate::bulb;
use crate::devices::{self, DeviceKind};
use crate::dimmer;
use crate::ops;
use super::shared::KasaContext;
async fn ensure_light_on(ctx: &KasaContext) -> Result<()> {
if bulb::parse(ctx.json()).is_some_and(|b| !b.light_state.is_on()) {
match ctx.kind() {
DeviceKind::Bulb => ops::bulb_on(ctx.ip()).await?,
DeviceKind::LightStrip => ops::lightstrip_set_power(ctx.ip(), true).await?,
_ => {}
}
}
Ok(())
}
pub async fn handle_dim(host: &str, level: u8) -> Result<()> {
let ctx = KasaContext::load(host, "dim").await?;
devices::can_dim(ctx.kind())?;
match ctx.kind() {
DeviceKind::Bulb => {
ensure_light_on(&ctx).await?;
ops::bulb_set_brightness(ctx.ip(), level).await?;
}
DeviceKind::LightStrip => ops::lightstrip_set_brightness(ctx.ip(), level).await?,
DeviceKind::Dimmer => {
if level > 0 && dimmer::parse(ctx.json()).is_some_and(|d| !d.is_on()) {
ops::relay_on(ctx.ip()).await?;
}
ops::dimmer_set_brightness(ctx.ip(), level).await?;
}
other => anyhow::bail!("`dim` is only supported on bulbs and dimmers, not {other}"),
}
crate::output::record(serde_json::json!({"brightness_pct":level}));
crate::output::println!("Brightness -> {level}%");
Ok(())
}
pub async fn handle_color_temp(host: &str, kelvin: u16) -> Result<()> {
let ctx = KasaContext::load(host, "color-temp").await?;
devices::can_set_color_temp(ctx.kind())?;
match ctx.kind() {
DeviceKind::Bulb => {
ensure_light_on(&ctx).await?;
ops::bulb_set_color_temp(ctx.ip(), kelvin).await?;
}
DeviceKind::LightStrip => ops::lightstrip_set_color_temp(ctx.ip(), kelvin).await?,
_ => unreachable!("capability guard accepted a non-light device"),
}
crate::output::record(serde_json::json!({"color_temp_k":kelvin}));
crate::output::println!("Color temperature -> {kelvin}K");
Ok(())
}
pub async fn handle_color(host: &str, hue: u16, saturation: u8, value: u8) -> Result<()> {
let ctx = KasaContext::load(host, "color").await?;
devices::can_set_color(ctx.kind())?;
match ctx.kind() {
DeviceKind::Bulb => {
ensure_light_on(&ctx).await?;
ops::bulb_set_color(ctx.ip(), hue, saturation, value).await?;
}
DeviceKind::LightStrip => {
ops::lightstrip_set_color(ctx.ip(), hue, saturation, value).await?
}
_ => unreachable!("capability guard accepted a non-light device"),
}
crate::output::record(
serde_json::json!({"hue_deg":hue,"saturation_pct":saturation,"brightness_pct":value}),
);
crate::output::println!("Color -> hue:{hue} sat:{saturation} val:{value}");
Ok(())
}
pub async fn handle_rainbow(host: &str, step_delay_ms: u64) -> Result<()> {
let ctx = KasaContext::load(host, "rainbow").await?;
devices::can_set_color(ctx.kind())?;
let state = bulb::parse(ctx.json())
.ok_or_else(|| anyhow::anyhow!("Could not read current light state"))?;
let mut hue = state.light_state.hue() % 360;
let saturation = state.light_state.saturation();
let brightness = state.light_state.brightness();
match ctx.kind() {
DeviceKind::Bulb => ensure_light_on(&ctx).await?,
DeviceKind::LightStrip => {}
other => anyhow::bail!("`rainbow` is only supported on color lights, not {other}"),
}
crate::output::println!("Cycling rainbow colors; press Ctrl-C to stop.");
let stop = tokio::signal::ctrl_c();
tokio::pin!(stop);
loop {
tokio::select! {
result = &mut stop => {
result?;
crate::output::println!("Rainbow stopped at {hue}° hue.");
return Ok(());
}
_ = tokio::time::sleep(Duration::from_millis(step_delay_ms)) => {}
}
hue = (hue + 10) % 360;
match ctx.kind() {
DeviceKind::Bulb => {
ops::bulb_set_color(ctx.ip(), hue, saturation, brightness).await?;
}
DeviceKind::LightStrip => {
ops::lightstrip_set_color(ctx.ip(), hue, saturation, brightness).await?;
}
_ => unreachable!("capability guard accepted a non-color light"),
}
}
}