diff --git a/src/lib.rs b/src/lib.rs index 18bf7b5..18b94be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,19 +147,23 @@ pub enum SolidColor { /// A pattern the light can be set to show. #[derive(Clone, Debug)] pub enum Pattern { - /// A preset pattern + /// A preset pattern that cycles between red and blue. Police, - /// A preset pattern + /// A preset pattern that cycles between green,. yellow, and red. TrafficLights, - /// A preset pattern + /// Preset random patterns Random(u8), - /// A preset pattern (accepted on Windows only) + /// A preset pattern + #[cfg(target_os = "windows")] Rainbow, - /// A preset pattern (accepted on Windows only) + /// A preset pattern + #[cfg(target_os = "windows")] Sea, - /// A preset pattern (accepted on Windows only) + /// A preset pattern + #[cfg(target_os = "windows")] WhiteWave, - /// A preset pattern (accepted on Windows only) + /// A preset pattern + #[cfg(target_os = "windows")] Synthetic, } @@ -254,9 +258,13 @@ impl Display for Pattern { Pattern::Police => "police".to_string(), Pattern::TrafficLights => "traffic lights".to_string(), Pattern::Random(n) => format!("random {}", n), + #[cfg(target_os = "windows")] Pattern::Rainbow => "rainbow".to_string(), + #[cfg(target_os = "windows")] Pattern::Sea => "sea".to_string(), + #[cfg(target_os = "windows")] Pattern::WhiteWave => "white wave".to_string(), + #[cfg(target_os = "windows")] Pattern::Synthetic => "synthetic".to_string(), } ) @@ -276,8 +284,13 @@ impl FromStr for Pattern { "random 3" => Ok(Pattern::Random(3)), "random 4" => Ok(Pattern::Random(4)), "random 5" => Ok(Pattern::Random(5)), + #[cfg(target_os = "windows")] + "rainbow" => Ok(Pattern::Rainbow), + #[cfg(target_os = "windows")] "sea" => Ok(Pattern::Sea), + #[cfg(target_os = "windows")] "white wave" => Ok(Pattern::WhiteWave), + #[cfg(target_os = "windows")] "synthetic" => Ok(Pattern::Synthetic), _ => Err(error::ErrorKind::InvalidPattern.into()), } diff --git a/src/usb_hid.rs b/src/usb_hid.rs index 36ad900..1a10b3f 100644 --- a/src/usb_hid.rs +++ b/src/usb_hid.rs @@ -1,5 +1,107 @@ /*! Implementation of the Device trait for USB connected lights. + +# Specification + +The identifiers used to discover the device using the HID API are as follows: + +1. The vendor identifier (VID) is `0x04D8`. +1. The product identifier (PID) is `0xF372`. + +The following command groups exist for controlling the lights. Note that byte 0, the USB HID _report +identifier_, must always be set to `0x00`. Also, trailing `0x00` values need not be written. + +| Command Group | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|----------------|--------|--------|--------|--------|--------|--------|--------|--------|--------| +| Simple | `0x00` | `0x00` | COLOR* | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | +| Solid | `0x00` | `0x01` | LED | RED | GREEN | BLUE | `0x00` | `0x00` | `0x00` | +| Fade | `0x00` | `0x02` | LED | RED | GREEN | BLUE | TIME | `0x00` | `0x00` | +| Strobe | `0x00` | `0x03` | LED | RED | GREEN | BLUE | SPEED | `0x00` | REPEAT | +| Wave | `0x00` | `0x04` | WTYPE | RED | GREEN | BLUE | `0x00` | REPEAT | SPEED | +| Pattern | `0x00` | `0x06` | PTYPE | REPEAT | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | +| Productivity | `0x00` | `0x0A` | COLOR | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | +| Get Ver/Serial | `0x00` | `0x80` | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | `0x00` | + +## Notes + +1. The values for LED, COLOR, WTYPE, and PTYPE, are shown in the corresponding tables below. +1. The values for RED, GREEN, BLUE, are `0..255` and correspond to standard RGB color. +1. The value for TIME is the number of seconds to fade from the current color to the new color specified. +1. The value for SPEED is the time to cycle through the change. +1. The value of REPEAT is the number of times to repeat the wave or pattern. +1. The response data for the get version and serial number command group is described below. + +## LED values + +| Value | Addressed LED | +|--------------|--------------------| +| `0x01..0x06` | Specific LED | +| `0x41` | all back LEDs | +| `0x42` | all front LEDs | +| `0xFF` | all LEDs one color | + +The addressable LEDs on the _Flag_ USB product are as follows, shown in vertical orientation: + +| Back | Front | +|------|-------| +| 6 | 3 | +| 5 | 2 | +| 4 | 1 | + +## COLOR values + +1. For the command group _Productivity_ all the values below are valid. +1. For the command group _Simple_ the values 'E' and 'D' are not valid. + +| Letter | Value | Color | +|--------|--------|-----------| +| 'E' | `0x45` | _Enable_ | +| 'D' | `0x44` | _Disable_ | +| 'R' | `0x52` | Red | +| 'G' | `0x47` | Green | +| 'B' | `0x42` | Blue | +| 'C' | `0x43` | Cyan | +| 'M' | `0x4D` | Magenta | +| 'Y' | `0x59` | Yellow | +| 'W' | `0x57` | White | +| 'O' | `0x4F` | Off | + +## WTYPE values + +| Value | Pattern | +|--------|--------------------| +| `0x01` | Short | +| `0x02` | Long | +| `0x03` | Overlapping Short | +| `0x04` | Overlapping Long | +| `0x05` | ? | + +1. Luxafor describe wave type as a value `0x01..0x05` and yet there seems to be no description of `0x05` anywhere. + +## PTYPE values + +| Value | Pattern | Windows Only | +|--------|--------------|--------------| +| `0x00` | ? | Unknown | +| `0x01` | Luxafor/Traffic Lights | No | +| `0x02` | Random 1 | No | +| `0x03` | Random 2 | No | +| `0x04` | Random 3 | No | +| `0x05` | Police | No | +| `0x06` | Random 4 | No | +| `0x07` | Random 5 | No | +| `0x08` | Rainbow Wave | Yes | + +1. Luxafor describe pattern type as a value `0x00..0x08` and yet there seems to be no description of `0x00` anywhere. + +## Version/Serial response + +| 0 | 1 | 2 | 3 | +|--------|------------|-------------|------------| +| `0x80` | FW Version | Serial High | Serial Low | + +The serial number is returned as a pair, (high,low) bytes. + */ use crate::{Device, Pattern, SolidColor}; @@ -33,6 +135,8 @@ pub struct USBDevice<'a> { const LUXAFOR_VENDOR_ID: u16 = 0x04d8; const LUXAFOR_PRODUCT_ID: u16 = 0xf372; +const HID_REPORT_ID: u8 = 0; + const MODE_SOLID: u8 = 1; #[allow(dead_code)] const MODE_FADE: u8 = 2; @@ -66,6 +170,7 @@ 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; // ------------------------------------------------------------------------------------------------ @@ -132,14 +237,7 @@ impl<'a> Device for USBDevice<'a> { }; 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()) - } - } + self.write(&[HID_REPORT_ID, mode, LED_ALL, r, g, b]) } fn set_pattern(&self, pattern: Pattern) -> crate::error::Result<()> { @@ -154,19 +252,16 @@ impl<'a> Device for USBDevice<'a> { 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, }; - 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()) - } - } + self.write(&[HID_REPORT_ID, MODE_PATTERN, pattern, 255]) } } @@ -186,6 +281,30 @@ impl<'a> USBDevice<'a> { ); Ok(Self { hid_device, id }) } + + fn write(&self, buffer: &[u8]) -> crate::error::Result<()> { + trace!( + "writing [{:?}]", + buffer + .iter() + .map(|b| format!("{:#04x}", b)) + .collect::>() + .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::ErrorKind::InvalidRequest.into()) + } + Err(err) => { + error!("Could not write to HID device: {:?}", err); + Err(crate::error::ErrorKind::InvalidRequest.into()) + } + } + } } // ------------------------------------------------------------------------------------------------