35

I'm using Ubuntu, but I have i3 as my window manager instead of a desktop environment.

When my battery reaches 0%, the computer will just abruptly shut down, no warning or anything.

Is there a simple script or configuration I can set up so that it goes to sleep at, say 4% battery?

gmarmstrong
  • 1,233
  • 1
  • 16
  • 36
o_o_o--
  • 785

6 Answers6

18

Here's a small script that checks for the battery level and calls a custom command, here pm-hibernate, in case the battery level is below a certain threshold.

#!/bin/sh

###########################################################################

Usage: system-low-battery

Checks if the battery level is low. If “low_threshold” is exceeded

a system notification is displayed, if “critical_threshold” is exceeded

a popup window is displayed as well. If “OK” is pressed, the system

shuts down after “timeout” seconds. If “Cancel” is pressed the script

does nothing.

This script is supposed to be called from a cron job.

###########################################################################

This is required because the script is invoked by cron. Dbus information

is stored in a file by the following script when a user logs in. Connect

it to your autostart mechanism of choice.

#!/bin/sh

touch $HOME/.dbus/Xdbus

chmod 600 $HOME/.dbus/Xdbus

env | grep DBUS_SESSION_BUS_ADDRESS > $HOME/.dbus/Xdbus

echo 'export DBUS_SESSION_BUS_ADDRESS' >> $HOME/.dbus/Xdbus

exit 0

if [ -r ~/.dbus/Xdbus ]; then source ~/.dbus/Xdbus fi

low_threshold=10 critical_threshold=4 timeout=59 shutdown_cmd='/usr/sbin/pm-hibernate'

level=$(cat /sys/devices/platform/smapi/BAT0/remaining_percent) state=$(cat /sys/devices/platform/smapi/BAT0/state)

if [ x"$state" != x'discharging' ]; then exit 0 fi

do_shutdown() { sleep $timeout && kill $zenity_pid 2>/dev/null

if [ x"$state" != x'discharging' ]; then exit 0 else $shutdown_cmd fi }

if [ "$level" -gt $critical_threshold ] && [ "$level" -lt $low_threshold ]; then notify-send "Battery level is low: $level%" fi

if [ "$level" -lt $critical_threshold ]; then

notify-send -u critical -t 20000 "Battery level is low: $level%"
'The system is going to shut down in 1 minute.'

DISPLAY=:0 zenity --question --ok-label 'OK' --cancel-label 'Cancel'
--text "Battery level is low: $level%.\n\n The system is going to shut down in 1 minute." & zenity_pid=$!

do_shutdown & shutdown_pid=$!

trap 'kill $shutdown_pid' 1

if ! wait $zenity_pid; then kill $shutdown_pid 2>/dev/null fi

fi

exit 0

It's a very simple script, but I think you get the idea and can easily adapt it to your needs. The path to the battery level might be different on your system. A little more portable would probably be to use something like acpi | cut -f2 -d, to obtain the battery level. This script can be scheduled by cron to run every minute. Edit your crontab with crontab -e and add the script:

*/1 * * * * /home/me/usr/bin/low-battery-shutdown

Another solution would be to install a desktop environment like Gnome or Xfce (and change your window manager to i3). Both mentioned destop environments feature power management daemons which take care of powering off the computer. But I assume you deliberately don't use them and are seeking for a more minimalistic solution.

Pablo A
  • 2,712
Marco
  • 33,548
  • Hm, I tried running sleepd -b 40 and nothing happened after the 40% mark. I also tried sudo sleepd -b 40 -s pm-suspend and nothing happens... – o_o_o-- Jul 25 '13 at 18:45
  • @NoamGagliardi Confirmed, it doesn't work here either. Furthermore the package seems unmaintained. I try if I can find a better alternative and update my answer, otherwise I'll delete it. – Marco Jul 25 '13 at 19:18
  • (TIL "cut".) The script works! I have acpi | cut -f2 -d, | cut -f1 d% -- I'll read about cron to get it to run on its own. Thanks! – o_o_o-- Jul 25 '13 at 22:05
  • I don't have /sys/devices/platform/smapi/ directory. Where can I find the remaining percentage of battery power? I am using custom kernel 3.10 – Martin Vegter Jan 16 '14 at 16:21
  • 2
    @MartinVegter It depends on your hardware, you can try /sys/class/power_supply/BAT0/capacity. Otherwise use the acpi command. – Marco Jan 16 '14 at 19:13
14

Instead of hacking your own scripts and if you are using Ubuntu as the tag suggests, you could just install the upower package. It should be available on all Debian derivatives including Ubuntu. By default it comes with a configuration in /etc/UPower/UPower.conf which activates hybrid sleep once the battery level reaches critical values. The default for the critical level is 2%.

