Files
asusctl/asusd/src/ctrl_power.rs
2023-06-24 13:15:11 +12:00

288 lines
9.0 KiB
Rust

use std::process::Command;
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use config_traits::StdConfig;
use log::{error, info, warn};
use rog_platform::power::AsusPower;
use rog_platform::supported::ChargeSupportedFunctions;
use systemd_zbus::{ManagerProxy as SystemdProxy, Mode, UnitFileState};
use tokio::time::sleep;
use zbus::export::futures_util::lock::Mutex;
use zbus::{dbus_interface, Connection, SignalContext};
use crate::config::Config;
use crate::error::RogError;
use crate::{task_watch_item, CtrlTask, GetSupported};
const ZBUS_PATH: &str = "/org/asuslinux/Power";
const NVIDIA_POWERD: &str = "nvidia-powerd.service";
impl GetSupported for CtrlPower {
type A = ChargeSupportedFunctions;
fn get_supported() -> Self::A {
ChargeSupportedFunctions {
charge_level_set: if let Ok(power) = AsusPower::new() {
power.has_charge_control_end_threshold()
} else {
false
},
}
}
}
#[derive(Clone)]
pub struct CtrlPower {
power: AsusPower,
config: Arc<Mutex<Config>>,
}
#[dbus_interface(name = "org.asuslinux.Daemon")]
impl CtrlPower {
async fn set_charge_control_end_threshold(
&mut self,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
limit: u8,
) -> zbus::fdo::Result<()> {
if !(20..=100).contains(&limit) {
return Err(RogError::ChargeLimit(limit))?;
}
self.set(limit)
.map_err(|err| {
warn!("CtrlCharge: set_limit {}", err);
err
})
.ok();
Self::notify_charge_control_end_threshold(&ctxt, limit)
.await
.ok();
Ok(())
}
fn charge_control_end_threshold(&self) -> u8 {
loop {
if let Some(mut config) = self.config.try_lock() {
let limit = self
.power
.get_charge_control_end_threshold()
.map_err(|err| {
warn!("CtrlCharge: get_charge_control_end_threshold {}", err);
err
})
.unwrap_or(100);
config.read();
config.bat_charge_limit = limit;
config.write();
return config.bat_charge_limit;
}
}
}
fn mains_online(&self) -> bool {
if self.power.has_online() {
if let Ok(v) = self.power.get_online() {
return v == 1;
}
}
false
}
#[dbus_interface(signal)]
async fn notify_charge_control_end_threshold(
ctxt: &SignalContext<'_>,
limit: u8,
) -> zbus::Result<()>;
#[dbus_interface(signal)]
async fn notify_mains_online(ctxt: &SignalContext<'_>, on: bool) -> zbus::Result<()>;
}
#[async_trait]
impl crate::ZbusRun for CtrlPower {
async fn add_to_server(self, server: &mut Connection) {
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
}
}
#[async_trait]
impl crate::Reloadable for CtrlPower {
async fn reload(&mut self) -> Result<(), RogError> {
if let Some(mut config) = self.config.try_lock() {
config.read();
self.set(config.bat_charge_limit)?;
}
Ok(())
}
}
impl CtrlPower {
task_watch_item!(charge_control_end_threshold power);
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
Ok(CtrlPower {
power: AsusPower::new()?,
config,
})
}
pub(super) fn set(&self, limit: u8) -> Result<(), RogError> {
if !(20..=100).contains(&limit) {
return Err(RogError::ChargeLimit(limit));
}
self.power.set_charge_control_end_threshold(limit)?;
info!("Battery charge limit: {}", limit);
if let Some(mut config) = self.config.try_lock() {
config.read();
config.bat_charge_limit = limit;
config.write();
}
Ok(())
}
}
#[async_trait]
impl CtrlTask for CtrlPower {
fn zbus_path() -> &'static str {
ZBUS_PATH
}
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
let conn = zbus::Connection::system().await?;
let sysd1 = SystemdProxy::new(&conn).await?;
let sysd2 = sysd1.clone();
let sysd3 = sysd1.clone();
let power1 = self.clone();
let power2 = self.clone();
self.create_sys_event_tasks(
move || async {},
move || {
let power = power1.clone();
let sysd = sysd1.clone();
async move {
info!("CtrlCharge reloading charge limit");
let lock = power.config.lock().await;
power
.set(lock.bat_charge_limit)
.map_err(|err| {
warn!("CtrlCharge: set_limit {}", err);
err
})
.ok();
if lock.disable_nvidia_powerd_on_battery {
if let Ok(value) = power.power.get_online() {
do_nvidia_powerd_action(&sysd, value == 1).await;
}
}
}
},
move || async {},
move || {
let power = power2.clone();
let sysd = sysd2.clone();
async move {
info!("CtrlCharge reloading charge limit");
let lock = power.config.lock().await;
power
.set(lock.bat_charge_limit)
.map_err(|err| {
warn!("CtrlCharge: set_limit {}", err);
err
})
.ok();
if lock.disable_nvidia_powerd_on_battery {
if let Ok(value) = power.power.get_online() {
do_nvidia_powerd_action(&sysd, value == 1).await;
}
}
}
},
)
.await;
let config = self.config.clone();
self.watch_charge_control_end_threshold(signal_ctxt.clone())
.await?;
let ctrl = self.clone();
tokio::spawn(async move {
let mut online = 10;
loop {
if let Ok(value) = ctrl.power.get_online() {
if online != value {
online = value;
let mut config = config.lock().await;
config.read();
if config.disable_nvidia_powerd_on_battery {
do_nvidia_powerd_action(&sysd3, value == 1).await;
}
Self::notify_mains_online(&signal_ctxt, value == 1)
.await
.unwrap();
let mut prog: Vec<&str> = Vec::new();
if value == 1 {
// AC ONLINE
prog = config.ac_command.split_whitespace().collect();
} else if value == 0 {
// BATTERY
prog = config.bat_command.split_whitespace().collect();
}
if prog.len() > 1 {
let mut cmd = Command::new(prog[0]);
for arg in prog.iter().skip(1) {
cmd.arg(*arg);
}
if let Err(e) = cmd.spawn() {
if value == 1 {
error!("AC power command error: {e}");
} else {
error!("Battery power command error: {e}");
}
}
}
}
}
// The inotify doesn't pick up events when the kernel changes internal value
// so we need to watch it with a thread and sleep unfortunately
sleep(Duration::from_secs(1)).await;
}
});
Ok(())
}
}
async fn do_nvidia_powerd_action(proxy: &SystemdProxy<'_>, ac_on: bool) {
if let Ok(res) = proxy.get_unit_file_state(NVIDIA_POWERD).await {
if res == UnitFileState::Enabled {
if ac_on {
proxy
.start_unit(NVIDIA_POWERD, Mode::Replace)
.await
.map_err(|e| error!("Error stopping {NVIDIA_POWERD}, {e:?}"))
.ok();
} else {
proxy
.stop_unit(NVIDIA_POWERD, Mode::Replace)
.await
.map_err(|e| error!("Error stopping {NVIDIA_POWERD}, {e:?}"))
.ok();
}
}
}
}