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:
parent
09a38dd136
commit
099e5c4ba6
4 changed files with 73 additions and 32 deletions
28
README.md
28
README.md
|
@ -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]
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
67
src/cpu.rs
67
src/cpu.rs
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue