Skip to content

Chargerate #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 108 additions & 13 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down Expand Up @@ -139,6 +126,7 @@ ALS: 76 Lux
Fan Speed: 0 RPM
```


## Check expansion bay (Framework 16)

```
Expand All @@ -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
Comment on lines +245 to +246
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the limit (out of 1.0) is always required and the state of charge (out of 100) is optional, and in the code the limit is always first and the SoC is second (in an Option), so would that mean that this example has the arguments reversed? Sorry if I'm missing something obvious; I can't test it because of the issue in the other comment I'm leaving.

```
2 changes: 2 additions & 0 deletions framework_lib/src/chromium_ec/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions framework_lib/src/chromium_ec/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EcResponseChargeStateGetV0> 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 {
Expand Down
64 changes: 64 additions & 0 deletions framework_lib/src/chromium_ec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u32>) -> 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<f32>) -> 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
Expand Down Expand Up @@ -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)?;
Expand Down
25 changes: 25 additions & 0 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ struct ClapCli {
#[arg(long)]
charge_limit: Option<Option<u8>>,

/// Get or set max charge current limit
#[arg(long)]
#[clap(num_args = ..2)]
charge_current_limit: Vec<u32>,

/// Get or set max charge current limit
#[arg(long)]
#[clap(num_args = ..2)]
Copy link

@tjkirch tjkirch May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding correctly, I believe this limits the argument count to a max of 1, and if you want to allow 2, you need ..=2 instead, right? Same for charge rate limit. I mention this because I'm trying to use framework_tool v0.4 with --charge-rate-limit 60 0.7 (similar to the syntax given in the new examples) but I get a clap error, "unexpected argument '0.7' found". It does work with a single argument, but I wanted to use the conditional form with the second argument based on battery state of charge.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right. Thanks!. #123

charge_rate_limit: Vec<f32>,

/// Get GPIO value by name
#[arg(long)]
get_gpio: Option<String>,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ pub struct Cli {
pub inputdeck_mode: Option<InputDeckModeArg>,
pub expansion_bay: bool,
pub charge_limit: Option<Option<u8>>,
pub charge_current_limit: Option<(u32, Option<u32>)>,
pub charge_rate_limit: Option<(f32, Option<f32>)>,
pub get_gpio: Option<String>,
pub fp_led_level: Option<Option<FpBrightnessArg>>,
pub fp_brightness: Option<Option<u8>>,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 [<VAL>] Get or set battery charge limit (Percentage number as arg, e.g. '100')
--charge-current-limit [<VAL>] Get or set battery current charge limit (Percentage number as arg, e.g. '100')
--get-gpio <GET_GPIO> Get GPIO value by name
--fp-led-level [<VAL>] Get or set fingerprint LED brightness level [possible values: high, medium, low]
--fp-brightness [<VAL>]Get or set fingerprint LED brightness percentage
Expand Down
Loading