use std::str::FromStr; #[derive(Default, Debug)] pub struct BulbMode { pub power: bool, pub color: BulbColor, } #[derive(Debug, Clone, Copy)] pub enum BulbColor { /// Light temperature, brightness Kelvin { t: f32, b: f32 }, /// Hue, Saturation, Brightness HSB { h: f32, s: f32, b: f32 }, } impl FromStr for BulbColor { type Err = anyhow::Error; fn from_str(s: &str) -> anyhow::Result { let parsed = u64::from_str_radix(s, 16)?; let to_f32 = |byte: u8| byte as f32 / 255.; let [.., red, green, blue, white, warm] = parsed.to_be_bytes().map(to_f32); if (red + green + blue) != 0. { let (h, s, b) = rgb_to_hsb(red, green, blue); Ok(BulbColor::hsb(h, s, b)) } else { let b = warm + white; let t = white / b; Ok(BulbColor::kelvin(t, b)) } } } impl BulbColor { pub fn color_string(self) -> String { let [mut red, mut green, mut blue, mut white, mut warm] = [0u8; 5]; match self { BulbColor::HSB { h, s, b } => (red, green, blue) = hsb_to_rgb(h, s, b), BulbColor::Kelvin { t, b } => { (white, warm) = ((t * b * 255.0) as u8, ((1. - t) * b * 255.0) as u8) } } let s = format!("{red:02x}{green:02x}{blue:02x}{white:02x}{warm:02x}"); s } } impl BulbColor { pub fn hsb(h: f32, s: f32, b: f32) -> Self { BulbColor::HSB { h: h.clamp(0.0, 1.0), s: s.clamp(0.0, 1.0), b: b.clamp(0.0, 1.0), } } pub fn kelvin(t: f32, b: f32) -> Self { BulbColor::Kelvin { t: t.clamp(0.0, 1.0), b: b.clamp(0.0, 1.0), } } } impl Default for BulbColor { fn default() -> Self { BulbColor::Kelvin { t: 0.0, b: 0.0 } } } fn rgb_to_hsb(red: f32, green: f32, blue: f32) -> (f32, f32, f32) { let x_max = red.max(green).max(blue); let x_min = red.min(green).min(blue); let b = x_max; let c = x_max - x_min; let h = match b { _ if c == 0. => 0., _ if b == red => (green - blue) / c, _ if b == green => 2. + (blue - red) / c, _ => 4. + (red - green) / c, }; let mut h = h * 60.0 / 360.0; if h < 0.0 { h += 1.0; } let s = if b == 0.0 { 0.0 } else { c / b }; (h, s, b) } fn hsb_to_rgb(h: f32, s: f32, b: f32) -> (u8, u8, u8) { let h = 360.0 * h.clamp(0.0, 1.0); let c = b * s; // chroma let x = c * (1. - ((h / 60.) % 2. - 1.).abs()); let m = b - c; let m = |v| ((v + m) * 255.0) as u8; let (r, g, b) = match h { _ if h < 60. => (c, x, 0.0), _ if h < 120.0 => (x, c, 0.0), _ if h < 180.0 => (0.0, c, x), _ if h < 240.0 => (0.0, x, c), _ if h < 300.0 => (x, 0.0, c), _ => (c, 0.0, x), }; (m(r), m(g), m(b)) }