use crate::error::{Error, Result};
use crate::{LED_INDEX, LED_TIMEOUT, VENDOR_OUT};
use rusb::{DeviceHandle, UsbContext};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl Rgb {
pub const BLACK: Self = Self { r: 0, g: 0, b: 0 };
pub const RED: Self = Self { r: 255, g: 0, b: 0 };
pub const GREEN: Self = Self { r: 0, g: 255, b: 0 };
pub const BLUE: Self = Self { r: 0, g: 0, b: 255 };
pub const WHITE: Self = Self {
r: 255,
g: 255,
b: 255,
};
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
pub fn from_hex(s: &str) -> Result<Self> {
let s = s.trim().trim_start_matches("0x").trim_start_matches("0X");
if s.len() != 6 {
return Err(Error::InvalidValue(format!(
"expected RRGGBB hex color, got `{s}`"
)));
}
let v = u32::from_str_radix(s, 16)
.map_err(|e| Error::InvalidValue(format!("invalid hex color `{s}`: {e}")))?;
Ok(Self {
r: ((v >> 16) & 0xff) as u8,
g: ((v >> 8) & 0xff) as u8,
b: (v & 0xff) as u8,
})
}
fn mono_payload(self) -> [u8; 4] {
[self.r, self.g, self.b, 0]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum VadLed {
Off = 0,
On = 1,
Auto = 2,
}
pub struct PixelRing<'a, T: UsbContext> {
handle: &'a DeviceHandle<T>,
}
impl<'a, T: UsbContext> PixelRing<'a, T> {
pub(crate) fn new(handle: &'a DeviceHandle<T>) -> Self {
Self { handle }
}
pub fn trace(&self) -> Result<()> {
self.write(0, &[0])
}
pub fn mono(&self, color: Rgb) -> Result<()> {
self.write(1, &color.mono_payload())
}
pub fn off(&self) -> Result<()> {
self.mono(Rgb::BLACK)
}
pub fn listen(&self) -> Result<()> {
self.write(2, &[0])
}
pub fn wakeup(&self) -> Result<()> {
self.listen()
}
pub fn speak(&self) -> Result<()> {
self.write(3, &[0])
}
pub fn think(&self) -> Result<()> {
self.write(4, &[0])
}
pub fn wait(&self) -> Result<()> {
self.think()
}
pub fn spin(&self) -> Result<()> {
self.write(5, &[0])
}
pub fn customize(&self, colors: &[Rgb; 12]) -> Result<()> {
let mut data = [0u8; 48];
for (i, c) in colors.iter().enumerate() {
let base = i * 4;
data[base] = c.r;
data[base + 1] = c.g;
data[base + 2] = c.b;
}
self.write(6, &data)
}
pub fn set_brightness(&self, brightness: u8) -> Result<()> {
if brightness > 0x1f {
return Err(Error::InvalidValue(
"brightness must be in 0..=31 (0x1F)".into(),
));
}
self.write(0x20, &[brightness])
}
pub fn set_color_palette(&self, a: Rgb, b: Rgb) -> Result<()> {
let data = [
a.r, a.g, a.b, 0, b.r, b.g, b.b, 0,
];
self.write(0x21, &data)
}
pub fn set_vad_led(&self, state: VadLed) -> Result<()> {
self.write(0x22, &[state as u8])
}
pub fn set_volume(&self, volume: u8) -> Result<()> {
if volume > 12 {
return Err(Error::InvalidValue("volume must be in 0..=12".into()));
}
self.write(0x23, &[volume])
}
fn write(&self, command: u16, data: &[u8]) -> Result<()> {
self.handle
.write_control(VENDOR_OUT, 0, command, LED_INDEX, data, LED_TIMEOUT)?;
Ok(())
}
}