Skip to main content

hidpp/feature/solar_dashboard/
mod.rs

1//! Implements the `SolarKeyboardDashboard` feature (ID `0x4301`) for Logitech's
2//! solar keyboards (e.g. the K750): scheduling light-measure reports, overriding
3//! the CheckLight LED, and receiving battery / light broadcast events.
4
5pub mod event;
6
7#[cfg(test)]
8mod tests;
9
10use std::sync::Arc;
11
12use num_enum::{IntoPrimitive, TryFromPrimitive};
13
14pub use event::{SolarEvent, SolarStatus};
15
16use crate::{
17    channel::{HidppChannel, MessageListenerGuard},
18    event::EventEmitter,
19    feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
20    protocol::v20::Hidpp20Error,
21};
22
23/// A CheckLight LED color for [`set_led`](SolarDashboardFeature::set_led).
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[non_exhaustive]
27#[repr(u8)]
28pub enum LedId {
29    /// All LEDs off.
30    Off = 0,
31    /// Red.
32    Red = 1,
33    /// Orange.
34    Orange = 2,
35    /// Green.
36    Green = 3,
37}
38
39/// Implements the `SolarKeyboardDashboard` / `0x4301` feature.
40pub struct SolarDashboardFeature {
41    /// The endpoint this feature talks to.
42    endpoint: FeatureEndpoint,
43
44    /// The emitter used to publish decoded events.
45    emitter: Arc<EventEmitter<SolarEvent>>,
46
47    /// Removes the message listener when the feature is dropped.
48    _msg_listener: MessageListenerGuard,
49}
50
51impl CreatableFeature for SolarDashboardFeature {
52    const ID: u16 = 0x4301;
53    const STARTING_VERSION: u8 = 0;
54
55    fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
56        let emitter = Arc::new(EventEmitter::new());
57
58        let listener = chan.add_msg_listener_guarded({
59            let emitter = Arc::clone(&emitter);
60
61            move |raw, matched| {
62                let Some((func, payload)) =
63                    event_payload(raw, matched, device_index, feature_index)
64                else {
65                    return;
66                };
67                if let Some(event) = event::decode_event(func.to_lo(), &payload) {
68                    emitter.emit(event);
69                }
70            }
71        });
72
73        Self {
74            endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
75            emitter,
76            _msg_listener: listener,
77        }
78    }
79}
80
81impl Feature for SolarDashboardFeature {}
82
83impl EmittingFeature<SolarEvent> for SolarDashboardFeature {
84    fn listen(&self) -> async_channel::Receiver<SolarEvent> {
85        self.emitter.create_receiver()
86    }
87}
88
89impl SolarDashboardFeature {
90    /// Schedules [`SolarEvent::LightMeasure`] reports.
91    ///
92    /// `max_reports` is the number of reports to send and `report_period` their
93    /// spacing in seconds. Passing `0` for either cancels reporting.
94    pub async fn set_light_measure(
95        &self,
96        max_reports: u8,
97        report_period: u8,
98    ) -> Result<(), Hidpp20Error> {
99        self.endpoint
100            .call(0, [max_reports, report_period, 0])
101            .await?;
102        Ok(())
103    }
104
105    /// Lights the CheckLight LED in the given color for a firmware-defined
106    /// duration.
107    ///
108    /// Intended to override the firmware's own CheckLight display in response to a
109    /// [`SolarEvent::CheckLightButton`]; the firmware waits 250 ms before showing
110    /// its own status, so call this within that window.
111    pub async fn set_led(&self, led: LedId) -> Result<(), Hidpp20Error> {
112        self.endpoint.call(1, [led.into(), 0, 0]).await?;
113        Ok(())
114    }
115}