mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
battery: clean up, rename to power_supply
This commit is contained in:
parent
87085f913b
commit
d0932ae78c
5 changed files with 190 additions and 295 deletions
267
src/battery.rs
267
src/battery.rs
|
@ -1,267 +0,0 @@
|
||||||
use crate::{config::types::BatteryChargeThresholds, util::error::ControlError, util::sysfs};
|
|
||||||
use std::{
|
|
||||||
fs, io,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
/// Represents a pattern of path suffixes used to control battery charge thresholds
|
|
||||||
/// for different device vendors.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ThresholdPathPattern {
|
|
||||||
pub description: &'static str,
|
|
||||||
pub start_path: &'static str,
|
|
||||||
pub stop_path: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Threshold patterns
|
|
||||||
const THRESHOLD_PATTERNS: &[ThresholdPathPattern] = &[
|
|
||||||
ThresholdPathPattern {
|
|
||||||
description: "Standard",
|
|
||||||
start_path: "charge_control_start_threshold",
|
|
||||||
stop_path: "charge_control_end_threshold",
|
|
||||||
},
|
|
||||||
ThresholdPathPattern {
|
|
||||||
description: "ASUS",
|
|
||||||
start_path: "charge_control_start_percentage",
|
|
||||||
stop_path: "charge_control_end_percentage",
|
|
||||||
},
|
|
||||||
// Combine Huawei and ThinkPad since they use identical paths
|
|
||||||
ThresholdPathPattern {
|
|
||||||
description: "ThinkPad/Huawei",
|
|
||||||
start_path: "charge_start_threshold",
|
|
||||||
stop_path: "charge_stop_threshold",
|
|
||||||
},
|
|
||||||
// Framework laptop support
|
|
||||||
ThresholdPathPattern {
|
|
||||||
description: "Framework",
|
|
||||||
start_path: "charge_behaviour_start_threshold",
|
|
||||||
stop_path: "charge_behaviour_end_threshold",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Represents a battery that supports charge threshold control
|
|
||||||
pub struct SupportedBattery<'a> {
|
|
||||||
pub name: String,
|
|
||||||
pub pattern: &'a ThresholdPathPattern,
|
|
||||||
pub path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set battery charge thresholds to protect battery health
|
|
||||||
///
|
|
||||||
/// This sets the start and stop charging thresholds for batteries that support this feature.
|
|
||||||
/// Different laptop vendors implement battery thresholds in different ways, so this function
|
|
||||||
/// attempts to handle multiple implementations (Lenovo, ASUS, etc.).
|
|
||||||
///
|
|
||||||
/// The thresholds determine at what percentage the battery starts charging (when below `start_threshold`)
|
|
||||||
/// and at what percentage it stops (when it reaches `stop_threshold`).
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `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)
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error if:
|
|
||||||
/// - The thresholds are invalid (start >= stop or stop > 100)
|
|
||||||
/// - No power supply path is found
|
|
||||||
/// - No batteries with threshold support are found
|
|
||||||
/// - Failed to set thresholds on any battery
|
|
||||||
pub fn set_battery_charge_thresholds(start_threshold: u8, stop_threshold: u8) -> Result<()> {
|
|
||||||
// Validate thresholds using `BatteryChargeThresholds`
|
|
||||||
let thresholds =
|
|
||||||
BatteryChargeThresholds::new(start_threshold, stop_threshold).map_err(|e| match e {
|
|
||||||
crate::config::types::ConfigError::Validation(msg) => {
|
|
||||||
ControlError::InvalidValueError(msg)
|
|
||||||
}
|
|
||||||
_ => ControlError::InvalidValueError(format!("Invalid battery threshold values: {e}")),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: Skip checking directory writability since /sys is a virtual filesystem
|
|
||||||
// Individual file writability will be checked by find_battery_with_threshold_support
|
|
||||||
|
|
||||||
let supported_batteries = find_supported_batteries(power_supply_path)?;
|
|
||||||
if supported_batteries.is_empty() {
|
|
||||||
return Err(ControlError::NotSupported(
|
|
||||||
"No batteries with charge threshold control support found".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_thresholds_to_batteries(&supported_batteries, thresholds.start, thresholds.stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds all batteries in the system that support threshold control
|
|
||||||
fn find_supported_batteries(power_supply_path: &Path) -> Result<Vec<SupportedBattery<'static>>> {
|
|
||||||
let entries = fs::read_dir(power_supply_path).map_err(|e| {
|
|
||||||
if e.kind() == io::ErrorKind::PermissionDenied {
|
|
||||||
ControlError::PermissionDenied(format!(
|
|
||||||
"Permission denied accessing power supply directory: {}",
|
|
||||||
power_supply_path.display()
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
ControlError::Io(e)
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut supported_batteries = Vec::new();
|
|
||||||
for entry in entries {
|
|
||||||
let entry = match entry {
|
|
||||||
Ok(e) => e,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Failed to read power-supply entry: {e}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let ps_path = entry.path();
|
|
||||||
if is_battery(&ps_path)? {
|
|
||||||
if let Some(battery) = find_battery_with_threshold_support(&ps_path) {
|
|
||||||
supported_batteries.push(battery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if supported_batteries.is_empty() {
|
|
||||||
log::warn!("No batteries with charge threshold support found");
|
|
||||||
} else {
|
|
||||||
log::debug!(
|
|
||||||
"Found {} batteries with threshold support",
|
|
||||||
supported_batteries.len()
|
|
||||||
);
|
|
||||||
for battery in &supported_batteries {
|
|
||||||
log::debug!(
|
|
||||||
"Battery '{}' supports {} threshold control",
|
|
||||||
battery.name,
|
|
||||||
battery.pattern.description
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(supported_batteries)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies the threshold settings to all supported batteries
|
|
||||||
fn apply_thresholds_to_batteries(
|
|
||||||
batteries: &[SupportedBattery<'_>],
|
|
||||||
start_threshold: u8,
|
|
||||||
stop_threshold: u8,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
let mut success_count = 0;
|
|
||||||
|
|
||||||
for battery in batteries {
|
|
||||||
let start_path = battery.path.join(battery.pattern.start_path);
|
|
||||||
let stop_path = battery.path.join(battery.pattern.stop_path);
|
|
||||||
|
|
||||||
// Read current thresholds in case we need to restore them
|
|
||||||
let current_stop = sysfs::read_sysfs_value(&stop_path).ok();
|
|
||||||
|
|
||||||
// Write stop threshold first (must be >= start threshold)
|
|
||||||
let stop_result = sysfs::write_sysfs_value(&stop_path, &stop_threshold.to_string());
|
|
||||||
|
|
||||||
// Only proceed to set start threshold if stop threshold was set successfully
|
|
||||||
if matches!(stop_result, Ok(())) {
|
|
||||||
let start_result = sysfs::write_sysfs_value(&start_path, &start_threshold.to_string());
|
|
||||||
|
|
||||||
match start_result {
|
|
||||||
Ok(()) => {
|
|
||||||
log::debug!(
|
|
||||||
"Set {}-{}% charge thresholds for {} battery '{}'",
|
|
||||||
start_threshold,
|
|
||||||
stop_threshold,
|
|
||||||
battery.pattern.description,
|
|
||||||
battery.name
|
|
||||||
);
|
|
||||||
success_count += 1;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// Start threshold failed, try to restore the previous stop threshold
|
|
||||||
if let Some(prev_stop) = ¤t_stop {
|
|
||||||
let restore_result = sysfs::write_sysfs_value(&stop_path, prev_stop);
|
|
||||||
if let Err(re) = restore_result {
|
|
||||||
log::warn!(
|
|
||||||
"Failed to restore previous stop threshold for battery '{}': {}. Battery may be in an inconsistent state.",
|
|
||||||
battery.name,
|
|
||||||
re
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log::debug!(
|
|
||||||
"Restored previous stop threshold ({}) for battery '{}'",
|
|
||||||
prev_stop,
|
|
||||||
battery.name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errors.push(format!(
|
|
||||||
"Failed to set start threshold for {} battery '{}': {}",
|
|
||||||
battery.pattern.description, battery.name, e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Err(e) = stop_result {
|
|
||||||
errors.push(format!(
|
|
||||||
"Failed to set stop threshold for {} battery '{}': {}",
|
|
||||||
battery.pattern.description, battery.name, e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if success_count > 0 {
|
|
||||||
if !errors.is_empty() {
|
|
||||||
log::warn!(
|
|
||||||
"Partial success setting battery thresholds: {}",
|
|
||||||
errors.join("; ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ControlError::WriteError(format!(
|
|
||||||
"Failed to set charge thresholds on any battery: {}",
|
|
||||||
errors.join("; ")
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines if a power supply entry is a battery
|
|
||||||
fn is_battery(path: &Path) -> Result<bool> {
|
|
||||||
let type_path = path.join("type");
|
|
||||||
|
|
||||||
if !type_path.exists() {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ps_type = sysfs::read_sysfs_value(&type_path).map_err(|e| {
|
|
||||||
ControlError::ReadError(format!("Failed to read {}: {}", type_path.display(), e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(ps_type == "Battery")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Identifies if a battery supports threshold control and which pattern it uses
|
|
||||||
fn find_battery_with_threshold_support(ps_path: &Path) -> Option<SupportedBattery<'static>> {
|
|
||||||
for pattern in THRESHOLD_PATTERNS {
|
|
||||||
let start_threshold_path = ps_path.join(pattern.start_path);
|
|
||||||
let stop_threshold_path = ps_path.join(pattern.stop_path);
|
|
||||||
|
|
||||||
// Ensure both paths exist and are writable before considering this battery supported
|
|
||||||
if sysfs::path_exists_and_writable(&start_threshold_path)
|
|
||||||
&& sysfs::path_exists_and_writable(&stop_threshold_path)
|
|
||||||
{
|
|
||||||
return Some(SupportedBattery {
|
|
||||||
name: ps_path.file_name()?.to_string_lossy().to_string(),
|
|
||||||
pattern,
|
|
||||||
path: ps_path.to_path_buf(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
|
@ -15,12 +15,12 @@ macro_rules! default_const {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct BatteryChargeThresholds {
|
pub struct PowerSupplyChargeThresholds {
|
||||||
pub start: u8,
|
pub start: u8,
|
||||||
pub stop: u8,
|
pub stop: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatteryChargeThresholds {
|
impl PowerSupplyChargeThresholds {
|
||||||
pub fn new(start: u8, stop: u8) -> Result<Self, ConfigError> {
|
pub fn new(start: u8, stop: u8) -> Result<Self, ConfigError> {
|
||||||
if stop == 0 {
|
if stop == 0 {
|
||||||
return Err(ConfigError::Validation(
|
return Err(ConfigError::Validation(
|
||||||
|
@ -42,7 +42,7 @@ impl BatteryChargeThresholds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<(u8, u8)> for BatteryChargeThresholds {
|
impl TryFrom<(u8, u8)> for PowerSupplyChargeThresholds {
|
||||||
type Error = ConfigError;
|
type Error = ConfigError;
|
||||||
|
|
||||||
fn try_from(values: (u8, u8)) -> Result<Self, Self::Error> {
|
fn try_from(values: (u8, u8)) -> Result<Self, Self::Error> {
|
||||||
|
@ -66,7 +66,7 @@ pub struct ProfileConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub enable_auto_turbo: bool,
|
pub enable_auto_turbo: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
pub battery_charge_thresholds: Option<PowerSupplyChargeThresholds>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProfileConfig {
|
impl Default for ProfileConfig {
|
||||||
|
@ -124,7 +124,7 @@ pub struct ProfileConfigToml {
|
||||||
#[serde(default = "default_enable_auto_turbo")]
|
#[serde(default = "default_enable_auto_turbo")]
|
||||||
pub enable_auto_turbo: bool,
|
pub enable_auto_turbo: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
pub battery_charge_thresholds: Option<PowerSupplyChargeThresholds>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||||
|
@ -134,7 +134,7 @@ pub struct AppConfigToml {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub battery: ProfileConfigToml,
|
pub battery: ProfileConfigToml,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
pub battery_charge_thresholds: Option<PowerSupplyChargeThresholds>,
|
||||||
pub ignored_power_supplies: Option<Vec<String>>,
|
pub ignored_power_supplies: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub daemon: DaemonConfigToml,
|
pub daemon: DaemonConfigToml,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::battery;
|
|
||||||
use crate::config::{AppConfig, ProfileConfig, TurboAutoSettings};
|
use crate::config::{AppConfig, ProfileConfig, TurboAutoSettings};
|
||||||
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
||||||
use crate::cpu::{self};
|
use crate::cpu::{self};
|
||||||
|
use crate::power_supply;
|
||||||
use crate::util::error::{ControlError, EngineError};
|
use crate::util::error::{ControlError, EngineError};
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -277,7 +277,7 @@ pub fn determine_and_apply_settings(
|
||||||
|
|
||||||
if start_threshold < stop_threshold && stop_threshold <= 100 {
|
if start_threshold < stop_threshold && stop_threshold <= 100 {
|
||||||
log::info!("Setting battery charge thresholds: {start_threshold}-{stop_threshold}%");
|
log::info!("Setting battery charge thresholds: {start_threshold}-{stop_threshold}%");
|
||||||
match battery::set_battery_charge_thresholds(start_threshold, stop_threshold) {
|
match power_supply::set_battery_charge_thresholds(start_threshold, stop_threshold) {
|
||||||
Ok(()) => log::debug!("Battery charge thresholds set successfully"),
|
Ok(()) => log::debug!("Battery charge thresholds set successfully"),
|
||||||
Err(e) => log::warn!("Failed to set battery charge thresholds: {e}"),
|
Err(e) => log::warn!("Failed to set battery charge thresholds: {e}"),
|
||||||
}
|
}
|
||||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -1,4 +1,3 @@
|
||||||
mod battery;
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod core;
|
mod core;
|
||||||
|
@ -6,6 +5,7 @@ mod cpu;
|
||||||
mod daemon;
|
mod daemon;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
|
mod power_supply;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use anyhow::{Context, anyhow, bail};
|
use anyhow::{Context, anyhow, bail};
|
||||||
|
@ -148,27 +148,14 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
cpu::set_platform_profile(platform_profile)?;
|
cpu::set_platform_profile(platform_profile)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is like this because [`cpu`] doesn't expose
|
for power_supply in power_supply::get_power_supplies()? {
|
||||||
// a way of setting them individually. Will clean this up
|
if let Some(threshold_start) = charge_threshold_start {
|
||||||
// after that is cleaned.
|
power_supply::set_charge_threshold_start(&power_supply, threshold_start)?;
|
||||||
if charge_threshold_start.is_some() || charge_threshold_end.is_some() {
|
|
||||||
let charge_threshold_start = charge_threshold_start.ok_or_else(|| {
|
|
||||||
anyhow!("both charge thresholds should be given at the same time")
|
|
||||||
})?;
|
|
||||||
let charge_threshold_end = charge_threshold_end.ok_or_else(|| {
|
|
||||||
anyhow!("both charge thresholds should be given at the same time")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if charge_threshold_start >= charge_threshold_end {
|
|
||||||
bail!(
|
|
||||||
"charge start threshold (given as {charge_threshold_start}) must be less than stop threshold (given as {charge_threshold_end})"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
battery::set_battery_charge_thresholds(
|
if let Some(threshold_end) = charge_threshold_end {
|
||||||
charge_threshold_start,
|
power_supply::set_charge_threshold_end(&power_supply, threshold_end)?;
|
||||||
charge_threshold_end,
|
}
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
175
src/power_supply.rs
Normal file
175
src/power_supply.rs
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a pattern of path suffixes used to control charge thresholds
|
||||||
|
/// for different device vendors.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct PowerSupplyConfig {
|
||||||
|
pub manufacturer: &'static str,
|
||||||
|
pub path_start: &'static str,
|
||||||
|
pub path_end: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Charge threshold configs.
|
||||||
|
const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[
|
||||||
|
PowerSupplyConfig {
|
||||||
|
manufacturer: "Standard",
|
||||||
|
path_start: "charge_control_start_threshold",
|
||||||
|
path_end: "charge_control_end_threshold",
|
||||||
|
},
|
||||||
|
PowerSupplyConfig {
|
||||||
|
manufacturer: "ASUS",
|
||||||
|
path_start: "charge_control_start_percentage",
|
||||||
|
path_end: "charge_control_end_percentage",
|
||||||
|
},
|
||||||
|
// Combine Huawei and ThinkPad since they use identical paths.
|
||||||
|
PowerSupplyConfig {
|
||||||
|
manufacturer: "ThinkPad/Huawei",
|
||||||
|
path_start: "charge_start_threshold",
|
||||||
|
path_end: "charge_stop_threshold",
|
||||||
|
},
|
||||||
|
// Framework laptop support.
|
||||||
|
PowerSupplyConfig {
|
||||||
|
manufacturer: "Framework",
|
||||||
|
path_start: "charge_behaviour_start_threshold",
|
||||||
|
path_end: "charge_behaviour_end_threshold",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Represents a power supply that supports charge threshold control.
|
||||||
|
pub struct PowerSupply {
|
||||||
|
pub name: String,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub config: PowerSupplyConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PowerSupply {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"power suppply '{name}' from manufacturer '{manufacturer}'",
|
||||||
|
name = &self.name,
|
||||||
|
manufacturer = &self.config.manufacturer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowerSupply {
|
||||||
|
pub fn charge_threshold_path_start(&self) -> PathBuf {
|
||||||
|
self.path.join(self.config.path_start)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn charge_threshold_path_end(&self) -> PathBuf {
|
||||||
|
self.path.join(self.config.path_end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Migrate to central utils file. Same exists in cpu.rs.
|
||||||
|
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
fs::write(path, value).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to write '{value}' to '{path}'",
|
||||||
|
path = path.display(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_power_supply(path: &Path) -> anyhow::Result<bool> {
|
||||||
|
let type_path = path.join("type");
|
||||||
|
|
||||||
|
let type_ = fs::read_to_string(&type_path)
|
||||||
|
.with_context(|| format!("failed to read '{path}'", path = type_path.display()))?;
|
||||||
|
|
||||||
|
Ok(type_ == "Battery")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all batteries in the system that support threshold control.
|
||||||
|
pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
||||||
|
const PATH: &str = "/sys/class/power_supply";
|
||||||
|
|
||||||
|
let mut power_supplies = Vec::new();
|
||||||
|
|
||||||
|
'entries: for entry in fs::read_dir(PATH).with_context(|| format!("failed to read '{PATH}'"))? {
|
||||||
|
let entry = match entry {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("failed to read power supply entry: {error}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry_path = entry.path();
|
||||||
|
|
||||||
|
if !is_power_supply(&entry_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to determine whether if '{path}' is a power supply",
|
||||||
|
path = entry_path.display(),
|
||||||
|
)
|
||||||
|
})? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for config in POWER_SUPPLY_CONFIGS {
|
||||||
|
if entry_path.join(config.path_start).exists()
|
||||||
|
&& entry_path.join(config.path_end).exists()
|
||||||
|
{
|
||||||
|
power_supplies.push(PowerSupply {
|
||||||
|
name: entry_path
|
||||||
|
.file_name()
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to get file name of '{path}'",
|
||||||
|
path = entry_path.display(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
|
||||||
|
path: entry_path,
|
||||||
|
|
||||||
|
config: *config,
|
||||||
|
});
|
||||||
|
continue 'entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(power_supplies)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_charge_threshold_start(
|
||||||
|
power_supply: &PowerSupply,
|
||||||
|
charge_threshold_start: u8,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
write(
|
||||||
|
&power_supply.charge_threshold_path_start(),
|
||||||
|
&charge_threshold_start.to_string(),
|
||||||
|
)
|
||||||
|
.with_context(|| format!("failed to set charge threshold start for {power_supply}"))?;
|
||||||
|
|
||||||
|
log::info!("set battery threshold start for {power_supply} to {charge_threshold_start}%");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_charge_threshold_end(
|
||||||
|
power_supply: &PowerSupply,
|
||||||
|
charge_threshold_end: u8,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
write(
|
||||||
|
&power_supply.charge_threshold_path_end(),
|
||||||
|
&charge_threshold_end.to_string(),
|
||||||
|
)
|
||||||
|
.with_context(|| format!("failed to set charge threshold end for {power_supply}"))?;
|
||||||
|
|
||||||
|
log::info!("set battery threshold end for {power_supply} to {charge_threshold_end}%");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue