Compare commits

..

No commits in common. "cf56783e3dc668344a54ae86033b4f497fe2cbfe" and "87a16b38e6858a0aae04d9351d77ab2adc3a6538" have entirely different histories.

9 changed files with 127 additions and 672 deletions

View File

@ -1,14 +0,0 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
commit-message:
prefix: "cargo prod"
prefix-development: "cargo dev"
include: "scope"
reviewers:
- johnstonskj

View File

@ -1,15 +0,0 @@
name: Security audit
on:
push:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,30 +1,24 @@
name: Rust name: Rust
on: [push] on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
CARGO_TERM_COLOR: always
jobs: jobs:
build: build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Install dependencies
run: rustup component add rustfmt
- name: Format
run: cargo fmt -- --check
- name: Build - name: Build
run: cargo build --verbose run: cargo build --verbose
- name: Run tests - name: Run tests
run: cargo test --all-features --verbose run: cargo test --verbose
- name: Document
- name: Docs run: cargo doc --verbose --no-deps
run: cargo doc --no-deps

View File

@ -1,13 +1,15 @@
[package] [package]
name = "rofaxul" name = "luxafor"
description = "Fork from luxafor" description = "Library, and CLI, for Luxafor lights via either USB or webhooks."
version = "0.1.0" version = "0.2.1"
authors = ["Simon Johnston <johnstonskj@gmail.com>", "Maxime Augier <max@xolus.net>"] authors = ["Simon Johnston <johnstonskj@gmail.com>"]
repository = "https://github.com/maugier/rofaxul" repository = "https://github.com/johnstonskj/rust-luxafor"
edition = "2021" documentation = "https://docs.rs/luxafor/0.1.0/luxafor/"
edition = "2018"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
publish = false publish = true
default-run = "lux"
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]
@ -29,7 +31,7 @@ log = "0.4.11"
error-chain = "0.12.2" error-chain = "0.12.2"
#[feature-dependencies] #[feature-dependencies]
hidapi = { version = "1.2.3", optional = true } hidapi = { version = "~0.4", optional = true }
pretty_env_logger = { version = "0.4.0", optional = true } pretty_env_logger = { version = "0.4.0", optional = true }
reqwest = { version = "0.11", 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,15 +80,6 @@ The following shows the command line tool turning the light off.
## Changes ## Changes
**Version 0.2.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, Wave}; use luxafor::{webhook, Device, Pattern, SolidColor};
use std::error::Error; use std::error::Error;
use structopt::StructOpt; use structopt::StructOpt;
@ -30,56 +30,16 @@ pub(crate) enum SubCommand {
#[structopt(name = "COLOR")] #[structopt(name = "COLOR")]
color: SolidColor, color: SolidColor,
}, },
/// Set the light to a to a strobing/blinking color /// Set the light to a to a blinking color
Strobe { Blink {
/// 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,
@ -114,23 +74,9 @@ 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), SubCommand::Solid { color } => device.set_solid_color(color, false),
SubCommand::Fade { SubCommand::Blink { color } => device.set_solid_color(color, true),
color, SubCommand::Pattern { pattern } => device.set_pattern(pattern),
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

@ -14,24 +14,22 @@ for the manipulation of the light state.
The following example shows a function that sets the light to a solid red color. It demonstrates The following example shows a function that sets the light to a solid red color. It demonstrates
the use of a USB connected device. the use of a USB connected device.
```rust,ignore ```rust,no_run
use luxafor::usb_hid::USBDeviceDiscovery; use luxafor::usb_hid::USBDeviceDiscovery;
use luxafor::{Device, SolidColor, TargetedDevice}; use luxafor::{Device, SolidColor};
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. Note that the webhook API The following shows the same function but using the webhook connection.
is more limited in the features it exposes; it does not support `set_specific_led` for a start.
```rust,ignore ```rust,no_run
use luxafor::webhook::new_device_for; use luxafor::webhook::new_device_for;
use luxafor::{Device, SolidColor}; use luxafor::{Device, SolidColor};
use luxafor::error::Result; use luxafor::error::Result;
@ -145,44 +143,23 @@ 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
Police, Police,
/// A preset pattern that cycles between green,. yellow, and red. /// A preset pattern
TrafficLights, TrafficLights,
/// Preset random patterns /// A preset pattern
Random(u8), Random(u8),
/// A preset pattern /// A preset pattern (accepted on Windows only)
#[cfg(target_os = "windows")]
Rainbow, Rainbow,
/// A preset pattern /// A preset pattern (accepted on Windows only)
#[cfg(target_os = "windows")]
Sea, Sea,
/// A preset pattern /// A preset pattern (accepted on Windows only)
#[cfg(target_os = "windows")]
WhiteWave, WhiteWave,
/// A preset pattern /// A preset pattern (accepted on Windows only)
#[cfg(target_os = "windows")]
Synthetic, Synthetic,
} }
@ -201,63 +178,14 @@ pub trait Device {
fn turn_off(&self) -> error::Result<()>; fn turn_off(&self) -> error::Result<()>;
/// ///
/// Set the light to a continuous solid color. /// Set the color, and blink status, of the light.
/// ///
fn set_solid_color(&self, color: SolidColor) -> error::Result<()>; fn set_solid_color(&self, color: SolidColor, blink: bool) -> error::Result<()>;
/// ///
/// Set the light to fade from its current color to a new one. /// Set the pattern displayed by the light.
/// ///
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> error::Result<()>; fn set_pattern(&self, pattern: Pattern) -> 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<()>;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -317,38 +245,6 @@ 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!(
@ -358,13 +254,9 @@ impl Display for Pattern {
Pattern::Police => "police".to_string(), Pattern::Police => "police".to_string(),
Pattern::TrafficLights => "traffic lights".to_string(), Pattern::TrafficLights => "traffic lights".to_string(),
Pattern::Random(n) => format!("random {}", n), Pattern::Random(n) => format!("random {}", n),
#[cfg(target_os = "windows")]
Pattern::Rainbow => "rainbow".to_string(), Pattern::Rainbow => "rainbow".to_string(),
#[cfg(target_os = "windows")]
Pattern::Sea => "sea".to_string(), Pattern::Sea => "sea".to_string(),
#[cfg(target_os = "windows")]
Pattern::WhiteWave => "white wave".to_string(), Pattern::WhiteWave => "white wave".to_string(),
#[cfg(target_os = "windows")]
Pattern::Synthetic => "synthetic".to_string(), Pattern::Synthetic => "synthetic".to_string(),
} }
) )
@ -384,56 +276,14 @@ impl FromStr for Pattern {
"random 3" => Ok(Pattern::Random(3)), "random 3" => Ok(Pattern::Random(3)),
"random 4" => Ok(Pattern::Random(4)), "random 4" => Ok(Pattern::Random(4)),
"random 5" => Ok(Pattern::Random(5)), "random 5" => Ok(Pattern::Random(5)),
#[cfg(target_os = "windows")]
"rainbow" => Ok(Pattern::Rainbow),
#[cfg(target_os = "windows")]
"sea" => Ok(Pattern::Sea), "sea" => Ok(Pattern::Sea),
#[cfg(target_os = "windows")]
"white wave" => Ok(Pattern::WhiteWave), "white wave" => Ok(Pattern::WhiteWave),
#[cfg(target_os = "windows")]
"synthetic" => Ok(Pattern::Synthetic), "synthetic" => Ok(Pattern::Synthetic),
_ => Err(error::ErrorKind::InvalidPattern.into()), _ => Err(error::ErrorKind::InvalidPattern.into()),
} }
} }
} }
// ------------------------------------------------------------------------------------------------
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
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -455,11 +305,6 @@ 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")
@ -480,11 +325,6 @@ 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

