use std::sync::Arc;
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint},
protocol::v20::Hidpp20Error,
};
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BrightnessCapabilities: u8 {
const HARDWARE_BRIGHTNESS = 1 << 0;
const EVENTS = 1 << 1;
const ILLUMINATION = 1 << 2;
const HARDWARE_ON_OFF = 1 << 3;
const TRANSIENT = 1 << 4;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct BrightnessInfo {
pub min_brightness: u16,
pub max_brightness: u16,
pub steps: u16,
pub capabilities: BrightnessCapabilities,
}
#[derive(Clone)]
pub struct BrightnessControlFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for BrightnessControlFeature {
const ID: u16 = 0x8040;
const STARTING_VERSION: u8 = 1;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for BrightnessControlFeature {}
impl BrightnessControlFeature {
pub async fn get_info(&self) -> Result<BrightnessInfo, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(BrightnessInfo::from_payload(payload))
}
pub async fn get_brightness(&self) -> Result<u16, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
Ok(u16::from_be_bytes([payload[0], payload[1]]))
}
pub async fn set_brightness(&self, brightness: u16) -> Result<(), Hidpp20Error> {
let [hi, lo] = brightness.to_be_bytes();
self.endpoint.call(2, [hi, lo, 0]).await?;
Ok(())
}
pub async fn get_illumination(&self) -> Result<bool, Hidpp20Error> {
Ok(self.endpoint.call(3, [0; 3]).await?.extend_payload()[0] & 1 != 0)
}
pub async fn set_illumination(&self, enabled: bool) -> Result<(), Hidpp20Error> {
self.endpoint.call(4, [u8::from(enabled), 0, 0]).await?;
Ok(())
}
}
impl BrightnessInfo {
fn from_payload(payload: [u8; 16]) -> Self {
Self {
min_brightness: u16::from_be_bytes([payload[4], payload[5]]),
max_brightness: u16::from_be_bytes([payload[0], payload[1]]),
steps: u16::from_be_bytes([payload[6], payload[2]]),
capabilities: BrightnessCapabilities::from_bits_retain(payload[3]),
}
}
}
#[cfg(test)]
mod tests {
use super::{BrightnessCapabilities, BrightnessInfo};
#[test]
fn parses_split_steps_field() {
let mut payload = [0; 16];
payload[0..=1].copy_from_slice(&1000u16.to_be_bytes());
payload[2] = 0x34;
payload[3] = BrightnessCapabilities::ILLUMINATION.bits();
payload[4..=5].copy_from_slice(&10u16.to_be_bytes());
payload[6] = 0x12;
let info = BrightnessInfo::from_payload(payload);
assert_eq!(info.min_brightness, 10);
assert_eq!(info.max_brightness, 1000);
assert_eq!(info.steps, 0x1234);
assert!(
info.capabilities
.contains(BrightnessCapabilities::ILLUMINATION)
);
}
}