Script to Play a Battery Charge Notification or Warning

I’ve been reading up on bash scripting, of which I am almost completely ignorant, unless you count being able to slavishly copy, paste, and cron other people’s work. However, I’m a great fan of logic, so the concept is not all that alien to me.

So, here’s my attempt at stringing together a script that audibly informs when the Librem 5’s juice is low, and also when, after plug-in, it has reached a charge of 80%. I’ve tested it a couple of times and it seems to be working. (Critique, suggestions, or corrections are very welcome; I’m sure my script has room for improvement.)

This is only a notification script; it doesn’t limit or otherwise affect charging.

Prerequisites:

  • upower
  • mplayer or another command-line music player
  • espeak
  • a short sound file (create or copy from somewhere)

(My script’s name is batwarn.sh, and my sound file is just a plink-plink-plink that I recorded from a music generator app.)

#!/bin/bash

# In order for cron to play sound files it needs to export an environment variable:
export XDG_RUNTIME_DIR="/run/user/1000"

state=$(upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery | grep "state" | awk '{print $2}')
battery=$(upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery | grep "percentage" | awk '{print $2}')
battery=${battery/\%/}

if [[ $state == discharging && $battery -le 40 ]]; then

    mplayer /home/purism/batalarm.ogg && espeak "charge battery"

elif [[ $state == charging && $battery -eq 80 ]]; then

    mplayer /home/purism/batalarm.ogg && espeak "80% charge"

elif [[ ( $state == charging || $state == fully-charged ) && $battery -ge 81 ]]; then

    exit

fi

For the benefit of other novices, like me:
-First update the L5.
-Copy the above code into a text file and save it as a shell script, e.g. batwarn.sh (home directory is fine)
-From the command line, make the file executable: chmod +x batwarn.sh (Be in the correct directory.)
-Run crontab -e and add a cron job (i.e. scheduled task):
*/5 * * * * bash /home/purism/batwarn.sh (but point it toward the actual location and name of your own file).

“Translating” the various elements of the script (from the top):

All bash (Bourne again shell) scripts start with a “shebang:” #!/bin/bash

Any line that starts with “#” is a comment, and is ignored by the script.
“Enable cron to play sound; this script runs for any user who is logged in.”

Short words (variables) are designated to substitute for long strings, mainly to ease typing the arguments. The long strings gather system information about the current battery charging/discharging state and the percentage of charge, and strip out the “%” sign; the variables are then used in the arguments to represent the gathered values.

The conditional statements and operators can be read as:

“If the current battery state is listed as ‘discharging,’ AND the charge percentage is less than or equal to 40, then launch mplayer to play the file batalarm.ogg AND then open espeak to say “charge battery””

“Else, if the state is ‘charging,’ AND the charge percentage is equal to 80, then play the file AND then say “80% charge””

“Else, if the state is ‘charging,’ OR ‘fully-charged,’ AND the charge percentage is greater than or equal to 81, then exit and finish.”

The cron job will cause the sounds to play every 5 minutes until the device gets plugged in, shuts down, or the charge meets the required upper threshold.

Either the espeak command or the mplayer sound file can be eliminated from the script. The percentage thresholds can be changed.

You can run the following directly in the terminal to see where the charge data comes from and how it’s filtered:

upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery

upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery | grep "state"

upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery | grep "state" | awk '{print $2}'

upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery | grep "percentage"

upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery | grep "percentage" | awk '{print $2}'

Fortunately there are a lot of bash tutorials on the internet.

See also: Why doesn't `upower -i` work?

EDIT:
mplayer /home/purism/batalarm.ogg && espeak "..."
I’ve noticed that unless I loop the sound file twice, it sometimes doesn’t play, only the espeak ending. To fix this, play the sound file twice:
mplayer /home/purism/batalarm.ogg -loop 2 && espeak "..."

7 Likes

