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, MessageListenerGuard},
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: MessageListenerGuard,
25}
26
27impl CreatableFeature for UnifiedBatteryFeature {
28 const ID: u16 = 0x1004;
29 const STARTING_VERSION: u8 = 0;
30
31 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
32 let emitter = Arc::new(EventEmitter::new());
33
34 let listener = chan.add_msg_listener_guarded({
35 let emitter = Arc::clone(&emitter);
36
37 move |raw, matched| {
38 let Some((func, payload)) =
39 event_payload(raw, matched, device_index, feature_index)
40 else {
41 return;
42 };
43 if func.to_lo() != 0 {
45 return;
46 }
47
48 let (Ok(level), Ok(status)) = (
49 BatteryLevel::try_from(payload[1]),
50 BatteryStatus::try_from(payload[2]),
51 ) else {
52 return;
53 };
54
55 emitter.emit(BatteryEvent::InfoUpdate(BatteryInfo {
56 charging_percentage: payload[0],
57 level,
58 status,
59 }));
60 }
61 });
62
63 Self {
64 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
65 emitter,
66 _msg_listener: listener,
67 }
68 }
69}
70
71impl Feature for UnifiedBatteryFeature {}
72
73impl EmittingFeature<BatteryEvent> for UnifiedBatteryFeature {
74 fn listen(&self) -> async_channel::Receiver<BatteryEvent> {
75 self.emitter.create_receiver()
76 }
77}
78
79impl UnifiedBatteryFeature {
80 pub async fn get_battery_capabilities(&self) -> Result<BatteryCapabilities, Hidpp20Error> {
82 let payload: [u8; 2] = self.endpoint.call(0, [0; 3]).await?.extend_payload()[..2]
83 .try_into()
84 .unwrap();
85
86 Ok(BatteryCapabilities::from(payload))
87 }
88
89 pub async fn get_battery_info(&self) -> Result<BatteryInfo, Hidpp20Error> {
91 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
92
93 Ok(BatteryInfo {
100 charging_percentage: payload[0],
101 level: BatteryLevel::try_from(payload[1])
102 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
103 status: BatteryStatus::try_from(payload[2])
104 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
105 })
106 }
107}
108
109#[derive(Clone, Debug, PartialEq, Eq)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize))]
112#[non_exhaustive]
113pub struct BatteryCapabilities {
114 pub reported_levels: HashSet<BatteryLevel>,
116
117 pub rechargeable: bool,
119
120 pub percentage: bool,
123}
124
125impl From<[u8; 2]> for BatteryCapabilities {
126 fn from(value: [u8; 2]) -> Self {
127 let mut reported_levels = HashSet::new();
128 if value[0] & 1 != 0 {
129 reported_levels.insert(BatteryLevel::Critical);
130 }
131 if value[0] & (1 << 1) != 0 {
132 reported_levels.insert(BatteryLevel::Low);
133 }
134 if value[0] & (1 << 2) != 0 {
135 reported_levels.insert(BatteryLevel::Good);
136 }
137 if value[0] & (1 << 3) != 0 {
138 reported_levels.insert(BatteryLevel::Full);
139 }
140
141 Self {
142 reported_levels,
143 rechargeable: value[1] & 1 != 0,
144 percentage: value[1] & (1 << 1) != 0,
145 }
146 }
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
152#[non_exhaustive]
153pub struct BatteryInfo {
154 pub charging_percentage: u8,
159
160 pub level: BatteryLevel,
165
166 pub status: BatteryStatus,
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize))]
173#[non_exhaustive]
174#[repr(u8)]
175pub enum BatteryLevel {
176 Critical = 1,
178 Low = 1 << 1,
180 Good = 1 << 2,
182 Full = 1 << 3,
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize))]
190#[non_exhaustive]
191#[repr(u8)]
192pub enum BatteryStatus {
193 Discharging = 0,
195 Charging = 1,
197 ChargingNearlyFull = 2,
199 Full = 3,
201 ChargingSlow = 4,
203 InvalidBattery = 5,
205 ThermalError = 6,
207 ChargingError = 7,
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize))]
214#[non_exhaustive]
215pub enum BatteryEvent {
216 InfoUpdate(BatteryInfo),
220}