How do I make my laptop sleep when it reaches some low battery threshold?

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
  . ~/.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.


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.


As of 2021, you can just create a file /etc/cron.d/check-battery that contains:

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

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

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 2021.