diff --git a/tailor_hwcaps/src/main.rs b/tailor_hwcaps/src/main.rs index bb4fea0..5aa6e12 100644 --- a/tailor_hwcaps/src/main.rs +++ b/tailor_hwcaps/src/main.rs @@ -107,4 +107,73 @@ async fn sysfs() { print_value("LED mode", &controller.mode()); print_value("LED device color", &controller.get_color().await); } + + let charging_profile = tuxedo_sysfs::charging::ChargingProfile::new() + .await + .unwrap(); + if let Some(mut charging_profile) = charging_profile { + print_value( + "Available charging profiles", + &charging_profile.available_charging_profiles, + ); + print_value( + "Current charging profile", + &charging_profile.get_charging_profile().await.unwrap(), + ); + } else { + print_info("Charging profile control is not available"); + } + + let charging_priority = tuxedo_sysfs::charging::ChargingPriority::new() + .await + .unwrap(); + if let Some(mut charging_priority) = charging_priority { + print_value( + "Available charging priorities", + &charging_priority.available_charging_priorities, + ); + print_value( + "Current charging priority", + &charging_priority.get_charging_priority().await.unwrap(), + ); + } else { + print_info("Charging priority control is not available"); + } + + 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..7cff791 --- /dev/null +++ b/tuxedo_sysfs/src/charging/charge_control.rs @@ -0,0 +1,147 @@ +use std::io; + +use crate::sysfs_util::{ + r_file, read_int_list, read_path_to_int_list, read_path_to_string, read_to_string, write_int, + write_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()) + } + + /// + /// (section charge_type) + pub async fn set_charge_type(&mut self, charge_type: String) -> Result<(), io::Error> { + write_string(&mut self.charge_type_file, charge_type).await + } + + /// generally: 0-100, + /// but may be further restricted to [`Self::available_start_thresholds`] + pub async fn set_start_threshold(&mut self, threshold: u32) -> Result<(), io::Error> { + write_int(&mut self.start_threshold_file, threshold).await + } + + /// generally: 0-100, + /// but may be further restricted to [`Self::available_end_thresholds`] + pub async fn set_end_threshold(&mut self, threshold: u32) -> Result<(), io::Error> { + write_int(&mut self.end_threshold_file, threshold).await + } +} diff --git a/tuxedo_sysfs/src/charging/charging_priority.rs b/tuxedo_sysfs/src/charging/charging_priority.rs new file mode 100644 index 0000000..17264f0 --- /dev/null +++ b/tuxedo_sysfs/src/charging/charging_priority.rs @@ -0,0 +1,43 @@ +use std::io; + +use crate::sysfs_util::{r_file, read_to_string, read_to_string_list, rw_file, write_string}; + +use super::ChargingPriority; + +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 ChargingPriority { + pub async fn new() -> Result, io::Error> { + let mut available_charging_priorities_file = + match r_file(CHARGING_PRIORITIES_AVAILABLE_PATH).await { + Ok(f) => f, + Err(_) => return Ok(None), + }; + let priorities = read_to_string_list(&mut available_charging_priorities_file).await?; + + let priority_file = rw_file(CHARGING_PRIORITY_PATH).await?; + + Ok(Some(ChargingPriority { + available_charging_priorities: priorities, + charging_priority_file: priority_file, + })) + } + + pub async fn get_charging_priority(&mut self) -> Result { + Ok(read_to_string(&mut self.charging_priority_file) + .await? + .trim() + .to_owned()) + } + + /// charge_battery, performance + /// src/ng-app/app/charging-settings/charging-settings.component.ts + /// in TCC + /// / src/uniwill_keyboard.h in tuxedo-keyboard + pub async fn set_charging_priority(&mut self, priority: String) -> Result<(), io::Error> { + write_string(&mut self.charging_priority_file, priority).await + } +} diff --git a/tuxedo_sysfs/src/charging/charging_profile.rs b/tuxedo_sysfs/src/charging/charging_profile.rs new file mode 100644 index 0000000..6a86d0e --- /dev/null +++ b/tuxedo_sysfs/src/charging/charging_profile.rs @@ -0,0 +1,43 @@ +use std::io; + +use crate::sysfs_util::{r_file, read_to_string, read_to_string_list, rw_file, write_string}; + +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"; + +impl ChargingProfile { + pub async fn new() -> Result, io::Error> { + let mut available_charging_profiles_file = + match r_file(CHARGING_PROFILES_AVAILABLE_PATH).await { + Ok(f) => f, + Err(_) => return Ok(None), + }; + let available_charging_profiles = + read_to_string_list(&mut available_charging_profiles_file).await?; + let charging_profile_file = rw_file(CHARGING_PROFILE_PATH).await?; + + Ok(Some(ChargingProfile { + available_charging_profiles, + charging_profile_file, + })) + } + + pub async fn get_charging_profile(&mut self) -> Result { + Ok(read_to_string(&mut self.charging_profile_file) + .await? + .trim() + .to_owned()) + } + + /// high_capacity, balanced, stationary according to + /// src/ng-app/app/charging-settings/charging-settings.component.ts + /// in TCC + /// / src/uniwill_keyboard.h in tuxedo-keyboard + pub async fn set_charging_profile(&mut self, profile: String) -> Result<(), io::Error> { + write_string(&mut self.charging_profile_file, profile).await + } +} diff --git a/tuxedo_sysfs/src/charging/mod.rs b/tuxedo_sysfs/src/charging/mod.rs new file mode 100644 index 0000000..fa4d79c --- /dev/null +++ b/tuxedo_sysfs/src/charging/mod.rs @@ -0,0 +1,40 @@ +mod charge_control; +mod charging_priority; +mod charging_profile; + +/// A type that manages all sysfs files related to +/// charging profile options provided by the tuxedo_keyboard driver. +/// +/// The charging profile imposes a firmware-enforced limit on the maximum charge of the +/// battery. +pub struct ChargingProfile { + pub available_charging_profiles: Vec, + charging_profile_file: tokio_uring::fs::File, +} + +/// A type that manages all sysfs files related to +/// charging priority options provided by the tuxedo_keyboard driver. +/// +/// The charging priority determines whether charging speed or +/// performance is prioritized when charging over USB-C. +pub struct ChargingPriority { + pub available_charging_priorities: Vec, + charging_priority_file: tokio_uring::fs::File, +} + +/// A type that manages all sysfs files related to charging start/end thresholds. +pub struct BatteryChargeControl { + pub name: String, + pub available_start_thresholds: Option>, + pub available_end_thresholds: Option>, + /// Percentage value between 0-100, + /// [`Self::available_start_thresholds`] lists further restrictions on accepted values. + start_threshold_file: tokio_uring::fs::File, + /// Percentage value between 0-100, + /// [`Self::available_end_thresholds`] lists further restrictions on accepted values. + end_threshold_file: tokio_uring::fs::File, + /// Must be 'Custom' to allow for custom thresholds. + /// + /// Possible values listed at (section: /sys/class/power_supply//charge_type) + charge_type_file: tokio_uring::fs::File, +} diff --git a/tuxedo_sysfs/src/lib.rs b/tuxedo_sysfs/src/lib.rs index abede48..ef716aa 100644 --- a/tuxedo_sysfs/src/lib.rs +++ b/tuxedo_sysfs/src/lib.rs @@ -1,2 +1,3 @@ +pub mod charging; pub mod led; pub(crate) mod sysfs_util; diff --git a/tuxedo_sysfs/src/sysfs_util.rs b/tuxedo_sysfs/src/sysfs_util.rs index b0eb510..5d10118 100644 --- a/tuxedo_sysfs/src/sysfs_util.rs +++ b/tuxedo_sysfs/src/sysfs_util.rs @@ -36,6 +36,14 @@ pub(crate) async fn read_to_string(file: &mut fs::File) -> Result(path: P) -> Result, io::Error> +where + P: AsRef, +{ + let mut file = r_file(path).await?; + read_int_list(&mut file).await +} + pub(crate) async fn read_int_list(file: &mut fs::File) -> Result, io::Error> { let content = read_to_string(file).await?; @@ -55,6 +63,14 @@ pub(crate) async fn read_int_list(file: &mut fs::File) -> Result, io::E } } +pub(crate) async fn read_to_string_list(file: &mut fs::File) -> Result, io::Error> { + let output = read_to_string(file).await?; + Ok(output + .split(' ') + .map(|s| s.trim().to_owned()) + .collect::>()) +} + async fn write_buffer(file: &mut fs::File, value: V) -> Result<(), io::Error> where V: tokio_uring::buf::IoBuf, @@ -66,3 +82,7 @@ where pub(crate) async fn write_string(file: &mut fs::File, string: String) -> Result<(), io::Error> { write_buffer(file, string.into_bytes()).await } + +pub(crate) async fn write_int(file: &mut fs::File, int: u32) -> Result<(), io::Error> { + write_string(file, format!("{}", int)).await +}