January 1, 2021

Alert on Low Battery in I3

Working on i3, you have to define a lot of functionalities yourself, or install the program that does it for you. One of those functionalities is to be alerted in case of a low battery. Let’s explore two options, the very simple and the very customizable, so that you can choose what fits you best. You’ll learn more about linux going the custom route, but it will take longer to get running.

Note: This post was written to work on Arch Linux, and you may have to adapt it to your distribution to get it to work.

Option 1: Installing cbatticon

If you don’t want to bother creating the functionality yourself, you just need to install and run cbatticon. Installing this tiny program is as easy as

$ pacman -S cbatticon

Once installed, run it on i3 startup by adding the following line to your i3 configuration file (~/.config/i3/config).

exec cbatticon

You’ll see a new little icon in your tray bar with the battery status, and will get notifications when the battery gets low, along with multiple other ones, like battery in charge or discharge.

Option 2: Creating your custom notification

Since I did not want an extra tray icon, and that some of cbatticons illustrations were not to my liking, I decided to go the custom route to completely get rid of it, keeping only the charge percentage in my status bar. I also wanted to understand how I could read those numbers to understand my system a little better.

You will learn a few things about your linux system:

  • The notify-send command, which allows you to send notifications to your desktop
  • The /sys/class/power_supply/BAT0 folder, containing information about the battery of your laptop
  • Systemd one shot services, which allow you to run commands or program once as services
  • Systemd timers, which let you run those one shot services at given intervals
  • Systemd services for users, that let any user create and manage their own services.

Reading information for the battery

First, let’s see how we can get the information about the charge of the battery.

There is the acpi command that allows you to get this information easily from the command line, but it is more suited for human users, and would need us to parse its output to get the percentage of battery remaining.

$ acpi
Battery 0: Charging, 31%, 01:24:19 until charged

However, we can easily get this information by simply reading it in a directory that is populated nicely by the kernel for us to get information about the system. All that is related to the battery should live in /sys/class/power_supply/BAT0 (the BAT0 part may be different on your machine, ensure you adapt this part to your system).

Let’s see what’s in there:

$ ls /sys/class/power_supply/BAT0
alarm
capacity
capacity_level
charge_full
charge_full_design
charge_now
current_now
cycle_count
device
hwmon3
manufacturer
model_name
power
present
serial_number
status
subsystem
technology
type
uevent
voltage_min_design
voltage_now

The files that jump out to me right away are charge_now, charge_full and status.

# /sys/class/power_supply/BAT0/status
Discharging
# /sys/class/power_supply/BAT0/charge_now
5003000
# /sys/class/power_supply/BAT0/charge_full
6277000

Right now, I’m at 79% battery, unplugged, so these values check out (5003000 / 6277000 = 0.79). But I’d rather not do any calculations on my own… I looked at the other files, and found the charge percentage in capacity

# /sys/class/power_supply/BAT0/capacity
79

I checked that this values did decrease with the battery and it does. All good !

We can then start a script to display the remaining battery when it is discharging and under 20%. I installed this script in /usr/local/bin/alert-battery

#!/bin/bash
bat_files="/sys/class/power_supply/BAT0"
bat_status=$(cat "${bat_files}/status")
capacity=$(cat "${bat_files}/capacity")
echo "${capacity}"
if [[ "${bat_status}"=="Discharging" && ${capacity} -le 20 ]]; then
    echo "Battery alert - ${capacity}%"
fi

When we run the script, modifying the capacity threshold to trigger the output

$ /usr/local/bin/alert-battery
Battery alert - 79%

Creating a desktop notification

Now that we have the conditions for sending a notification encoded, let’s send it to the desktop using the notify-send command. Let’s try it out first

$ notify-send "Amazing notification"

Simple notification

Pretty easy !

We can also give a description of the notification, and an icon

$ notify-send \
    "Amazing notification" \
    "A very good description of the notification" \
    --icon=notification-icon.png

Better notification

Icon made by Pixel perfect from Flaticon

Let’s add a notification to our script with a low battery icon (which I like to put inside /usr/local/share/icons) and the amount of battery remaining:

