Added new methods to Device, and added new TargetedDevice trait

This commit is contained in:
Simon Johnston 2020-08-19 14:42:10 -07:00
parent 0e4105b669
commit 90192b6a41
6 changed files with 457 additions and 77 deletions

View File

@ -1,7 +1,7 @@
[package] [package]
name = "luxafor" name = "luxafor"
description = "Library, and CLI, for Luxafor lights via either USB or webhooks." description = "Library, and CLI, for Luxafor lights via either USB or webhooks."
version = "0.2.1" version = "0.2.2"
authors = ["Simon Johnston <johnstonskj@gmail.com>"] authors = ["Simon Johnston <johnstonskj@gmail.com>"]
repository = "https://github.com/johnstonskj/rust-luxafor" repository = "https://github.com/johnstonskj/rust-luxafor"
documentation = "https://docs.rs/luxafor/0.1.0/luxafor/" documentation = "https://docs.rs/luxafor/0.1.0/luxafor/"
@ -31,7 +31,7 @@ log = "0.4.11"
error-chain = "0.12.2" error-chain = "0.12.2"
#[feature-dependencies] #[feature-dependencies]
hidapi = { version = "~0.4", optional = true } hidapi = { version = "1.2.3", optional = true }
pretty_env_logger = { version = "0.4.0", optional = true } pretty_env_logger = { version = "0.4.0", optional = true }
reqwest = { version = "0.10", features = ["blocking"], optional = true } reqwest = { version = "0.10", features = ["blocking"], optional = true }
structopt = { version = "0.3.14", optional = true } structopt = { version = "0.3.14", optional = true }

View File

@ -80,6 +80,15 @@ The following shows the command line tool turning the light off.
## Changes ## Changes
**Version 02.2.**
* Added HID specification documents to the `usb_hid` module.
* Added new `Wave` enum
* Added new methods to `Device`.
* Fully supported on USB HID,
* Not all are supported on webooks.
* Added new `TargetedDevice` trait and USB implementation.
**Version 0.2.1** **Version 0.2.1**
* Removed the `DeviceIdentifier` trait, and `Device` now returns a String. * Removed the `DeviceIdentifier` trait, and `Device` now returns a String.

View File

