Skip to content
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

added support for firmware-based thresholds #163

Closed

Conversation

double-thinker
Copy link

Thanks to Hector Martin @marcan for the reverse engineering.

This branch uses a new SMC key (CHWA) to set a firmware-based limit of 80% without any userspace daemon running therefore this limits works while the mac is sleep or shutdown down. This requires an updated firmware so if you cannot see CHWA key (sudo smc -r -k CHWA) please update your MacOs before reporting an issue.

If the user picks a limit below 80% a mixed strategy is applied: the daemon and the firmware-limit will be enabled.

I tried to keep your code structure adding only the needing parts but some subtle changes in the logic were added to handle a few corner cases. Mainly in the daemon auto removes itself if detects that is not needed.

I suppose battery maintain_synchronous is not expected to be run by users but after the first restart after updating battery tool or your MacOS this daemon could start with a 80% threshold.

I am testing this right now in my laptop and seems to work but consider it under heavy testing before merging it to the main branch. A few interesting points that I have found out:

  • CHO[BCI] are "ignored" if the CHWA is set to 01: meaning you cannot force charge above 80% when this limit is active
  • Seems there is a small delay to see changes in the battery status (a few seconds) after appling CHWA.

More research is needed:

  • How this interacts with "Optimized battery charging" setting? Changing CHWA does not affect this setting at least in the UI. Now I have disabled it before using this branch. Any feedback is welcome regarding this.
  • How persistent is CHWA? Affects the battery calibration or the SMC manages it somehow so it makes a full charge once a while to keep the calibration? (More context)
  • It is worthy to keep this mixed strategy? More charge cycles are expected if the laptop is suspended. My first guess it is not. Maybe moving to a only-firmware-limit version makes sense.

Thank you @actuallymentor for all your work and this handy tool ;)

Thanks to @marcan for the reverse engineering
@double-thinker
Copy link
Author

TLDR: How to install it

This is a preliminary work. If you are not comfortable using the terminal or debugging errors please wait until more research is done. Messing up with power management could have unexpected effects in your hardware.

  1. Install battery with brew: brew install battery and run it once to get the battery cli.
  2. Check battery cli is installed: ls /usr/local/bin/battery
  3. Disable any daemon if running: battery maintain stop
  4. Substitute it with this alpha cli
curl "https://raw.githubusercontent.com/double-thinker/battery/native-limit/battery.sh" > /usr/local/bin/battery

Now you can try your brand-new charging threshold (only works with 80%!):

battery maintain 80

@actuallymentor
Copy link
Owner

Thank you very much for this PR @double-thinker! It's very good work and will make the whole app much more stable across systems.

Is there a reason you coded this to use the 80% limit only or is that just for testing purposes?

@double-thinker
Copy link
Author

double-thinker commented Aug 14, 2023

Thank you very much for this PR @double-thinker! It's very good work and will make the whole app much more stable across systems.

Is there a reason you coded this to use the 80% limit only or is that just for testing purposes?

Apparently is the only one supported by the firmware. This is a binary flag to set and no custom limit apart of 80% is allowed. Based on Hector's commit this limit is harcoded into the firmware.

For that reason I keep the "mixed" strategy that combines the best of both worlds although I think using 80% managed by firmware is the best for the battery and should be the main/¿only? option in the future after more testing.

Please note that this branch breaks the GUI. I have no time to investigate it at least for now, maybe you can find the problem faster than me.

Also the sudoers file it is not working at least in my setup. Probably a syntax error or a typo.

@bmansvk
Copy link

bmansvk commented Sep 9, 2023

When this feature will be merged to main branch/release version? It seems that this is "killer feature" :)

@santilococo
Copy link

Feel free to reach out if you need help; I can lend a hand to merge this.

@wpiekutowski
Copy link

I experimented with CHWA alone, without any other battery limiter active.

Power on

There's a few seconds delay between writing 01 to CHWA and battery status icon change.

Power off

It seems that CHWA resets after power off, so battery charges to 100% in that situation. You need to set CHWA to 01 at startup again. To avoid 100% charge during power off, you need to disconnect the power adapter.

Sleep

It works fine in sleep, even with power adapter reconnection or when you put your mac to sleep while charging. That's a great improvement.

Summary

I believe using this SMC key would be a great addition to this app. It should be always on when limiter is active to prevent 100% charge in sleep. If user chooses 80% limit, we don't need to write to other SMC registries to stop charging, because macOS firmware will do this for us. It seems that this is what this PR is doing, so I'm going to give it a try. Thanks @double-thinker for putting this together.

@NightMachinery
Copy link

Do we need a whole binary for this? Can we just set this flag using a command invocation? It seems pretty good. We can disable the flag whenever we want a full charge, correct?

@lulor
Copy link

lulor commented Oct 22, 2023

@NightMachinery
Once you have smc installed you could simply use something like this to have a more user-friendly command:

