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 all-features = true
[features] [features]
default = ["webhook"]
usb = ["hidapi"] usb = ["hidapi"]
webhook = ["reqwest"] webhook = ["ureq"]
command-line = ["pretty_env_logger", "structopt", "usb", "webhook"]
[[bin]] [[bin]]
name = "lux" name = "lux"
@ -26,10 +24,10 @@ required-features = ["command-line"]
[dependencies] [dependencies]
log = "0.4.11" log = "0.4.11"
error-chain = "0.12.2" thiserror = "1.0.56"
#[feature-dependencies] #[feature-dependencies]
hidapi = { version = "1.2.3", optional = true } hidapi = { version = "2.4.1", optional = true }
pretty_env_logger = { version = "0.4.0", optional = true } ureq = { version = "2.9.1", optional = true }
reqwest = { version = "0.11", features = ["blocking"], optional = true } clap = { version = "4.4.18", optional = true, features = ["derive"] }
structopt = { version = "0.3.14", optional = true }

View File

@ -101,12 +101,7 @@ The following shows the how to set USB connected lights.
unused_results, unused_results,
)] )]
#[macro_use] use thiserror::Error;
extern crate error_chain;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
@ -198,17 +193,17 @@ pub trait Device {
/// ///
/// Turn the light off. /// Turn the light off.
/// ///
fn turn_off(&self) -> error::Result<()>; fn turn_off(&self) -> Result<(),()>;
/// ///
/// Set the light to a continuous solid color. /// 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. /// 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. /// Strobe the light, this will dim and brighten the same color.
@ -218,7 +213,7 @@ pub trait Device {
color: SolidColor, color: SolidColor,
strobe_speed: u8, strobe_speed: u8,
repeat_count: u8, repeat_count: u8,
) -> error::Result<()>; ) -> Result<(),()>;
/// ///
/// Set the light to repeat one of a pre-defined set of wave patterns. /// 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_pattern: Wave,
wave_speed: u8, wave_speed: u8,
repeat_count: u8, repeat_count: u8,
) -> error::Result<()>; ) -> Result<(),()>;
/// ///
/// Set the light to repeat one of a pre-defined set of patterns. /// 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), Number(u8),
} }
#[derive(Debug,Error)]
#[error("invalid led id {0}")]
pub struct InvalidLed(u8);
/// ///
/// Extension trait to allow targeting specific LEDs on the device. /// Extension trait to allow targeting specific LEDs on the device.
/// ///
pub trait TargetedDevice: Device { pub trait TargetedDevice: Device {
/// Set the LED to be used for future operations. /// 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 { impl FromStr for SolidColor {
type Err = error::Error; type Err = InvalidColor;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase(); let s = s.to_lowercase();
match s.as_str() { let solid_color = match &*s {
"red" => Ok(SolidColor::Red), "red" => SolidColor::Red,
"green" => Ok(SolidColor::Green), "green" => SolidColor::Green,
"yellow" => Ok(SolidColor::Yellow), "yellow" => SolidColor::Yellow,
"blue" => Ok(SolidColor::Blue), "blue" => SolidColor::Blue,
"white" => Ok(SolidColor::White), "white" => SolidColor::White,
"cyan" => Ok(SolidColor::Cyan), "cyan" => SolidColor::Cyan,
"magenta" => Ok(SolidColor::Magenta), "magenta" => SolidColor::Magenta,
_ => {
if s.len() == 6 && s.chars().all(|c| c.is_ascii_hexdigit()) { hex if hex.len() == 6 && hex.chars().all(|c| c.is_ascii_hexdigit()) =>
Ok(SolidColor::Custom { SolidColor::Custom {
red: u8::from_str_radix(&s[0..1], 16)?, red: u8::from_str_radix(&s[0..1], 16).unwrap(),
green: u8::from_str_radix(&s[2..3], 16)?, green: u8::from_str_radix(&s[2..3], 16).unwrap(),
blue: u8::from_str_radix(&s[4..5], 16)?, blue: u8::from_str_radix(&s[4..5], 16).unwrap(),
}) },
} else { _ => return Err(InvalidColor(s))
Err(error::ErrorKind::InvalidColor.into()) };
} 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 { impl FromStr for Wave {
type Err = error::Error; type Err = InvalidPattern;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase(); let s = s.to_lowercase();
@ -342,7 +347,7 @@ impl FromStr for Wave {
"long" => Ok(Wave::Long), "long" => Ok(Wave::Long),
"overlapping short" => Ok(Wave::OverlappingShort), "overlapping short" => Ok(Wave::OverlappingShort),
"overlapping long" => Ok(Wave::OverlappingLong), "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 { impl FromStr for Pattern {
type Err = error::Error; type Err = InvalidPattern;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase(); let s = s.to_lowercase();
@ -392,7 +397,7 @@ impl FromStr for Pattern {
"white wave" => Ok(Pattern::WhiteWave), "white wave" => Ok(Pattern::WhiteWave),
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
"synthetic" => Ok(Pattern::Synthetic), "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 { impl FromStr for SpecificLED {
type Err = error::Error; type Err = ParseLedError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase(); let s = s.to_lowercase();
@ -423,13 +436,14 @@ impl FromStr for SpecificLED {
"all" => Ok(SpecificLED::All), "all" => Ok(SpecificLED::All),
"front" => Ok(SpecificLED::AllFront), "front" => Ok(SpecificLED::AllFront),
"back" => Ok(SpecificLED::AllBack), "back" => Ok(SpecificLED::AllBack),
"1" => Ok(SpecificLED::Number(1)), num => {
"2" => Ok(SpecificLED::Number(2)), match num.parse()? {
"3" => Ok(SpecificLED::Number(3)), n@1..=6 => Ok(SpecificLED::Number(n)),
"4" => Ok(SpecificLED::Number(4)), invalid => Err(ParseLedError::InvalidLed(
"5" => Ok(SpecificLED::Number(5)), InvalidLed(invalid)
"6" => Ok(SpecificLED::Number(6)), ))
_ => Err(error::ErrorKind::InvalidLED.into()), }
}
} }
} }
} }
@ -443,56 +457,33 @@ impl FromStr for SpecificLED {
/// ///
#[allow(missing_docs)] #[allow(missing_docs)]
pub mod error { pub mod error {
error_chain! { use thiserror::Error;
errors {
#[doc("The color value supplied was not recognized")] #[derive(Debug, Error)]
InvalidColor { #[error("invalid device ID")]
description("The color value supplied was not recognized") struct InvalidDeviceID {}
display("The color value supplied was not recognized")
} #[derive(Debug, Error)]
#[doc("The pattern value supplied was not recognized")] #[error("device not found")]
InvalidPattern { struct DeviceNotFound;
description("The pattern value supplied was not recognized")
display("The pattern value supplied was not recognized")
} //#[derive(Error)]
#[doc("The LED number is either invalid or not supported by the connected device")] //#[error("invalid request")]
InvalidLED { //InvalidRequest {
description("The LED number is either invalid or not supported by the connected device") // description("The server indicated an invalid request")
display("The LED number is either invalid or not supported by the connected device") // display("The server indicated an invalid request")
} //}
#[doc("The provided device ID was incorrectly formatted")] //#[doc("An unexpected HTTP error was returned")]
InvalidDeviceID { //UnexpectedError(sc: u16) {
description("The provided device ID was incorrectly formatted") // description("An unexpected HTTP error was returned")
display("The provided device ID was incorrectly formatted") // display("An unexpected HTTP error was returned: {}", sc)
} //}
#[doc("No device was discovered, or the ID did not resolve to a device")] #[derive(Debug, Error)]
DeviceNotFound { #[error("unsupported command")]
description("No device was discovered, or the ID did not resolve to a device") struct UnsupportedCommand;
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);
}
}
} }
#[cfg(feature = "usb")] #[cfg(feature = "usb")]
pub mod usb_hid; 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}; use hidapi::{HidApi, HidDevice};
pub use hidapi::HidError;
use log::{error, info, trace};
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// Public Types // Public Types
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -184,28 +188,25 @@ 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() -> Result<Self, HidError> {
match HidApi::new() { HidApi::new()
Ok(hid_api) => Ok(Self { hid_api }), .map(|hid_api| Self { hid_api })
Err(err) => { .map_err(|err| {
error!("Could not connect to USB, error: {:?}", err); log::error!("could not open HID api: {0}", err);
Err(crate::error::ErrorKind::DeviceNotFound.into()) err
} })
}
} }
/// ///
/// 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) -> Result<USBDevice, HidError> {
let result = self.hid_api.open(LUXAFOR_VENDOR_ID, LUXAFOR_PRODUCT_ID); self.hid_api.open(LUXAFOR_VENDOR_ID, LUXAFOR_PRODUCT_ID)
match result { .map(|dev| USBDevice::new(dev))
Ok(hid_device) => USBDevice::new(hid_device), .map_err(|err| {
Err(err) => { log::error!("could not open device: {err}");
error!("Could not open HID device: {:?}", err); err
Err(crate::error::ErrorKind::DeviceNotFound.into()) })
}
}
} }
} }
@ -216,18 +217,18 @@ impl Device for USBDevice {
self.id.clone() self.id.clone()
} }
fn turn_off(&self) -> crate::error::Result<()> { fn turn_off(&self) -> Result<(),HidError> {
info!("Turning device '{}' off", self.id); info!("Turning device '{}' off", self.id);
self.write(&[HID_REPORT_ID, MODE_SIMPLE, SIMPLE_COLOR_OFF]) 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); info!("Setting the color of device '{}' to {}", self.id, color);
let (r, g, b) = self.color_to_bytes(color); let (r, g, b) = self.color_to_bytes(color);
self.write(&[HID_REPORT_ID, MODE_SOLID, self.target_led, r, g, b]) 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!( info!(
"Setting the fade-to color of device '{}' to {}, over {}", "Setting the fade-to color of device '{}' to {}, over {}",
self.id, color, fade_duration self.id, color, fade_duration
@ -249,7 +250,7 @@ impl Device for USBDevice {
color: SolidColor, color: SolidColor,
strobe_speed: u8, strobe_speed: u8,
repeat_count: u8, repeat_count: u8,
) -> crate::error::Result<()> { ) -> Result<(),()> {
info!( info!(
"Setting the device '{}' to strobe {}, at {}, {} times", "Setting the device '{}' to strobe {}, at {}, {} times",
self.id, color, strobe_speed, repeat_count self.id, color, strobe_speed, repeat_count
@ -274,7 +275,7 @@ impl Device for USBDevice {
wave_pattern: Wave, wave_pattern: Wave,
wave_speed: u8, wave_speed: u8,
repeat_count: u8, repeat_count: u8,
) -> crate::error::Result<()> { ) -> Result<(),()> {
info!( info!(
"Setting the device '{}' to wave {}, at {}, {} times", "Setting the device '{}' to wave {}, at {}, {} times",
self.id, color, wave_speed, repeat_count 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); 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,
@ -325,7 +326,7 @@ impl Device for USBDevice {
} }
impl TargetedDevice 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 { self.target_led = match led {
SpecificLED::All => LED_ALL, SpecificLED::All => LED_ALL,
SpecificLED::AllFront => LED_FRONT_ALL, SpecificLED::AllFront => LED_FRONT_ALL,
@ -337,7 +338,7 @@ impl TargetedDevice for USBDevice {
4 => LED_BACK_BOTTOM, 4 => LED_BACK_BOTTOM,
5 => LED_BACK_MIDDLE, 5 => LED_BACK_MIDDLE,
6 => LED_BACK_TOP, 6 => LED_BACK_TOP,
_ => return Err(crate::error::ErrorKind::InvalidLED.into()), other => return Err(InvalidLed(other)),
}, },
}; };
Ok(()) Ok(())
@ -345,7 +346,7 @@ impl TargetedDevice for USBDevice {
} }
impl USBDevice { impl USBDevice {
fn new(hid_device: HidDevice) -> crate::error::Result<USBDevice> { fn new(hid_device: HidDevice) -> USBDevice {
let id = format!( let id = format!(
"{}::{}::{}", "{}::{}::{}",
hid_device hid_device
@ -361,11 +362,11 @@ impl USBDevice {
.unwrap_or(Some("<error>".to_string())) .unwrap_or(Some("<error>".to_string()))
.unwrap_or("<unknown>".to_string()), .unwrap_or("<unknown>".to_string()),
); );
Ok(Self { Self {
hid_device, hid_device,
id, id,
target_led: LED_ALL, target_led: LED_ALL,
}) }
} }
fn color_to_bytes(&self, color: SolidColor) -> (u8, u8, u8) { 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!( trace!(
"writing [{:?}]", "writing [{:?}]",
buffer buffer
@ -390,24 +391,12 @@ impl USBDevice {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
); );
let result = self.hid_device.write(buffer); let all = buffer.len();
match result { let sent = self.hid_device.write(buffer)?;
Ok(bytes_written) => { if sent != all {
if bytes_written == buffer.len() { Err(HidError::IncompleteSendError { sent, all })
Ok(()) } else {
} else { Ok(())
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())
}
} }
} }
} }