Skip to content

Commit

Permalink
Merge pull request #85 from liketechnik/feat/sysfs-charging-profiles
Browse files Browse the repository at this point in the history
feat(tuxedo_sysfs): implement charging profiles
  • Loading branch information
AaronErhardt authored Jun 23, 2024
2 parents a06f7e7 + 9d41b95 commit 801683a
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 0 deletions.
69 changes: 69 additions & 0 deletions tailor_hwcaps/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
147 changes: 147 additions & 0 deletions tuxedo_sysfs/src/charging/charge_control.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u32>>,
available_end_thresholds: Option<Vec<u32>>,
start_threshold_file: tokio_uring::fs::File,
end_threshold_file: tokio_uring::fs::File,
charge_type_file: tokio_uring::fs::File,
) -> Result<Self, io::Error> {
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<Option<Self>, 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<u32, io::Error> {
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<u32, io::Error> {
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<String, io::Error> {
Ok(read_to_string(&mut self.charge_type_file)
.await?
.trim()
.to_owned())
}

/// <https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power>
/// (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
}
}
43 changes: 43 additions & 0 deletions tuxedo_sysfs/src/charging/charging_priority.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Self>, 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<String, io::Error> {
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
}
}
43 changes: 43 additions & 0 deletions tuxedo_sysfs/src/charging/charging_profile.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Self>, 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<String, io::Error> {
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
}
}
40 changes: 40 additions & 0 deletions tuxedo_sysfs/src/charging/mod.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
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<String>,
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<Vec<u32>>,
pub available_end_thresholds: Option<Vec<u32>>,
/// 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 <https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power> (section: /sys/class/power_supply/<supply_name>/charge_type)
charge_type_file: tokio_uring::fs::File,
}
1 change: 1 addition & 0 deletions tuxedo_sysfs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod charging;
pub mod led;
pub(crate) mod sysfs_util;
20 changes: 20 additions & 0 deletions tuxedo_sysfs/src/sysfs_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ pub(crate) async fn read_to_string(file: &mut fs::File) -> Result<String, io::Er
String::from_utf8(buffer).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
}

pub(crate) async fn read_path_to_int_list<P>(path: P) -> Result<Vec<u32>, io::Error>
where
P: AsRef<Path>,
{
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<Vec<u32>, io::Error> {
let content = read_to_string(file).await?;

Expand All @@ -55,6 +63,14 @@ pub(crate) async fn read_int_list(file: &mut fs::File) -> Result<Vec<u32>, io::E
}
}

pub(crate) async fn read_to_string_list(file: &mut fs::File) -> Result<Vec<String>, io::Error> {
let output = read_to_string(file).await?;
Ok(output
.split(' ')
.map(|s| s.trim().to_owned())
.collect::<Vec<String>>())
}

async fn write_buffer<V>(file: &mut fs::File, value: V) -> Result<(), io::Error>
where
V: tokio_uring::buf::IoBuf,
Expand All @@ -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
}

0 comments on commit 801683a

Please sign in to comment.