diff --git a/EXAMPLES.md b/EXAMPLES.md index 76d6755a..2cccc7a4 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -93,19 +93,6 @@ LED Matrix ALS: 76 Lux ``` -## Check power (AC and battery) status - -``` -> sudo ./target/debug/framework_tool --power - AC is: not connected - Battery is: connected - Battery LFCC: 3949 mAh (Last Full Charge Capacity) - Battery Capacity: 2770 mAh - 44.729 Wh - Charge level: 70% - Battery discharging -``` - ## Set custom fan duty/RPM ``` @@ -139,6 +126,7 @@ ALS: 76 Lux Fan Speed: 0 RPM ``` + ## Check expansion bay (Framework 16) ``` @@ -149,4 +137,111 @@ Expansion Bay Hatch closed: true Board: DualInterposer Serial Number: FRAXXXXXXXXXXXXXXX + +## Check charger and battery status (Framework 12/13/16) + +``` +> sudo framework_tool --power +Charger Status + AC is: not connected + Charger Voltage: 17048mV + Charger Current: 0mA + Chg Input Current:384mA + Battery SoC: 93% +Battery Status + AC is: not connected + Battery is: connected + Battery LFCC: 3693 mAh (Last Full Charge Capacity) + Battery Capacity: 3409 mAh + 58.96 Wh + Charge level: 92% + Battery discharging +``` + +Get more information + +``` +> sudo framework_tool --power -vv +Charger Status + AC is: not connected + Charger Voltage: 14824mV + Charger Current: 0mA + Chg Input Current:384mA + Battery SoC: 33% +Battery Status + AC is: not connected + Battery is: connected + Battery LFCC: 4021 mAh (Last Full Charge Capacity) + Battery Capacity: 1300 mAh + 19.267 Wh + Charge level: 32% + Manufacturer: NVT + Model Number: FRANGWA + Serial Number: 038F + Battery Type: LION + Present Voltage: 14.821 V + Present Rate: 943 mA + Design Capacity: 3915 mAh + 60.604 Wh + Design Voltage: 15.480 V + Cycle Count: 64 + Battery discharging +``` + +### Setting a custom charger current limit + +``` +# 1C = normal charging rate +# This means charging from 0 to 100% takes 1 hour +# Set charging rate to 0.8C +> sudo framework_tool --charge-rate-limit 0.8 + +# Limit charge current to the battery to to 2A +# In the output of `framework_tool --power -vv` above you can se "Design Capacity" +# Dividing that by 1h gives you the maximum charging current (1C) +# For example Design Capacity: 3915 mAh => 3915mA +> sudo framework_tool --charge-current-limit 2000 + +# And then plug in a power adapter +> sudo framework_tool --power +Charger Status + AC is: connected + Charger Voltage: 17800mV + Charger Current: 2000mA + 0.51C + Chg Input Current:3084mA + Battery SoC: 87% +Battery Status + AC is: connected + Battery is: connected + Battery LFCC: 3713 mAh (Last Full Charge Capacity) + Battery Capacity: 3215 mAh + 56.953 Wh + Charge level: 86% + Battery charging + +# Remove limit (set rate to 1C) +> sudo framework_tool --charge-rate-limit 1 + +# Back to normal +> sudo framework_tool --power +Charger Status + AC is: connected + Charger Voltage: 17800mV + Charger Current: 2740mA + 0.70C + Chg Input Current:3084mA + Battery SoC: 92% +Battery Status + AC is: connected + Battery is: connected + Battery LFCC: 3713 mAh (Last Full Charge Capacity) + Battery Capacity: 3387 mAh + 60.146 Wh + Charge level: 91% + Battery charging + +# Set charge rate/current limit only if battery is >80% charged +> sudo framework_tool --charge-rate-limit 80 0.8 +> sudo framework_tool --charge-current-limit 80 2000 ``` diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 21fb5f57..057681e5 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -40,6 +40,8 @@ pub enum EcCommands { I2cPassthrough = 0x009e, ConsoleSnapshot = 0x0097, ConsoleRead = 0x0098, + ChargeState = 0x00A0, + ChargeCurrentLimit = 0x00A1, /// List the features supported by the firmware GetFeatures = 0x000D, /// Force reboot, causes host reboot as well diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index e84f7687..5b909663 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -377,6 +377,67 @@ impl EcRequest<()> for EcRequestConsoleRead { } } +#[repr(u8)] +pub enum ChargeStateCmd { + GetState = 0, + GetParam, + SetParam, + NumCmds, +} + +#[repr(C, packed)] +pub struct EcRequestChargeStateGetV0 { + pub cmd: u8, + pub param: u32, +} + +#[repr(C, packed)] +pub struct EcResponseChargeStateGetV0 { + pub ac: u32, + pub chg_voltage: u32, + pub chg_current: u32, + pub chg_input_current: u32, + pub batt_state_of_charge: u32, +} + +impl EcRequest for EcRequestChargeStateGetV0 { + fn command_id() -> EcCommands { + EcCommands::ChargeState + } + fn command_version() -> u8 { + 0 + } +} + +pub struct EcRequestCurrentLimitV0 { + /// Current limit in mA + pub current: u32, +} + +impl EcRequest<()> for EcRequestCurrentLimitV0 { + fn command_id() -> EcCommands { + EcCommands::ChargeCurrentLimit + } +} + +pub struct EcRequestCurrentLimitV1 { + /// Current limit in mA + pub current: u32, + /// Battery state of charge is the minimum charge percentage at which + /// the battery charge current limit will apply. + /// When not set, the limit will apply regardless of state of charge. + pub battery_soc: u8, +} + +impl EcRequest<()> for EcRequestCurrentLimitV1 { + fn command_id() -> EcCommands { + EcCommands::ChargeCurrentLimit + } + fn command_version() -> u8 { + 1 + } +} + /// Supported features #[derive(Debug, FromPrimitive)] pub enum EcFeatureCode { diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index ff0327d4..a37b5a63 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -10,6 +10,7 @@ use crate::ec_binary; use crate::os_specific; +use crate::power; use crate::smbios; #[cfg(feature = "uefi")] use crate::uefi::shell_get_execution_break_flag; @@ -394,6 +395,42 @@ impl CrosEc { Ok((limits.min_percentage, limits.max_percentage)) } + pub fn set_charge_current_limit(&self, current: u32, battery_soc: Option) -> EcResult<()> { + if let Some(battery_soc) = battery_soc { + let battery_soc = battery_soc as u8; + EcRequestCurrentLimitV1 { + current, + battery_soc, + } + .send_command(self) + } else { + EcRequestCurrentLimitV0 { current }.send_command(self) + } + } + + pub fn set_charge_rate_limit(&self, rate: f32, battery_soc: Option) -> EcResult<()> { + let power_info = power::power_info(self).ok_or(EcError::DeviceError( + "Failed to get battery info".to_string(), + ))?; + let battery = power_info + .battery + .ok_or(EcError::DeviceError("No battery present".to_string()))?; + println!("Requested Rate: {}C", rate); + println!("Design Current: {}mA", battery.design_capacity); + let current = (rate * (battery.design_capacity as f32)) as u32; + println!("Limiting Current to: {}mA", current); + if let Some(battery_soc) = battery_soc { + let battery_soc = battery_soc as u8; + EcRequestCurrentLimitV1 { + current, + battery_soc, + } + .send_command(self) + } else { + EcRequestCurrentLimitV0 { current }.send_command(self) + } + } + pub fn set_fp_led_percentage(&self, percentage: u8) -> EcResult<()> { // Sending bytes manually because the Set command, as opposed to the Get command, // does not return any data @@ -1083,6 +1120,33 @@ impl CrosEc { } } + pub fn get_charge_state(&self, power_info: &power::PowerInfo) -> EcResult<()> { + let res = EcRequestChargeStateGetV0 { + cmd: ChargeStateCmd::GetState as u8, + param: 0, + } + .send_command(self)?; + println!("Charger Status"); + println!( + " AC is: {}", + if res.ac == 1 { + "connected" + } else { + "not connected" + } + ); + println!(" Charger Voltage: {}mV", { res.chg_voltage }); + println!(" Charger Current: {}mA", { res.chg_current }); + if let Some(battery) = &power_info.battery { + let charge_rate = (res.chg_current as f32) / (battery.design_capacity as f32); + println!(" {:.2}C", charge_rate); + } + println!(" Chg Input Current:{}mA", { res.chg_input_current }); + println!(" Battery SoC: {}%", { res.batt_state_of_charge }); + + Ok(()) + } + /// Check features supported by the firmware pub fn get_features(&self) -> EcResult<()> { let data = EcRequestGetFeatures {}.send_command(self)?; diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index e94a330c..591b9b4b 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -155,6 +155,16 @@ struct ClapCli { #[arg(long)] charge_limit: Option>, + /// Get or set max charge current limit + #[arg(long)] + #[clap(num_args = ..2)] + charge_current_limit: Vec, + + /// Get or set max charge current limit + #[arg(long)] + #[clap(num_args = ..2)] + charge_rate_limit: Vec, + /// Get GPIO value by name #[arg(long)] get_gpio: Option, @@ -305,6 +315,19 @@ pub fn parse(args: &[String]) -> Cli { 1 => Some((None, args.fansetrpm[0])), _ => None, }; + let charge_current_limit = match args.charge_current_limit.len() { + 2 => Some(( + args.charge_current_limit[0], + Some(args.charge_current_limit[1]), + )), + 1 => Some((args.charge_current_limit[0], None)), + _ => None, + }; + let charge_rate_limit = match args.charge_rate_limit.len() { + 2 => Some((args.charge_rate_limit[0], Some(args.charge_rate_limit[1]))), + 1 => Some((args.charge_rate_limit[0], None)), + _ => None, + }; Cli { verbosity: args.verbosity.log_level_filter(), @@ -358,6 +381,8 @@ pub fn parse(args: &[String]) -> Cli { inputdeck_mode: args.inputdeck_mode, expansion_bay: args.expansion_bay, charge_limit: args.charge_limit, + charge_current_limit, + charge_rate_limit, get_gpio: args.get_gpio, fp_led_level: args.fp_led_level, fp_brightness: args.fp_brightness, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 75c8300a..f16b5489 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -175,6 +175,8 @@ pub struct Cli { pub inputdeck_mode: Option, pub expansion_bay: bool, pub charge_limit: Option>, + pub charge_current_limit: Option<(u32, Option)>, + pub charge_rate_limit: Option<(f32, Option)>, pub get_gpio: Option, pub fp_led_level: Option>, pub fp_brightness: Option>, @@ -762,6 +764,10 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if let Some(maybe_limit) = args.charge_limit { print_err(handle_charge_limit(&ec, maybe_limit)); + } else if let Some((limit, soc)) = args.charge_current_limit { + print_err(ec.set_charge_current_limit(limit, soc)); + } else if let Some((limit, soc)) = args.charge_rate_limit { + print_err(ec.set_charge_rate_limit(limit, soc)); } else if let Some(gpio_name) = &args.get_gpio { print!("Getting GPIO value {}: ", gpio_name); if let Ok(value) = ec.get_gpio(gpio_name) { @@ -1081,6 +1087,7 @@ Options: --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) --expansion-bay Show status of the expansion bay (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') + --charge-current-limit [] Get or set battery current charge limit (Percentage number as arg, e.g. '100') --get-gpio Get GPIO value by name --fp-led-level [] Get or set fingerprint LED brightness level [possible values: high, medium, low] --fp-brightness []Get or set fingerprint LED brightness percentage diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 3c097344..4b32d765 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -88,6 +88,8 @@ pub fn parse(args: &[String]) -> Cli { inputdeck_mode: None, expansion_bay: false, charge_limit: None, + charge_current_limit: None, + charge_rate_limit: None, get_gpio: None, fp_led_level: None, fp_brightness: None, @@ -266,6 +268,64 @@ pub fn parse(args: &[String]) -> Cli { Some(None) }; found_an_option = true; + } else if arg == "--charge-current-limit" { + cli.charge_current_limit = if args.len() > i + 2 { + let limit = args[i + 1].parse::(); + let soc = args[i + 2].parse::(); + if let (Ok(limit), Ok(soc)) = (limit, soc) { + Some((limit, Some(soc))) + } else { + println!( + "Invalid values for --charge-current-limit: '{} {}'. Must be u32 integers.", + args[i + 1], + args[i + 2] + ); + None + } + } else if args.len() > i + 1 { + if let Ok(limit) = args[i + 1].parse::() { + Some((limit, None)) + } else { + println!( + "Invalid values for --charge-current-limit: '{}'. Must be an integer.", + args[i + 1], + ); + None + } + } else { + println!("--charge-current-limit requires one or two. [limit] [soc] or [limit]"); + None + }; + found_an_option = true; + } else if arg == "--charge-rate-limit" { + cli.charge_rate_limit = if args.len() > i + 2 { + let limit = args[i + 1].parse::(); + let soc = args[i + 2].parse::(); + if let (Ok(limit), Ok(soc)) = (limit, soc) { + Some((limit, Some(soc))) + } else { + println!( + "Invalid values for --charge-rate-limit: '{} {}'. Must be u32 integers.", + args[i + 1], + args[i + 2] + ); + None + } + } else if args.len() > i + 1 { + if let Ok(limit) = args[i + 1].parse::() { + Some((limit, None)) + } else { + println!( + "Invalid values for --charge-rate-limit: '{}'. Must be an integer.", + args[i + 1], + ); + None + } + } else { + println!("--charge-rate-limit requires one or two. [limit] [soc] or [limit]"); + None + }; + found_an_option = true; } else if arg == "--get-gpio" { cli.get_gpio = if args.len() > i + 1 { Some(args[i + 1].clone()) diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index b97ec0e2..bb3e9ea4 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -472,6 +472,7 @@ pub fn is_standalone(ec: &CrosEc) -> bool { pub fn get_and_print_power_info(ec: &CrosEc) -> i32 { if let Some(power_info) = power_info(ec) { + print_err_ref(&ec.get_charge_state(&power_info)); print_battery_information(&power_info); if let Some(_battery) = &power_info.battery { return 0; @@ -481,6 +482,7 @@ pub fn get_and_print_power_info(ec: &CrosEc) -> i32 { } fn print_battery_information(power_info: &PowerInfo) { + println!("Battery Status"); print!(" AC is: "); if power_info.ac_present { println!("connected");