From f42efab0b005a1d389d9089d6d2cd1f7bfa27050 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sun, 26 Mar 2023 10:49:17 -0400 Subject: [PATCH] Add battery charge limit functionality --- backend/Cargo.lock | 2 +- backend/Cargo.toml | 2 +- backend/src/api/api_types.rs | 2 + backend/src/api/battery.rs | 80 +++++ backend/src/api/general.rs | 6 +- backend/src/api/handler.rs | 50 ++- backend/src/main.rs | 23 +- backend/src/persist/battery.rs | 10 + backend/src/persist/mod.rs | 2 +- backend/src/power_worker.rs | 19 ++ backend/src/settings/general.rs | 58 +++- backend/src/settings/generic/battery.rs | 9 + backend/src/settings/generic/cpu.rs | 18 +- backend/src/settings/generic/gpu.rs | 2 + backend/src/settings/generic_amd/cpu.rs | 4 + backend/src/settings/generic_amd/gpu.rs | 2 + backend/src/settings/mod.rs | 2 +- backend/src/settings/steam_deck/battery.rs | 369 +++++++++++++++++++-- backend/src/settings/steam_deck/cpu.rs | 2 + backend/src/settings/steam_deck/gpu.rs | 2 + backend/src/settings/traits.rs | 52 ++- backend/src/settings/unknown/battery.rs | 9 + backend/src/settings/unknown/cpu.rs | 2 + backend/src/settings/unknown/gpu.rs | 2 + backend/src/state/steam_deck/battery.rs | 13 +- backend/src/state/steam_deck/mod.rs | 2 +- package.json | 2 +- src/backend.ts | 26 +- src/components/battery.tsx | 53 ++- src/consts.ts | 1 + src/index.tsx | 4 +- translations/fr-CA.po | 17 +- translations/pt.pot | 15 + 33 files changed, 799 insertions(+), 63 deletions(-) create mode 100644 backend/src/power_worker.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 67dcd52..579cc0e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "powertools" -version = "1.3.0-alpha" +version = "1.3.0-beta1" dependencies = [ "async-trait", "libryzenadj", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 1c5195d..f370cf0 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "powertools" -version = "1.3.0-alpha" +version = "1.3.0-beta1" edition = "2021" authors = ["NGnius (Graham) "] description = "Backend (superuser) functionality for PowerTools" diff --git a/backend/src/api/api_types.rs b/backend/src/api/api_types.rs index 2107852..23e6611 100644 --- a/backend/src/api/api_types.rs +++ b/backend/src/api/api_types.rs @@ -26,6 +26,8 @@ pub struct BatteryLimits { pub charge_current: Option>, pub charge_current_step: u64, pub charge_modes: Vec, + pub charge_limit: Option>, + pub charge_limit_step: f64, } #[derive(Serialize, Deserialize)] diff --git a/backend/src/api/battery.rs b/backend/src/api/battery.rs index e7c4638..f5cf2b1 100644 --- a/backend/src/api/battery.rs +++ b/backend/src/api/battery.rs @@ -193,3 +193,83 @@ pub fn unset_charge_mode( vec![true.into()] } } + +/// Generate unplugged event receiver web method +pub fn on_unplugged( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + move |_| { + sender.lock().unwrap().send(ApiMessage::OnUnplugged).expect("on_unplugged send failed"); + vec![true.into()] + } +} + +/// Generate plugged in event receiver web method +pub fn on_plugged( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + move |_| { + sender.lock().unwrap().send(ApiMessage::OnPluggedIn).expect("on_plugged send failed"); + vec![true.into()] + } +} + +/// Generate set battery charge limit web method +pub fn set_charge_limit( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let setter = move |limit: f64| + sender.lock() + .unwrap() + .send(ApiMessage::Battery(BatteryMessage::SetChargeLimit(Some(limit)))) + .expect("set_charge_limit send failed"); + move |params_in: super::ApiParameterType| { + if let Some(&Primitive::F64(new_val)) = params_in.get(0) { + setter(new_val); + vec![new_val.into()] + } else { + vec!["set_charge_limit missing parameter".into()] + } + } +} + +/// Generate unset battery charge limit web method +pub fn unset_charge_limit( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let unsetter = move || + sender.lock() + .unwrap() + .send(ApiMessage::Battery(BatteryMessage::SetChargeLimit(None))) + .expect("unset_charge_limit send failed"); + move |_: super::ApiParameterType| { + unsetter(); + vec![true.into()] + } +} + +/// Charge design web method +pub fn get_charge_limit( + sender: Sender, +) -> impl AsyncCallable { + let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety + let getter = move || { + let sender2 = sender.clone(); + move || { + let (tx, rx) = mpsc::channel(); + let callback = move |val: Option| tx.send(val).expect("get_charge_limit callback send failed"); + sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::GetChargeLimit(Box::new(callback)))).expect("get_charge_limit send failed"); + rx.recv().expect("get_charge_limit callback recv failed") + } + }; + super::async_utils::AsyncIshGetter { + set_get: getter, + trans_getter: |result| { + super::utility::map_optional_result(Ok(result)) + } + } +} diff --git a/backend/src/api/general.rs b/backend/src/api/general.rs index ff9afce..4b14aa3 100644 --- a/backend/src/api/general.rs +++ b/backend/src/api/general.rs @@ -47,14 +47,14 @@ pub fn load_settings( sender: Sender, ) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety - let setter = move |path: String, name: String| + let setter = move |path: i64, name: String| sender.lock() .unwrap() .send(ApiMessage::LoadSettings(path, name)).expect("load_settings send failed"); move |params_in: super::ApiParameterType| { - if let Some(Primitive::String(path)) = params_in.get(0) { + if let Some(Primitive::F64(id)) = params_in.get(0) { if let Some(Primitive::String(name)) = params_in.get(1) { - setter(path.to_owned(), name.to_owned()); + setter(*id as i64, name.to_owned()); vec![true.into()] } else { vec!["load_settings missing name parameter".into()] diff --git a/backend/src/api/handler.rs b/backend/src/api/handler.rs index b9dc25f..49ec63e 100644 --- a/backend/src/api/handler.rs +++ b/backend/src/api/handler.rs @@ -1,7 +1,7 @@ use std::sync::mpsc::{self, Receiver, Sender}; use std::fmt::Write; -use crate::settings::{Settings, TCpus, TGpu, TBattery, TGeneral, OnSet, OnResume, MinMax}; +use crate::settings::{Settings, TCpus, TGpu, TBattery, TGeneral, OnSet, OnResume, MinMax, OnPowerEvent, PowerMode}; use crate::persist::SettingsJson; use crate::utility::unwrap_maybe_fatal; @@ -13,8 +13,15 @@ pub enum ApiMessage { Gpu(GpuMessage), General(GeneralMessage), OnResume, + #[allow(dead_code)] + OnPluggedIn, + #[allow(dead_code)] + OnUnplugged, + #[allow(dead_code)] + OnChargeChange(f64), // battery fill amount: 0 = empty, 1 = full + PowerVibeCheck, WaitForEmptyQueue(Callback<()>), - LoadSettings(String, String), // (path, name) + LoadSettings(i64, String), // (path, name) LoadMainSettings, LoadSystemSettings, GetLimits(Callback), @@ -30,6 +37,8 @@ pub enum BatteryMessage { ReadChargeNow(Callback>), ReadChargeDesign(Callback>), ReadCurrentNow(Callback>), + SetChargeLimit(Option), + GetChargeLimit(Callback>), } impl BatteryMessage { @@ -44,6 +53,8 @@ impl BatteryMessage { Self::ReadChargeNow(cb) => cb(settings.read_charge_now()), Self::ReadChargeDesign(cb) => cb(settings.read_charge_design()), Self::ReadCurrentNow(cb) => cb(settings.read_current_now()), + Self::SetChargeLimit(limit) => settings.charge_limit(limit), + Self::GetChargeLimit(cb) => cb(settings.get_charge_limit()), } dirty } @@ -287,11 +298,44 @@ impl ApiMessageHandler { } false } + ApiMessage::OnPluggedIn => { + if let Err(e) = settings.on_power_event(PowerMode::PluggedIn) { + print_errors("on_power_event(PluggedIn)", e); + } + true + } + ApiMessage::OnUnplugged => { + if let Err(e) = settings.on_power_event(PowerMode::PluggedOut) { + print_errors("on_power_event(PluggedOut)", e); + } + true + } + ApiMessage::OnChargeChange(charge) => { + if let Err(e) = settings.on_power_event(PowerMode::BatteryCharge(charge)) { + print_errors(&format!("on_power_event(BatteryCharge={:#0.5})", charge), e); + } + true + } + ApiMessage::PowerVibeCheck => { + match settings.battery.check_power() { + Err(e) => print_errors("check_power()", e), + Ok(events) => { + for ev in events { + let name = format!("on_power_event([vibe]{:?})", ev); + if let Err(e) = settings.on_power_event(ev) { + print_errors(&name, e); + } + } + } + } + true + } ApiMessage::WaitForEmptyQueue(callback) => { self.on_empty.push(callback); false }, - ApiMessage::LoadSettings(path, name) => { + ApiMessage::LoadSettings(id, name) => { + let path = format!("{}.json", id); match settings.load_file(path.into(), name, false) { Ok(success) => log::info!("Loaded settings file? {}", success), Err(e) => log::warn!("Load file err: {}", e), diff --git a/backend/src/main.rs b/backend/src/main.rs index 5dd0557..688a0b3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -5,6 +5,7 @@ mod state; mod consts; use consts::*; +mod power_worker; mod resume_worker; //mod save_worker; mod api_worker; @@ -70,6 +71,7 @@ fn main() -> Result<(), ()> { //let (_save_handle, save_sender) = save_worker::spawn(loaded_settings.clone()); let _resume_handle = resume_worker::spawn(api_sender.clone()); + let _power_handle = power_worker::spawn(api_sender.clone()); let instance = Instance::new(PORT) .register("V_INFO", |_: Vec| { @@ -111,6 +113,18 @@ fn main() -> Result<(), ()> { "BATTERY_unset_charge_mode", api::battery::unset_charge_mode(api_sender.clone()), ) + .register( + "BATTERY_set_charge_limit", + api::battery::set_charge_limit(api_sender.clone()), + ) + .register( + "BATTERY_unset_charge_limit", + api::battery::unset_charge_limit(api_sender.clone()), + ) + .register_async( + "BATTERY_get_charge_limit", + api::battery::get_charge_limit(api_sender.clone()), + ) // cpu API functions .register("CPU_count", api::cpu::max_cpus) .register( @@ -232,10 +246,17 @@ fn main() -> Result<(), ()> { api::general::get_provider(api_sender.clone()) ) .register("GENERAL_idk", api::general::gunter) - // general API functions .register( "GENERAL_apply_now", api::general::force_apply(api_sender.clone()) + ) + .register( + "GENERAL_on_pluggedin", + api::battery::on_plugged(api_sender.clone()) + ) + .register( + "GENERAL_on_unplugged", + api::battery::on_unplugged(api_sender.clone()) ); if let Err(e) = loaded_settings.on_set() { diff --git a/backend/src/persist/battery.rs b/backend/src/persist/battery.rs index 962cfbb..1e8ed57 100644 --- a/backend/src/persist/battery.rs +++ b/backend/src/persist/battery.rs @@ -7,6 +7,15 @@ use serde::{Deserialize, Serialize}; pub struct BatteryJson { pub charge_rate: Option, pub charge_mode: Option, + #[serde(default)] + pub events: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct BatteryEventJson { + pub trigger: String, + pub charge_rate: Option, + pub charge_mode: Option, } impl Default for BatteryJson { @@ -14,6 +23,7 @@ impl Default for BatteryJson { Self { charge_rate: None, charge_mode: None, + events: Vec::new(), } } } diff --git a/backend/src/persist/mod.rs b/backend/src/persist/mod.rs index ef9a677..12ac68a 100644 --- a/backend/src/persist/mod.rs +++ b/backend/src/persist/mod.rs @@ -5,7 +5,7 @@ mod error; mod general; mod gpu; -pub use battery::BatteryJson; +pub use battery::{BatteryJson, BatteryEventJson}; pub use cpu::CpuJson; pub use driver::DriverJson; pub use general::{MinMaxJson, SettingsJson}; diff --git a/backend/src/power_worker.rs b/backend/src/power_worker.rs new file mode 100644 index 0000000..9b103d4 --- /dev/null +++ b/backend/src/power_worker.rs @@ -0,0 +1,19 @@ +use std::thread::{self, JoinHandle}; +use std::time::Duration; +use std::sync::mpsc::Sender; + +use crate::api::handler::ApiMessage; +//use crate::utility::unwrap_maybe_fatal; + +const PERIOD: Duration = Duration::from_secs(5); + +pub fn spawn(sender: Sender) -> JoinHandle<()> { + thread::spawn(move || { + log::info!("power_worker starting..."); + loop { + sender.send(ApiMessage::PowerVibeCheck).expect("power_worker send failed"); + thread::sleep(PERIOD); + } + //log::warn!("resume_worker completed!"); + }) +} diff --git a/backend/src/settings/general.rs b/backend/src/settings/general.rs index 8da9489..7db6d40 100644 --- a/backend/src/settings/general.rs +++ b/backend/src/settings/general.rs @@ -48,6 +48,8 @@ impl OnResume for General { } } +impl crate::settings::OnPowerEvent for General {} + impl TGeneral for General { fn limits(&self) -> crate::api::GeneralLimits { crate::api::GeneralLimits { } @@ -92,11 +94,23 @@ pub struct Settings { impl OnSet for Settings { fn on_set(&mut self) -> Result<(), Vec> { - self.battery.on_set()?; - self.cpus.on_set()?; - self.gpu.on_set()?; - self.general.on_set()?; - Ok(()) + let mut errors = Vec::new(); + + log::debug!("Applying settings for on_resume"); + self.general.on_set().unwrap_or_else(|mut e| errors.append(&mut e)); + log::debug!("Resumed general"); + self.battery.on_set().unwrap_or_else(|mut e| errors.append(&mut e)); + log::debug!("Resumed battery"); + self.cpus.on_set().unwrap_or_else(|mut e| errors.append(&mut e)); + log::debug!("Resumed CPUs"); + self.gpu.on_set().unwrap_or_else(|mut e| errors.append(&mut e)); + log::debug!("Resumed GPU"); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } } } @@ -226,14 +240,40 @@ impl Settings { impl OnResume for Settings { fn on_resume(&self) -> Result<(), Vec> { + let mut errors = Vec::new(); + log::debug!("Applying settings for on_resume"); - self.battery.on_resume()?; + self.general.on_resume().unwrap_or_else(|mut e| errors.append(&mut e)); + log::debug!("Resumed general"); + self.battery.on_resume().unwrap_or_else(|mut e| errors.append(&mut e)); log::debug!("Resumed battery"); - self.cpus.on_resume()?; + self.cpus.on_resume().unwrap_or_else(|mut e| errors.append(&mut e)); log::debug!("Resumed CPUs"); - self.gpu.on_resume()?; + self.gpu.on_resume().unwrap_or_else(|mut e| errors.append(&mut e)); log::debug!("Resumed GPU"); - Ok(()) + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl crate::settings::OnPowerEvent for Settings { + fn on_power_event(&mut self, new_mode: super::PowerMode) -> Result<(), Vec> { + let mut errors = Vec::new(); + + self.general.on_power_event(new_mode).unwrap_or_else(|mut e| errors.append(&mut e)); + self.battery.on_power_event(new_mode).unwrap_or_else(|mut e| errors.append(&mut e)); + self.cpus.on_power_event(new_mode).unwrap_or_else(|mut e| errors.append(&mut e)); + self.gpu.on_power_event(new_mode).unwrap_or_else(|mut e| errors.append(&mut e)); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } } } diff --git a/backend/src/settings/generic/battery.rs b/backend/src/settings/generic/battery.rs index f2551a0..20ef9d3 100644 --- a/backend/src/settings/generic/battery.rs +++ b/backend/src/settings/generic/battery.rs @@ -18,6 +18,7 @@ impl Into for Battery { BatteryJson { charge_rate: None, charge_mode: None, + events: Vec::default(), } } } @@ -65,12 +66,16 @@ impl OnResume for Battery { } } +impl crate::settings::OnPowerEvent for Battery {} + impl TBattery for Battery { fn limits(&self) -> crate::api::BatteryLimits { crate::api::BatteryLimits { charge_current: None, charge_current_step: 50, charge_modes: vec![], + charge_limit: None, + charge_limit_step: 1.0, } } @@ -126,6 +131,10 @@ impl TBattery for Battery { None } + fn charge_limit(&mut self, _limit: Option) {} + + fn get_charge_limit(&self) -> Option { None } + fn provider(&self) -> crate::persist::DriverJson { crate::persist::DriverJson::Generic } diff --git a/backend/src/settings/generic/cpu.rs b/backend/src/settings/generic/cpu.rs index 873a98d..fb944da 100644 --- a/backend/src/settings/generic/cpu.rs +++ b/backend/src/settings/generic/cpu.rs @@ -138,7 +138,21 @@ impl + AsRef + TCpu + FromGenericCpuInfo> Cpus { } } -impl + AsRef + TCpu + OnResume + OnSet> TCpus for Cpus { +impl + AsRef + TCpu + crate::settings::OnPowerEvent> crate::settings::OnPowerEvent for Cpus { + fn on_power_event(&mut self, new_mode: crate::settings::PowerMode) -> Result<(), Vec> { + let mut errors = Vec::new(); + for cpu in &mut self.cpus { + cpu.on_power_event(new_mode).unwrap_or_else(|mut e| errors.append(&mut e)); + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl + AsRef + TCpu + OnResume + OnSet + crate::settings::OnPowerEvent> TCpus for Cpus { fn limits(&self) -> crate::api::CpusLimits { crate::api::CpusLimits { cpus: self.cpus.iter().map(|x| x.as_ref().limits()).collect(), @@ -333,6 +347,8 @@ impl OnResume for Cpu { } } +impl crate::settings::OnPowerEvent for Cpu {} + impl TCpu for Cpu { fn online(&mut self) -> &mut bool { &mut self.online diff --git a/backend/src/settings/generic/gpu.rs b/backend/src/settings/generic/gpu.rs index 064e87a..aa91159 100644 --- a/backend/src/settings/generic/gpu.rs +++ b/backend/src/settings/generic/gpu.rs @@ -80,6 +80,8 @@ impl OnResume for Gpu { } } +impl crate::settings::OnPowerEvent for Gpu {} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { crate::api::GpuLimits { diff --git a/backend/src/settings/generic_amd/cpu.rs b/backend/src/settings/generic_amd/cpu.rs index 8285fd0..312f495 100644 --- a/backend/src/settings/generic_amd/cpu.rs +++ b/backend/src/settings/generic_amd/cpu.rs @@ -37,6 +37,8 @@ impl OnSet for Cpus { } } +impl crate::settings::OnPowerEvent for Cpus {} + impl TCpus for Cpus { fn limits(&self) -> crate::api::CpusLimits { self.generic.limits() @@ -110,6 +112,8 @@ impl OnSet for Cpu { } } +impl crate::settings::OnPowerEvent for Cpu {} + impl TCpu for Cpu { fn online(&mut self) -> &mut bool { self.generic.online() diff --git a/backend/src/settings/generic_amd/gpu.rs b/backend/src/settings/generic_amd/gpu.rs index cb3fb49..713f3a9 100644 --- a/backend/src/settings/generic_amd/gpu.rs +++ b/backend/src/settings/generic_amd/gpu.rs @@ -213,6 +213,8 @@ impl OnSet for Gpu { } } +impl crate::settings::OnPowerEvent for Gpu {} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { self.generic.limits() diff --git a/backend/src/settings/mod.rs b/backend/src/settings/mod.rs index 115f42d..f04b8f3 100644 --- a/backend/src/settings/mod.rs +++ b/backend/src/settings/mod.rs @@ -17,7 +17,7 @@ pub use general::{SettingVariant, Settings, General}; pub use min_max::{MinMax, min_max_from_json}; pub use error::SettingError; -pub use traits::{OnResume, OnSet, SettingsRange, TGeneral, TGpu, TCpus, TBattery, TCpu}; +pub use traits::{OnResume, OnSet, TGeneral, TGpu, TCpus, TBattery, TCpu, OnPowerEvent, PowerMode}; #[cfg(test)] mod tests { diff --git a/backend/src/settings/steam_deck/battery.rs b/backend/src/settings/steam_deck/battery.rs index 532e56a..99bca3a 100644 --- a/backend/src/settings/steam_deck/battery.rs +++ b/backend/src/settings/steam_deck/battery.rs @@ -1,9 +1,9 @@ use std::convert::Into; use crate::api::RangeLimit; -use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{OnResume, OnSet, SettingError, OnPowerEvent, PowerMode}; use crate::settings::TBattery; -use crate::persist::BatteryJson; +use crate::persist::{BatteryJson, BatteryEventJson}; use super::util::ChargeMode; use super::oc_limits::{BatteryLimits, OverclockLimits}; @@ -11,11 +11,155 @@ use super::oc_limits::{BatteryLimits, OverclockLimits}; pub struct Battery { pub charge_rate: Option, pub charge_mode: Option, + events: Vec, limits: BatteryLimits, state: crate::state::steam_deck::Battery, driver_mode: crate::persist::DriverJson, } +#[derive(Debug, Clone)] +enum EventTrigger { + PluggedIn, + PluggedOut, + BatteryAbove(f64), + BatteryBelow(f64), + Ignored, +} + +#[derive(Debug, Clone)] +struct EventInstruction { + trigger: EventTrigger, + charge_rate: Option, + charge_mode: Option, + is_triggered: bool, +} + +impl OnPowerEvent for EventInstruction { + fn on_power_event(&mut self, new_mode: PowerMode) -> Result<(), Vec> { + match (&self.trigger, new_mode) { + (EventTrigger::PluggedIn, PowerMode::PluggedIn) => { + log::info!("Steam Deck plugged in event handled"); + self.set_all() + }, + (EventTrigger::PluggedOut, PowerMode::PluggedOut) => { + log::info!("Steam Deck plugged out event handled"); + self.set_all() + }, + (EventTrigger::BatteryAbove(exp), PowerMode::BatteryCharge(act)) => { + if act > *exp { + if self.is_triggered { + Ok(()) + } else { + self.is_triggered = true; + log::info!("Steam Deck battery above {} event handled", exp); + self.set_all() + } + } else { + self.is_triggered = false; + Ok(()) + } + }, + (EventTrigger::BatteryBelow(exp), PowerMode::BatteryCharge(act)) => { + if act < *exp { + if self.is_triggered { + Ok(()) + } else { + self.is_triggered = true; + log::info!("Steam Deck battery below {} event handled", exp); + self.set_all() + } + } else { + self.is_triggered = false; + Ok(()) + } + }, + _ => Ok(()) + } + } +} + +impl EventInstruction { + #[inline] + fn trigger_to_str(mode: EventTrigger) -> String { + match mode { + EventTrigger::PluggedIn => "plug-in".to_owned(), + EventTrigger::PluggedOut => "plug-out".to_owned(), + EventTrigger::BatteryAbove(x) => format!(">{:#0.2}", x * 100.0), + EventTrigger::BatteryBelow(x) => format!("<{:#0.2}", x * 100.0), + EventTrigger::Ignored => "/shrug".to_owned(), + } + } + + #[inline] + fn str_to_trigger(s: &str) -> Option { + match s { + "normal" => Some(EventTrigger::PluggedIn), + "idle" => Some(EventTrigger::PluggedOut), + s if s.starts_with('>') => s.trim_start_matches('>').parse::().ok().map(|x| EventTrigger::BatteryAbove(x)), + s if s.starts_with('<') => s.trim_start_matches('<').parse::().ok().map(|x| EventTrigger::BatteryBelow(x)), + _ => None, + } + } + + fn from_json(other: BatteryEventJson, _version: u64) -> Self { + Self { + trigger: Self::str_to_trigger(&other.trigger).unwrap_or(EventTrigger::Ignored), + charge_rate: other.charge_rate, + charge_mode: other.charge_mode.map(|x| Battery::str_to_charge_mode(&x)).flatten(), + is_triggered: false, + } + } + + fn set_charge_mode(&self) -> Result<(), SettingError> { + if let Some(charge_mode) = self.charge_mode { + super::util::set(super::util::Setting::ChargeMode, charge_mode as _).map_err( + |e| SettingError { + msg: format!("Failed to set charge mode: {}", e), + setting: crate::settings::SettingVariant::Battery, + }, + ).map(|_| ()) + } else { + Ok(()) + } + } + + fn set_charge_rate(&self) -> Result<(), SettingError> { + if let Some(charge_rate) = self.charge_rate { + usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err( + |e| SettingError { + msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }, + ).map(|_| ()) + } else { + Ok(()) + } + } + + fn set_all(&self) -> Result<(), Vec> { + let mut errors = Vec::new(); + + self.set_charge_rate().unwrap_or_else(|e| errors.push(e)); + self.set_charge_mode().unwrap_or_else(|e| errors.push(e)); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl Into for EventInstruction { + fn into(self) -> BatteryEventJson { + BatteryEventJson { + trigger: Self::trigger_to_str(self.trigger), + charge_rate: self.charge_rate, + charge_mode: self.charge_mode.map(|c| Battery::charge_mode_to_str(c)), + } + } +} + const BATTERY_VOLTAGE: f64 = 7.7; const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only @@ -23,6 +167,7 @@ const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/power_supply/BAT1/charge_now"; // read-only const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/power_supply/BAT1/charge_full"; // read-only const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/power_supply/BAT1/charge_full_design"; // read-only +const USB_PD_IN_MVOLTAGE_PATH: &str = "/sys/class/hwmon/hwmon5/in0_input"; // read-only impl Battery { #[inline] @@ -34,6 +179,7 @@ impl Battery { 0 => Self { charge_rate: other.charge_rate, charge_mode: other.charge_mode.map(|x| Self::str_to_charge_mode(&x)).flatten(), + events: other.events.into_iter().map(|x| EventInstruction::from_json(x, version)).collect(), limits: oc_limits, state: crate::state::steam_deck::Battery::default(), driver_mode: driver, @@ -41,6 +187,7 @@ impl Battery { _ => Self { charge_rate: other.charge_rate, charge_mode: other.charge_mode.map(|x| Self::str_to_charge_mode(&x)).flatten(), + events: other.events.into_iter().map(|x| EventInstruction::from_json(x, version)).collect(), limits: oc_limits, state: crate::state::steam_deck::Battery::default(), driver_mode: driver, @@ -62,11 +209,33 @@ impl Battery { match s { "normal" => Some(ChargeMode::Normal), "idle" => Some(ChargeMode::Idle), - "discharge" | "disacharge" => Some(ChargeMode::Discharge), + "discharge" => Some(ChargeMode::Discharge), _ => None, } } + fn set_charge_mode(&mut self) -> Result<(), SettingError> { + if let Some(charge_mode) = self.charge_mode { + self.state.charge_mode_set = true; + super::util::set(super::util::Setting::ChargeMode, charge_mode as _).map_err( + |e| SettingError { + msg: format!("Failed to set charge mode: {}", e), + setting: crate::settings::SettingVariant::Battery, + }, + ).map(|_| ()) + } else if self.state.charge_mode_set { + self.state.charge_mode_set = false; + super::util::set(super::util::Setting::ChargeMode, ChargeMode::Normal as _).map_err( + |e| SettingError { + msg: format!("Failed to set charge mode: {}", e), + setting: crate::settings::SettingVariant::Battery, + }, + ).map(|_| ()) + } else { + Ok(()) + } + } + fn set_all(&mut self) -> Result<(), Vec> { let mut errors = Vec::new(); if let Some(charge_rate) = self.charge_rate { @@ -86,23 +255,7 @@ impl Battery { }, ).unwrap_or_else(|e| errors.push(e)); } - if let Some(charge_mode) = self.charge_mode { - self.state.charge_mode_set = true; - super::util::set(super::util::Setting::ChargeMode, charge_mode as _).map_err( - |e| SettingError { - msg: format!("Failed to set charge mode: {}", e), - setting: crate::settings::SettingVariant::Battery, - }, - ).unwrap_or_else(|e| {errors.push(e); 0}); - } else if self.state.charge_mode_set { - self.state.charge_mode_set = false; - super::util::set(super::util::Setting::ChargeMode, ChargeMode::Normal as _).map_err( - |e| SettingError { - msg: format!("Failed to set charge mode: {}", e), - setting: crate::settings::SettingVariant::Battery, - }, - ).unwrap_or_else(|e| {errors.push(e); 0}); - } + self.set_charge_mode().unwrap_or_else(|e| errors.push(e)); if errors.is_empty() { Ok(()) } else { @@ -161,6 +314,17 @@ impl Battery { } } + pub fn read_usb_voltage() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(USB_PD_IN_MVOLTAGE_PATH) { + Err(e) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", USB_PD_IN_MVOLTAGE_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + // convert to V (from mV) + Ok(val) => Ok((val as f64)/1000.0), + } + } + pub fn system_default() -> Self { let (oc_limits, is_default) = OverclockLimits::load_or_default(); let oc_limits = oc_limits.battery; @@ -168,11 +332,40 @@ impl Battery { Self { charge_rate: None, charge_mode: None, + events: Vec::new(), limits: oc_limits, state: crate::state::steam_deck::Battery::default(), driver_mode: driver, } } + + fn find_limit_event(&self) -> Option { + for (i, event) in self.events.iter().enumerate() { + match event.trigger { + EventTrigger::BatteryAbove(_) => { + if event.charge_mode.is_some() { + return Some(i); + } + }, + _ => {}, + } + } + None + } + + fn find_unlimit_event(&self) -> Option { + for (i, event) in self.events.iter().enumerate() { + match event.trigger { + EventTrigger::BatteryBelow(_) => { + if event.charge_mode.is_some() { + return Some(i); + } + }, + _ => {}, + } + } + None + } } impl Into for Battery { @@ -181,6 +374,7 @@ impl Into for Battery { BatteryJson { charge_rate: self.charge_rate, charge_mode: self.charge_mode.map(Self::charge_mode_to_str), + events: self.events.into_iter().map(|x| x.into()).collect() } } } @@ -198,6 +392,35 @@ impl OnResume for Battery { } } +impl OnPowerEvent for Battery { + fn on_power_event(&mut self, new_mode: PowerMode) -> Result<(), Vec> { + let mut errors = Vec::new(); + match new_mode { + PowerMode::PluggedIn => { + // plug event resets battery settings + self.events.iter_mut().for_each(|ev| ev.is_triggered = false); + self.set_charge_mode() + .map_err(|e| vec![e]) + }, + PowerMode::PluggedOut => { + // plug event resets battery settings + self.events.iter_mut().for_each(|ev| ev.is_triggered = false); + self.set_charge_mode() + .map_err(|e| vec![e]) + }, + PowerMode::BatteryCharge(_) => Ok(()) + }.unwrap_or_else(|mut e| errors.append(&mut e)); + for ev in &mut self.events { + ev.on_power_event(new_mode).unwrap_or_else(|mut e| errors.append(&mut e)); + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + impl TBattery for Battery { fn limits(&self) -> crate::api::BatteryLimits { crate::api::BatteryLimits { @@ -207,6 +430,11 @@ impl TBattery for Battery { }), charge_current_step: 50, charge_modes: vec!["normal".to_owned(), "discharge".to_owned(), "idle".to_owned()], + charge_limit: Some(RangeLimit { + min: 10.0, + max: 90.0, + }), + charge_limit_step: 1.0, } } @@ -270,6 +498,107 @@ impl TBattery for Battery { } } + fn charge_limit(&mut self, limit: Option) { + // upper limit + let index = self.find_limit_event(); + if let Some(index) = index { + if let Some(limit) = limit { + log::info!("Updating Steam Deck charge limit event instruction to >{}", limit); + self.events[index] = EventInstruction { + trigger: EventTrigger::BatteryAbove(limit/100.0), + charge_rate: None, + charge_mode: Some(ChargeMode::Idle), + is_triggered: false, + }; + } else { + self.events.remove(index); + } + } else if let Some(limit) = limit { + log::info!("Creating Steam Deck charge limit event instruction of >{}", limit); + self.events.push( + EventInstruction { + trigger: EventTrigger::BatteryAbove(limit/100.0), + charge_rate: None, + charge_mode: Some(ChargeMode::Idle), + is_triggered: false, + } + ); + } + // lower limit + let index = self.find_unlimit_event(); + if let Some(index) = index { + if let Some(limit) = limit { + let limit = (limit - 10.0).clamp(0.0, 100.0); + log::info!("Updating Steam Deck charge limit event instruction to <{}", limit); + self.events[index] = EventInstruction { + trigger: EventTrigger::BatteryBelow(limit/100.0), + charge_rate: None, + charge_mode: Some(ChargeMode::Normal), + is_triggered: false, + }; + } else { + self.events.remove(index); + } + } else if let Some(limit) = limit { + let limit = (limit - 10.0).clamp(0.0, 100.0); + log::info!("Creating Steam Deck charge limit event instruction of <{}", limit); + self.events.push( + EventInstruction { + trigger: EventTrigger::BatteryBelow(limit/100.0), + charge_rate: None, + charge_mode: Some(ChargeMode::Normal), + is_triggered: false, + } + ); + } + } + + fn get_charge_limit(&self) -> Option { + let index = self.find_limit_event(); + if let Some(index) = index { + if let EventTrigger::BatteryAbove(limit) = self.events[index].trigger { + Some(limit * 100.0) + } else { + log::error!("Got index {} for battery charge limit which does not have expected event trigger: {:?}", index, &self.events); + None + } + } else { + None + } + } + + fn check_power(&mut self) -> Result, Vec> { + log::debug!("Steam Deck power vibe check"); + let mut errors = Vec::new(); + let mut events = Vec::new(); + match (Self::read_charge_full(), Self::read_charge_now()) { + (Ok(full), Ok(now)) => events.push(PowerMode::BatteryCharge(now/full)), + (Err(e1), Err(e2)) => { + errors.push(e1); + errors.push(e2); + }, + (Err(e), _) => errors.push(e), + (_, Err(e)) => errors.push(e), + } + match Self::read_usb_voltage() { + Ok(voltage) => { + if voltage > 0.0 && self.state.charger_state != crate::state::steam_deck::ChargeState::PluggedIn { + events.push(PowerMode::PluggedIn); + self.state.charger_state = crate::state::steam_deck::ChargeState::PluggedIn; + } else if voltage == 0.0 && self.state.charger_state != crate::state::steam_deck::ChargeState::Unplugged { + events.push(PowerMode::PluggedOut); + self.state.charger_state = crate::state::steam_deck::ChargeState::Unplugged; + } + }, + Err(e) => errors.push(e), + } + if errors.is_empty() { + Ok(events) + } else { + Err(errors) + } + } + fn provider(&self) -> crate::persist::DriverJson { self.driver_mode.clone() } diff --git a/backend/src/settings/steam_deck/cpu.rs b/backend/src/settings/steam_deck/cpu.rs index 28ab7a7..2f76790 100644 --- a/backend/src/settings/steam_deck/cpu.rs +++ b/backend/src/settings/steam_deck/cpu.rs @@ -158,6 +158,8 @@ impl Cpus { } } +impl crate::settings::OnPowerEvent for Cpus {} + impl TCpus for Cpus { fn limits(&self) -> crate::api::CpusLimits { crate::api::CpusLimits { diff --git a/backend/src/settings/steam_deck/gpu.rs b/backend/src/settings/steam_deck/gpu.rs index b89be00..11179d4 100644 --- a/backend/src/settings/steam_deck/gpu.rs +++ b/backend/src/settings/steam_deck/gpu.rs @@ -249,6 +249,8 @@ impl OnResume for Gpu { } } +impl crate::settings::OnPowerEvent for Gpu {} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { crate::api::GpuLimits { diff --git a/backend/src/settings/traits.rs b/backend/src/settings/traits.rs index 304bb4c..cb8762c 100644 --- a/backend/src/settings/traits.rs +++ b/backend/src/settings/traits.rs @@ -10,12 +10,37 @@ pub trait OnResume { fn on_resume(&self) -> Result<(), Vec>; } -pub trait SettingsRange { - fn max() -> Self; - fn min() -> Self; +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum PowerMode { + PluggedIn, + PluggedOut, // unplugged + BatteryCharge(f64), // battery fill amount: 0 = empty, 1 = full } -pub trait TGpu: OnResume + OnSet + Debug + Send { +pub trait OnPowerEvent { + fn on_plugged_in(&mut self) -> Result<(), Vec> { + Ok(()) + } + + fn on_plugged_out(&mut self) -> Result<(), Vec> { + Ok(()) + } + + fn on_charge_amount(&mut self, _amount: f64) -> Result<(), Vec> { + Ok(()) + } + + fn on_power_event(&mut self, new_mode: PowerMode) -> Result<(), Vec> { + match new_mode { + PowerMode::PluggedIn => self.on_plugged_in(), + PowerMode::PluggedOut => self.on_plugged_out(), + PowerMode::BatteryCharge(now) => self.on_charge_amount(now), + } + } +} + +pub trait TGpu: OnSet + OnResume + OnPowerEvent + Debug + Send { fn limits(&self) -> crate::api::GpuLimits; fn json(&self) -> crate::persist::GpuJson; @@ -35,7 +60,7 @@ pub trait TGpu: OnResume + OnSet + Debug + Send { } } -pub trait TCpus: OnResume + OnSet + Debug + Send { +pub trait TCpus: OnSet + OnResume + OnPowerEvent + Debug + Send { fn limits(&self) -> crate::api::CpusLimits; fn json(&self) -> Vec; @@ -63,7 +88,7 @@ pub trait TCpu: Debug + Send { fn get_clock_limits(&self) -> Option<&MinMax>; } -pub trait TGeneral: OnResume + OnSet + Debug + Send { +pub trait TGeneral: OnSet + OnResume + OnPowerEvent + Debug + Send { fn limits(&self) -> crate::api::GeneralLimits; fn get_persistent(&self) -> bool; @@ -81,7 +106,7 @@ pub trait TGeneral: OnResume + OnSet + Debug + Send { fn provider(&self) -> crate::persist::DriverJson; } -pub trait TBattery: OnResume + OnSet + Debug + Send { +pub trait TBattery: OnSet + OnResume + OnPowerEvent + Debug + Send { fn limits(&self) -> crate::api::BatteryLimits; fn json(&self) -> crate::persist::BatteryJson; @@ -102,6 +127,19 @@ pub trait TBattery: OnResume + OnSet + Debug + Send { fn read_current_now(&self) -> Option; + fn charge_limit(&mut self, limit: Option); + + fn get_charge_limit(&self) -> Option; + + fn check_power(&mut self) -> Result, Vec> { + log::warn!("Power event check using default trait implementation"); + let mut events = Vec::new(); + if let (Some(full), Some(now)) = (self.read_charge_full(), self.read_charge_now()) { + events.push(PowerMode::BatteryCharge(now/full)); + } + Ok(events) + } + fn provider(&self) -> crate::persist::DriverJson { crate::persist::DriverJson::AutoDetect } diff --git a/backend/src/settings/unknown/battery.rs b/backend/src/settings/unknown/battery.rs index 21554f3..abb5008 100644 --- a/backend/src/settings/unknown/battery.rs +++ b/backend/src/settings/unknown/battery.rs @@ -13,6 +13,7 @@ impl Into for Battery { BatteryJson { charge_rate: None, charge_mode: None, + events: Vec::default(), } } } @@ -29,12 +30,16 @@ impl OnResume for Battery { } } +impl crate::settings::OnPowerEvent for Battery {} + impl TBattery for Battery { fn limits(&self) -> crate::api::BatteryLimits { crate::api::BatteryLimits { charge_current: None, charge_current_step: 50, charge_modes: vec![], + charge_limit: None, + charge_limit_step: 1.0, } } @@ -64,6 +69,10 @@ impl TBattery for Battery { fn read_current_now(&self) -> Option { None } + fn charge_limit(&mut self, _limit: Option) {} + + fn get_charge_limit(&self) -> Option { None } + fn provider(&self) -> crate::persist::DriverJson { crate::persist::DriverJson::Unknown } diff --git a/backend/src/settings/unknown/cpu.rs b/backend/src/settings/unknown/cpu.rs index 36c800f..976c0f4 100644 --- a/backend/src/settings/unknown/cpu.rs +++ b/backend/src/settings/unknown/cpu.rs @@ -68,6 +68,8 @@ impl OnResume for Cpus { } } +impl crate::settings::OnPowerEvent for Cpus {} + impl Cpus { pub fn cpu_count() -> Option { let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) diff --git a/backend/src/settings/unknown/gpu.rs b/backend/src/settings/unknown/gpu.rs index 4cec037..b04d78d 100644 --- a/backend/src/settings/unknown/gpu.rs +++ b/backend/src/settings/unknown/gpu.rs @@ -49,6 +49,8 @@ impl OnResume for Gpu { } } +impl crate::settings::OnPowerEvent for Gpu {} + impl TGpu for Gpu { fn limits(&self) -> crate::api::GpuLimits { crate::api::GpuLimits { diff --git a/backend/src/state/steam_deck/battery.rs b/backend/src/state/steam_deck/battery.rs index 83826a2..3c66e36 100644 --- a/backend/src/state/steam_deck/battery.rs +++ b/backend/src/state/steam_deck/battery.rs @@ -2,13 +2,22 @@ pub struct Battery { pub charge_rate_set: bool, pub charge_mode_set: bool, + pub charger_state: ChargeState, } impl std::default::Default for Battery { fn default() -> Self { Self { - charge_rate_set: false, - charge_mode_set: false, + charge_rate_set: true, + charge_mode_set: true, + charger_state: ChargeState::Unknown, } } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ChargeState { + PluggedIn, + Unplugged, + Unknown, +} diff --git a/backend/src/state/steam_deck/mod.rs b/backend/src/state/steam_deck/mod.rs index c7dca59..0680a3e 100644 --- a/backend/src/state/steam_deck/mod.rs +++ b/backend/src/state/steam_deck/mod.rs @@ -2,6 +2,6 @@ mod battery; mod cpu; mod gpu; -pub use battery::Battery; +pub use battery::{Battery, ChargeState}; pub use cpu::Cpu; pub use gpu::Gpu; diff --git a/package.json b/package.json index 0949532..661d6f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "PowerTools", - "version": "1.3.0-alpha", + "version": "1.3.0-beta1", "description": "Power tweaks for power users", "scripts": { "build": "shx rm -rf dist && rollup -c", diff --git a/src/backend.ts b/src/backend.ts index 8333a62..5712428 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -63,6 +63,8 @@ export type BatteryLimits = { charge_current: RangeLimit | null; charge_current_step: number; charge_modes: string[]; + charge_limit: RangeLimit | null; + charge_limit_step: number; }; export type CpuLimits = { @@ -139,6 +141,18 @@ export async function unsetBatteryChargeMode(): Promise { return await call_backend("BATTERY_unset_charge_mode", []); } +export async function getBatteryChargeLimit(): Promise { + return (await call_backend("BATTERY_get_charge_limit", []))[0]; +} + +export async function setBatteryChargeLimit(val: number): Promise { + return (await call_backend("BATTERY_set_charge_limit", [val]))[0]; +} + +export async function unsetBatteryChargeLimit(): Promise { + return await call_backend("BATTERY_unset_charge_limit", []); +} + // CPU export async function setCpuSmt(status: boolean): Promise { @@ -229,8 +243,8 @@ export async function getGeneralPersistent(): Promise { return (await call_backend("GENERAL_get_persistent", []))[0]; } -export async function loadGeneralSettings(path: string, name: string): Promise { - return (await call_backend("GENERAL_load_settings", [path, name]))[0]; +export async function loadGeneralSettings(id: number, name: string): Promise { + return (await call_backend("GENERAL_load_settings", [id, name]))[0]; } export async function loadGeneralDefaultSettings(): Promise { @@ -280,3 +294,11 @@ export async function idk(): Promise { export async function forceApplySettings(): Promise { return (await call_backend("GENERAL_apply_now", []))[0]; } + +export async function onPluggedIn(): Promise { + return (await call_backend("GENERAL_on_pluggedin", []))[0]; +} + +export async function onUnplugged(): Promise { + return (await call_backend("GENERAL_on_unplugged", []))[0]; +} diff --git a/src/components/battery.tsx b/src/components/battery.tsx index 3534fdf..14b86ee 100644 --- a/src/components/battery.tsx +++ b/src/components/battery.tsx @@ -19,6 +19,7 @@ import { CHARGE_RATE_BATT, CHARGE_MODE_BATT, CURRENT_BATT, + CHARGE_LIMIT_BATT, } from "../consts"; import { set_value, get_value} from "usdpl-front"; @@ -53,6 +54,12 @@ export class Battery extends Component { {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) } + + + {get_value(CURRENT_BATT)} mA + + {(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current != null && { description={tr("Control battery charge rate when awake")} onChange={(value: boolean) => { if (value) { - set_value(CHARGE_RATE_BATT, 2500); + set_value(CHARGE_RATE_BATT, (get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.max); reloadGUI("BATTChargeRateToggle"); } else { set_value(CHARGE_RATE_BATT, null); @@ -128,12 +135,44 @@ export class Battery extends Component { /> } } - - - {get_value(CURRENT_BATT)} mA - - + {(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_limit != null && + { + if (value) { + set_value(CHARGE_LIMIT_BATT, (get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_limit!.max); + reloadGUI("BATTChargeLimitToggle"); + } else { + set_value(CHARGE_LIMIT_BATT, null); + backend.resolve(backend.unsetBatteryChargeLimit(), (_: any[]) => { + reloadGUI("BATTUnsetChargeRate"); + }); + } + }} + /> + { get_value(CHARGE_LIMIT_BATT) != null && { + backend.log(backend.LogLevel.Debug, "Charge limit is now " + val.toString()); + const rateNow = get_value(CHARGE_LIMIT_BATT); + if (val != rateNow) { + backend.resolve(backend.setBatteryChargeLimit(val), + (rate: number) => { + set_value(CHARGE_LIMIT_BATT, rate); + reloadGUI("BATTChargeLimit"); + }); + } + }} + />} + } ); } } diff --git a/src/consts.ts b/src/consts.ts index 38e1496..749bbe8 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -6,6 +6,7 @@ export const LIMITS_INFO = "LIMITS_all"; export const CURRENT_BATT = "BATTERY_current_now"; export const CHARGE_RATE_BATT = "BATTERY_charge_rate"; export const CHARGE_MODE_BATT = "BATTERY_charge_mode"; +export const CHARGE_LIMIT_BATT = "BATTERY_charge_limit"; export const CHARGE_NOW_BATT = "BATTERY_charge_now"; export const CHARGE_FULL_BATT = "BATTERY_charge_full"; export const CHARGE_DESIGN_BATT = "BATTERY_charge_design"; diff --git a/src/index.tsx b/src/index.tsx index 9350e10..69a9b40 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -35,6 +35,7 @@ import { CURRENT_BATT, CHARGE_RATE_BATT, CHARGE_MODE_BATT, + CHARGE_LIMIT_BATT, CHARGE_NOW_BATT, CHARGE_FULL_BATT, CHARGE_DESIGN_BATT, @@ -108,6 +109,7 @@ const reload = function() { backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); backend.resolve_nullable(backend.getBatteryChargeRate(), (rate: number | null) => { set_value(CHARGE_RATE_BATT, rate) }); backend.resolve_nullable(backend.getBatteryChargeMode(), (mode: string | null) => { set_value(CHARGE_MODE_BATT, mode) }); + backend.resolve_nullable(backend.getBatteryChargeLimit(), (limit: number | null) => { set_value(CHARGE_LIMIT_BATT, limit) }); backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) }); backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) }); backend.resolve(backend.getBatteryChargeDesign(), (rate: number) => { set_value(CHARGE_DESIGN_BATT, rate) }); @@ -175,7 +177,7 @@ const reload = function() { let gameInfo: any = appStore.GetAppOverviewByGameID(id); // don't use gameInfo.appid, haha backend.resolve( - backend.loadGeneralSettings(id.toString() + ".json", gameInfo.display_name), + backend.loadGeneralSettings(id, gameInfo.display_name), (ok: boolean) => {backend.log(backend.LogLevel.Debug, "Loading settings ok? " + ok)} ); }); diff --git a/translations/fr-CA.po b/translations/fr-CA.po index 469549b..d7e8c2a 100644 --- a/translations/fr-CA.po +++ b/translations/fr-CA.po @@ -83,7 +83,7 @@ msgstr "Maximum (mA)" # (Battery charge mode override toggle) #: components/battery.tsx:97,115 msgid "Charge Mode" -msgstr "Mode de Charge" +msgstr "Mode de charge" # (Battery charge mode override toggle description) #: components/battery.tsx:98 @@ -100,6 +100,21 @@ msgstr "Mode" msgid "Current" msgstr "Courant" +#: components/battery.tsx:141 +# (Battery charging maximum) +msgid "Charge Limit" +msgstr "Limite de charge" + +#: components/battery.tsx:142 +# (Battery charging maximum description) +msgid "Limit battery charge when awake" +msgstr "Limiter la charge de la batterie quand éveillé" + +#: components/battery.tsx:156 +# (Battery charging maximum slider) +msgid "Maximum (%)" +msgstr "Maximum (%)" + # -- components/cpus.tsx -- # (CPU section title) #: components/cpus.tsx:64 diff --git a/translations/pt.pot b/translations/pt.pot index dc63d04..79fe733 100644 --- a/translations/pt.pot +++ b/translations/pt.pot @@ -99,6 +99,21 @@ msgstr "" msgid "Current" msgstr "" +#: components/battery.tsx:141 +# (Battery charging maximum) +msgid "Charge Limit" +msgstr "" + +#: components/battery.tsx:142 +# (Battery charging maximum description) +msgid "Limit battery charge when awake" +msgstr "" + +#: components/battery.tsx:156 +# (Battery charging maximum slider) +msgid "Maximum (%)" +msgstr "" + # -- components/cpus.tsx -- #: components/cpus.tsx:64