Skip to main content

hidpp/feature/
brightness_control.rs

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