From 099e5c4ba626dfc6085d50f604dd458bac67b8a9 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 15 May 2025 17:06:30 +0300 Subject: [PATCH] docs: mention battery management; update sample config --- README.md | 28 +++++++++++++++++++ src/config/load.rs | 4 +-- src/cpu.rs | 67 +++++++++++++++++++++++++++------------------- src/engine.rs | 6 +++-- 4 files changed, 73 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 88ebe6a..2ddf235 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,26 @@ sudo superfreq set-min-freq 1200 --core-id 0 sudo superfreq set-max-freq 2800 --core-id 1 ``` +### Battery Management + +```bash +# Set battery charging thresholds to extend battery lifespan +sudo superfreq set-battery-thresholds 40 80 # Start charging at 40%, stop at 80% +``` + +Battery charging thresholds help extend battery longevity by preventing constant +charging to 100%. Different laptop vendors implement this feature differently, +but Superfreq attempts to support multiple vendor implementations including: + +- Lenovo ThinkPad/IdeaPad (Standard implementation) +- ASUS laptops +- Huawei laptops +- Other devices using the standard Linux power_supply API + +Note that battery management is sensitive, and that your mileage may vary. +Please open an issue if your vendor is not supported, but patches would help +more than issue reports, as supporting hardware _needs_ hardware. + ## Configuration Superfreq uses TOML configuration files. Default locations: @@ -139,6 +159,8 @@ platform_profile = "performance" # Min/max frequency in MHz (optional) min_freq_mhz = 800 max_freq_mhz = 3500 +# Optional: Profile-specific battery charge thresholds (overrides global setting) +# battery_charge_thresholds = [40, 80] # Start at 40%, stop at 80% # Settings for when on battery power [battery] @@ -149,6 +171,12 @@ epb = "balance_power" platform_profile = "low-power" min_freq_mhz = 800 max_freq_mhz = 2500 +# Optional: Profile-specific battery charge thresholds (overrides global setting) +# battery_charge_thresholds = [60, 80] # Start at 60%, stop at 80% (more conservative) + +# Global battery charging thresholds (applied to both profiles unless overridden) +# Start charging at 40%, stop at 80% - extends battery lifespan +battery_charge_thresholds = [40, 80] # Daemon configuration [daemon] diff --git a/src/config/load.rs b/src/config/load.rs index 1414557..72c9c0c 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -85,12 +85,12 @@ fn load_and_parse_config(path: &Path) -> Result { // Handle inheritance of values from global to profile configs let mut charger_profile = toml_app_config.charger.clone(); let mut battery_profile = toml_app_config.battery.clone(); - + // If profile-specific battery thresholds are not set, inherit from global config if charger_profile.battery_charge_thresholds.is_none() { charger_profile.battery_charge_thresholds = toml_app_config.battery_charge_thresholds; } - + if battery_profile.battery_charge_thresholds.is_none() { battery_profile.battery_charge_thresholds = toml_app_config.battery_charge_thresholds; } diff --git a/src/cpu.rs b/src/cpu.rs index 188ff70..10a7acb 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -2,7 +2,11 @@ use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::util::error::ControlError; use core::str; use log::debug; -use std::{fs, io, path::{Path, PathBuf}, string::ToString}; +use std::{ + fs, io, + path::{Path, PathBuf}, + string::ToString, +}; pub type Result = std::result::Result; @@ -357,7 +361,7 @@ pub fn get_governor_override() -> Option { /// /// * `start_threshold` - The battery percentage at which charging should start (typically 0-99) /// * `stop_threshold` - The battery percentage at which charging should stop (typically 1-100) -/// +/// pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> Result<()> { // Validate threshold values if start_threshold >= stop_threshold { @@ -369,7 +373,7 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> if stop_threshold > 100 { return Err(ControlError::InvalidValueError(format!( - "Stop threshold ({}) cannot exceed 100%", + "Stop threshold ({}) cannot exceed 100%", stop_threshold ))); } @@ -386,7 +390,7 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> ThresholdPathPattern { description: "ASUS", start_path: "charge_control_start_percentage", - stop_path: "charge_control_end_percentage", + stop_path: "charge_control_end_percentage", }, // Huawei-specific paths ThresholdPathPattern { @@ -399,7 +403,7 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> let power_supply_path = Path::new("/sys/class/power_supply"); if !power_supply_path.exists() { return Err(ControlError::NotSupported( - "Power supply path not found, battery threshold control not supported".to_string() + "Power supply path not found, battery threshold control not supported".to_string(), )); } @@ -415,22 +419,22 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> })?; let mut supported_batteries = Vec::new(); - + // Scan all power supplies for battery threshold support for entry in entries.flatten() { let ps_path = entry.path(); let name = entry.file_name().into_string().unwrap_or_default(); - + // Skip non-battery devices if !is_battery(&ps_path)? { continue; } - + // Try each threshold path pattern for this battery for pattern in &threshold_paths { let start_threshold_path = ps_path.join(&pattern.start_path); let stop_threshold_path = ps_path.join(&pattern.stop_path); - + if start_threshold_path.exists() && stop_threshold_path.exists() { // Found a battery with threshold support supported_batteries.push(SupportedBattery { @@ -438,57 +442,64 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> pattern: pattern.clone(), path: ps_path.clone(), }); - + // Found a supported pattern, no need to check others for this battery break; } } } - + if supported_batteries.is_empty() { return Err(ControlError::NotSupported( - "No batteries with charge threshold control support found".to_string() + "No batteries with charge threshold control support found".to_string(), )); } - + // Apply thresholds to all supported batteries let mut errors = Vec::new(); let mut success_count = 0; - + for battery in supported_batteries { let start_path = battery.path.join(&battery.pattern.start_path); let stop_path = battery.path.join(&battery.pattern.stop_path); - + // Attempt to set both thresholds match ( write_sysfs_value(&start_path, &start_threshold.to_string()), - write_sysfs_value(&stop_path, &stop_threshold.to_string()) + write_sysfs_value(&stop_path, &stop_threshold.to_string()), ) { (Ok(_), Ok(_)) => { - debug!("Set {}-{}% charge thresholds for {} battery '{}'", - start_threshold, stop_threshold, battery.pattern.description, battery.name); + debug!( + "Set {}-{}% charge thresholds for {} battery '{}'", + start_threshold, stop_threshold, battery.pattern.description, battery.name + ); success_count += 1; - }, + } (start_result, stop_result) => { - let mut error_msg = format!("Failed to set thresholds for {} battery '{}'", - battery.pattern.description, battery.name); - + let mut error_msg = format!( + "Failed to set thresholds for {} battery '{}'", + battery.pattern.description, battery.name + ); + if let Err(e) = start_result { error_msg.push_str(&format!(": start threshold error: {}", e)); } if let Err(e) = stop_result { error_msg.push_str(&format!(": stop threshold error: {}", e)); } - + errors.push(error_msg); } } } - + if success_count > 0 { // As long as we successfully set thresholds on at least one battery, consider it a success if !errors.is_empty() { - debug!("Partial success setting battery thresholds: {}", errors.join("; ")); + debug!( + "Partial success setting battery thresholds: {}", + errors.join("; ") + ); } Ok(()) } else { @@ -517,15 +528,15 @@ struct SupportedBattery { /// Check if a power supply entry is a battery fn is_battery(path: &Path) -> Result { let type_path = path.join("type"); - + if !type_path.exists() { return Ok(false); } - + let ps_type = fs::read_to_string(&type_path) .map_err(|_| ControlError::ReadError(format!("Failed to read {}", type_path.display())))? .trim() .to_string(); - + Ok(ps_type == "Battery") } diff --git a/src/engine.rs b/src/engine.rs index 99cd2f8..b1ddb38 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -200,7 +200,7 @@ fn apply_battery_charge_thresholds( ) -> Result<(), EngineError> { // Try profile-specific thresholds first, fall back to global thresholds let thresholds = profile_thresholds.or(global_thresholds); - + if let Some((start_threshold, stop_threshold)) = thresholds { info!("Setting battery charge thresholds: {start_threshold}-{stop_threshold}%"); match cpu::set_battery_charge_thresholds(start_threshold, stop_threshold) { @@ -216,7 +216,9 @@ fn apply_battery_charge_thresholds( } else { // For permission errors, provide more helpful message if matches!(e, ControlError::PermissionDenied(_)) { - debug!("Permission denied setting battery thresholds - requires root privileges"); + debug!( + "Permission denied setting battery thresholds - requires root privileges" + ); } Err(EngineError::ControlError(e)) }