use crate::{Device, Pattern, SolidColor, SpecificLED, TargetedDevice, Wave};
use hidapi::{HidApi, HidDevice};
#[allow(missing_debug_implementations)]
pub struct USBDeviceDiscovery {
hid_api: HidApi,
}
#[allow(missing_debug_implementations)]
pub struct USBDevice {
hid_device: HidDevice,
id: String,
target_led: u8,
}
const LUXAFOR_VENDOR_ID: u16 = 0x04d8;
const LUXAFOR_PRODUCT_ID: u16 = 0xf372;
const HID_REPORT_ID: u8 = 0;
const MODE_SIMPLE: u8 = 0;
const MODE_SOLID: u8 = 1;
const MODE_FADE: u8 = 2;
const MODE_STROBE: u8 = 3;
const MODE_WAVE: u8 = 4;
const MODE_PATTERN: u8 = 6;
const SIMPLE_COLOR_OFF: u8 = b'O';
const LED_FRONT_TOP: u8 = 1;
const LED_FRONT_MIDDLE: u8 = 2;
const LED_FRONT_BOTTOM: u8 = 3;
const LED_BACK_TOP: u8 = 4;
const LED_BACK_MIDDLE: u8 = 5;
const LED_BACK_BOTTOM: u8 = 6;
const LED_FRONT_ALL: u8 = 65;
const LED_BACK_ALL: u8 = 66;
const LED_ALL: u8 = 255;
const WAVE_SHORT: u8 = 1;
const WAVE_LONG: u8 = 2;
const WAVE_OVERLAPPING_SHORT: u8 = 3;
const WAVE_OVERLAPPING_LONG: u8 = 4;
const PATTERN_LUXAFOR: u8 = 1;
const PATTERN_RANDOM_1: u8 = 2;
const PATTERN_RANDOM_2: u8 = 3;
const PATTERN_RANDOM_3: u8 = 4;
const PATTERN_RANDOM_4: u8 = 6;
const PATTERN_RANDOM_5: u8 = 7;
const PATTERN_POLICE: u8 = 5;
#[cfg(target_os = "windows")]
const PATTERN_RAINBOW_WAVE: u8 = 8;
impl USBDeviceDiscovery {
pub fn new() -> crate::error::Result<Self> {
match HidApi::new() {
Ok(hid_api) => Ok(Self { hid_api }),
Err(err) => {
error!("Could not connect to USB, error: {:?}", err);
Err(crate::error::Error::DeviceNotFound)
}
}
}
pub fn device(&self) -> crate::error::Result<USBDevice> {
let result = self.hid_api.open(LUXAFOR_VENDOR_ID, LUXAFOR_PRODUCT_ID);
match result {
Ok(hid_device) => USBDevice::new(hid_device),
Err(err) => {
error!("Could not open HID device: {:?}", err);
Err(crate::error::Error::DeviceNotFound)
}
}
}
}
impl Device for USBDevice {
fn id(&self) -> String {
self.id.clone()
}
fn turn_off(&self) -> crate::error::Result<()> {
info!("Turning device '{}' off", self.id);
self.write(&[HID_REPORT_ID, MODE_SIMPLE, SIMPLE_COLOR_OFF])
}
fn set_solid_color(&self, color: SolidColor) -> crate::error::Result<()> {
info!("Setting the color of device '{}' to {}", self.id, color);
let (r, g, b) = self.color_to_bytes(color);
self.write(&[HID_REPORT_ID, MODE_SOLID, self.target_led, r, g, b])
}
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> crate::error::Result<()> {
info!(
"Setting the fade-to color of device '{}' to {}, over {}",
self.id, color, fade_duration
);
let (r, g, b) = self.color_to_bytes(color);
self.write(&[
HID_REPORT_ID,
MODE_FADE,
self.target_led,
r,
g,
b,
fade_duration,
])
}
fn set_color_strobe(
&self,
color: SolidColor,
strobe_speed: u8,
repeat_count: u8,
) -> crate::error::Result<()> {
info!(
"Setting the device '{}' to strobe {}, at {}, {} times",
self.id, color, strobe_speed, repeat_count
);
let (r, g, b) = self.color_to_bytes(color);
self.write(&[
HID_REPORT_ID,
MODE_STROBE,
self.target_led,
r,
g,
b,
strobe_speed,
0x00,
repeat_count,
])
}
fn set_color_wave(
&self,
color: SolidColor,
wave_pattern: Wave,
wave_speed: u8,
repeat_count: u8,
) -> crate::error::Result<()> {
info!(
"Setting the device '{}' to wave {}, at {}, {} times",
self.id, color, wave_speed, repeat_count
);
let wave_pattern = match wave_pattern {
Wave::Short => WAVE_SHORT,
Wave::Long => WAVE_LONG,
Wave::OverlappingShort => WAVE_OVERLAPPING_SHORT,
Wave::OverlappingLong => WAVE_OVERLAPPING_LONG,
};
let (r, g, b) = self.color_to_bytes(color);
self.write(&[
HID_REPORT_ID,
MODE_WAVE,
wave_pattern,
r,
g,
b,
0x00,
repeat_count,
wave_speed,
])
}
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> crate::error::Result<()> {
info!("Setting the pattern of device '{}' to {}", self.id, pattern);
let pattern = match pattern {
Pattern::Police => PATTERN_POLICE,
Pattern::TrafficLights => PATTERN_LUXAFOR,
Pattern::Random(n) => match n {
1 => PATTERN_RANDOM_1,
2 => PATTERN_RANDOM_2,
3 => PATTERN_RANDOM_3,
4 => PATTERN_RANDOM_4,
_ => PATTERN_RANDOM_5,
},
#[cfg(target_os = "windows")]
Pattern::Rainbow => PATTERN_RAINBOW_WAVE,
#[cfg(target_os = "windows")]
Pattern::Sea => 9,
#[cfg(target_os = "windows")]
Pattern::WhiteWave => 10,
#[cfg(target_os = "windows")]
Pattern::Synthetic => 11,
};
self.write(&[HID_REPORT_ID, MODE_PATTERN, pattern, repeat_count])
}
}
impl TargetedDevice for USBDevice {
fn set_specific_led(&mut self, led: SpecificLED) -> crate::error::Result<()> {
self.target_led = match led {
SpecificLED::All => LED_ALL,
SpecificLED::AllFront => LED_FRONT_ALL,
SpecificLED::AllBack => LED_BACK_ALL,
SpecificLED::Number(n) => match n {
1 => LED_FRONT_BOTTOM,
2 => LED_FRONT_MIDDLE,
3 => LED_FRONT_TOP,
4 => LED_BACK_BOTTOM,
5 => LED_BACK_MIDDLE,
6 => LED_BACK_TOP,
_ => return Err(crate::error::Error::InvalidLED),
},
};
Ok(())
}
}
impl USBDevice {
fn new(hid_device: HidDevice) -> crate::error::Result<USBDevice> {
let id = format!(
"{}::{}::{}",
hid_device
.get_manufacturer_string()
.unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()),
hid_device
.get_product_string()
.unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()),
hid_device
.get_serial_number_string()
.unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()),
);
Ok(Self {
hid_device,
id,
target_led: LED_ALL,
})
}
fn color_to_bytes(&self, color: SolidColor) -> (u8, u8, u8) {
match color {
SolidColor::Red => (255, 0, 0),
SolidColor::Green => (0, 255, 0),
SolidColor::Yellow => (255, 255, 0),
SolidColor::Blue => (0, 0, 255),
SolidColor::White => (255, 255, 255),
SolidColor::Cyan => (0, 255, 255),
SolidColor::Magenta => (255, 0, 255),
SolidColor::Custom { red, green, blue } => (red, green, blue),
}
}
fn write(&self, buffer: &[u8]) -> crate::error::Result<()> {
trace!(
"writing [{:?}]",
buffer
.iter()
.map(|b| format!("{:#04x}", b))
.collect::<Vec<String>>()
.join(", ")
);
let result = self.hid_device.write(buffer);
match result {
Ok(bytes_written) => {
if bytes_written == buffer.len() {
Ok(())
} else {
error!(
"Bytes written, {}, did not match buffer length {}",
bytes_written,
buffer.len()
);
Err(crate::error::Error::InvalidRequest)
}
}
Err(err) => {
error!("Could not write to HID device: {:?}", err);
Err(crate::error::Error::InvalidRequest)
}
}
}
}
#[cfg(device_test)]
mod tests {
use crate::{Device, SolidColor};
#[test]
fn test_discovery() {
let result = super::USBDeviceDiscovery::new();
if result.is_ok() {
let discovery = result.unwrap();
let result = discovery.device();
assert!(result.is_ok());
let device = result.unwrap();
println!("{}", device.id());
let result = device.set_solid_color(SolidColor::Green);
assert!(result.is_ok());
}
}
}