From b4fb6dceb5b8aef8696a335c302ce88c5ab72ad2 Mon Sep 17 00:00:00 2001 From: Florian Warzecha Date: Wed, 1 May 2024 18:24:44 +0200 Subject: [PATCH 1/3] feat(tuxedo_sysfs): implement charging profiles --- tailor_hwcaps/src/main.rs | 57 ++++++++ tuxedo_sysfs/src/charging/charge_control.rs | 128 ++++++++++++++++++ tuxedo_sysfs/src/charging/charging_profile.rs | 68 ++++++++++ tuxedo_sysfs/src/charging/mod.rs | 38 ++++++ tuxedo_sysfs/src/lib.rs | 1 + tuxedo_sysfs/src/sysfs_util.rs | 24 ++++ 6 files changed, 316 insertions(+) create mode 100644 tuxedo_sysfs/src/charging/charge_control.rs create mode 100644 tuxedo_sysfs/src/charging/charging_profile.rs create mode 100644 tuxedo_sysfs/src/charging/mod.rs diff --git a/tailor_hwcaps/src/main.rs b/tailor_hwcaps/src/main.rs index bb4fea0..0a18d87 100644 --- a/tailor_hwcaps/src/main.rs +++ b/tailor_hwcaps/src/main.rs @@ -107,4 +107,61 @@ async fn sysfs() { print_value("LED mode", &controller.mode()); print_value("LED device color", &controller.get_color().await); } + + let mut charging_profile = tuxedo_sysfs::charging::ChargingProfile::new() + .await + .unwrap(); + print_value( + "Available charging profiles", + &charging_profile.available_charging_profiles, + ); + print_value( + "Current charging profile", + &charging_profile.get_charging_profile().await.unwrap(), + ); + if let Some(priorities) = &charging_profile.available_charging_priorities { + print_value("Available charging priorities", priorities); + } else { + print_info("Charging priority control is not available"); + } + if let Some(priority) = charging_profile.get_charging_priority().await.unwrap() { + print_value("Current charging priority", &priority); + } + + let first_battery = tuxedo_sysfs::charging::BatteryChargeControl::new_first_battery() + .await + .unwrap(); + if let Some(mut first_battery) = first_battery { + print_value("Battery name", &first_battery.name); + print_value( + "Battery charge type", + &first_battery.get_charge_type().await.unwrap(), + ); + if let Some(available_start_thresholds) = &first_battery.available_start_thresholds { + print_value( + "Available charge control start thresholds", + available_start_thresholds, + ); + } else { + print_info("Available charge control start thresholds not available"); + } + print_value( + "Battery start threshold", + &first_battery.get_start_threshold().await.unwrap(), + ); + if let Some(available_end_thresholds) = &first_battery.available_end_thresholds { + print_value( + "Available charge control end thresholds", + available_end_thresholds, + ); + } else { + print_info("Available charge control end thresholds not available"); + } + print_value( + "Battery end threshold", + &first_battery.get_end_threshold().await.unwrap(), + ); + } else { + print_info("Charge control for start/end thresholds is not available"); + } } diff --git a/tuxedo_sysfs/src/charging/charge_control.rs b/tuxedo_sysfs/src/charging/charge_control.rs new file mode 100644 index 0000000..b9b2970 --- /dev/null +++ b/tuxedo_sysfs/src/charging/charge_control.rs @@ -0,0 +1,128 @@ +use std::io; + +use crate::sysfs_util::{ + r_file, read_int_list, read_path_to_int_list, read_path_to_string, read_to_string, +}; + +use super::BatteryChargeControl; + +const SYSFS_POWER_SUPPLY_PATH: &str = "/sys/class/power_supply"; +const TYPE: &str = "/type"; +const CHARGE_TYPE: &str = "/charge_type"; +const START_THRESHOLD: &str = "/charge_control_start_threshold"; +const END_THRESHOLD: &str = "/charge_control_end_threshold"; +const AVAILABLE_START_THRESHOLDS: &str = "/charge_control_start_available_thresholds"; +const AVAILABLE_END_THRESHOLDS: &str = "/charge_control_end_available_thresholds"; + +impl BatteryChargeControl { + pub async fn new( + name: String, + available_start_thresholds: Option>, + available_end_thresholds: Option>, + start_threshold_file: tokio_uring::fs::File, + end_threshold_file: tokio_uring::fs::File, + charge_type_file: tokio_uring::fs::File, + ) -> Result { + Ok(Self { + name, + available_start_thresholds, + available_end_thresholds, + start_threshold_file, + end_threshold_file, + charge_type_file, + }) + } + + pub async fn new_first_battery() -> Result, io::Error> { + let mut dirs = tokio::fs::read_dir(SYSFS_POWER_SUPPLY_PATH).await?; + while let Some(dir) = dirs.next_entry().await? { + let path = dir.path(); + + let file_name = path + .file_name() + .expect("the sysfs path must have a last segment"); + + let type_path = path.join(TYPE); + if let Ok(typ) = read_path_to_string(type_path).await { + // not a battery, uninteresting + if typ.trim() != "Battery" { + continue; + } + } else { + tracing::warn!("Type file can't be read: {:?}", file_name); + continue; + } + + let start_threshold_file = + if let Ok(start_threshold_file) = r_file(path.join(START_THRESHOLD)).await { + start_threshold_file + } else { + // thresholds not supported + continue; + }; + let end_threshold_file = + if let Ok(end_threshold_file) = r_file(path.join(END_THRESHOLD)).await { + end_threshold_file + } else { + // thresholds not supported + continue; + }; + let charge_type_file = + if let Ok(charge_type_file) = r_file(path.join(CHARGE_TYPE)).await { + charge_type_file + } else { + // thresholds not supported + continue; + }; + + let available_start_thresholds_file = path.join(AVAILABLE_START_THRESHOLDS); + let available_start_thresholds = read_path_to_int_list(available_start_thresholds_file) + .await + .ok(); + + let available_end_thresholds_file = path.join(AVAILABLE_END_THRESHOLDS); + let available_end_thresholds = read_path_to_int_list(available_end_thresholds_file) + .await + .ok(); + + let name = file_name.to_string_lossy().into_owned(); + + // TODO: if there is more than 1 battery in the system, + // there is no guarantee which one is returned + return Ok(Some( + BatteryChargeControl::new( + name, + available_start_thresholds, + available_end_thresholds, + start_threshold_file, + end_threshold_file, + charge_type_file, + ) + .await?, + )); + } + + Ok(None) + } + + pub async fn get_start_threshold(&mut self) -> Result { + Ok(*read_int_list(&mut self.start_threshold_file) + .await? + .first() + .expect("start threshold file returns a value on read")) + } + + pub async fn get_end_threshold(&mut self) -> Result { + Ok(*read_int_list(&mut self.end_threshold_file) + .await? + .first() + .expect("end threshold file returns a value on read")) + } + + pub async fn get_charge_type(&mut self) -> Result { + Ok(read_to_string(&mut self.charge_type_file) + .await? + .trim() + .to_owned()) + } +} diff --git a/tuxedo_sysfs/src/charging/charging_profile.rs b/tuxedo_sysfs/src/charging/charging_profile.rs new file mode 100644 index 0000000..765d52c --- /dev/null +++ b/tuxedo_sysfs/src/charging/charging_profile.rs @@ -0,0 +1,68 @@ +use std::io; + +use crate::sysfs_util::{ + r_file, read_path_to_string_list, read_to_string, read_to_string_list, rw_file, +}; + +use super::ChargingProfile; + +const CHARGING_PROFILE_PATH: &str = + "/sys/devices/platform/tuxedo_keyboard/charging_profile/charging_profile"; +const CHARGING_PROFILES_AVAILABLE_PATH: &str = + "/sys/devices/platform/tuxedo_keyboard/charging_profile/charging_profiles_available"; +const CHARGING_PRIORITY_PATH: &str = + "/sys/devices/platform/tuxedo_keyboard/charging_profile/charging_prio"; +const CHARGING_PRIORITIES_AVAILABLE_PATH: &str = + "/sys/devices/platform/tuxedo_keyboard/charging_profile/charging_prios_available"; + +impl ChargingProfile { + // TODO: should this return Option in case the whole charging_profile thing is unavailable? + pub async fn new() -> Result { + let available_charging_profiles = + read_path_to_string_list(CHARGING_PROFILES_AVAILABLE_PATH).await?; + let charging_profile_file = rw_file(CHARGING_PROFILE_PATH).await?; + + let (available_charging_priorities, charging_priority_file) = + if let Ok(mut available_charging_priorities_file) = + r_file(CHARGING_PRIORITIES_AVAILABLE_PATH).await + { + let priorities = + read_to_string_list(&mut available_charging_priorities_file).await?; + + let priority_file = rw_file(CHARGING_PRIORITY_PATH).await?; + (Some(priorities), Some(priority_file)) + } else { + (None, None) + }; + + Ok(ChargingProfile { + available_charging_profiles, + charging_profile_file, + available_charging_priorities, + charging_priority_file, + }) + } + + pub async fn get_charging_profile(&mut self) -> Result { + Ok(read_to_string(&mut self.charging_profile_file) + .await? + .trim() + .to_owned()) + } + + // TODO: should this return Result