hidpp/feature/
unified_battery.rs1use std::{collections::HashSet, hash::Hash};
5
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7use openlogi_hidpp_derive::Feature;
8
9use crate::{
10 feature::{DecodeEvent, EventSource, FeatureEndpoint},
11 protocol::v20::Hidpp20Error,
12};
13
14#[derive(Feature)]
16#[creatable(id = 0x1004, version = 0)]
17pub struct UnifiedBatteryFeature {
18 endpoint: FeatureEndpoint,
20
21 events: EventSource<BatteryEvent>,
23}
24
25impl DecodeEvent for BatteryEvent {
26 fn decode(sub_id: u8, payload: &[u8; 16]) -> Option<Self> {
27 if sub_id != 0 {
29 return None;
30 }
31
32 let (Ok(level), Ok(status)) = (
33 BatteryLevel::try_from(payload[1]),
34 BatteryStatus::try_from(payload[2]),
35 ) else {
36 return None;
37 };
38
39 Some(BatteryEvent::InfoUpdate(BatteryInfo {
40 charging_percentage: payload[0],
41 level,
42 status,
43 }))
44 }
45}
46
47impl BatteryEvent {
48 #[must_use]
54 pub fn decode(function_id: u8, payload: &[u8; 16]) -> Option<Self> {
55 <Self as DecodeEvent>::decode(function_id, payload)
56 }
57}
58
59impl UnifiedBatteryFeature {
60 pub async fn get_battery_capabilities(&self) -> Result<BatteryCapabilities, Hidpp20Error> {
62 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
63
64 Ok(BatteryCapabilities::from([payload[0], payload[1]]))
65 }
66
67 pub async fn get_battery_info(&self) -> Result<BatteryInfo, Hidpp20Error> {
69 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
70
71 Ok(BatteryInfo {
78 charging_percentage: payload[0],
79 level: BatteryLevel::try_from(payload[1])
80 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
81 status: BatteryStatus::try_from(payload[2])
82 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
83 })
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize))]
90#[non_exhaustive]
91pub struct BatteryCapabilities {
92 pub reported_levels: HashSet<BatteryLevel>,
94
95 pub rechargeable: bool,
97
98 pub percentage: bool,
101}
102
103impl From<[u8; 2]> for BatteryCapabilities {
104 fn from(value: [u8; 2]) -> Self {
105 let mut reported_levels = HashSet::new();
106 if value[0] & 1 != 0 {
107 reported_levels.insert(BatteryLevel::Critical);
108 }
109 if value[0] & (1 << 1) != 0 {
110 reported_levels.insert(BatteryLevel::Low);
111 }
112 if value[0] & (1 << 2) != 0 {
113 reported_levels.insert(BatteryLevel::Good);
114 }
115 if value[0] & (1 << 3) != 0 {
116 reported_levels.insert(BatteryLevel::Full);
117 }
118
119 Self {
120 reported_levels,
121 rechargeable: value[1] & 1 != 0,
122 percentage: value[1] & (1 << 1) != 0,
123 }
124 }
125}
126
127#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize))]
130#[non_exhaustive]
131pub struct BatteryInfo {
132 pub charging_percentage: u8,
137
138 pub level: BatteryLevel,
143
144 pub status: BatteryStatus,
146}
147
148#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize))]
151#[non_exhaustive]
152#[repr(u8)]
153pub enum BatteryLevel {
154 Critical = 1,
156 Low = 1 << 1,
158 Good = 1 << 2,
160 Full = 1 << 3,
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
167#[cfg_attr(feature = "serde", derive(serde::Serialize))]
168#[non_exhaustive]
169#[repr(u8)]
170pub enum BatteryStatus {
171 Discharging = 0,
173 Charging = 1,
175 ChargingNearlyFull = 2,
177 Full = 3,
179 ChargingSlow = 4,
181 InvalidBattery = 5,
183 ThermalError = 6,
185 ChargingError = 7,
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize))]
192#[non_exhaustive]
193pub enum BatteryEvent {
194 InfoUpdate(BatteryInfo),
198}