Skip to main content

hidpp/feature/brightness_control/
mod.rs

1//! Implements `BrightnessControl` (feature `0x8040`).
2
3use std::sync::Arc;
4
5use crate::{
6    channel::HidppChannel,
7    feature::{CreatableFeature, Feature, FeatureEndpoint},
8    protocol::v20::Hidpp20Error,
9};
10
11bitflags::bitflags! {
12    /// Capabilities reported by `BrightnessControl`.
13    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
15    pub struct BrightnessCapabilities: u8 {
16        /// Hardware can change brightness directly.
17        const HARDWARE_BRIGHTNESS = 1 << 0;
18        /// The device emits brightness or illumination change events.
19        const EVENTS = 1 << 1;
20        /// Illumination can be queried and controlled separately from brightness.
21        const ILLUMINATION = 1 << 2;
22        /// Hardware can toggle illumination on and off directly.
23        const HARDWARE_ON_OFF = 1 << 3;
24        /// Brightness is transient and not persisted by the device.
25        const TRANSIENT = 1 << 4;
26    }
27}
28
29/// Brightness range and capability information.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32#[non_exhaustive]
33pub struct BrightnessInfo {
34    /// Minimum accepted brightness.
35    pub min_brightness: u16,
36    /// Maximum accepted brightness.
37    pub max_brightness: u16,
38    /// Number of brightness steps advertised by the device.
39    pub steps: u16,
40    /// Feature capabilities.
41    pub capabilities: BrightnessCapabilities,
42}
43
44/// Implements the `BrightnessControl` / `0x8040` feature.
45#[derive(Clone)]
46pub struct BrightnessControlFeature {
47    /// The endpoint this feature talks to.
48    endpoint: FeatureEndpoint,
49}
50
51impl CreatableFeature for BrightnessControlFeature {
52    const ID: u16 = 0x8040;
53    const STARTING_VERSION: u8 = 1;
54
55    fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
56        Self {
57            endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
58        }
59    }
60}
61
62impl Feature for BrightnessControlFeature {}
63
64impl BrightnessControlFeature {
65    /// Retrieves brightness range and capability information.
66    pub async fn get_info(&self) -> Result<BrightnessInfo, Hidpp20Error> {
67        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
68        Ok(BrightnessInfo::from_payload(payload))
69    }
70
71    /// Retrieves the current brightness value.
72    pub async fn get_brightness(&self) -> Result<u16, Hidpp20Error> {
73        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
74        Ok(u16::from_be_bytes([payload[0], payload[1]]))
75    }
76
77    /// Sets the current brightness value.
78    pub async fn set_brightness(&self, brightness: u16) -> Result<(), Hidpp20Error> {
79        let [hi, lo] = brightness.to_be_bytes();
80        self.endpoint.call(2, [hi, lo, 0]).await?;
81        Ok(())
82    }
83
84    /// Retrieves whether illumination is currently enabled.
85    pub async fn get_illumination(&self) -> Result<bool, Hidpp20Error> {
86        Ok(self.endpoint.call(3, [0; 3]).await?.extend_payload()[0] & 1 != 0)
87    }
88
89    /// Enables or disables illumination.
90    pub async fn set_illumination(&self, enabled: bool) -> Result<(), Hidpp20Error> {
91        self.endpoint.call(4, [u8::from(enabled), 0, 0]).await?;
92        Ok(())
93    }
94}
95
96impl BrightnessInfo {
97    fn from_payload(payload: [u8; 16]) -> Self {
98        Self {
99            min_brightness: u16::from_be_bytes([payload[4], payload[5]]),
100            max_brightness: u16::from_be_bytes([payload[0], payload[1]]),
101            steps: u16::from_be_bytes([payload[6], payload[2]]),
102            capabilities: BrightnessCapabilities::from_bits_retain(payload[3]),
103        }
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::{BrightnessCapabilities, BrightnessInfo};
110
111    #[test]
112    fn parses_split_steps_field() {
113        let mut payload = [0; 16];
114        payload[0..=1].copy_from_slice(&1000u16.to_be_bytes());
115        payload[2] = 0x34;
116        payload[3] = BrightnessCapabilities::ILLUMINATION.bits();
117        payload[4..=5].copy_from_slice(&10u16.to_be_bytes());
118        payload[6] = 0x12;
119
120        let info = BrightnessInfo::from_payload(payload);
121
122        assert_eq!(info.min_brightness, 10);
123        assert_eq!(info.max_brightness, 1000);
124        assert_eq!(info.steps, 0x1234);
125        assert!(
126            info.capabilities
127                .contains(BrightnessCapabilities::ILLUMINATION)
128        );
129    }
130}