@ -1,110 +1,8 @@
/*! /*!
Implementation of the Device trait for USB connected lights. 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, SpecificLED, TargetedDevice, Wave}; use crate::{Device, Pattern, SolidColor};
use hidapi::{HidApi, HidDevice}; use hidapi::{HidApi, HidDevice};
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -123,10 +21,9 @@ 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 { pub struct USBDevice<'a> {
hid_device: HidDevice, hid_device: HidDevice<'a>,
id: String, id: String,
target_led: u8,
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -136,32 +33,32 @@ pub struct USBDevice {
const LUXAFOR_VENDOR_ID: u16 = 0x04d8; const LUXAFOR_VENDOR_ID: u16 = 0x04d8;
const LUXAFOR_PRODUCT_ID: u16 = 0xf372; const LUXAFOR_PRODUCT_ID: u16 = 0xf372;
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;
const SIMPLE_COLOR_OFF: u8 = b'O'; #[allow(dead_code)]
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;
@ -169,7 +66,6 @@ const PATTERN_RANDOM_3: u8 = 4;
const PATTERN_RANDOM_4: u8 = 6; const PATTERN_RANDOM_4: u8 = 6;
const PATTERN_RANDOM_5: u8 = 7; const PATTERN_RANDOM_5: u8 = 7;
const PATTERN_POLICE: u8 = 5; const PATTERN_POLICE: u8 = 5;
#[cfg(target_os = "windows")]
const PATTERN_RAINBOW_WAVE: u8 = 8; const PATTERN_RAINBOW_WAVE: u8 = 8;
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -185,19 +81,14 @@ 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> {
match HidApi::new() { let hid_api = HidApi::new()?;
Ok(hid_api) => Ok(Self { 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),
@ -211,95 +102,47 @@ impl USBDeviceDiscovery {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
impl Device for USBDevice { impl<'a> Device for USBDevice<'a> {
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<()> {
info!("Turning device '{}' off", self.id); self.set_solid_color(
self.write(&[HID_REPORT_ID, MODE_SIMPLE, SIMPLE_COLOR_OFF]) SolidColor::Custom {
red: 00,
green: 00,
blue: 00,
},
false,
)
} }
fn set_solid_color(&self, color: SolidColor) -> crate::error::Result<()> { fn set_solid_color(&self, color: SolidColor, blink: bool) -> 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) = self.color_to_bytes(color); let (r, g, b) = match color {
self.write(&[HID_REPORT_ID, MODE_SOLID, self.target_led, r, g, b]) SolidColor::Red => (255, 0, 0),
} SolidColor::Green => (0, 255, 0),
SolidColor::Yellow => (255, 255, 0),
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> crate::error::Result<()> { SolidColor::Blue => (0, 0, 255),
info!( SolidColor::White => (255, 255, 255),
"Setting the fade-to color of device '{}' to {}, over {}", SolidColor::Cyan => (0, 255, 255),
self.id, color, fade_duration SolidColor::Magenta => (255, 0, 255),
); SolidColor::Custom { red, green, blue } => (red, green, blue),
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); let mode = if blink { MODE_STROBE } else { MODE_SOLID };
self.write(&[ trace!("{} ({:#04x},{:#04x},{:#04x})", mode, r, g, b);
HID_REPORT_ID, let result = self.hid_device.write(&[mode, LED_ALL, r, g, b]);
MODE_WAVE, match result {
wave_pattern, Ok(_) => Ok(()),
r, Err(err) => {
g, error!("Could not write to HID device: {:?}", err);
b, Err(crate::error::ErrorKind::InvalidRequest.into())
0x00, }
repeat_count, }
wave_speed,
])
} }
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> crate::error::Result<()> { fn set_pattern(&self, pattern: Pattern) -> 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,
@ -311,99 +154,14 @@ impl Device for USBDevice {
4 => PATTERN_RANDOM_4, 4 => PATTERN_RANDOM_4,
_ => PATTERN_RANDOM_5, _ => PATTERN_RANDOM_5,
}, },
#[cfg(target_os = "windows")]
Pattern::Rainbow => PATTERN_RAINBOW_WAVE, Pattern::Rainbow => PATTERN_RAINBOW_WAVE,
#[cfg(target_os = "windows")]
Pattern::Sea => 9, Pattern::Sea => 9,
#[cfg(target_os = "windows")]
Pattern::WhiteWave => 10, Pattern::WhiteWave => 10,
#[cfg(target_os = "windows")]
Pattern::Synthetic => 11, Pattern::Synthetic => 11,
}; };
self.write(&[HID_REPORT_ID, MODE_PATTERN, pattern, repeat_count]) let result = self.hid_device.write(&[MODE_PATTERN, LED_ALL, pattern]);
}
}
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::ErrorKind::InvalidLED.into()),
},
};
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 { match result {
Ok(bytes_written) => { Ok(_) => Ok(()),
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) => { 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())
@ -412,6 +170,24 @@ impl USBDevice {
} }
} }
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 // Unit Tests
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -431,7 +207,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); let result = device.set_solid_color(SolidColor::Green, false);
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, Wave}; use crate::{Device, Pattern, SolidColor};
use reqwest::blocking::Client; use reqwest::blocking::Client;
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -51,14 +51,17 @@ impl Device for WebhookDevice {
} }
fn turn_off(&self) -> crate::error::Result<()> { fn turn_off(&self) -> crate::error::Result<()> {
self.set_solid_color(SolidColor::Custom { self.set_solid_color(
SolidColor::Custom {
red: 00, red: 00,
green: 00, green: 00,
blue: 00, blue: 00,
}) },
false,
)
} }
fn set_solid_color(&self, color: SolidColor) -> crate::error::Result<()> { fn set_solid_color(&self, color: SolidColor, blink: bool) -> 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 {
@ -87,81 +90,13 @@ impl Device for WebhookDevice {
.replace("COLOR", &color.to_string()) .replace("COLOR", &color.to_string())
}; };
let url = &format!("{}/{}", API_V1, "solid_color"); let url = &format!("{}/{}", API_V1, if blink { "blink" } else { "solid_color" });
send_request(url, body) send_request(url, body)
} }
fn set_fade_to_color( fn set_pattern(&self, pattern: Pattern) -> crate::error::Result<()> {
&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",