For users of other distributions, the relevant entries for /etc/UPower/UPower.conf are:

PercentageAction=2
CriticalPowerAction=HybridSleep

You can also use TimeAction together with UsePercentageForPolicy=false to let the action be carried out once only the specified time is left:

TimeAction=120

The valid values for CriticalPowerAction are PowerOff, Hibernate and HybridSleep. If HybridSleep is set but not available, Hibernate will be used. If Hibernate is set but not available, PowerOff will be used.

The advantage of HybridSleep is, that in addition to writing out memory into your swap area, it then suspends the system. Suspend will still consume some battery but if you come back before the battery ran out, you can much more quickly resume from a suspended system than from a hibernated one. In case the battery does run out before you get back to a power socket, you can resume the system from hibernation once you have power again.

josch
  • 355
  • 1
    Note: I think HybridSleep requires to have a swap space. –  Mar 31 '17 at 21:01
  • 3
    @cipricus that is correct but upower will gracefully choose to shut the machine down instead if it cannot hibernate. – josch Apr 03 '17 at 12:13
  • is resume automatic when the battery gets charged or does one have to manually press the power button? – Martin Massera Sep 06 '21 at 13:57
  • @MartinMassera one has to manually press the power button – josch Sep 07 '21 at 02:39
  • 1
    To get related info: cat /etc/UPower/UPower.conf | grep -v "^#" | egrep "Critical|Low|Action|Time"; gsettings list-recursively org.gnome.settings-daemon.plugins.power. To get suspend/hibernate support use pm-is-supported from pm-utils package – Pablo A Oct 19 '21 at 15:39
  • Should it (editing UPower.conf) work w/out reboot? I've tried on Linux Mint and it does not. Maybe daemon restart is needed? – Alex Martian Nov 22 '21 at 13:39
14

As of Debian ≥ 10 (and comparably recent Linux systems), you can just create a file /etc/cron.d/check-battery that contains:

* * * * * root [ "$(cat /sys/class/power_supply/BAT0/status)" != Discharging -o "$(cat /sys/class/power_supply/BAT0/capacity)" -gt 30 ] || systemctl suspend

This will suspend your system whenever the battery level reaches 30%.

Of course, feel free to replace the final suspend with hybrid-sleep, hibernate, poweroff or whatever fits your needs.

No external tools are required, not even the acpi package. This is based on the idea of Matija Nalis' answer, but adjusted to the year 2023.

vog
  • 409
  • 2
    I don't know if the upower answer is obsolete or not. It just didn't work for me, and I didn't find enough time to investigate and to debug that stuff. And to put it bluntly: Why should I? This simple one-liner is doing its job well. It is reliable since the day I created it, with a minimum of dependencies and system requirements. The only thing I changed since then is increasing the threshold from 10% to 30%. – vog Dec 25 '20 at 03:06
  • What do the stars mean? Why did you write them? – wondering Feb 25 '21 at 10:44
  • Every file in /etc/cron.d/ is a crontab. The 5 stars mean that the following command is to be executed every minute, and "root" is the user under whose permissions the command is to be executed. See man 5 crontab for more details. – vog Feb 26 '21 at 12:58
  • 1
    note: .../BAT0/status maybe return Unknown,so it is better to use [ "$(cat /sys/class/power_supply/BAT0/status)" = Discharging -a "$(cat /sys/class/power_supply/BAT0/capacity)" -lt 10 ] && systemctl suspend instead. – ipcjs Jul 22 '21 at 10:35
  • 1
    @ipcjs Thanks for the hint. I improved my answer accordingly. Note that I'm preferring "!= Discharging" over inverting the whole logic, as the latter would make the command return with a non-zero exit code when everything is fine, which would be quite confusing. – vog Jul 26 '21 at 00:52
  • 1
    @lucidbrot, Just now to Linux Mint 20.2 at least on the fly (w/out reboot) editing UPower.conf has not resulted in action(s) when thresholds were achieved and % decreased even further. Maybe it needs daemon restart, but help (as man page itself notices "not fully documented"). – Alex Martian Nov 22 '21 at 13:37
  • I needed to replace /BAT0/ with /BAT1/ on a 2022 "frame.work" laptop running Linux Mint. – hobs Jan 01 '24 at 22:41
3

The currently accepted answer is great, but a little bit outdated for Ubuntu 16.04:

  • The commands to get battery status have changed.
  • The environment variables required for notify-send to work have changed.
  • The script given there no longer works from user cron as hibernate requires root.
  • systemctl hibernate is preferred over pm-hibernate.

So, here is the script I use:

#!/usr/bin/env bash

Notifies the user if the battery is low.

Executes some command (like hibernate) on critical battery.

This script is supposed to be called from a cron job.

If you change this script's name/path, don't forget to update it in crontab !!

level=$(cat /sys/class/power_supply/BAT1/capacity) status=$(cat /sys/class/power_supply/BAT1/status)

Exit if not discharging

if [ "${status}" != "Discharging" ]; then exit 0 fi

Source the environment variables required for notify-send to work.

env_vars_path="$HOME/.env_vars" source "${env_vars_path}"

low_notif_percentage=20 critical_notif_percentage=15 critical_action_percentage=10

if [ "${level}" -le ${critical_action_percentage} ]; then

sudo is required when running from cron

sudo systemctl hibernate exit 0 fi

if [ "${level}" -le ${critical_notif_percentage} ]; then notify-send -i '/usr/share/icons/gnome/256x256/status/battery-caution.png' "Battery critical: ${level}%" exit 0 fi

if [ "${level}" -le ${low_notif_percentage} ]; then notify-send -i '/usr/share/icons/gnome/256x256/status/battery-low.png' "Battery low: $level%" exit 0 fi

The environment variables required for notify-send to work are created using this script:

#!/usr/bin/env bash

Create a new file containing the values of the environment variables

required for cron scripts to work.

This script is supposed to be scheduled to run at startup.

env_vars_path="$HOME/.env_vars"

rm -f "${env_vars_path}" touch "${env_vars_path}" chmod 600 "${env_vars_path}"

Array of the environment variables.

env_vars=("DBUS_SESSION_BUS_ADDRESS" "XAUTHORITY" "DISPLAY")

for env_var in "${env_vars[@]}" do echo "$env_var" env | grep "${env_var}" >> "${env_vars_path}"; echo "export ${env_var}" >> "${env_vars_path}"; done

This file needs to run at startup (can be done using any method of your choice; I use Ubuntu's builtin Startup Applications).

Note: sudo systemctl hibernate might not work from cron. Follow this to solve it.

Pablo A
  • 2,712
0

There are many ways it could be implemented, as there are many different power managment schemes implemented depending on what you have installed.

This simple one works for me on minimalistic Debian Jessie without any desktop environment, just with small and fast icewm window manager. (It is trimmed down because is just way too slow otherwise, and this way it outperforms GNOME on much better hardware)

Specifically, I DO have installed following packages: acpi acpi-fakekey acpi-support acpi-support-base acpid pm-utils but have NONE of the following (having purged them): gnome* kde* systemd* uswsusp upower laptop-mode-tools hibernate policykit-1

So I just put this in /etc/cron.d/battery_low_check (all in one line, split for readability):

*/5 * * * *   root  acpi --battery | 
   awk -F, '/Discharging/ { if (int($2) < 10) print }' | 
   xargs -ri acpi_fakekey 205

It is quick, low-resource-usage, and does not depend on other deamons (if fact, it will be ignored if they're active - see /usr/share/acpi-support/policy-funcs for details).

What it does: every 5 minutes (*/5 - you can change to every minute by just using * if you need it to check battery more often) it will poll battery status ("acpi --battery") and execute command after xargs -ri only if battery is "Discharging" (that is, you're not connected to AC) and battery status is less than 10% ("int ($2) < 10" - feel free to tune it to your needs)

acpi_fakekey 205 will by default send KEY_SUSPEND ACPI event (like you pressed a key on laptop requesting suspend), which will then do whatever it usually does for you (configured in /etc/default/acpi-support) - for me it hibernates to disk.

You could use other command instead of acpi_fakekey 205 of course: like hibernate (from hibernate package), s2disk or s2mem (from uswsusp package), pm-suspend-hybrid (from pm-utils package) etc.

BTW, magic key numbers like KEY_SUSPEND=205 above are defined in /usr/share/acpi-support/key-constants (other interesting one is probably KEY_SLEEP=142)

Matija Nalis
  • 3,111
  • 1
  • 14
  • 27
  • that seems very nice! but could that be used with systemd timer instead of cron? (example here) I'm on Solus OS where cron is absent. –  Mar 31 '17 at 21:32
  • @cipricus I guess so, but I avoid systemd so can't give example. I do seem to recall systemd has its own ACPI power handlers, so if you're stuck with systemd you would probably want to avoid clashing with that – Matija Nalis Apr 04 '17 at 20:04
  • thanks, I have found an alternative involving uname: https://github.com/jerrinfrncs/batterynotif/blob/master/batterynotif%28uname%29.sh –  Apr 04 '17 at 21:10
0

I like this solution, that is partly inspired by other answers: https://github.com/jerrinfrncs/batterynotif, namely the script batterynotif(uname).sh.

See the script here: https://github.com/jerrinfrncs/batterynotif/blob/master/batterynotif%28uname%29.sh

For my own use I have changed the script to enter hybrid-sleep instead of shut-down, by using the command systemctl hybrid-sleep. (Swap space is needed by this option.)