use huesmith_core::color;
use huesmith_core::light::LightOutput;
use esp_idf_hal::gpio::OutputPin;
use esp_idf_hal::ledc::{LedcChannel, LedcDriver, LedcTimer, LedcTimerDriver, LowSpeed};
use super::electrical_duty;
pub struct CctPwm<'d> {
_timer: LedcTimerDriver<'d, LowSpeed>,
cool: LedcDriver<'d>,
warm: LedcDriver<'d>,
max_duty: u32,
brightness: u8,
color_temp_mireds: u16,
on: bool,
inverted: bool,
}
impl<'d> CctPwm<'d> {
pub const MIN_MIREDS: u16 = 153;
pub const MAX_MIREDS: u16 = 454;
pub fn new<T, C0, C1>(
timer: T,
cool_channel: C0,
cool_pin: impl OutputPin + 'd,
warm_channel: C1,
warm_pin: impl OutputPin + 'd,
inverted: bool,
) -> Self
where
T: LedcTimer<SpeedMode = LowSpeed> + 'd,
C0: LedcChannel<SpeedMode = LowSpeed> + 'd,
C1: LedcChannel<SpeedMode = LowSpeed> + 'd,
{
let timer_driver =
LedcTimerDriver::new(timer, &esp_idf_hal::ledc::config::TimerConfig::default())
.expect("CCT LEDC timer init failed");
let cool = LedcDriver::new(cool_channel, &timer_driver, cool_pin)
.expect("CCT cool channel init failed");
let warm = LedcDriver::new(warm_channel, &timer_driver, warm_pin)
.expect("CCT warm channel init failed");
let max_duty = cool.get_max_duty();
let mut this = Self {
_timer: timer_driver,
cool,
warm,
max_duty,
brightness: 254,
color_temp_mireds: 370,
on: false,
inverted,
};
this.all_off();
this
}
fn set_channel_level(driver: &mut LedcDriver<'_>, max_duty: u32, level: u8, inverted: bool) {
let duty = electrical_duty(level, max_duty, inverted);
if let Err(e) = driver.set_duty(duty) {
log::error!("LEDC set_duty failed: {e}");
}
}
fn all_off(&mut self) {
let inverted = self.inverted;
let max_duty = self.max_duty;
Self::set_channel_level(&mut self.cool, max_duty, 0, inverted);
Self::set_channel_level(&mut self.warm, max_duty, 0, inverted);
}
fn apply_current(&mut self) {
if !self.on {
self.all_off();
return;
}
let (cool_level, warm_level) = color::cct_mix_from_mireds(
self.color_temp_mireds,
self.brightness,
Self::MIN_MIREDS,
Self::MAX_MIREDS,
);
let max_duty = self.max_duty;
let inverted = self.inverted;
Self::set_channel_level(&mut self.cool, max_duty, cool_level, inverted);
Self::set_channel_level(&mut self.warm, max_duty, warm_level, inverted);
}
fn mireds_from_rgb(&mut self, r: u8, g: u8, b: u8) {
if r == 0 && g == 0 && b == 0 {
return;
}
self.color_temp_mireds =
color::cct_mireds_from_rgb(r, g, b, Self::MIN_MIREDS, Self::MAX_MIREDS);
}
}
impl<'d> LightOutput for CctPwm<'d> {
fn set_on(&mut self, on: bool) {
self.on = on;
if on {
self.apply_current();
} else {
self.all_off();
}
}
fn set_brightness(&mut self, level: u8) {
self.brightness = level.min(254);
}
fn set_color_xy(&mut self, x: u16, y: u16) {
let (r, g, b) = color::xy_to_rgb(x, y, self.brightness);
self.set_rgb(r, g, b);
}
fn set_color_temp(&mut self, mireds: u16) {
self.color_temp_mireds = mireds.clamp(Self::MIN_MIREDS, Self::MAX_MIREDS);
self.apply_current();
}
fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
self.mireds_from_rgb(r, g, b);
self.apply_current();
}
}