#![allow(deprecated)]
use huesmith_core::color;
use huesmith_core::light::LightOutput;
use core::time::Duration;
use enumset::EnumSet;
use esp_idf_hal::gpio::OutputPin;
use esp_idf_hal::interrupt::InterruptType;
use esp_idf_hal::rmt::config::TransmitConfig;
use esp_idf_hal::rmt::{PinState, Pulse, RmtChannel, TxRmtDriver, VariableLengthSignal};
const T0H_NS: u64 = 350;
const T0L_NS: u64 = 800;
const T1H_NS: u64 = 700;
const T1L_NS: u64 = 600;
pub struct Ws2812b<'d> {
tx: TxRmtDriver<'d>,
t0h: Pulse,
t0l: Pulse,
t1h: Pulse,
t1l: Pulse,
num_leds: usize,
active_leds: usize,
signal: VariableLengthSignal,
brightness: u8,
on: bool,
current_r: u8,
current_g: u8,
current_b: u8,
}
impl<'d> Ws2812b<'d> {
pub fn new<C, P>(channel: C, pin: P, num_leds: usize, active_leds: usize) -> Self
where
C: RmtChannel + 'd,
P: OutputPin + 'd,
{
let config = TransmitConfig::new()
.clock_divider(1)
.mem_block_num(2)
.intr_flags(EnumSet::only(InterruptType::Iram));
let tx = TxRmtDriver::new(channel, pin, &config).expect("WS2812B RMT init failed");
let ticks_hz = tx.counter_clock().expect("WS2812B counter clock");
let pulse = |state: PinState, ns: u64| {
Pulse::new_with_duration(ticks_hz, state, &Duration::from_nanos(ns))
.expect("WS2812B pulse")
};
let active_leds = active_leds.min(num_leds);
log::info!(
"WS2812B: legacy RMT @ {} Hz ({num_leds} LEDs, {active_leds} active)",
u32::from(ticks_hz),
);
let mut this = Self {
tx,
t0h: pulse(PinState::High, T0H_NS),
t0l: pulse(PinState::Low, T0L_NS),
t1h: pulse(PinState::High, T1H_NS),
t1l: pulse(PinState::Low, T1L_NS),
num_leds,
active_leds,
signal: VariableLengthSignal::with_capacity(num_leds * 24 * 2),
brightness: 254,
on: false,
current_r: 0,
current_g: 0,
current_b: 0,
};
this.send_pixels(0, 0, 0);
this
}
fn push_pixel(
signal: &mut VariableLengthSignal,
pulses: &(Pulse, Pulse, Pulse, Pulse),
r: u8,
g: u8,
b: u8,
) -> bool {
let (t0h, t0l, t1h, t1l) = pulses;
for &byte in &[g, r, b] {
for bit_pos in (0..8).rev() {
let (high, low) = if (byte >> bit_pos) & 1 != 0 {
(t1h, t1l)
} else {
(t0h, t0l)
};
if signal.push([high, low]).is_err() {
return false;
}
}
}
true
}
fn send_pixels(&mut self, r: u8, g: u8, b: u8) {
if self.num_leds == 0 {
return;
}
let pulses = (self.t0h, self.t0l, self.t1h, self.t1l);
self.signal.clear();
for i in 0..self.num_leds {
let (pr, pg, pb) = if i < self.active_leds {
(r, g, b)
} else {
(0, 0, 0)
};
if !Self::push_pixel(&mut self.signal, &pulses, pr, pg, pb) {
log::error!("WS2812B: signal push failed");
return;
}
}
if let Err(e) = self.tx.start_blocking(&self.signal) {
log::error!("WS2812B RMT transmit failed: {e}");
}
}
}
impl<'d> LightOutput for Ws2812b<'d> {
fn set_on(&mut self, on: bool) {
self.on = on;
if on {
self.send_pixels(self.current_r, self.current_g, self.current_b);
} else {
self.send_pixels(0, 0, 0);
}
}
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) {
let (r, g, b) = color::scale_rgb(color::mireds_to_rgb(mireds), self.brightness);
self.set_rgb(r, g, b);
}
fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
self.current_r = r;
self.current_g = g;
self.current_b = b;
if self.on {
self.send_pixels(r, g, b);
}
}
}