1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

docs: mention battery management; update sample config

This commit is contained in:
NotAShelf 2025-05-15 17:06:30 +03:00
parent 09a38dd136
commit 099e5c4ba6
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
4 changed files with 73 additions and 32 deletions

View file

@ -111,6 +111,26 @@ sudo superfreq set-min-freq 1200 --core-id 0
sudo superfreq set-max-freq 2800 --core-id 1 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 ## Configuration
Superfreq uses TOML configuration files. Default locations: Superfreq uses TOML configuration files. Default locations:
@ -139,6 +159,8 @@ platform_profile = "performance"
# Min/max frequency in MHz (optional) # Min/max frequency in MHz (optional)
min_freq_mhz = 800 min_freq_mhz = 800
max_freq_mhz = 3500 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 # Settings for when on battery power
[battery] [battery]
@ -149,6 +171,12 @@ epb = "balance_power"
platform_profile = "low-power" platform_profile = "low-power"
min_freq_mhz = 800 min_freq_mhz = 800
max_freq_mhz = 2500 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 configuration
[daemon] [daemon]

View file

@ -85,12 +85,12 @@ fn load_and_parse_config(path: &Path) -> Result<AppConfig, ConfigError> {
// Handle inheritance of values from global to profile configs // Handle inheritance of values from global to profile configs
let mut charger_profile = toml_app_config.charger.clone(); let mut charger_profile = toml_app_config.charger.clone();
let mut battery_profile = toml_app_config.battery.clone(); let mut battery_profile = toml_app_config.battery.clone();
// If profile-specific battery thresholds are not set, inherit from global config // If profile-specific battery thresholds are not set, inherit from global config
if charger_profile.battery_charge_thresholds.is_none() { if charger_profile.battery_charge_thresholds.is_none() {
charger_profile.battery_charge_thresholds = toml_app_config.battery_charge_thresholds; charger_profile.battery_charge_thresholds = toml_app_config.battery_charge_thresholds;
} }
if battery_profile.battery_charge_thresholds.is_none() { if battery_profile.battery_charge_thresholds.is_none() {
battery_profile.battery_charge_thresholds = toml_app_config.battery_charge_thresholds; battery_profile.battery_charge_thresholds = toml_app_config.battery_charge_thresholds;
} }

View file

@ -2,7 +2,11 @@ use crate::core::{GovernorOverrideMode, TurboSetting};
use crate::util::error::ControlError; use crate::util::error::ControlError;
use core::str; use core::str;
use log::debug; use log::debug;
use std::{fs, io, path::{Path, PathBuf}, string::ToString}; use std::{
fs, io,
path::{Path, PathBuf},
string::ToString,
};
pub type Result<T, E = ControlError> = std::result::Result<T, E>; pub type Result<T, E = ControlError> = std::result::Result<T, E>;
@ -357,7 +361,7 @@ pub fn get_governor_override() -> Option<String> {
/// ///
/// * `start_threshold` - The battery percentage at which charging should start (typically 0-99) /// * `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) /// * `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<()> { pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> Result<()> {
// Validate threshold values // Validate threshold values
if start_threshold >= stop_threshold { 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 { if stop_threshold > 100 {
return Err(ControlError::InvalidValueError(format!( return Err(ControlError::InvalidValueError(format!(
"Stop threshold ({}) cannot exceed 100%", "Stop threshold ({}) cannot exceed 100%",
stop_threshold stop_threshold
))); )));
} }
@ -386,7 +390,7 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) ->
ThresholdPathPattern { ThresholdPathPattern {
description: "ASUS", description: "ASUS",
start_path: "charge_control_start_percentage", start_path: "charge_control_start_percentage",
stop_path: "charge_control_end_percentage", stop_path: "charge_control_end_percentage",
}, },
// Huawei-specific paths // Huawei-specific paths
ThresholdPathPattern { 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"); let power_supply_path = Path::new("/sys/class/power_supply");
if !power_supply_path.exists() { if !power_supply_path.exists() {
return Err(ControlError::NotSupported( 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(); let mut supported_batteries = Vec::new();
// Scan all power supplies for battery threshold support // Scan all power supplies for battery threshold support
for entry in entries.flatten() { for entry in entries.flatten() {
let ps_path = entry.path(); let ps_path = entry.path();
let name = entry.file_name().into_string().unwrap_or_default(); let name = entry.file_name().into_string().unwrap_or_default();
// Skip non-battery devices // Skip non-battery devices
if !is_battery(&ps_path)? { if !is_battery(&ps_path)? {
continue; continue;
} }
// Try each threshold path pattern for this battery // Try each threshold path pattern for this battery
for pattern in &threshold_paths { for pattern in &threshold_paths {
let start_threshold_path = ps_path.join(&pattern.start_path); let start_threshold_path = ps_path.join(&pattern.start_path);
let stop_threshold_path = ps_path.join(&pattern.stop_path); let stop_threshold_path = ps_path.join(&pattern.stop_path);
if start_threshold_path.exists() && stop_threshold_path.exists() { if start_threshold_path.exists() && stop_threshold_path.exists() {
// Found a battery with threshold support // Found a battery with threshold support
supported_batteries.push(SupportedBattery { supported_batteries.push(SupportedBattery {
@ -438,57 +442,64 @@ pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) ->
pattern: pattern.clone(), pattern: pattern.clone(),
path: ps_path.clone(), path: ps_path.clone(),
}); });
// Found a supported pattern, no need to check others for this battery // Found a supported pattern, no need to check others for this battery
break; break;
} }
} }
} }
if supported_batteries.is_empty() { if supported_batteries.is_empty() {
return Err(ControlError::NotSupported( 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 // Apply thresholds to all supported batteries
let mut errors = Vec::new(); let mut errors = Vec::new();
let mut success_count = 0; let mut success_count = 0;
for battery in supported_batteries { for battery in supported_batteries {
let start_path = battery.path.join(&battery.pattern.start_path); let start_path = battery.path.join(&battery.pattern.start_path);
let stop_path = battery.path.join(&battery.pattern.stop_path); let stop_path = battery.path.join(&battery.pattern.stop_path);
// Attempt to set both thresholds // Attempt to set both thresholds
match ( match (
write_sysfs_value(&start_path, &start_threshold.to_string()), 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(_)) => { (Ok(_), Ok(_)) => {
debug!("Set {}-{}% charge thresholds for {} battery '{}'", debug!(
start_threshold, stop_threshold, battery.pattern.description, battery.name); "Set {}-{}% charge thresholds for {} battery '{}'",
start_threshold, stop_threshold, battery.pattern.description, battery.name
);
success_count += 1; success_count += 1;
}, }
(start_result, stop_result) => { (start_result, stop_result) => {
let mut error_msg = format!("Failed to set thresholds for {} battery '{}'", let mut error_msg = format!(
battery.pattern.description, battery.name); "Failed to set thresholds for {} battery '{}'",
battery.pattern.description, battery.name
);
if let Err(e) = start_result { if let Err(e) = start_result {
error_msg.push_str(&format!(": start threshold error: {}", e)); error_msg.push_str(&format!(": start threshold error: {}", e));
} }
if let Err(e) = stop_result { if let Err(e) = stop_result {
error_msg.push_str(&format!(": stop threshold error: {}", e)); error_msg.push_str(&format!(": stop threshold error: {}", e));
} }
errors.push(error_msg); errors.push(error_msg);
} }
} }
} }
if success_count > 0 { if success_count > 0 {
// As long as we successfully set thresholds on at least one battery, consider it a success // As long as we successfully set thresholds on at least one battery, consider it a success
if !errors.is_empty() { if !errors.is_empty() {
debug!("Partial success setting battery thresholds: {}", errors.join("; ")); debug!(
"Partial success setting battery thresholds: {}",
errors.join("; ")
);
} }
Ok(()) Ok(())
} else { } else {
@ -517,15 +528,15 @@ struct SupportedBattery {
/// Check if a power supply entry is a battery /// Check if a power supply entry is a battery
fn is_battery(path: &Path) -> Result<bool> { fn is_battery(path: &Path) -> Result<bool> {
let type_path = path.join("type"); let type_path = path.join("type");
if !type_path.exists() { if !type_path.exists() {
return Ok(false); return Ok(false);
} }
let ps_type = fs::read_to_string(&type_path) let ps_type = fs::read_to_string(&type_path)
.map_err(|_| ControlError::ReadError(format!("Failed to read {}", type_path.display())))? .map_err(|_| ControlError::ReadError(format!("Failed to read {}", type_path.display())))?
.trim() .trim()
.to_string(); .to_string();
Ok(ps_type == "Battery") Ok(ps_type == "Battery")
} }

View file

@ -200,7 +200,7 @@ fn apply_battery_charge_thresholds(
) -> Result<(), EngineError> { ) -> Result<(), EngineError> {
// Try profile-specific thresholds first, fall back to global thresholds // Try profile-specific thresholds first, fall back to global thresholds
let thresholds = profile_thresholds.or(global_thresholds); let thresholds = profile_thresholds.or(global_thresholds);
if let Some((start_threshold, stop_threshold)) = thresholds { if let Some((start_threshold, stop_threshold)) = thresholds {
info!("Setting battery charge thresholds: {start_threshold}-{stop_threshold}%"); info!("Setting battery charge thresholds: {start_threshold}-{stop_threshold}%");
match cpu::set_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 { } else {
// For permission errors, provide more helpful message // For permission errors, provide more helpful message
if matches!(e, ControlError::PermissionDenied(_)) { 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)) Err(EngineError::ControlError(e))
} }