#!/bin/bash
bat_files="/sys/class/power_supply/BAT0"
bat_status=$(cat ${bat_files}/status)
capacity=$(cat "${bat_files}/capacity")
if [[ ${bat_status}=="Discharging" && ${capacity} -le 20 ]]; then
    echo "Battery alert - ${capacity}%"
    notify-send \
        --icon=/usr/local/share/icons/battery_low_dark.png \
        "Low battery" \
        "Only ${capacity}% battery remaining"
fi

The result looks quite nice and simple (well the percentage does not match…)

Battery notification

Running the script every 5 minutes

In ArchLinux, there is no crontab by default to run scripts periodically. To run such jobs, you need to create a systemd timer which will trigger a systemd service that will run once when called. While this is more steps than a simple cronjob, it allows for easier debugging and management, since you can run the service manually at any time. You also get a saner environment and log management for free.

Let’s start by creating the service and running it manually. We are going to create a new service which will be user managed, since it only applies to the current user’s desktop. Create an empty file for this service using the systemctl edit, to make it in the right place

$ systemctl edit --user --force --full alert-battery.service

This will open your editor, which you can fill with the following service definition:

[Unit]
Description=Alert in case of low remaining battery

[Service]
Type=oneshot
ExecStart=/usr/local/bin/alert-battery

[Install]
WantedBy=graphical.target

Once you save and quit, this file will be written to ~/.config/systemd/user/alert-battery.service.

The two most important statements of this file for us are Type=oneshot, which ensures that the script is run once then quits, and ExecStart=/usr/local/bin/alert-battery letting our service know which script to run.

You can then make the system pick up this new service by running

$ systemctl --user daemon-reload

And run the service manually (make sure to modify the low battery threshold for the notification to appear)

$ systemctl --user start alert-battery.service

You should see the notification appear, and you can see the status of your service by running

$ systemctl --user status alert-battery.service
● alert-battery.service - Alert in case of low remaining battery
     Loaded: loaded (/home/user/.config/systemd/user/alert-battery.service; disabled; vendor preset: enabled)
     Active: inactive (dead) since Tue 2020-12-29 11:35:51 CET; 4min 33s ago
    Process: 2177341 ExecStart=/usr/local/bin/alert-battery (code=exited, status=0/SUCCESS)
   Main PID: 2177341 (code=exited, status=0/SUCCESS)

déc. 29 11:35:51 machine systemd[2078]: Starting Alert in case of low remaining battery...
déc. 29 11:35:51 machine systemd[2078]: alert-battery.service: Succeeded.
déc. 29 11:35:51 machine systemd[2078]: Finished Alert in case of low remaining battery.

Now that we are confident our service works, we can create the timer to trigger it every 5 minutes. Let’s create the file manually. Create ~/.config/systemd/user/alert-battery.timer, with the following configuration

[Unit]
Description=Check on battery every 5 minutes to warn the user in case of low battery

[Timer]
OnActiveSec=5m
OnUnitActiveSec=5m

[Install]
WantedBy=timers.target

The option OnActiveSec=5m configures our timer to first trigger 5 minutes after our service started, and OnUnitActiveSec=5m to activate again 5 minutes after our latest execution.

Start your service, and enable it to make sure that it will run after rebooting:

$ systemctl --user service start alert-battery.timer
$ systemctl --user service enable alert-battery.timer

Then list all of your activated timers to confirm that it is running

$ systemctl --user list-timers
NEXT                        LEFT          LAST PASSED UNIT                ACTIVATES
Sun 2020-12-27 21:14:54 CET 3min 20s left n/a  n/a    alert-battery.timer alert-battery.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.

And you’re good to go ! Make sure to notice next time you’re low on battery if you do get the notification, ensuring that everything is running smoothly.

What’s next ?

From this example, we can extract a method on how to run programs periodically. You could run other recurring tasks, like backups or updates. You just need to create a working script, the service running it and finally trigger the service with a timer.

While this takes more time to create, you can customize it however you want. You could send yourself an email when battery is low (why would you want to do that is an other question), reduce brightess, or even stop some processes. If you think this should trigger something else, you can code it yourself and get it done.

How can I help ?

I’m going to make more examples to improve your experience with customized window managers. There often are some quality of life processes that we expect from our desktop experience but don’t always take the time to create because it could be a lot of research. Those tools are what desktop environment brings on top of the window manager, for ease of use.

What would you like to learn about next ? Drop me a message on Twitter (@mrngilles) and let me know.

Copyright Marin Gilles 2019-2021