Compare commits
10 Commits
87a16b38e6
...
cf56783e3d
Author | SHA1 | Date | |
---|---|---|---|
cf56783e3d | |||
f632d3a596 | |||
|
d535c79ebc | ||
|
c4fbc56e81 | ||
|
d09f40887c | ||
|
8e0b0267fa | ||
|
10b155ae94 | ||
|
90192b6a41 | ||
|
0e4105b669 | ||
|
7b4ebd5432 |
14
.github/dependabot.yml
vendored
Normal file
14
.github/dependabot.yml
vendored
Normal 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
15
.github/workflows/cargo-audit.yml
vendored
Normal 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 }}
|
||||||
|
|
38
.github/workflows/rust.yml
vendored
38
.github/workflows/rust.yml
vendored
@ -1,24 +1,30 @@
|
|||||||
name: Rust
|
name: Rust
|
||||||
|
|
||||||
on:
|
on: [push]
|
||||||
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: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v1
|
||||||
- name: Build
|
|
||||||
run: cargo build --verbose
|
- name: Install dependencies
|
||||||
- name: Run tests
|
run: rustup component add rustfmt
|
||||||
run: cargo test --verbose
|
|
||||||
- name: Document
|
- name: Format
|
||||||
run: cargo doc --verbose --no-deps
|
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
|
||||||
|
|
||||||
|
20
Cargo.toml
20
Cargo.toml
@ -1,15 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "luxafor"
|
name = "rofaxul"
|
||||||
description = "Library, and CLI, for Luxafor lights via either USB or webhooks."
|
description = "Fork from luxafor"
|
||||||
version = "0.2.1"
|
version = "0.1.0"
|
||||||
authors = ["Simon Johnston <johnstonskj@gmail.com>"]
|
authors = ["Simon Johnston <johnstonskj@gmail.com>", "Maxime Augier <max@xolus.net>"]
|
||||||
repository = "https://github.com/johnstonskj/rust-luxafor"
|
repository = "https://github.com/maugier/rofaxul"
|
||||||
documentation = "https://docs.rs/luxafor/0.1.0/luxafor/"
|
edition = "2021"
|
||||||
edition = "2018"
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
publish = true
|
publish = false
|
||||||
default-run = "lux"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
targets = ["x86_64-unknown-linux-gnu"]
|
||||||
@ -31,7 +29,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.11", 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 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.
|
||||||
|
@ -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(),
|
||||||
}?;
|
}?;
|
||||||
|
|
190
src/lib.rs
190
src/lib.rs
@ -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 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,no_run
|
```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,no_run
|
```rust,ignore
|
||||||
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;
|
||||||
@ -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.
|
/// A pattern the light can be set to show.
|
||||||
|
///
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
/// A preset pattern
|
/// A preset pattern that cycles between red and blue.
|
||||||
Police,
|
Police,
|
||||||
/// A preset pattern
|
/// A preset pattern that cycles between green,. yellow, and red.
|
||||||
TrafficLights,
|
TrafficLights,
|
||||||
/// A preset pattern
|
/// Preset random patterns
|
||||||
Random(u8),
|
Random(u8),
|
||||||
/// A preset pattern (accepted on Windows only)
|
/// A preset pattern
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
Rainbow,
|
Rainbow,
|
||||||
/// A preset pattern (accepted on Windows only)
|
/// A preset pattern
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
Sea,
|
Sea,
|
||||||
/// A preset pattern (accepted on Windows only)
|
/// A preset pattern
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
WhiteWave,
|
WhiteWave,
|
||||||
/// A preset pattern (accepted on Windows only)
|
/// A preset pattern
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
Synthetic,
|
Synthetic,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,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<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -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 {
|
impl Display for Pattern {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
@ -254,9 +358,13 @@ 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(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -276,14 +384,56 @@ 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
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -305,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")
|
||||||
@ -325,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);
|
||||||
|
360
src/usb_hid.rs
360
src/usb_hid.rs
@ -1,8 +1,110 @@
|
|||||||
/*!
|
/*!
|
||||||
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};
|
use crate::{Device, Pattern, SolidColor, SpecificLED, TargetedDevice, Wave};
|
||||||
use hidapi::{HidApi, HidDevice};
|
use hidapi::{HidApi, HidDevice};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -21,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -33,32 +136,32 @@ pub struct USBDevice<'a> {
|
|||||||
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;
|
||||||
|
|
||||||
#[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;
|
||||||
@ -66,6 +169,7 @@ 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;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -81,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),
|
||||||
@ -102,47 +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);
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@ -154,14 +311,99 @@ impl<'a> Device for USBDevice<'a> {
|
|||||||
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,
|
||||||
};
|
};
|
||||||
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 {
|
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) => {
|
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())
|
||||||
@ -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
|
// Unit Tests
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -207,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