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 UnifiedBatteryFeature {
48 pub async fn get_battery_capabilities(&self) -> Result<BatteryCapabilities, Hidpp20Error> {
50 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
51
52 Ok(BatteryCapabilities::from([payload[0], payload[1]]))
53 }
54
55 pub async fn get_battery_info(&self) -> Result<BatteryInfo, Hidpp20Error> {
57 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
58
59 Ok(BatteryInfo {
66 charging_percentage: payload[0],
67 level: BatteryLevel::try_from(payload[1])
68 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
69 status: BatteryStatus::try_from(payload[2])
70 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
71 })
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize))]
78#[non_exhaustive]
79pub struct BatteryCapabilities {
80 pub reported_levels: HashSet<BatteryLevel>,
82
83 pub rechargeable: bool,
85
86 pub percentage: bool,
89}
90
91impl From<[u8; 2]> for BatteryCapabilities {
92 fn from(value: [u8; 2]) -> Self {
93 let mut reported_levels = HashSet::new();
94 if value[0] & 1 != 0 {
95 reported_levels.insert(BatteryLevel::Critical);
96 }
97 if value[0] & (1 << 1) != 0 {
98 reported_levels.insert(BatteryLevel::Low);
99 }
100 if value[0] & (1 << 2) != 0 {
101 reported_levels.insert(BatteryLevel::Good);
102 }
103 if value[0] & (1 << 3) != 0 {
104 reported_levels.insert(BatteryLevel::Full);
105 }
106
107 Self {
108 reported_levels,
109 rechargeable: value[1] & 1 != 0,
110 percentage: value[1] & (1 << 1) != 0,
111 }
112 }
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize))]
118#[non_exhaustive]
119pub struct BatteryInfo {
120 pub charging_percentage: u8,
125
126 pub level: BatteryLevel,
131
132 pub status: BatteryStatus,
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize))]
139#[non_exhaustive]
140#[repr(u8)]
141pub enum BatteryLevel {
142 Critical = 1,
144 Low = 1 << 1,
146 Good = 1 << 2,
148 Full = 1 << 3,
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize))]
156#[non_exhaustive]
157#[repr(u8)]
158pub enum BatteryStatus {
159 Discharging = 0,
161 Charging = 1,
163 ChargingNearlyFull = 2,
165 Full = 3,
167 ChargingSlow = 4,
169 InvalidBattery = 5,
171 ThermalError = 6,
173 ChargingError = 7,
175}
176
177#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize))]
180#[non_exhaustive]
181pub enum BatteryEvent {
182 InfoUpdate(BatteryInfo),
186}