By the way, to use this script on a different device, battery_max170xx_battery won’t work. Run upower -i upower -e | grep ‘BAT’` to see the correct native-path for the other device, e.g. battery_BAT0, battery_BAT1, etc., then substitute it.

2 Likes

Why check for fully charged? The battery has to be >= 81% to be fully charged.

1 Like

I was thinking it was necessary, because the states “charging” and “fully-charged” are both actual possible readings when you run upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery. (As is “discharging.”) Since the conditional argument is checking and matching that reading, I assumed that if it returned “fully-charged,” then the argument would be ignored and the alert would continue to sound, or start sounding again, instead of stopping or staying silent.

I could be wrong, though.

Then maybe what you’d rather want is “not discharging”?

Pseudocode: if ($state != discharging && batteryCharge >= 81)

Edit: now that I take another look, I don’t see why you’d need to worry about the state at all. You could just put a large, overarching if statement “if (battery <= 80)” and not care if it’s greater than that, since it isn’t written to do anything above 80% except to not run. You could then get rid of the last if statement altogether.

1 Like

That should work. So the second statement would be:

elif [[ $state != discharging && $battery -ge 80 ]]; then

“Else, if state is “not discharging” AND charge is greater than or equal to 80, then”

And drop the third statement.

Actually, I can’t use that in place of the second statement if I want to keep:
mplayer /home/purism/batalarm.ogg && espeak "80% charge"

But I could replace the third statement.

Testing with the replaced third statement, using bash -x batwarn.sh returns:

purism@pureos:~$ bash -x batwarn.sh
+ export XDG_RUNTIME_DIR=/run/user/1000
+ XDG_RUNTIME_DIR=/run/user/1000
++ upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery
++ grep state
++ awk '{print $2}'
+ state=discharging
++ upower -i /org/freedesktop/UPower/devices/battery_max170xx_battery
++ grep percentage
++ awk '{print $2}'
+ battery=70%
+ battery=70
+ [[ discharging == discharging ]]
+ [[ 70 -le 40 ]]
+ [[ discharging == charging ]]
+ [[ discharging != discharging ]]

EDIT: But of course if one plans to regularly charge all the way to full, then maybe the 80% notification isn’t necessary.

1 Like

4.20V+4%=4.368V and as recently I’ve measured 4.362V on plus and minus poles of the BPP-L503 when (or rather if charged) with some external 4.35V type of universal battery charger. Therefore as mentioned already, as I actually asked before about LiHv batteries (in particular), 4.35V in our usage scenario, allow for a higher cut-off charging voltage (please read below linked article as it explains, in several details, knowledgeable approach from Purism Team, very well thought out development approach, even unique):

Now and when above understood let us take important things (for example, and as accustomed to the usage of some/any Android based smartphone, how to extended battery life if it charged only up to 81% of its nominal capacity) seriously:

This very important post from @dos and in particular this “typical process” refers to the Librem 5 only and as such (with its build-in battery charging components, taking care of all necessary, all well thought out in advance, for customer benefit, as mentioned above) it provides following as well:

Now let me transfer/switch from “termination current” to “cut-off charging voltage” (as correlated and happening within very same time-frame), to the point (or millisecond) when actually red LED light that was on turns off.

Now we might even distinguish Librem 5 (ID 316D) and its belonging BPP-L5 (01,02 or 03) as two different end products where only Librem 5 regulates correctly (that I know of today) its final cut-off voltage (recently I’ve measured 4.137V, not sure yet if that was right after it was fully charged) up to the max. of 4.20V:

@guru, thanks a lot for your thread! Thanks actually for your clear awareness, to include @fsflover contribution, about correct voltage output on BPP-L503 + and poles (just right after red color light turns off, the red one from its RGB LED)!

This correct (maximal value) cut-off voltage number of POWER_SUPPLY_CHARGE_FULL_DESIGN=4199160 aka 4.20V (and below) is firmely written down within Librem 5 Linux phone main driving software, called PureOS, as shown here (from: $ tail -n+0 /sys/class/power_supply/*/uevent):

If I’m allowed to write (perhaps :white_check_mark:): not quite, as 95% or even 100% (right after red LED light off) are just about very fine numbers as well (while my above thoughts related to the Librem 5 only). And finally, @Gavaudan, content of this post (message that I wanted to bring over to you) is important, as not that I’ve even tried to clearly rewrite what already noted within this Forum, therefore hope this post helps (somehow), including @amarok “alarm” script contribution here, helpful one (that I’ll test when more time for it), to understand that even when BPP-L503 battery charged up to 100% (no red LED light although original USB Type C 5.0V power supply plugged in) it is well taken care of Librem 5 battery health (cannot happen to be overcharged within Librem 5) and care taken about max. capacity that is actually used from it as well (few % below 4500mAh, when original battery full up to the max. allowed of 4.20V, as officially and already presented).

P.S. I’ve mentioned some external (intelligent) battery charger above, but still none of them is singing one, not based on here contributed Linux :notes: script from @amarok. Thanks therefore!

P.P.S. @Gavaudan, I do not drive around some EV, not yet, but if and once this happens I’ll recall about your 81% post for sure. Thanks therefore!

1 Like

This is CHARGE, not VOLTAGE, so it says 4200mAh (rough estimate of capacity when charged up to 4.2V instead of 4.35V, just to give the gauge something to work with until it’s calibrated and corrects itself), not 4.20V.

3 Likes

Sure and to be precise. Thanks for this reaction/correction (I do learn from you)! I just wanted to bring over my message (somehow), in broader terms where charging needs to be stopped (cut-off voltage at 100% within Librem 5 is just fine as taken care it will not exceed 4.20V), and hope it will be welcomed.

EDIT: Correct output line, visible within my above post upload as well (as not edited), is:
POWER_SUPPLY_VOLTAGE_MAX=4200000

But does always charging to 100% reduce the battery’s lifetime?

1 Like

Already and precisely answered here:

Meaning that BPP-L503 isn’t charged up to its maximum capacity and therefore already within “green” range, carrying very well out for its prolonged life (extended one).

1 Like

I may not have been as clear as I could have, It was late last night when I wrote that (although, admittedly, it is early this morning that I write this, so take it with a grain of salt). This is what I had envisioned for you:

if [[ $battery -le 81 ]]; then

    if [[ $state == discharging && $battery -le 40 ]]; then

        mplayer /home/purism/batalarm.ogg && espeak "charge battery"

    elif [[ $state == charging && $battery -eq 80 ]]; then

        mplayer /home/purism/batalarm.ogg && espeak "80% charge" 

fi
1 Like

Yes, your nested conditionals are better, and more efficient. I’ll use your version. Thanks!

EDIT:
@Gavaudan. It needs an extra fi, though:

if [[ $battery -le 81 ]]; then

    if [[ $state == discharging && $battery -le 40 ]]; then

        mplayer /home/purism/batalarm.ogg -loop 2 && espeak "charge battery"

    elif [[ $state == charging && $battery -eq 80 ]]; then

        mplayer /home/purism/batalarm.ogg -loop 2 && espeak "80% charge" 

        fi

fi
2 Likes

I’m always happy to help, but I fear you may be giving me a little too much credit here. I was just writing under the naive assumption that “fully charged” means “upower command returned 100%” regardless of the actual capacity of the battery, so if say the battery degrades to 90%, then perhaps upower will return “fully charged” once the battery reaches that percentage (or so I was thinking).

Put another way, I guess you could say I was thinking (perhaps incorrectly) that upower returned usable capacity versus total capacity.

2 Likes

I’m not knowledgeable enough to say how does it influence battery wear, but yes, the battery is rated up to 4.35V, and we’re charging it only up to 4.2V, so we’re not using its full capacity anyway.

Note that this isn’t exactly the same as charging at 4.35V and stopping early, as late-stage charging happens with constant voltage and asymptotically decreasing current. I guess it’s very likely that it has a similar effect on battery wear, but I don’t know for sure.

2 Likes

The gauge updates CHARGE_FULL dynamically, so it should reach 100% regardless of battery degradation (although the gauge does it by using a bunch of heuristics as it doesn’t actually know for sure why the charging has stopped, so there may be charging cycles where it doesn’t actually reach 100% anyway).

3 Likes

Oops.

Hopefully now it works (literally) harmoniously?

2 Likes

Not at all, at least not in my humble opinion. All while I wish myself (and will proof) that any EV (or at least my future one) can handle its batteries (in total) charging like Librem 5 (just don’t know much about EV charging termination current). I meant that your post helped me for real (just trust me), on where might be safe (>= 81%) to stop charging some EV. And sure I’ve noted you are kindly helping to @amarok, noted that slightly disturbing (with good reason) your very focused contribution here.

1 Like

I gotcha, I just don’t want to come across as more technically-minded than I am if it’s going to mess with your future EV. Software and I get along pretty alright. Hardware… is a bit of a struggle.

1 Like

Actually, I think this is a better version for getting both the low and the high warning:

#!/bin/bash

# In order for cron to play sound files it needs to export an environment variable:
export XDG_RUNTIME_DIR="/run/user/1000"

state=$(upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep "state" | awk '{print $2}')
battery=$(upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep "percentage" | awk '{print $2}')
battery=${battery/\%/}

if [[ $state == discharging && $battery -le 40 ]]; then

    mplayer /home/purism/batalarm.ogg -loop 2 && espeak "charge battery"

elif [[ $state == charging && $battery -eq 90 ]]; then

    mplayer /home/purism/batalarm.ogg -loop 2 && espeak "90% charge"

fi

That lets the low-battery warning keep playing until plugged in, and the 90% charge notification play just once (probably), given that the repetition is every 5 minutes, as specified in the cron job.

When charging, the level would certainly rise fast enough from 90%, so the notification wouldn’t keep sounding. But the -eq (is equal to) could be replaced with -ge (is greater than or equal to) to allow the notification to repeat every 5 minutes if still charging, if desired. The “90% charge” message could also be changed to, e.g. “charge complete” or “disconnect charger.”

2 Likes