hidpp/feature/battery_status/mod.rs
1//! Implements the legacy `BatteryStatus` feature (ID `0x1000`) that reports a
2//! device's battery charge as a discharge level plus a charging status.
3//!
4//! This is the predecessor of `UnifiedBattery` (`0x1004`): older mice such as
5//! the MX Master 2S expose `0x1000` and never `0x1004`, so the inventory probe
6//! falls back to this feature when the unified one is absent — the same
7//! enhanced-then-legacy pattern `SmartShift` uses for `0x2111` / `0x2110`.
8//!
9//! Only `getBatteryLevelStatus` (function `0`) is implemented; the optional
10//! `getBatteryCapability` (function `1`) and the broadcast event aren't needed
11//! to display a charge reading.
12
13use std::{hash::Hash, sync::Arc};
14
15use num_enum::{IntoPrimitive, TryFromPrimitive};
16
17use crate::{
18 channel::HidppChannel,
19 feature::{CreatableFeature, Feature, FeatureEndpoint},
20 protocol::v20::Hidpp20Error,
21};
22
23/// Implements the legacy `BatteryStatus` / `0x1000` feature.
24pub struct BatteryStatusFeature {
25 /// The endpoint this feature talks to.
26 endpoint: FeatureEndpoint,
27}
28
29impl CreatableFeature for BatteryStatusFeature {
30 const ID: u16 = 0x1000;
31 const STARTING_VERSION: u8 = 0;
32
33 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
34 Self {
35 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
36 }
37 }
38}
39
40impl Feature for BatteryStatusFeature {}
41
42impl BatteryStatusFeature {
43 /// Reads the current battery level and charging status (function `0`,
44 /// `getBatteryLevelStatus`).
45 pub async fn get_battery_level_status(&self) -> Result<LegacyBatteryInfo, Hidpp20Error> {
46 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
47
48 Ok(LegacyBatteryInfo {
49 discharge_level: payload[0],
50 next_level: payload[1],
51 status: LegacyBatteryStatus::try_from(payload[2])
52 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
53 })
54 }
55}
56
57/// A reading from the legacy `0x1000` `getBatteryLevelStatus` function.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize))]
60#[non_exhaustive]
61pub struct LegacyBatteryInfo {
62 /// Current battery charge as a percentage (`0`–`100`). Logitech firmware
63 /// reports this in coarse steps rather than a continuous value.
64 pub discharge_level: u8,
65
66 /// The next lower discharge step the firmware will report — a hint at the
67 /// reporting granularity. Unused for display.
68 pub next_level: u8,
69
70 /// The current charging status.
71 pub status: LegacyBatteryStatus,
72}
73
74/// Charging status reported by the legacy `0x1000` feature. Values follow the
75/// HID++ `batteryStatus` enumeration (see Solaar / `hid-logitech-hidpp`).
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize))]
78#[non_exhaustive]
79#[repr(u8)]
80pub enum LegacyBatteryStatus {
81 /// Battery is discharging.
82 Discharging = 0,
83 /// Battery is recharging.
84 Recharging = 1,
85 /// Battery is charging and nearly full.
86 AlmostFull = 2,
87 /// Battery charge is complete.
88 Full = 3,
89 /// Battery is recharging below optimal speed.
90 SlowRecharge = 4,
91 /// The battery type is invalid.
92 InvalidBattery = 5,
93 /// The battery subsystem reported a thermal error.
94 ThermalError = 6,
95 /// "Other charging error" (Solaar lists value 7). Kept explicit so a device
96 /// reporting it surfaces as Unknown instead of failing the parse and making
97 /// the battery indicator vanish from the UI.
98 Other = 7,
99}