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
156 /// Check if a battery is available on this system.
157 fn is_available() -> bool
158 where
159 Self: Sized,
160 {
161 use starship_battery::Manager;
162 Manager::new()
163 .ok()
164 .and_then(|m| m.batteries().ok())
165 .and_then(|mut b| b.next())
166 .and_then(|b| b.ok())
167 .is_some()
168 }
169}