Added new methods to Device
, and added new TargetedDevice
trait
This commit is contained in:
parent
0e4105b669
commit
90192b6a41
@ -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 }
|
||||||
|
@ -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.
|
||||||
|
@ -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(),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
159
src/lib.rs
159
src/lib.rs
@ -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);
|
||||||
|
201
src/usb_hid.rs
201
src/usb_hid.rs
@ -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,12 +392,18 @@ 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) => {
|
||||||
|
if bytes_written == buffer.len() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
error!("Bytes written, {}, did not match buffer length {}", bytes_written, buffer.len());
|
error!(
|
||||||
|
"Bytes written, {}, did not match buffer length {}",
|
||||||
|
bytes_written,
|
||||||
|
buffer.len()
|
||||||
|
);
|
||||||
Err(crate::error::ErrorKind::InvalidRequest.into())
|
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);
|
||||||
Err(crate::error::ErrorKind::InvalidRequest.into())
|
Err(crate::error::ErrorKind::InvalidRequest.into())
|
||||||
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user