Skip to content

Commit 20b37c0

Browse files
CasperVMbe-westAdnanHodzicCopilot
authored
Fix: start/stop thresholds not being set because of initial values (#900)
* fix: (WIP) start/stop thresholds not being set because of initial values * refactor: rewrite battery scripts more cleanly + fix bug where start/stop charge thres settings don't apply * fix: battery start/stop pr: small typos, pr comments.. * fix: actually validate start/stop, and enforce both to be present * fix: start/stop threshold value check + add more docs + add override check_thresholds=false for exceptional cases * add: debug prints for testers * fix: type errors in battery config, prefer charge_control_{start,end}_threshold values over charge_{start,stop}_threshold * fix: do not change charge thresholds with --monitor flag * fix: reference ideapad_laptop kernel module in Readme * Regularly apply battery thresholds, add note in README about conflicting software, remove debug statements * Apply copilot suggestions. Remove misleading section about fixed thresholds from README * Update auto_cpufreq/battery_scripts/shared.py Fix grammatical error Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unnecessary shell option from run call Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Do not change Ideapad conservation mode if not explicitly set in config, fix config not being overridden in IdeapadBatteryDevice class * Suggest setting start threshold to 0 in README --------- Co-authored-by: Benedikt Westreicher <benedikt.westreicher@hotmail.com> Co-authored-by: Adnan Hodzic <adnan@hodzic.org> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Benedikt Westreicher <48734011+be-west@users.noreply.github.com>
1 parent 6452056 commit 20b37c0

File tree

9 files changed

+374
-240
lines changed

9 files changed

+374
-240
lines changed

README.md

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -608,53 +608,59 @@ As of [v2.2.0](https://github.com/AdnanHodzic/auto-cpufreq/releases/tag/v2.2.0),
608608

609609
- **Lenovo ThinkPad** (thinkpad_acpi)*
610610
- **Lenovo IdeaPad** (ideapad_acpi)*
611+
- **Lenovo Laptop** (ideapad_laptop)*
611612
- **ASUS :Laptops** (asus_wmi)*
612613

613-
***Please note, your laptop must have an installed ACPI kernel driver specific to the manufacturer.** To check if you have the correct module installed and loaded run `lsmod [module]`
614+
**Please note, your laptop must have an installed ACPI kernel driver specific to the manufacturer.** To check if you have the correct module installed and loaded run `lsmod [module]`
615+
616+
Additionally, **you should make sure that you have no other software running that may conflict with auto-cpufreq's battery threshold management** (e.g., GNOME's *Preserve Battery Health* option). Using both at the same time will lead to conflicts and to your battery potentially never charging.
614617

615618
**To request that your device be supported, please open an [issue](https://github.com/AdnanHodzic/auto-cpufreq/issues/new). In your issue, make us aware of the driver that works with your laptop**
616619

617620
### Battery config
621+
618622
Edit the config at `/etc/auto-cpufreq.conf`
619623

620624
Example config for battery ([already part of example config file](https://github.com/AdnanHodzic/auto-cpufreq/#example-config-file-contents))
621-
```
625+
626+
```ini
622627
[battery]
623628
enable_thresholds = true
624629
start_threshold = 20
625630
stop_threshold = 80
626631
```
627632

628-
### Lenovo_laptop conservation mode
629-
630-
this works only with `lenovo_laptop` kernel module compatable laptops.
633+
You will need to specify both `start_threshold` AND `stop_threshold` in most cases.
634+
For these changes to have an effect, you will need to restart the daemon:
631635

632-
add `ideapad_laptop_conservation_mode = true` to your `auto-cpufreq.conf` file
636+
```shell
637+
systemctl restart auto-cpufreq.service
638+
```
633639

634-
### Special cases of Lenovo_ideapad (or some other models with fixed threshold)
640+
See more here on the kernel doc pages: [docs.kernel.org](https://docs.kernel.org/admin-guide/laptops/thinkpad-acpi.html#battery-charge-control)
635641

636-
As you may know, for some laptop models you can only decide to limit battery charging but can not set the limit value. The limit value is set by the manufacturer in the system (generally 60% and sometimes 80%). Also, you can not set the value of start charging.
642+
### A few notes about these battery config options
637643

638-
This limit value is not always accessible for users to avoid changing it, but you can try looking in some of these paths :
644+
When you remove/uninstall the auto-cpufreq daemon, the last applied settings for battery thresholds will still apply. You might need to manually set these yourself to whatever default they were before. E.g. (as sudo):
639645

640-
```
641-
cat /sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/charge_control_end_threshold
642-
cat /sys/class/power_supply/BAT0/charge_control_end_threshold
643-
cat /sys/class/power_supply/BAT0/charge_control_start_threshold
646+
```shell
647+
echo 0 > /sys/class/power_supply/BAT0/charge_start_threshold
648+
echo 100 > /sys/class/power_supply/BAT0/charge_stop_threshold
644649
```
645650

646-
This is the config to apply at /etc/auto-cpufreq.conf in order to stop battery charging at 60% or 80% depending on the value set in the system by the manufacturer.
651+
### Ideapad conservation mode
647652

648-
```
649-
[battery]
650-
enable_thresholds = true
651-
start_threshold = 20
652-
stop_threshold = 1
653+
This works only with laptops that have the `ideapad_laptop` kernel module.
653654

655+
add `ideapad_laptop_conservation_mode = true` to your `auto-cpufreq.conf` file
656+
657+
```ini
658+
[battery]
659+
ideapad_laptop_conservation_mode = true
654660
```
655-
start_threshold = 20 (should be present with a valid number but it's ignored)
656661

657-
stop_threshold = 1 (to stop charging the battery at the limit value 60% or 80%)
662+
This is a special mode which limits the maximum charge level of the battery to around 60-80% depending on the model.
663+
658664

659665
### Ignoring power supplies
660666

@@ -663,7 +669,7 @@ to limit preformence to ignore them add to you config file the name of the power
663669

664670
the name of the power supply can be found with `ls /sys/class/power_supply/`
665671

666-
```
672+
```ini
667673
[power_supply_ignore_list]
668674

669675
name1 = this
@@ -688,7 +694,7 @@ xboxctrl = {the xbox controler power supply name}
688694

689695
This can be done by editing the `GRUB_CMDLINE_LINUX_DEFAULT` params in `/etc/default/grub`. For instance:
690696

691-
```
697+
```shell
692698
sudo nano /etc/default/grub
693699
# make sure you have nano installed, or you can use your favorite text editor
694700
```
@@ -707,35 +713,35 @@ GRUB_CMDLINE_LINUX_DEFAULT="quiet splash initcall_blacklist=amd_pstate_init amd_
707713

708714
Once you have made the necessary changes to the GRUB configuration file, you can update GRUB by running `sudo update-grub` on Debian/Ubuntu, `sudo grub-mkconfig -o /boot/grub/grub.cfg` on Arch Linux, or one of the following on Fedora:
709715

710-
```
716+
```shell
711717
sudo grub2-mkconfig -o /etc/grub2.cfg
712718
```
713719

714-
```
720+
```shell
715721
sudo grub2-mkconfig -o /etc/grub2-efi.cfg
716722
```
717723

718-
```
724+
```shell
719725
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
720726
# legacy boot method
721727
```
722728

723729
For systemd-boot users:
724730

725-
```
731+
```shell
726732
sudo nano /etc/kernel/cmdline
727733
# make sure you have nano installed, or you can use your favorite text editor
728734
```
729735

730736
For Intel users:
731737

732-
```
738+
```shell
733739
quiet splash intel_pstate=disable
734740
```
735741

736742
For AMD users:
737743

738-
```
744+
```shell
739745
quiet splash initcall_blacklist=amd_pstate_init amd_pstate.enable=0
740746
```
741747

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,8 @@
11
#!/usr/bin/env python3
2-
import os
3-
from subprocess import check_output
42

5-
from auto_cpufreq.config.config import config
6-
from auto_cpufreq.globals import POWER_SUPPLY_DIR
3+
from auto_cpufreq.battery_scripts.shared import BatteryDevice
74

8-
def set_battery(value, mode, bat):
9-
path = f"{POWER_SUPPLY_DIR}{bat}/charge_{mode}_threshold"
10-
fallback_mode = "start" if mode == "start" else "end"
11-
fallback_path = f"{POWER_SUPPLY_DIR}{bat}/charge_control_{fallback_mode}_threshold"
12-
if os.path.isfile(path): check_output(f"echo {value} | tee {path}", shell=True, text=True)
13-
elif os.path.isfile(fallback_path): check_output(f"echo {value} | tee {fallback_path}", shell=True, text=True)
14-
else: print(f"WARNING: {path} does NOT exist")
155

16-
def get_threshold_value(mode):
17-
conf = config.get_config()
18-
return conf["battery"][f"{mode}_threshold"] if conf.has_option("battery", f"{mode}_threshold") else (0 if mode == "start" else 100)
19-
20-
def asus_setup():
21-
conf = config.get_config()
22-
23-
if not (conf.has_option("battery", "enable_thresholds") and conf["battery"]["enable_thresholds"] == "true"): return
24-
25-
if os.path.exists(POWER_SUPPLY_DIR):
26-
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith('BAT')]
27-
28-
for bat in batteries:
29-
set_battery(get_threshold_value("start"), "start", bat)
30-
set_battery(get_threshold_value("stop"), "stop", bat)
31-
else: print(f"WARNING {POWER_SUPPLY_DIR} does NOT esixt")
32-
33-
34-
def asus_print_thresholds():
35-
batteries = [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith('BAT')]
36-
print("\n-------------------------------- Battery Info ---------------------------------\n")
37-
print(f"battery count = {len(batteries)}")
38-
for bat in batteries:
39-
try:
40-
primary_start = f"{POWER_SUPPLY_DIR}{bat}/charge_start_threshold"
41-
primary_stop = f"{POWER_SUPPLY_DIR}{bat}/charge_stop_threshold"
42-
fallback_start = f"{POWER_SUPPLY_DIR}{bat}/charge_control_start_threshold"
43-
fallback_stop = f"{POWER_SUPPLY_DIR}{bat}/charge_control_end_threshold"
44-
if os.path.isfile(primary_start): print(bat, "start threshold =", check_output(["cat", primary_start]))
45-
elif os.path.isfile(fallback_start): print(bat, "start threshold =", check_output(["cat", fallback_start]))
46-
else: print(f"{bat} start threshold: file not found")
47-
if os.path.isfile(primary_stop): print(bat, "stop threshold =", check_output(["cat", primary_stop]))
48-
elif os.path.isfile(fallback_stop): print(bat, "stop threshold =", check_output(["cat", fallback_stop]))
49-
else: print(f"{bat} stop threshold: file not found")
50-
except Exception as e: print(f"ERROR: failed to read battery {bat} thresholds:", repr(e))
6+
class AsusBatteryDevice(BatteryDevice):
7+
def __init__(self):
8+
super().__init__()
Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,57 @@
11
#!/usr/bin/env python3
22
from subprocess import PIPE, run
3+
from threading import Thread
4+
from time import sleep
35

4-
from auto_cpufreq.battery_scripts.ideapad_acpi import ideapad_acpi_print_thresholds, ideapad_acpi_setup
5-
from auto_cpufreq.battery_scripts.ideapad_laptop import ideapad_laptop_print_thresholds, ideapad_laptop_setup
6-
from auto_cpufreq.battery_scripts.thinkpad import thinkpad_print_thresholds, thinkpad_setup
7-
from auto_cpufreq.battery_scripts.asus import asus_print_thresholds, asus_setup
6+
from auto_cpufreq.battery_scripts.asus import AsusBatteryDevice
7+
from auto_cpufreq.battery_scripts.ideapad_laptop import IdeapadBatteryDevice
8+
from auto_cpufreq.battery_scripts.shared import BatteryDevice
9+
10+
BATTERY_APPLY_INTERVAL = 3600 # 1 hour
11+
12+
13+
def lsmod(module):
14+
return (
15+
module in run(["lsmod"], stdout=PIPE, stderr=PIPE, text=True).stdout
16+
)
817

9-
def lsmod(module): return module in run(['lsmod'], stdout=PIPE, stderr=PIPE, text=True, shell=True).stdout
1018

1119
def battery_get_thresholds():
12-
if lsmod("ideapad_acpi"): ideapad_acpi_print_thresholds()
13-
elif lsmod("ideapad_laptop"): ideapad_laptop_print_thresholds()
14-
elif lsmod("thinkpad_acpi"): thinkpad_print_thresholds()
15-
elif lsmod("asus_wmi"): asus_print_thresholds()
16-
else: return
17-
18-
def battery_setup():
19-
if lsmod("ideapad_acpi"): ideapad_acpi_setup()
20-
elif lsmod("ideapad_laptop"): ideapad_laptop_setup()
21-
elif lsmod("thinkpad_acpi"): thinkpad_setup()
22-
elif lsmod("asus_wmi"): asus_setup()
23-
else: return
20+
dev = get_battery_device()
21+
if dev is not None:
22+
return dev.print_thresholds()
23+
24+
25+
def start_battery_daemon():
26+
"""Battery daemon that applies battery charge thresholds at regular intervals."""
27+
dev = get_battery_device()
28+
if dev is None:
29+
print(
30+
"WARNING: No supported battery device found, battery thresholds will not be applied."
31+
)
32+
return
33+
34+
def battery_daemon():
35+
while True:
36+
try:
37+
dev.apply_threshold_settings()
38+
except Exception as e:
39+
print(
40+
f"ERROR: An error occurred while applying battery thresholds: {e}"
41+
)
42+
sleep(BATTERY_APPLY_INTERVAL)
43+
44+
Thread(target=battery_daemon, daemon=True).start()
45+
46+
47+
def get_battery_device():
48+
if lsmod("ideapad_acpi"):
49+
return BatteryDevice()
50+
elif lsmod("ideapad_laptop"):
51+
return IdeapadBatteryDevice()
52+
elif lsmod("thinkpad_acpi"):
53+
return BatteryDevice()
54+
elif lsmod("asus_wmi"):
55+
return AsusBatteryDevice()
56+
else:
57+
return None

auto_cpufreq/battery_scripts/ideapad_acpi.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)