jolt_platform/
battery.rs

1//! Battery monitoring traits and types.
2
3use std::time::Duration;
4
5use color_eyre::eyre::Result;
6
7use crate::types::{BatteryTechnology, ChargeState};
8
9/// Battery information snapshot.
10///
11/// All values represent the current state at the time of the last refresh.
12#[derive(Debug, Clone, Default)]
13pub struct BatteryInfo {
14    /// Current charge level as a percentage (0-100).
15    pub charge_percent: f32,
16
17    /// Current charging state.
18    pub state: ChargeState,
19
20    /// Maximum capacity in watt-hours (current full charge capacity).
21    pub max_capacity_wh: f32,
22
23    /// Design capacity in watt-hours (original factory capacity).
24    pub design_capacity_wh: f32,
25
26    /// Current voltage in millivolts.
27    pub voltage_mv: u32,
28
29    /// Current amperage in milliamps. Negative when discharging.
30    /// May be 0 on platforms that don't report this.
31    pub amperage_ma: i32,
32
33    /// Battery health as a percentage (0-100).
34    /// Calculated as max_capacity / design_capacity * 100.
35    pub health_percent: f32,
36
37    /// Number of charge cycles, if available.
38    pub cycle_count: Option<u32>,
39
40    /// Estimated time until fully charged, if charging.
41    pub time_to_full: Option<Duration>,
42
43    /// Estimated time until empty, if discharging.
44    pub time_to_empty: Option<Duration>,
45
46    /// Battery temperature in Celsius, if available.
47    pub temperature_c: Option<f32>,
48
49    /// Whether external power is connected.
50    pub external_connected: bool,
51
52    /// Battery vendor/manufacturer name (e.g., "Apple", "Samsung SDI").
53    pub vendor: Option<String>,
54
55    /// Battery model identifier (e.g., "bq20z451").
56    pub model: Option<String>,
57
58    /// Battery serial number.
59    pub serial_number: Option<String>,
60
61    /// Battery technology/chemistry type.
62    pub technology: BatteryTechnology,
63
64    /// Current energy remaining in watt-hours.
65    pub energy_wh: f32,
66
67    /// Instantaneous power rate in watts (positive = charging, negative = discharging).
68    pub energy_rate_watts: f32,
69
70    // === macOS-specific fields (None on other platforms) ===
71    /// Charger wattage rating (e.g., 96W), macOS only.
72    pub charger_watts: Option<u32>,
73
74    /// Minimum state of charge today (0-100), macOS only.
75    pub daily_min_soc: Option<f32>,
76
77    /// Maximum state of charge today (0-100), macOS only.
78    pub daily_max_soc: Option<f32>,
79}
80
81impl BatteryInfo {
82    /// Calculate the current charging power in watts.
83    ///
84    /// Returns Some if charging and amperage is available.
85    pub fn charging_watts(&self) -> Option<f32> {
86        if self.state == ChargeState::Charging && self.amperage_ma > 0 {
87            let watts = (self.amperage_ma as f32 / 1000.0) * (self.voltage_mv as f32 / 1000.0);
88            Some(watts)
89        } else {
90            None
91        }
92    }
93
94    /// Calculate the current discharge power in watts.
95    ///
96    /// Returns Some if discharging and amperage is available.
97    pub fn discharge_watts(&self) -> Option<f32> {
98        if self.state == ChargeState::Discharging && self.amperage_ma < 0 {
99            let watts =
100                (self.amperage_ma.abs() as f32 / 1000.0) * (self.voltage_mv as f32 / 1000.0);
101            Some(watts)
102        } else {
103            None
104        }
105    }
106
107    /// Get the time remaining (to full or empty depending on state).
108    pub fn time_remaining(&self) -> Option<Duration> {
109        match self.state {
110            ChargeState::Charging => self.time_to_full,
111            ChargeState::Discharging => self.time_to_empty,
112            _ => None,
113        }
114    }
115
116    /// Format time remaining as a human-readable string.
117    pub fn time_remaining_formatted(&self) -> Option<String> {
118        self.time_remaining().and_then(|d| {
119            let total_mins = d.as_secs() / 60;
120            if total_mins == 0 {
121                return None;
122            }
123            let hours = total_mins / 60;
124            let mins = total_mins % 60;
125
126            if hours > 0 {
127                Some(format!("{}h {}m", hours, mins))
128            } else {
129                Some(format!("{}m", mins))
130            }
131        })
132    }
133}
134
135/// Trait for platform-specific battery providers.
136pub trait BatteryProvider {
137    /// Create a new battery provider instance.
138    fn new() -> Result<Self>
139    where
140        Self: Sized;
141
142    /// Refresh battery information from the system.
143    fn refresh(&mut self) -> Result<()>;
144
145    /// Get the current battery information.
146    fn info(&self) -> &BatteryInfo;
147
148    /// Check if battery monitoring is supported on this system.
149    fn is_supported() -> bool
150    where
151        Self: Sized,
152    {
153        true
154    }
155}