#!/bin/bash

smc="/usr/local/bin/smc"
key="CHWA"
status_on="01"
status_off="00"

print_usage_and_exit () {
    echo "Usage: $(basename $0) on|off|status"
    exit 1
}

if [ "$#" -ne 1 ]
then
    print_usage_and_exit
fi

case "$1" in

    "on")
        sudo "$smc" -k "$key" -w "$status_on"
        ;;

    "off")
        sudo "$smc" -k "$key" -w "$status_off"
        ;;
    
    "status")
        status="$($smc -k $key -r | awk '{print $4}' | sed s:\)::)"
        case "$status" in
            "$status_on")
                echo "on"
                ;;
            "$status_off")
                echo "off"
                ;;
            *)
                echo "Unknown $key status: $status"
                ;;
        esac
        ;;

    *)
        print_usage_and_exit
        ;;

esac

@NightMachinery
Copy link

@lulor I created a minimal usage guide based on your script.

@hexclann
Copy link

Any update on this PR? Will it be merged? @actuallymentor

@hexclann
Copy link

+1

@hexclann
Copy link

+1

@double-thinker double-thinker removed their assignment Jan 2, 2024
@double-thinker
Copy link
Author

Hi! I've unassigned this PR from myself.

Sadly, I won't be able to find the time to resolve conflicts for this PR. Currently, it is 22 commits behind, and although I haven't delved into all those changes, a quick glance suggests they are compatible with the modifications I made.

Additionally, there are some questions that need some thinking of the app. Specifically regarding this:

It is worthy to keep this mixed strategy? More charge cycles are expected if the laptop is suspended. My first guess it is not. Maybe moving to a only-firmware-limit version makes sense.

I've added some comments to the code to illustrate my initial approach, which combines firmware-based and daemon-based methods.

After several months of using this on my Mac, I've come to believe that combining both approaches is unnecessary complex. I think a purely firmware-based solution is more appropriate if you have compatible hardware. The downside to Apple's method is the fixed 80% threshold. However, 80% is a reasonable choice and, for my part, I don't require much flexibility, so I switched to another tool.

That being said, this PR is totally functional and I believe this could be merged with some effort, feel free to reopen or fork it. If anyone want to do it, I will try to answer questions related with CHWA mechanism to anyone eager to merge it. (cc @santilococo @actuallymentor )

@maparaschivei
Copy link

The downside to Apple's method is the fixed 80% threshold. However, 80% is a reasonable choice and, for my part, I don't require much flexibility, so I switched to another tool.

Would you mind sharing which tool you are using? @double-thinker

@double-thinker
Copy link
Author

The downside to Apple's method is the fixed 80% threshold. However, 80% is a reasonable choice and, for my part, I don't require much flexibility, so I switched to another tool.

Would you mind sharing which tool you are using? @double-thinker

I'm using bclm. The author added CHWA compatibility and fits to my needs.

@wpiekutowski
Copy link

I went a similar route:

  1. https://github.com/hholtmann/smcFanControl provides smc binary, which can set CHWA to 01:
    sudo /Applications/smcFanControl.app/Contents/Resources/smc -k CHWA -w 01
  2. A LaunchDaemon to run it during boot

When I need to charge to 100%, I'm just executing
sudo /Applications/smcFanControl.app/Contents/Resources/smc -k CHWA -w 00

@actuallymentor
Copy link
Owner

Thank you for your work @double-thinker! And apologies for my late response.

I suspect you know well the risks of side projects getting out of hand and taking up your time, and that balancing them with your "actual" work can be challenging.

I'd love for someone to pick this up. Perhaps I will in the coming year, but I can make no guarantees of course.

For now, thank you so much for your work, and if you have more time in the future I'd love your input of course.

Thanks for the work so far, people like you make open source work :)

@dawithers
Copy link

dawithers commented Mar 3, 2024

I'm using bclm. The author added CHWA compatibility and fits to my needs.

I made a fork that switches to bclm under the hood and still works with the gui and still allows use of the non-maintenance smc functions. I'm not sure the best way to incorporate it into the original here as a pull request though because I'm not sure whether my choice of implementation would be accepted. I completely threw out the software daemon and just stuck with the natively supported 80% value that also works during sleep. I had no need or desire for arbitrary maintenance values and getting rid of that portion altogether made it so much simpler.

I haven't uploaded a gui just yet since I'm not an apple dev so it wouldn't be notarized or anything but I may throw one up there later.

I also considered renaming it to avoid confusion, but choosing a good name is always a big deal and implementing it throughout the app would take a little bit of work, so maybe later if it doesn't end up getting merged into this project to my liking.

https://github.com/dawithers/battery

@dawithers
Copy link

I've since just fully switched to using bclm by itself. I leave it enabled at 80% most of the time, but occasionally use the commandline to switch to 100% when I want to calibrate or to have a full charge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request ⏱️ waiting Waiting for response
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants