Work in progress

This commit is contained in:
Maxime Augier 2024-02-12 09:04:38 +01:00
parent cf56783e3d
commit 22da3e03f3
3 changed files with 129 additions and 151 deletions

View File

@ -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"] }

View File

@ -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;

View File

@ -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(())
}
}
}