Work in progress
This commit is contained in:
parent
cf56783e3d
commit
22da3e03f3
14
Cargo.toml
14
Cargo.toml
@ -14,10 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
all-features = true
|
||||
|
||||
[features]
|
||||
default = ["webhook"]
|
||||
usb = ["hidapi"]
|
||||
webhook = ["reqwest"]
|
||||
command-line = ["pretty_env_logger", "structopt", "usb", "webhook"]
|
||||
webhook = ["ureq"]
|
||||
|
||||
[[bin]]
|
||||
name = "lux"
|
||||
@ -26,10 +24,10 @@ required-features = ["command-line"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.11"
|
||||
error-chain = "0.12.2"
|
||||
thiserror = "1.0.56"
|
||||
|
||||
|
||||
#[feature-dependencies]
|
||||
hidapi = { version = "1.2.3", optional = true }
|
||||
pretty_env_logger = { version = "0.4.0", optional = true }
|
||||
reqwest = { version = "0.11", features = ["blocking"], optional = true }
|
||||
structopt = { version = "0.3.14", optional = true }
|
||||
hidapi = { version = "2.4.1", optional = true }
|
||||
ureq = { version = "2.9.1", optional = true }
|
||||
clap = { version = "4.4.18", optional = true, features = ["derive"] }
|
||||
|
181
src/lib.rs
181
src/lib.rs
@ -101,12 +101,7 @@ The following shows the how to set USB connected lights.
|
||||
unused_results,
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use thiserror::Error;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
@ -198,17 +193,17 @@ pub trait Device {
|
||||
///
|
||||
/// Turn the light off.
|
||||
///
|
||||
fn turn_off(&self) -> error::Result<()>;
|
||||
fn turn_off(&self) -> Result<(),()>;
|
||||
|
||||
///
|
||||
/// Set the light to a continuous solid color.
|
||||
///
|
||||
fn set_solid_color(&self, color: SolidColor) -> error::Result<()>;
|
||||
fn set_solid_color(&self, color: SolidColor) -> Result<(),()>;
|
||||
|
||||
///
|
||||
/// Set the light to fade from its current color to a new one.
|
||||
///
|
||||
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> error::Result<()>;
|
||||
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> Result<(),()>;
|
||||
|
||||
///
|
||||
/// Strobe the light, this will dim and brighten the same color.
|
||||
@ -218,7 +213,7 @@ pub trait Device {
|
||||
color: SolidColor,
|
||||
strobe_speed: u8,
|
||||
repeat_count: u8,
|
||||
) -> error::Result<()>;
|
||||
) -> Result<(),()>;
|
||||
|
||||
///
|
||||
/// Set the light to repeat one of a pre-defined set of wave patterns.
|
||||
@ -229,12 +224,12 @@ pub trait Device {
|
||||
wave_pattern: Wave,
|
||||
wave_speed: u8,
|
||||
repeat_count: u8,
|
||||
) -> error::Result<()>;
|
||||
) -> 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<()>;
|
||||
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> Result<(),()>;
|
||||
}
|
||||
|
||||
///
|
||||
@ -252,12 +247,16 @@ pub enum SpecificLED {
|
||||
Number(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug,Error)]
|
||||
#[error("invalid led id {0}")]
|
||||
pub struct InvalidLed(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<()>;
|
||||
fn set_specific_led(&mut self, led: SpecificLED) -> Result<(),InvalidLed>;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@ -287,31 +286,33 @@ impl Display for SolidColor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Error)]
|
||||
#[error("invalid color string `{0}`")]
|
||||
struct InvalidColor(String);
|
||||
|
||||
impl FromStr for SolidColor {
|
||||
type Err = error::Error;
|
||||
type Err = InvalidColor;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"red" => Ok(SolidColor::Red),
|
||||
"green" => Ok(SolidColor::Green),
|
||||
"yellow" => Ok(SolidColor::Yellow),
|
||||
"blue" => Ok(SolidColor::Blue),
|
||||
"white" => Ok(SolidColor::White),
|
||||
"cyan" => Ok(SolidColor::Cyan),
|
||||
"magenta" => Ok(SolidColor::Magenta),
|
||||
_ => {
|
||||
if s.len() == 6 && s.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
Ok(SolidColor::Custom {
|
||||
red: u8::from_str_radix(&s[0..1], 16)?,
|
||||
green: u8::from_str_radix(&s[2..3], 16)?,
|
||||
blue: u8::from_str_radix(&s[4..5], 16)?,
|
||||
})
|
||||
} else {
|
||||
Err(error::ErrorKind::InvalidColor.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
let solid_color = match &*s {
|
||||
"red" => SolidColor::Red,
|
||||
"green" => SolidColor::Green,
|
||||
"yellow" => SolidColor::Yellow,
|
||||
"blue" => SolidColor::Blue,
|
||||
"white" => SolidColor::White,
|
||||
"cyan" => SolidColor::Cyan,
|
||||
"magenta" => SolidColor::Magenta,
|
||||
|
||||
hex if hex.len() == 6 && hex.chars().all(|c| c.is_ascii_hexdigit()) =>
|
||||
SolidColor::Custom {
|
||||
red: u8::from_str_radix(&s[0..1], 16).unwrap(),
|
||||
green: u8::from_str_radix(&s[2..3], 16).unwrap(),
|
||||
blue: u8::from_str_radix(&s[4..5], 16).unwrap(),
|
||||
},
|
||||
_ => return Err(InvalidColor(s))
|
||||
};
|
||||
Ok(solid_color)
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,8 +333,12 @@ impl Display for Wave {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("invalid pattern `{0}`")]
|
||||
struct InvalidPattern(String);
|
||||
|
||||
impl FromStr for Wave {
|
||||
type Err = error::Error;
|
||||
type Err = InvalidPattern;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
@ -342,7 +347,7 @@ impl FromStr for Wave {
|
||||
"long" => Ok(Wave::Long),
|
||||
"overlapping short" => Ok(Wave::OverlappingShort),
|
||||
"overlapping long" => Ok(Wave::OverlappingLong),
|
||||
_ => Err(error::ErrorKind::InvalidPattern.into()),
|
||||
_ => Err(InvalidPattern(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -372,7 +377,7 @@ impl Display for Pattern {
|
||||
}
|
||||
|
||||
impl FromStr for Pattern {
|
||||
type Err = error::Error;
|
||||
type Err = InvalidPattern;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
@ -392,7 +397,7 @@ impl FromStr for Pattern {
|
||||
"white wave" => Ok(Pattern::WhiteWave),
|
||||
#[cfg(target_os = "windows")]
|
||||
"synthetic" => Ok(Pattern::Synthetic),
|
||||
_ => Err(error::ErrorKind::InvalidPattern.into()),
|
||||
_ => Err(InvalidPattern(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -414,8 +419,16 @@ impl Display for SpecificLED {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Error)]
|
||||
pub enum ParseLedError {
|
||||
#[error("parse error: {0}")]
|
||||
ParseIntError(#[from] std::num::ParseIntError),
|
||||
#[error("invalid led: {0}")]
|
||||
InvalidLed(#[from] InvalidLed),
|
||||
}
|
||||
|
||||
impl FromStr for SpecificLED {
|
||||
type Err = error::Error;
|
||||
type Err = ParseLedError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
@ -423,13 +436,14 @@ impl FromStr for SpecificLED {
|
||||
"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()),
|
||||
num => {
|
||||
match num.parse()? {
|
||||
n@1..=6 => Ok(SpecificLED::Number(n)),
|
||||
invalid => Err(ParseLedError::InvalidLed(
|
||||
InvalidLed(invalid)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -443,57 +457,34 @@ impl FromStr for SpecificLED {
|
||||
///
|
||||
#[allow(missing_docs)]
|
||||
pub mod error {
|
||||
error_chain! {
|
||||
errors {
|
||||
#[doc("The color value supplied was not recognized")]
|
||||
InvalidColor {
|
||||
description("The color value supplied was not recognized")
|
||||
display("The color value supplied was not recognized")
|
||||
}
|
||||
#[doc("The pattern value supplied was not recognized")]
|
||||
InvalidPattern {
|
||||
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")
|
||||
display("The provided device ID was incorrectly formatted")
|
||||
}
|
||||
#[doc("No device was discovered, or the ID did not resolve to a device")]
|
||||
DeviceNotFound {
|
||||
description("No device was discovered, or the ID did not resolve to a device")
|
||||
display("No device was discovered, or the ID did not resolve to a device")
|
||||
}
|
||||
#[doc("The server indicated an invalid request")]
|
||||
InvalidRequest {
|
||||
description("The server indicated an invalid request")
|
||||
display("The server indicated an invalid request")
|
||||
}
|
||||
#[doc("An unexpected HTTP error was returned")]
|
||||
UnexpectedError(sc: u16) {
|
||||
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);
|
||||
Request(::reqwest::Error);
|
||||
Fmt(::std::fmt::Error);
|
||||
}
|
||||
}
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("invalid device ID")]
|
||||
struct InvalidDeviceID {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("device not found")]
|
||||
struct DeviceNotFound;
|
||||
|
||||
|
||||
//#[derive(Error)]
|
||||
//#[error("invalid request")]
|
||||
//InvalidRequest {
|
||||
// description("The server indicated an invalid request")
|
||||
// display("The server indicated an invalid request")
|
||||
//}
|
||||
//#[doc("An unexpected HTTP error was returned")]
|
||||
//UnexpectedError(sc: u16) {
|
||||
// description("An unexpected HTTP error was returned")
|
||||
// display("An unexpected HTTP error was returned: {}", sc)
|
||||
//}
|
||||
#[derive(Debug, Error)]
|
||||
#[error("unsupported command")]
|
||||
struct UnsupportedCommand;
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "usb")]
|
||||
pub mod usb_hid;
|
||||
|
||||
|
@ -104,9 +104,13 @@ The serial number is returned as a pair, (high,low) bytes.
|
||||
|
||||
*/
|
||||
|
||||
use crate::{Device, Pattern, SolidColor, SpecificLED, TargetedDevice, Wave};
|
||||
use crate::{Device, Pattern, SolidColor, SpecificLED, TargetedDevice, Wave, InvalidLed};
|
||||
use hidapi::{HidApi, HidDevice};
|
||||
|
||||
pub use hidapi::HidError;
|
||||
|
||||
use log::{error, info, trace};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Public Types
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@ -184,28 +188,25 @@ impl USBDeviceDiscovery {
|
||||
///
|
||||
/// Construct a new discovery object, this initializes the USB HID interface and thus can fail.
|
||||
///
|
||||
pub fn new() -> crate::error::Result<Self> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
pub fn new() -> Result<Self, HidError> {
|
||||
HidApi::new()
|
||||
.map(|hid_api| Self { hid_api })
|
||||
.map_err(|err| {
|
||||
log::error!("could not open HID api: {0}", err);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Return a device, if found, that corresponds to a Luxafor light.
|
||||
///
|
||||
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),
|
||||
Err(err) => {
|
||||
error!("Could not open HID device: {:?}", err);
|
||||
Err(crate::error::ErrorKind::DeviceNotFound.into())
|
||||
}
|
||||
}
|
||||
pub fn device(&self) -> Result<USBDevice, HidError> {
|
||||
self.hid_api.open(LUXAFOR_VENDOR_ID, LUXAFOR_PRODUCT_ID)
|
||||
.map(|dev| USBDevice::new(dev))
|
||||
.map_err(|err| {
|
||||
log::error!("could not open device: {err}");
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,18 +217,18 @@ impl Device for USBDevice {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn turn_off(&self) -> crate::error::Result<()> {
|
||||
fn turn_off(&self) -> Result<(),HidError> {
|
||||
info!("Turning device '{}' off", self.id);
|
||||
self.write(&[HID_REPORT_ID, MODE_SIMPLE, SIMPLE_COLOR_OFF])
|
||||
}
|
||||
|
||||
fn set_solid_color(&self, color: SolidColor) -> crate::error::Result<()> {
|
||||
fn set_solid_color(&self, color: SolidColor) -> Result<(),HidError> {
|
||||
info!("Setting the color of device '{}' to {}", self.id, color);
|
||||
let (r, g, b) = self.color_to_bytes(color);
|
||||
self.write(&[HID_REPORT_ID, MODE_SOLID, self.target_led, r, g, b])
|
||||
}
|
||||
|
||||
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> crate::error::Result<()> {
|
||||
fn set_fade_to_color(&self, color: SolidColor, fade_duration: u8) -> Result<(),()> {
|
||||
info!(
|
||||
"Setting the fade-to color of device '{}' to {}, over {}",
|
||||
self.id, color, fade_duration
|
||||
@ -249,7 +250,7 @@ impl Device for USBDevice {
|
||||
color: SolidColor,
|
||||
strobe_speed: u8,
|
||||
repeat_count: u8,
|
||||
) -> crate::error::Result<()> {
|
||||
) -> Result<(),()> {
|
||||
info!(
|
||||
"Setting the device '{}' to strobe {}, at {}, {} times",
|
||||
self.id, color, strobe_speed, repeat_count
|
||||
@ -274,7 +275,7 @@ impl Device for USBDevice {
|
||||
wave_pattern: Wave,
|
||||
wave_speed: u8,
|
||||
repeat_count: u8,
|
||||
) -> crate::error::Result<()> {
|
||||
) -> Result<(),()> {
|
||||
info!(
|
||||
"Setting the device '{}' to wave {}, at {}, {} times",
|
||||
self.id, color, wave_speed, repeat_count
|
||||
@ -299,7 +300,7 @@ impl Device for USBDevice {
|
||||
])
|
||||
}
|
||||
|
||||
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> crate::error::Result<()> {
|
||||
fn set_pattern(&self, pattern: Pattern, repeat_count: u8) -> Result<(),()> {
|
||||
info!("Setting the pattern of device '{}' to {}", self.id, pattern);
|
||||
let pattern = match pattern {
|
||||
Pattern::Police => PATTERN_POLICE,
|
||||
@ -325,7 +326,7 @@ impl Device for USBDevice {
|
||||
}
|
||||
|
||||
impl TargetedDevice for USBDevice {
|
||||
fn set_specific_led(&mut self, led: SpecificLED) -> crate::error::Result<()> {
|
||||
fn set_specific_led(&mut self, led: SpecificLED) -> Result<(),InvalidLed> {
|
||||
self.target_led = match led {
|
||||
SpecificLED::All => LED_ALL,
|
||||
SpecificLED::AllFront => LED_FRONT_ALL,
|
||||
@ -337,7 +338,7 @@ impl TargetedDevice for USBDevice {
|
||||
4 => LED_BACK_BOTTOM,
|
||||
5 => LED_BACK_MIDDLE,
|
||||
6 => LED_BACK_TOP,
|
||||
_ => return Err(crate::error::ErrorKind::InvalidLED.into()),
|
||||
other => return Err(InvalidLed(other)),
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
@ -345,7 +346,7 @@ impl TargetedDevice for USBDevice {
|
||||
}
|
||||
|
||||
impl USBDevice {
|
||||
fn new(hid_device: HidDevice) -> crate::error::Result<USBDevice> {
|
||||
fn new(hid_device: HidDevice) -> USBDevice {
|
||||
let id = format!(
|
||||
"{}::{}::{}",
|
||||
hid_device
|
||||
@ -361,11 +362,11 @@ impl USBDevice {
|
||||
.unwrap_or(Some("<error>".to_string()))
|
||||
.unwrap_or("<unknown>".to_string()),
|
||||
);
|
||||
Ok(Self {
|
||||
Self {
|
||||
hid_device,
|
||||
id,
|
||||
target_led: LED_ALL,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn color_to_bytes(&self, color: SolidColor) -> (u8, u8, u8) {
|
||||
@ -381,7 +382,7 @@ impl USBDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&self, buffer: &[u8]) -> crate::error::Result<()> {
|
||||
fn write(&self, buffer: &[u8]) -> Result<(),HidError> {
|
||||
trace!(
|
||||
"writing [{:?}]",
|
||||
buffer
|
||||
@ -390,24 +391,12 @@ impl USBDevice {
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
let result = self.hid_device.write(buffer);
|
||||
match result {
|
||||
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())
|
||||
}
|
||||
let all = buffer.len();
|
||||
let sent = self.hid_device.write(buffer)?;
|
||||
if sent != all {
|
||||
Err(HidError::IncompleteSendError { sent, all })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user