hidpp/feature/unified_battery/
mod.rs1use std::{collections::HashSet, hash::Hash, sync::Arc};
5
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7
8use crate::{
9 channel::HidppChannel,
10 event::EventEmitter,
11 feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
12 protocol::v20::Hidpp20Error,
13};
14
15pub struct UnifiedBatteryFeature {
17 endpoint: FeatureEndpoint,
19
20 emitter: Arc<EventEmitter<BatteryEvent>>,
22
23 msg_listener_hdl: u32,
27}
28
29impl CreatableFeature for UnifiedBatteryFeature {
30 const ID: u16 = 0x1004;
31 const STARTING_VERSION: u8 = 0;
32
33 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
34 let emitter = Arc::new(EventEmitter::new());
35
36 let hdl = chan.add_msg_listener({
37 let emitter = Arc::clone(&emitter);
38
39 move |raw, matched| {
40 let Some((func, payload)) =
41 event_payload(raw, matched, device_index, feature_index)
42 else {
43 return;
44 };
45 if func.to_lo() != 0 {
47 return;
48 }
49
50 let (Ok(level), Ok(status)) = (
51 BatteryLevel::try_from(payload[1]),
52 BatteryStatus::try_from(payload[2]),
53 ) else {
54 return;
55 };
56
57 emitter.emit(BatteryEvent::InfoUpdate(BatteryInfo {
58 charging_percentage: payload[0],
59 level,
60 status,
61 }));
62 }
63 });
64
65 Self {
66 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
67 emitter,
68 msg_listener_hdl: hdl,
69 }
70 }
71}
72
73impl Feature for UnifiedBatteryFeature {}
74
75impl EmittingFeature<BatteryEvent> for UnifiedBatteryFeature {
76 fn listen(&self) -> async_channel::Receiver<BatteryEvent> {
77 self.emitter.create_receiver()
78 }
79}
80
81impl Drop for UnifiedBatteryFeature {
82 fn drop(&mut self) {
83 self.endpoint
84 .chan()
85 .remove_msg_listener(self.msg_listener_hdl);
86 }
87}
88
89impl UnifiedBatteryFeature {
90 pub async fn get_battery_capabilities(&self) -> Result<BatteryCapabilities, Hidpp20Error> {
92 let payload: [u8; 2] = self.endpoint.call(0, [0; 3]).await?.extend_payload()[..2]
93 .try_into()
94 .unwrap();
95
96 Ok(BatteryCapabilities::from(payload))
97 }
98
99 pub async fn get_battery_info(&self) -> Result<BatteryInfo, Hidpp20Error> {
101 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
102
103 Ok(BatteryInfo {
110 charging_percentage: payload[0],
111 level: BatteryLevel::try_from(payload[1])
112 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
113 status: BatteryStatus::try_from(payload[2])
114 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
115 })
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize))]
122#[non_exhaustive]
123pub struct BatteryCapabilities {
124 pub reported_levels: HashSet<BatteryLevel>,
126
127 pub rechargeable: bool,
129
130 pub percentage: bool,
133}
134
135impl From<[u8; 2]> for BatteryCapabilities {
136 fn from(value: [u8; 2]) -> Self {
137 let mut reported_levels = HashSet::new();
138 if value[0] & 1 != 0 {
139 reported_levels.insert(BatteryLevel::Critical);
140 }
141 if value[0] & (1 << 1) != 0 {
142 reported_levels.insert(BatteryLevel::Low);
143 }
144 if value[0] & (1 << 2) != 0 {
145 reported_levels.insert(BatteryLevel::Good);
146 }
147 if value[0] & (1 << 3) != 0 {
148 reported_levels.insert(BatteryLevel::Full);
149 }
150
151 Self {
152 reported_levels,
153 rechargeable: value[1] & 1 != 0,
154 percentage: value[1] & (1 << 1) != 0,
155 }
156 }
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
161#[cfg_attr(feature = "serde", derive(serde::Serialize))]
162#[non_exhaustive]
163pub struct BatteryInfo {
164 pub charging_percentage: u8,
169
170 pub level: BatteryLevel,
175
176 pub status: BatteryStatus,
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize))]
183#[non_exhaustive]
184#[repr(u8)]
185pub enum BatteryLevel {
186 Critical = 1,
187 Low = 1 << 1,
188 Good = 1 << 2,
189 Full = 1 << 3,
190}
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize))]
195#[non_exhaustive]
196#[repr(u8)]
197pub enum BatteryStatus {
198 Discharging = 0,
199 Charging = 1,
200 ChargingSlow = 2,
201 Full = 3,
202 Error = 4,
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
207#[cfg_attr(feature = "serde", derive(serde::Serialize))]
208#[non_exhaustive]
209pub enum BatteryEvent {
210 InfoUpdate(BatteryInfo),
214}