rofaxul/src/usb_hid.rs

215 lines
6.8 KiB
Rust

/*!
Implementation of the Device trait for USB connected lights.
*/
use crate::{Device, Pattern, SolidColor};
use hidapi::{HidApi, HidDevice};
// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------
///
/// This enables the discovery of the device using the USB HID descriptor.
///
#[allow(missing_debug_implementations)]
pub struct USBDeviceDiscovery {
hid_api: HidApi,
}
///
/// The device implementation for a USB connected light.
///
#[allow(missing_debug_implementations)]
pub struct USBDevice<'a> {
hid_device: HidDevice<'a>,
id: String,
}
// ------------------------------------------------------------------------------------------------
// API Constants
// ------------------------------------------------------------------------------------------------
const LUXAFOR_VENDOR_ID: u16 = 0x04d8;
const LUXAFOR_PRODUCT_ID: u16 = 0xf372;
const MODE_SOLID: u8 = 1;
#[allow(dead_code)]
const MODE_FADE: u8 = 2;
const MODE_STROBE: u8 = 3;
#[allow(dead_code)]
const MODE_WAVE: u8 = 4;
const MODE_PATTERN: u8 = 6;
#[allow(dead_code)]
const LED_FRONT_TOP: u8 = 1;
#[allow(dead_code)]
const LED_FRONT_MIDDLE: u8 = 2;
#[allow(dead_code)]
const LED_FRONT_BOTTOM: u8 = 3;
#[allow(dead_code)]
const LED_BACK_TOP: u8 = 4;
#[allow(dead_code)]
const LED_BACK_MIDDLE: u8 = 5;
#[allow(dead_code)]
const LED_BACK_BOTTOM: u8 = 6;
#[allow(dead_code)]
const LED_FRONT_ALL: u8 = 65;
#[allow(dead_code)]
const LED_BACK_ALL: u8 = 66;
const LED_ALL: u8 = 255;
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;
const PATTERN_RAINBOW_WAVE: u8 = 8;
// ------------------------------------------------------------------------------------------------
// Public Functions
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------
impl USBDeviceDiscovery {
///
/// Construct a new discovery object, this initializes the USB HID interface and thus can fail.
///
pub fn new() -> crate::error::Result<Self> {
let hid_api = HidApi::new()?;
Ok(Self { hid_api })
}
///
/// Return a device, if found, that corresponds to a Luxafor light.
///
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::ErrorKind::DeviceNotFound.into())
}
}
}
}
// ------------------------------------------------------------------------------------------------
impl<'a> Device for USBDevice<'a> {
fn id(&self) -> String {
self.id.clone()
}
fn turn_off(&self) -> crate::error::Result<()> {
self.set_solid_color(
SolidColor::Custom {
red: 00,
green: 00,
blue: 00,
},
false,
)
}
fn set_solid_color(&self, color: SolidColor, blink: bool) -> crate::error::Result<()> {
info!("Setting the color of device '{}' to {}", self.id, color);
let (r, g, b) = 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),
};
let mode = if blink { MODE_STROBE } else { MODE_SOLID };
trace!("{} ({:#04x},{:#04x},{:#04x})", mode, r, g, b);
let result = self.hid_device.write(&[mode, LED_ALL, r, g, b]);
match result {
Ok(_) => Ok(()),
Err(err) => {
error!("Could not write to HID device: {:?}", err);
Err(crate::error::ErrorKind::InvalidRequest.into())
}
}
}
fn set_pattern(&self, pattern: Pattern) -> 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,
},
Pattern::Rainbow => PATTERN_RAINBOW_WAVE,
Pattern::Sea => 9,
Pattern::WhiteWave => 10,
Pattern::Synthetic => 11,
};
let result = self.hid_device.write(&[MODE_PATTERN, LED_ALL, pattern]);
match result {
Ok(_) => Ok(()),
Err(err) => {
error!("Could not write to HID device: {:?}", err);
Err(crate::error::ErrorKind::InvalidRequest.into())
}
}
}
}
impl<'a> USBDevice<'a> {
fn new(hid_device: HidDevice<'a>) -> crate::error::Result<USBDevice<'a>> {
let id = format!(
"{}::{}::{}",
hid_device
.get_manufacturer_string()
.unwrap_or("<unknown>".to_string()),
hid_device
.get_product_string()
.unwrap_or("<unknown>".to_string()),
hid_device
.get_serial_number_string()
.unwrap_or("<unknown>".to_string())
);
Ok(Self { hid_device, id })
}
}
// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------
#[cfg(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, false);
assert!(result.is_ok());
}
}
}