@ -3,7 +3,7 @@
extern crate log; extern crate log;
use luxafor::usb_hid::USBDeviceDiscovery; use luxafor::usb_hid::USBDeviceDiscovery;
use luxafor::{webhook, Device, Pattern, SolidColor}; use luxafor::{webhook, Device, Pattern, SolidColor, Wave};
use std::error::Error; use std::error::Error;
use structopt::StructOpt; use structopt::StructOpt;
@ -30,16 +30,56 @@ pub(crate) enum SubCommand {
#[structopt(name = "COLOR")] #[structopt(name = "COLOR")]
color: SolidColor, color: SolidColor,
}, },
/// Set the light to a to a blinking color /// Set the light to a to a strobing/blinking color
Blink { Strobe {
/// The color to set /// The color to set
#[structopt(name = "COLOR")] #[structopt(name = "COLOR")]
color: SolidColor, color: SolidColor,
/// The speed of each strobe cycle
#[structopt(long, short, default_value = "10")]
speed: u8,
/// The number of times to repeat the strobe
#[structopt(long, short, default_value = "255")]
repeat: u8,
},
/// Set the light to fade from the current to a new color
Fade {
/// The color to set
#[structopt(name = "COLOR")]
color: SolidColor,
/// The speed of each strobe cycle
#[structopt(long, short, default_value = "60")]
fade_duration: u8,
},
/// Set the light to a to a pre-defined wave pattern
Wave {
/// The color to set
#[structopt(name = "COLOR")]
color: SolidColor,
/// The pattern to set
#[structopt(default_value = "short")]
pattern: Wave,
/// The speed of each wave cycle
#[structopt(long, short, default_value = "30")]
speed: u8,
/// The number of times to repeat the pattern
#[structopt(long, short, default_value = "255")]
repeat: u8,
}, },
/// Set the light to a to a pre-defined pattern /// Set the light to a to a pre-defined pattern
Pattern { Pattern {
/// The pattern to set /// The pattern to set
pattern: Pattern, pattern: Pattern,
/// The number of times to repeat the pattern
#[structopt(long, short, default_value = "255")]
repeat: u8,
}, },
/// Turn the light off /// Turn the light off
Off, Off,
@ -74,9 +114,23 @@ fn main() -> Result<(), Box<dyn Error>> {
fn set_lights(args: CommandLine, device: impl Device) -> Result<(), Box<dyn Error>> { fn set_lights(args: CommandLine, device: impl Device) -> Result<(), Box<dyn Error>> {
match args.cmd { match args.cmd {
SubCommand::Solid { color } => device.set_solid_color(color, false), SubCommand::Solid { color } => device.set_solid_color(color),
SubCommand::Blink { color } => device.set_solid_color(color, true), SubCommand::Fade {
SubCommand::Pattern { pattern } => device.set_pattern(pattern), color,
fade_duration,
} => device.set_fade_to_color(color, fade_duration),
SubCommand::Strobe {
color,
speed,
repeat,
} => device.set_color_strobe(color, speed, repeat),
SubCommand::Wave {
color,
pattern,
speed,
repeat,
} => device.set_color_wave(color, pattern, speed, repeat),
SubCommand::Pattern { pattern, repeat } => device.set_pattern(pattern, repeat),
SubCommand::Off => device.turn_off(), SubCommand::Off => device.turn_off(),
}?; }?;

View File

@ -16,18 +16,20 @@ the use of a USB connected device.
```rust,ignore ```rust,ignore
use luxafor::usb_hid::USBDeviceDiscovery; use luxafor::usb_hid::USBDeviceDiscovery;
use luxafor::{Device, SolidColor}; use luxafor::{Device, SolidColor, TargetedDevice};
use luxafor::error::Result; use luxafor::error::Result;
fn set_do_not_disturb() -> Result<()> { fn set_do_not_disturb() -> Result<()> {
let discovery = USBDeviceDiscovery::new()?; let discovery = USBDeviceDiscovery::new()?;
let device = discovery.device()?; let device = discovery.device()?;
println!("USB device: '{}'", device.id()); println!("USB device: '{}'", device.id());
device.set_specific_led(SpecificLED::AllFront);
device.set_solid_color(SolidColor::Red, false) device.set_solid_color(SolidColor::Red, false)
} }
``` ```
The following shows the same function but using the webhook connection. The following shows the same function but using the webhook connection. Note that the webhook API
is more limited in the features it exposes; it does not support `set_specific_led` for a start.
```rust,ignore ```rust,ignore
use luxafor::webhook::new_device_for; use luxafor::webhook::new_device_for;
@ -143,8 +145,25 @@ pub enum SolidColor {
}, },
} }
///
/// Waves produce a pattern that starts at the bottom of the light, fills the light and then
/// fades out at the top.
///
#[derive(Clone, Debug)]
pub enum Wave {
/// A short transition, completed before the next wave starts.
Short,
/// A long transition, completed before the next wave starts.
Long,
/// A short transition, which _does not_ complete before the next wave starts.
OverlappingShort,
/// A long transition, which _does not_ complete before the next wave starts.
OverlappingLong,
}
/// ///
/// A pattern the light can be set to show. /// A pattern the light can be set to show.
///
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Pattern { pub enum Pattern {
/// A preset pattern that cycles between red and blue. /// A preset pattern that cycles between red and blue.
@ -182,14 +201,63 @@ pub trait Device {
fn turn_off(&self) -> error::Result<()>; fn turn_off(&self) -> error::Result<()>;
/// ///
/// Set the color, and blink status, of the light. /// Set the light to a continuous solid color.
/// ///
fn set_solid_color(&self, color: SolidColor, blink: bool) -> error::Result<()>; fn set_solid_color(&self, color: SolidColor) -> error::Result<()>;
/// ///
/// Set the pattern displayed by the light. /// Set the light to fade from its current color to a new one.
/// ///
fn set_pattern(&self, pattern: Pattern) -> error::Result<()>; fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> error::Result<()>;
///
/// Strobe the light, this will dim and brighten the same color.
///
fn set_color_strobe(
&self,
color: SolidColor,
strobe_speed: u8,
repeat_count: u8,
) -> error::Result<()>;
///
/// Set the light to repeat one of a pre-defined set of wave patterns.
///
fn set_color_wave(
&self,
color: SolidColor,
wave_pattern: Wave,
wave_speed: u8,
repeat_count: u8,
) -> error::Result<()>;
///
/// Set the light to repeat one of a pre-defined set of patterns.
///
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> error::Result<()>;
}
///
/// Denotes which LED in the light should be the target of any device operations.
///
#[derive(Clone, Debug)]
pub enum SpecificLED {
/// All supported LEDs
All,
/// Only the LEDs on the front (tab) of the light
AllFront,
/// Only the LEDs on the back of the light
AllBack,
/// Only one specific LED (value: 1..6)
Number(u8),
}
///
/// Extension trait to allow targeting specific LEDs on the device.
///
pub trait TargetedDevice: Device {
/// Set the LED to be used for future operations.
fn set_specific_led(&mut self, led: SpecificLED) -> error::Result<()>;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -249,6 +317,38 @@ impl FromStr for SolidColor {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
impl Display for Wave {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Wave::Short => "short",
Wave::Long => "long",
Wave::OverlappingShort => "overlapping short",
Wave::OverlappingLong => "overlapping long",
}
)
}
}
impl FromStr for Wave {
type Err = error::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"short" => Ok(Wave::Short),
"long" => Ok(Wave::Long),
"overlapping short" => Ok(Wave::OverlappingShort),
"overlapping long" => Ok(Wave::OverlappingLong),
_ => Err(error::ErrorKind::InvalidPattern.into()),
}
}
}
// ------------------------------------------------------------------------------------------------
impl Display for Pattern { impl Display for Pattern {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
@ -297,6 +397,43 @@ impl FromStr for Pattern {
} }
} }
// ------------------------------------------------------------------------------------------------
impl Display for SpecificLED {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
SpecificLED::All => "all".to_string(),
SpecificLED::AllFront => "front".to_string(),
SpecificLED::AllBack => "back".to_string(),
SpecificLED::Number(n) => n.to_string(),
}
)
}
}
impl FromStr for SpecificLED {
type Err = error::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"all" => Ok(SpecificLED::All),
"front" => Ok(SpecificLED::AllFront),
"back" => Ok(SpecificLED::AllBack),
"1" => Ok(SpecificLED::Number(1)),
"2" => Ok(SpecificLED::Number(2)),
"3" => Ok(SpecificLED::Number(3)),
"4" => Ok(SpecificLED::Number(4)),
"5" => Ok(SpecificLED::Number(5)),
"6" => Ok(SpecificLED::Number(6)),
_ => Err(error::ErrorKind::InvalidLED.into()),
}
}
}
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// Modules // Modules
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -318,6 +455,11 @@ pub mod error {
description("The pattern value supplied was not recognized") description("The pattern value supplied was not recognized")
display("The pattern value supplied was not recognized") display("The pattern value supplied was not recognized")
} }
#[doc("The LED number is either invalid or not supported by the connected device")]
InvalidLED {
description("The LED number is either invalid or not supported by the connected device")
display("The LED number is either invalid or not supported by the connected device")
}
#[doc("The provided device ID was incorrectly formatted")] #[doc("The provided device ID was incorrectly formatted")]
InvalidDeviceID { InvalidDeviceID {
description("The provided device ID was incorrectly formatted") description("The provided device ID was incorrectly formatted")
@ -338,6 +480,11 @@ pub mod error {
description("An unexpected HTTP error was returned") description("An unexpected HTTP error was returned")
display("An unexpected HTTP error was returned: {}", sc) display("An unexpected HTTP error was returned: {}", sc)
} }
#[doc("The command is not supported by the current device, or connection to the device")]
UnsupportedCommand {
description("The command is not supported by the current device, or connection to the device")
display("The command is not supported by the current device, or connection to the device")
}
} }
foreign_links { foreign_links {
CustomFmt(::std::num::ParseIntError); CustomFmt(::std::num::ParseIntError);

View File

@ -104,7 +104,7 @@ The serial number is returned as a pair, (high,low) bytes.
*/ */
use crate::{Device, Pattern, SolidColor}; use crate::{Device, Pattern, SolidColor, SpecificLED, TargetedDevice, Wave};
use hidapi::{HidApi, HidDevice}; use hidapi::{HidApi, HidDevice};
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -123,9 +123,10 @@ pub struct USBDeviceDiscovery {
/// The device implementation for a USB connected light. /// The device implementation for a USB connected light.
/// ///
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct USBDevice<'a> { pub struct USBDevice {
hid_device: HidDevice<'a>, hid_device: HidDevice,
id: String, id: String,
target_led: u8,
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -137,32 +138,30 @@ const LUXAFOR_PRODUCT_ID: u16 = 0xf372;
const HID_REPORT_ID: u8 = 0; const HID_REPORT_ID: u8 = 0;
const MODE_SIMPLE: u8 = 0;
const MODE_SOLID: u8 = 1; const MODE_SOLID: u8 = 1;
#[allow(dead_code)]
const MODE_FADE: u8 = 2; const MODE_FADE: u8 = 2;
const MODE_STROBE: u8 = 3; const MODE_STROBE: u8 = 3;
#[allow(dead_code)]
const MODE_WAVE: u8 = 4; const MODE_WAVE: u8 = 4;
const MODE_PATTERN: u8 = 6; const MODE_PATTERN: u8 = 6;
#[allow(dead_code)] const SIMPLE_COLOR_OFF: u8 = b'O';
const LED_FRONT_TOP: u8 = 1; const LED_FRONT_TOP: u8 = 1;
#[allow(dead_code)]
const LED_FRONT_MIDDLE: u8 = 2; const LED_FRONT_MIDDLE: u8 = 2;
#[allow(dead_code)]
const LED_FRONT_BOTTOM: u8 = 3; const LED_FRONT_BOTTOM: u8 = 3;
#[allow(dead_code)]
const LED_BACK_TOP: u8 = 4; const LED_BACK_TOP: u8 = 4;
#[allow(dead_code)]
const LED_BACK_MIDDLE: u8 = 5; const LED_BACK_MIDDLE: u8 = 5;
#[allow(dead_code)]
const LED_BACK_BOTTOM: u8 = 6; const LED_BACK_BOTTOM: u8 = 6;
#[allow(dead_code)]
const LED_FRONT_ALL: u8 = 65; const LED_FRONT_ALL: u8 = 65;
#[allow(dead_code)]
const LED_BACK_ALL: u8 = 66; const LED_BACK_ALL: u8 = 66;
const LED_ALL: u8 = 255; 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_LUXAFOR: u8 = 1;
const PATTERN_RANDOM_1: u8 = 2; const PATTERN_RANDOM_1: u8 = 2;
const PATTERN_RANDOM_2: u8 = 3; const PATTERN_RANDOM_2: u8 = 3;
@ -186,14 +185,19 @@ impl USBDeviceDiscovery {
/// Construct a new discovery object, this initializes the USB HID interface and thus can fail. /// Construct a new discovery object, this initializes the USB HID interface and thus can fail.
/// ///
pub fn new() -> crate::error::Result<Self> { pub fn new() -> crate::error::Result<Self> {
let hid_api = HidApi::new()?; match HidApi::new() {
Ok(Self { hid_api }) Ok(hid_api) => Ok(Self { hid_api }),
Err(err) => {
error!("Could not connect to USB, error: {:?}", err);
Err(crate::error::ErrorKind::DeviceNotFound.into())
}
}
} }
/// ///
/// Return a device, if found, that corresponds to a Luxafor light. /// Return a device, if found, that corresponds to a Luxafor light.
/// ///
pub fn device(&self) -> crate::error::Result<USBDevice<'_>> { pub fn device(&self) -> crate::error::Result<USBDevice> {
let result = self.hid_api.open(LUXAFOR_VENDOR_ID, LUXAFOR_PRODUCT_ID); let result = self.hid_api.open(LUXAFOR_VENDOR_ID, LUXAFOR_PRODUCT_ID);
match result { match result {
Ok(hid_device) => USBDevice::new(hid_device), Ok(hid_device) => USBDevice::new(hid_device),
@ -207,40 +211,95 @@ impl USBDeviceDiscovery {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
impl<'a> Device for USBDevice<'a> { impl Device for USBDevice {
fn id(&self) -> String { fn id(&self) -> String {
self.id.clone() self.id.clone()
} }
fn turn_off(&self) -> crate::error::Result<()> { fn turn_off(&self) -> crate::error::Result<()> {
self.set_solid_color( info!("Turning device '{}' off", self.id);
SolidColor::Custom { self.write(&[HID_REPORT_ID, MODE_SIMPLE, SIMPLE_COLOR_OFF])
red: 00,
green: 00,
blue: 00,
},
false,
)
} }
fn set_solid_color(&self, color: SolidColor, blink: bool) -> crate::error::Result<()> { fn set_solid_color(&self, color: SolidColor) -> crate::error::Result<()> {
info!("Setting the color of device '{}' to {}", self.id, color); info!("Setting the color of device '{}' to {}", self.id, color);
let (r, g, b) = match color { let (r, g, b) = self.color_to_bytes(color);
SolidColor::Red => (255, 0, 0), self.write(&[HID_REPORT_ID, MODE_SOLID, self.target_led, r, g, b])
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);
self.write(&[HID_REPORT_ID, mode, LED_ALL, r, g, b])
} }
fn set_pattern(&self, pattern: Pattern) -> crate::error::Result<()> { 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); info!("Setting the pattern of device '{}' to {}", self.id, pattern);
let pattern = match pattern { let pattern = match pattern {
Pattern::Police => PATTERN_POLICE, Pattern::Police => PATTERN_POLICE,
@ -261,25 +320,65 @@ impl<'a> Device for USBDevice<'a> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
Pattern::Synthetic => 11, Pattern::Synthetic => 11,
}; };
self.write(&[HID_REPORT_ID, MODE_PATTERN, pattern, 255]) self.write(&[HID_REPORT_ID, MODE_PATTERN, pattern, repeat_count])
} }
} }
impl<'a> USBDevice<'a> { impl TargetedDevice for USBDevice {
fn new(hid_device: HidDevice<'a>) -> crate::error::Result<USBDevice<'a>> { 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::ErrorKind::InvalidLED.into()),
},
};
Ok(())
}
}
impl USBDevice {
fn new(hid_device: HidDevice) -> crate::error::Result<USBDevice> {
let id = format!( let id = format!(
"{}::{}::{}", "{}::{}::{}",
hid_device hid_device
.get_manufacturer_string() .get_manufacturer_string()
.unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()), .unwrap_or("<unknown>".to_string()),
hid_device hid_device
.get_product_string() .get_product_string()
.unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()), .unwrap_or("<unknown>".to_string()),
hid_device hid_device
.get_serial_number_string() .get_serial_number_string()
.unwrap_or("<unknown>".to_string()) .unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()),
); );
Ok(Self { hid_device, id }) 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<()> { fn write(&self, buffer: &[u8]) -> crate::error::Result<()> {
@ -293,11 +392,17 @@ impl<'a> USBDevice<'a> {
); );
let result = self.hid_device.write(buffer); let result = self.hid_device.write(buffer);
match result { match result {
Ok(bytes_written) => if bytes_written == buffer.len() { Ok(bytes_written) => {
Ok(()) if bytes_written == buffer.len() {
} else { Ok(())
error!("Bytes written, {}, did not match buffer length {}", bytes_written, buffer.len()); } else {
Err(crate::error::ErrorKind::InvalidRequest.into()) error!(
"Bytes written, {}, did not match buffer length {}",
bytes_written,
buffer.len()
);
Err(crate::error::ErrorKind::InvalidRequest.into())
}
} }
Err(err) => { Err(err) => {
error!("Could not write to HID device: {:?}", err); error!("Could not write to HID device: {:?}", err);
@ -326,7 +431,7 @@ mod tests {
let device = result.unwrap(); let device = result.unwrap();
println!("{}", device.id()); println!("{}", device.id());
let result = device.set_solid_color(SolidColor::Green, false); let result = device.set_solid_color(SolidColor::Green);
assert!(result.is_ok()); assert!(result.is_ok());
} }
} }

View File

@ -3,7 +3,7 @@ Implementation of the Device trait for webhook connected lights.
*/ */
use crate::{Device, Pattern, SolidColor}; use crate::{Device, Pattern, SolidColor, Wave};
use reqwest::blocking::Client; use reqwest::blocking::Client;
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -51,17 +51,14 @@ impl Device for WebhookDevice {
} }
fn turn_off(&self) -> crate::error::Result<()> { fn turn_off(&self) -> crate::error::Result<()> {
self.set_solid_color( self.set_solid_color(SolidColor::Custom {
SolidColor::Custom { red: 00,
red: 00, green: 00,
green: 00, blue: 00,
blue: 00, })
},
false,
)
} }
fn set_solid_color(&self, color: SolidColor, blink: bool) -> crate::error::Result<()> { fn set_solid_color(&self, color: SolidColor) -> crate::error::Result<()> {
info!("Setting the color of device '{}' to {}", self.id, color); info!("Setting the color of device '{}' to {}", self.id, color);
let body = if let SolidColor::Custom { let body = if let SolidColor::Custom {
@ -90,13 +87,81 @@ impl Device for WebhookDevice {
.replace("COLOR", &color.to_string()) .replace("COLOR", &color.to_string())
}; };
let url = &format!("{}/{}", API_V1, if blink { "blink" } else { "solid_color" }); let url = &format!("{}/{}", API_V1, "solid_color");
send_request(url, body) send_request(url, body)
} }
fn set_pattern(&self, pattern: Pattern) -> crate::error::Result<()> { fn set_fade_to_color(
&self,
_color: SolidColor,
_fade_duration: u8,
) -> crate::error::Result<()> {
Err(crate::error::ErrorKind::UnsupportedCommand.into())
}
fn set_color_strobe(
&self,
color: SolidColor,
_strobe_speed: u8,
repeat_count: u8,
) -> crate::error::Result<()> {
info!(
"Setting the strobe color of device '{}' to {}",
self.id, color
);
let body = if let SolidColor::Custom {
red: _,
green: _,
blue: _,
} = color
{
r#"{
"userId": "DID",
"actionFields":{
"repeat": RPT,
"color": "custom",
"custom_color": "COLOR"
}
}"#
.replace("DID", &self.id.to_string())
.replace("COLOR", &color.to_string())
.replace("RPT", &repeat_count.to_string())
} else {
r#"{
"userId": "DID",
"actionFields":{
"repeat": RPT,
"color": "COLOR"
}
}"#
.replace("DID", &self.id.to_string())
.replace("COLOR", &color.to_string())
.replace("RPT", &repeat_count.to_string())
};
let url = &format!("{}/{}", API_V1, "blink");
send_request(url, body)
}
fn set_color_wave(
&self,
_color: SolidColor,
_wave_pattern: Wave,
_wave_speed: u8,
_repeat_count: u8,
) -> crate::error::Result<()> {
Err(crate::error::ErrorKind::UnsupportedCommand.into())
}
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> crate::error::Result<()> {
info!("Setting the pattern of device '{}' to {}", self.id, pattern); info!("Setting the pattern of device '{}' to {}", self.id, pattern);
warn!(
"Ignoring repeat count {}, not supported in the webhook API",
repeat_count
);
let body = r#"{ let body = r#"{
"userId": "DID", "userId": "DID",