Compare commits

..

10 Commits

Author SHA1 Message Date
cf56783e3d Fork info 2024-01-19 20:59:45 +01:00
f632d3a596 Rename executable entry point 2024-01-19 20:59:07 +01:00
Simon Johnston
d535c79ebc
Merge pull request #1 from johnstonskj/dependabot/cargo/reqwest-0.11
cargo prod(deps): update reqwest requirement from 0.10 to 0.11
2021-01-05 22:45:12 -08:00
dependabot[bot]
c4fbc56e81
cargo prod(deps): update reqwest requirement from 0.10 to 0.11
Updates the requirements on [reqwest](https://github.com/seanmonstar/reqwest) to permit the latest version.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.10.0...v0.11.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 06:20:54 +00:00
Simon Johnston
d09f40887c
Add files via upload 2020-10-28 11:24:09 -07:00
Simon Johnston
8e0b0267fa
Add files via upload 2020-10-28 11:23:43 -07:00
Simon Johnston
10b155ae94 Fixed version string typo 2020-08-19 14:43:12 -07:00
Simon Johnston
90192b6a41 Added new methods to Device, and added new TargetedDevice trait 2020-08-19 14:42:10 -07:00
Simon Johnston
0e4105b669 Added HID documentation and added cfg(windows) to the relevant patterns. 2020-08-19 12:40:04 -07:00
Simon Johnston
7b4ebd5432
no_run -> ignore for examples 2020-08-18 13:08:44 -07:00
9 changed files with 673 additions and 128 deletions

14
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,14 @@
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

15
.github/workflows/cargo-audit.yml vendored Normal file
View File

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

View File

@ -1,15 +1,13 @@
[package]
name = "luxafor"
description = "Library, and CLI, for Luxafor lights via either USB or webhooks."
version = "0.2.1"
authors = ["Simon Johnston <johnstonskj@gmail.com>"]
repository = "https://github.com/johnstonskj/rust-luxafor"
documentation = "https://docs.rs/luxafor/0.1.0/luxafor/"
edition = "2018"
name = "rofaxul"
description = "Fork from luxafor"
version = "0.1.0"
authors = ["Simon Johnston <johnstonskj@gmail.com>", "Maxime Augier <max@xolus.net>"]
repository = "https://github.com/maugier/rofaxul"
edition = "2021"
license = "MIT"
readme = "README.md"
publish = true
default-run = "lux"
publish = false
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
@ -31,7 +29,7 @@ log = "0.4.11"
error-chain = "0.12.2"
#[feature-dependencies]
hidapi = { version = "~0.4", optional = true }
hidapi = { version = "1.2.3", optional = true }
pretty_env_logger = { version = "0.4.0", optional = true }
reqwest = { version = "0.10", features = ["blocking"], optional = true }
reqwest = { version = "0.11", features = ["blocking"], 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
**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**
* Removed the `DeviceIdentifier` trait, and `Device` now returns a String.

View File

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

View File

@ -14,22 +14,24 @@ 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 use of a USB connected device.
```rust,no_run
```rust,ignore
use luxafor::usb_hid::USBDeviceDiscovery;
use luxafor::{Device, SolidColor};
use luxafor::{Device, SolidColor, TargetedDevice};
use luxafor::error::Result;
fn set_do_not_disturb() -> Result<()> {
let discovery = USBDeviceDiscovery::new()?;
let device = discovery.device()?;
println!("USB device: '{}'", device.id());
device.set_specific_led(SpecificLED::AllFront);
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,no_run
```rust,ignore
use luxafor::webhook::new_device_for;
use luxafor::{Device, SolidColor};
use luxafor::error::Result;
@ -143,23 +145,44 @@ 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.
///
#[derive(Clone, Debug)]
pub enum Pattern {
/// A preset pattern
/// A preset pattern that cycles between red and blue.
Police,
/// A preset pattern
/// A preset pattern that cycles between green,. yellow, and red.
TrafficLights,
/// A preset pattern
/// Preset random patterns
Random(u8),
/// A preset pattern (accepted on Windows only)
/// A preset pattern
#[cfg(target_os = "windows")]
Rainbow,
/// A preset pattern (accepted on Windows only)
/// A preset pattern
#[cfg(target_os = "windows")]
Sea,
/// A preset pattern (accepted on Windows only)
/// A preset pattern
#[cfg(target_os = "windows")]
WhiteWave,
/// A preset pattern (accepted on Windows only)
/// A preset pattern
#[cfg(target_os = "windows")]
Synthetic,
}
@ -178,14 +201,63 @@ pub trait Device {
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<()>;
}
// ------------------------------------------------------------------------------------------------
@ -245,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 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
@ -254,9 +358,13 @@ impl Display for Pattern {
Pattern::Police => "police".to_string(),
Pattern::TrafficLights => "traffic lights".to_string(),
Pattern::Random(n) => format!("random {}", n),
#[cfg(target_os = "windows")]
Pattern::Rainbow => "rainbow".to_string(),
#[cfg(target_os = "windows")]
Pattern::Sea => "sea".to_string(),
#[cfg(target_os = "windows")]
Pattern::WhiteWave => "white wave".to_string(),
#[cfg(target_os = "windows")]
Pattern::Synthetic => "synthetic".to_string(),
}
)
@ -276,14 +384,56 @@ impl FromStr for Pattern {
"random 3" => Ok(Pattern::Random(3)),
"random 4" => Ok(Pattern::Random(4)),
"random 5" => Ok(Pattern::Random(5)),
#[cfg(target_os = "windows")]
"rainbow" => Ok(Pattern::Rainbow),
#[cfg(target_os = "windows")]
"sea" => Ok(Pattern::Sea),
#[cfg(target_os = "windows")]
"white wave" => Ok(Pattern::WhiteWave),
#[cfg(target_os = "windows")]
"synthetic" => Ok(Pattern::Synthetic),
_ => Err(error::ErrorKind::InvalidPattern.into()),
}
}
}
// ------------------------------------------------------------------------------------------------
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
// ------------------------------------------------------------------------------------------------
@ -305,6 +455,11 @@ pub mod error {
description("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")]
InvalidDeviceID {
description("The provided device ID was incorrectly formatted")
@ -325,6 +480,11 @@ pub mod error {
description("An unexpected HTTP error was returned")
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 {
CustomFmt(::std::num::ParseIntError);

View File

@ -1,8 +1,110 @@
/*!
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};
use crate::{Device, Pattern, SolidColor, SpecificLED, TargetedDevice, Wave};
use hidapi::{HidApi, HidDevice};
// ------------------------------------------------------------------------------------------------
@ -21,9 +123,10 @@ pub struct USBDeviceDiscovery {
/// The device implementation for a USB connected light.
///
#[allow(missing_debug_implementations)]
pub struct USBDevice<'a> {
hid_device: HidDevice<'a>,
pub struct USBDevice {
hid_device: HidDevice,
id: String,
target_led: u8,
}
// ------------------------------------------------------------------------------------------------
@ -33,32 +136,32 @@ pub struct USBDevice<'a> {
const LUXAFOR_VENDOR_ID: u16 = 0x04d8;
const LUXAFOR_PRODUCT_ID: u16 = 0xf372;
const HID_REPORT_ID: u8 = 0;
const MODE_SIMPLE: u8 = 0;
const MODE_SOLID: u8 = 1;
#[allow(dead_code)]
const MODE_FADE: u8 = 2;
const MODE_STROBE: u8 = 3;
#[allow(dead_code)]
const MODE_WAVE: u8 = 4;
const MODE_PATTERN: u8 = 6;
#[allow(dead_code)]
const SIMPLE_COLOR_OFF: u8 = b'O';
const LED_FRONT_TOP: u8 = 1;
#[allow(dead_code)]
const LED_FRONT_MIDDLE: u8 = 2;
#[allow(dead_code)]
const LED_FRONT_BOTTOM: u8 = 3;
#[allow(dead_code)]
const LED_BACK_TOP: u8 = 4;
#[allow(dead_code)]
const LED_BACK_MIDDLE: u8 = 5;
#[allow(dead_code)]
const LED_BACK_BOTTOM: u8 = 6;
#[allow(dead_code)]
const LED_FRONT_ALL: u8 = 65;
#[allow(dead_code)]
const LED_BACK_ALL: u8 = 66;
const LED_ALL: u8 = 255;
const 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_RANDOM_1: u8 = 2;
const PATTERN_RANDOM_2: u8 = 3;
@ -66,6 +169,7 @@ const PATTERN_RANDOM_3: u8 = 4;
const PATTERN_RANDOM_4: u8 = 6;
const PATTERN_RANDOM_5: u8 = 7;
const PATTERN_POLICE: u8 = 5;
#[cfg(target_os = "windows")]
const PATTERN_RAINBOW_WAVE: u8 = 8;
// ------------------------------------------------------------------------------------------------
@ -81,14 +185,19 @@ impl USBDeviceDiscovery {
/// Construct a new discovery object, this initializes the USB HID interface and thus can fail.
///
pub fn new() -> crate::error::Result<Self> {
let hid_api = HidApi::new()?;
Ok(Self { hid_api })
match HidApi::new() {
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.
///
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);
match result {
Ok(hid_device) => USBDevice::new(hid_device),
@ -102,47 +211,95 @@ impl USBDeviceDiscovery {
// ------------------------------------------------------------------------------------------------
impl<'a> Device for USBDevice<'a> {
impl Device for USBDevice {
fn id(&self) -> String {
self.id.clone()
}
fn turn_off(&self) -> crate::error::Result<()> {
self.set_solid_color(
SolidColor::Custom {
red: 00,
green: 00,
blue: 00,
},
false,
)
info!("Turning device '{}' off", self.id);
self.write(&[HID_REPORT_ID, MODE_SIMPLE, SIMPLE_COLOR_OFF])
}
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);
let (r, g, b) = match color {
SolidColor::Red => (255, 0, 0),
SolidColor::Green => (0, 255, 0),
SolidColor::Yellow => (255, 255, 0),
SolidColor::Blue => (0, 0, 255),
SolidColor::White => (255, 255, 255),
SolidColor::Cyan => (0, 255, 255),
SolidColor::Magenta => (255, 0, 255),
SolidColor::Custom { red, green, blue } => (red, green, blue),
};
let mode = if blink { MODE_STROBE } else { MODE_SOLID };
trace!("{} ({:#04x},{:#04x},{:#04x})", mode, r, g, b);
let result = self.hid_device.write(&[mode, LED_ALL, r, g, b]);
match result {
Ok(_) => Ok(()),
Err(err) => {
error!("Could not write to HID device: {:?}", err);
Err(crate::error::ErrorKind::InvalidRequest.into())
}
}
let (r, g, b) = self.color_to_bytes(color);
self.write(&[HID_REPORT_ID, MODE_SOLID, self.target_led, 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);
let pattern = match pattern {
Pattern::Police => PATTERN_POLICE,
@ -154,14 +311,99 @@ impl<'a> Device for USBDevice<'a> {
4 => PATTERN_RANDOM_4,
_ => PATTERN_RANDOM_5,
},
#[cfg(target_os = "windows")]
Pattern::Rainbow => PATTERN_RAINBOW_WAVE,
#[cfg(target_os = "windows")]
Pattern::Sea => 9,
#[cfg(target_os = "windows")]
Pattern::WhiteWave => 10,
#[cfg(target_os = "windows")]
Pattern::Synthetic => 11,
};
let result = self.hid_device.write(&[MODE_PATTERN, LED_ALL, pattern]);
self.write(&[HID_REPORT_ID, MODE_PATTERN, pattern, repeat_count])
}
}
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 {
Ok(_) => Ok(()),
Ok(bytes_written) => {
if bytes_written == buffer.len() {
Ok(())
} else {
error!(
"Bytes written, {}, did not match buffer length {}",
bytes_written,
buffer.len()
);
Err(crate::error::ErrorKind::InvalidRequest.into())
}
}
Err(err) => {
error!("Could not write to HID device: {:?}", err);
Err(crate::error::ErrorKind::InvalidRequest.into())
@ -170,24 +412,6 @@ impl<'a> Device for USBDevice<'a> {
}
}
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
// ------------------------------------------------------------------------------------------------
@ -207,7 +431,7 @@ mod tests {
let device = result.unwrap();
println!("{}", device.id());
let result = device.set_solid_color(SolidColor::Green, false);
let result = device.set_solid_color(SolidColor::Green);
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;
// ------------------------------------------------------------------------------------------------
@ -51,17 +51,14 @@ impl Device for WebhookDevice {
}
fn turn_off(&self) -> crate::error::Result<()> {
self.set_solid_color(
SolidColor::Custom {
red: 00,
green: 00,
blue: 00,
},
false,
)
self.set_solid_color(SolidColor::Custom {
red: 00,
green: 00,
blue: 00,
})
}
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);
let body = if let SolidColor::Custom {
@ -90,13 +87,81 @@ impl Device for WebhookDevice {
.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)
}
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);
warn!(
"Ignoring repeat count {}, not supported in the webhook API",
repeat_count
);
let body = r#"{
"userId": "DID",