Forum › Forums › General › Software › How do I implement an Automated Emergency Suspend or Shutdown?
- This topic has 29 replies, 5 voices, and was last updated Feb 1-9:34 am by Lead Farmer.
-
AuthorPosts
-
August 22, 2022 at 8:40 pm #87604Moderator
BobC
If my system is running on battery and its running low, I have an alert script running from cron to notice the problem and sound an alarm. But if the battery level is so low that its critical, I would like the system to do a shutdown on its own without me being there to type any keys.
How can I do that?
August 22, 2022 at 10:14 pm #87607Moderator
christophe
::These looks promising:
https://superuser.com/questions/1057868/shutdown-a-linux-computer-depending-on-the-battery-level
https://unix.stackexchange.com/questions/84437/how-do-i-make-my-laptop-sleep-when-it-reaches-some-low-battery-thresholdconfirmed antiX frugaler, since 2019
August 22, 2022 at 10:19 pm #87608Member
iznit
::BobC, you should get a good result from combining the scripted ideas in these linked pages
https://unix.stackexchange.com/questions/84437/how-do-i-make-my-laptop-sleep-when-it-reaches-some-low-battery-threshold
https://wiki.archlinux.org/title/laptop#hibernate_on_low_battery_level
http://mindspill.net/computing/linux-notes/acpi/acpi-low-battery-warning/
https://www.geeksforgeeks.org/bash-script-to-get-low-battery-alert-in-linux/I would choose to include a boolean AND for the triggering logic
“only if AC power is not connected && …..”August 22, 2022 at 10:20 pm #87609ModeratorBobC
::The first requires sudo
I will try the 2nd. Maybe running the cron job as root will make it work.
Thanks
August 29, 2022 at 10:57 pm #87824ModeratorBobC
::Ok, this works running from cron as root, except one thing. I get all the messages and sounds, but the system doesn’t shutdown. Any ideas?
#!/bin/bash # lowbattery-check - low battery check and alarm # runs from root crontab TOO_LOW=50 # how low is too low? CRITICALLY_LOW=40 # initiate shutdown LOW_TIME_REM=00:15:00 export DISPLAY=:0.0 BATTERY_LEVEL=$(acpi -b | grep -P -o '[0-9]+(?=%)') STATUS=$(acpi --b) BATTINFO=<code>acpi -b</code> echo "$(date) lowbattery-check ran, status: $STATUS battinfo: $BATTINFO" >> /home/bobc/bin/lowbattery-check.log if [[ <code>echo $BATTINFO | grep Discharging</code> ]]; then echo "$(date) lowbattery-check battery discharging: $STATUS" >> /home/bobc/bin/lowbattery-check.log if [[ $BATTERY_LEVEL -lt $TOO_LOW ]]; then echo "$(date) lowbattery-check battery too low: $BATTERY_LEVEL" >> /home/bobc/bin/lowbattery-check.log espeak "BATTERY TOO LOW!" \ && play /usr/lib/libreoffice/share/gallery/sounds/ups.wav \ && play /usr/lib/libreoffice/share/gallery/sounds/untie.wav \ && play /usr/lib/libreoffice/share/gallery/sounds/ups.wav \ && yad --no-buttons --center --borders=22 --timeout=10 --title="BATTERY LOW" --text="Plug computer in NOW" if [[ $BATTERY_LEVEL -lt $CRITICALLY_LOW ]]; then echo "$(date) lowbattery-check battery CRITICALLY low, SHUTDOWN: $BATTERY_LEVEL" >> /home/bobc/bin/lowbattery-check.log espeak "BATTERY CRITICALLY LOW, SHUTDOWN INITIATED!" \ && play /usr/lib/libreoffice/share/gallery/sounds/ups.wav \ && play /usr/lib/libreoffice/share/gallery/sounds/untie.wav \ && play /usr/lib/libreoffice/share/gallery/sounds/ups.wav \ && yad --no-buttons --center --borders=22 --title="BATTERY CRITICALLY LOW" --text="SHUTDOWN INITIATED" \ && desktop-session-exit --shutdown fi fi fi exit 0August 29, 2022 at 11:58 pm #87825Moderator
christophe
::Instead of
desktop-session-exit --shutdown
trypowerofforhaltDoes that work?
confirmed antiX frugaler, since 2019
August 30, 2022 at 12:30 am #87826Member
iznit
::if [[ $BATTERY_LEVEL -lt $CRITICALLY_LOW ]]; then
Well the commands don’t need to be boolean chained all on one line.
Easier debug if they are separate.echo yahdahyahdah
espeak yahdahyahdah
play yahdahyahdah
play yahdahyahdah
play yahdahyahdah
yad yahdahyahdah
/full/path/to/desktop-session-exit yahdahyahdahAnd full path because the rooty shell in which the cronned script is running in may not have $PATH you expect. Also the environment [variables] of this shell may lack one/several envvars needed by expected by desktop-session-exit. As a debug test, does plain ole “shutdown” command work here?
Might also wanna change the logic to an if [critical] elif [too low] instead of deep nesting.
August 30, 2022 at 12:39 am #87827Moderator
christophe
::/full/path/to/desktop-session-exit yahdahyahdah
And full path because …
I like that. Makes sense to me…
confirmed antiX frugaler, since 2019
August 30, 2022 at 3:54 am #87835ModeratorBobC
::Yes, good points, all. I had copied many snips of code from many places and put them together like a Frankenstein monster, which didn’t work quite right, LOL.
Revised code:
#!/bin/bash # lowbattery-check - low battery check and alarm # runs from root crontab # requires espeak and libreoffice for audible alerts TOO_LOW=50 # how low is too low? CRITICALLY_LOW=40 # initiate shutdown LOW_TIME_REM=00:15:00 export DISPLAY=:0.0 BATTERY_LEVEL=$(acpi -b | grep -P -o '[0-9]+(?=%)') STATUS=$(acpi --b) BATTINFO=<code>acpi -b</code> echo "$(date) lowbattery-check ran, status: $STATUS battinfo: $BATTINFO" >> /home/bobc/bin/lowbattery-check.log if [[ <code>echo $BATTINFO | grep Discharging</code> ]]; then echo "$(date) lowbattery-check battery discharging: $STATUS" >> /home/bobc/bin/lowbattery-check.log if [[ $BATTERY_LEVEL -lt $TOO_LOW ]]; then echo "$(date) lowbattery-check battery too low: $BATTERY_LEVEL" >> /home/bobc/bin/lowbattery-check.log espeak "BATTERY TOO LOW!" play /usr/lib/libreoffice/share/gallery/sounds/ups.wav play /usr/lib/libreoffice/share/gallery/sounds/untie.wav play /usr/lib/libreoffice/share/gallery/sounds/ups.wav yad --no-buttons --center --borders=22 --timeout=10 --title="BATTERY LOW" --text="Plug computer in NOW" if [[ $BATTERY_LEVEL -lt $CRITICALLY_LOW ]]; then echo "$(date) lowbattery-check battery CRITICALLY low, SHUTDOWN: $BATTERY_LEVEL" >> /home/bobc/bin/lowbattery-check.log espeak "BATTERY CRITICALLY LOW, SHUTDOWN INITIATED!" play /usr/lib/libreoffice/share/gallery/sounds/ups.wav play /usr/lib/libreoffice/share/gallery/sounds/untie.wav play /usr/lib/libreoffice/share/gallery/sounds/ups.wav yad --no-buttons --center --borders=22 --title="BATTERY CRITICALLY LOW" --text="SHUTDOWN INITIATED" /usr/sbin/halt -p fi fi fi exit 0- This reply was modified 8 months, 1 week ago by BobC. Reason: had to use /usr/sbin/halt -p
August 30, 2022 at 4:43 am #87836ModeratorBobC
::I will clean it up, more. I want it to be configurable. I might base it on battery minutes left rather than percent. This one is only lasting 30 minutes or so, but it is very optimistic in the beginning.
August 30, 2022 at 10:14 am #87842Member
sybok
::Hi @BobC, I did some cleaning and restructuring of the code. Hope that helps you.
BTW, I dare to suggest to check first for the critically low level not to get into both low and then critically low.
Unless this is intended design.Please see below
#!/bin/bash ############################################################################### ## lowbattery-check - low battery check and alarm ## runs from root crontab ## ## requires espeak and libreoffice-files for audible alerts ############################################################################### TOO_LOW=50 # how low is too low? CRITICALLY_LOW=40 # initiate shutdown LOW_TIME_REM=00:15:00 export DISPLAY=:0.0 # Array of sounds to play: re_sound=('/usr/lib/libreoffice/share/gallery/sounds/ups.wav' '/usr/lib/libreoffice/share/gallery/sounds/untie.wav' '/usr/lib/libreoffice/share/gallery/sounds/ups.wav') log_file='/home/bobc/bin/lowbattery-check.log' # Placeholders: BATTINFO='' BATTERY_LEVEL='' STATUS='' get_battery(){ # Update/Refresh battery info: BATTINFO=<code>acpi -b</code> BATTERY_LEVEL=$(acpi -b | grep -P -o '[0-9]+(?=%)') STATUS=$(acpi --b) } # get_battery play_sounds(){ # Play configured sounds: local count_it count_it=0 for one_file in "${re_sound[@]}"; do if ! [ -f "${one_file}" ]; then continue fi play "${one_file}" count_it=$((count_it + 1)) done if [ "${count_it}" == "0" ]; then # Fall-back sound, ring the system bell, see [https://superuser.com/questions/969080] tput bel || echo -e "\07" fi } # play_sounds run_checked(){ # Run the check of battery: echo "$(date) lowbattery-check ran, status: [${STATUS}] battinfo: [${BATTINFO}]" >> "${log_file}" if [[ -z "$(echo "${BATTINFO}" | grep 'Discharging')" ]]; then # Charging, nothing to do, return = exit this function return fi echo "$(date) lowbattery-check battery discharging: ${STATUS}" >> "${log_file}" if [[ "${BATTERY_LEVEL}" -lt "${TOO_LOW}" ]]; then echo "$(date) lowbattery-check battery too low: [${BATTERY_LEVEL}]" >> "${log_file}" espeak "BATTERY TOO LOW!" play_sounds # TODO: Is it possible to make it stick/appear at all desktops? yad --no-buttons --center --borders=22 --timeout=10 --title="BATTERY LOW" --text="Plug computer in NOW" fi if [[ "${BATTERY_LEVEL}" -lt "${CRITICALLY_LOW}" ]]; then echo "$(date) lowbattery-check battery CRITICALLY low, SHUTDOWN: [${BATTERY_LEVEL}]" >> "${log_file}" espeak "BATTERY CRITICALLY LOW, SHUTDOWN INITIATED!" play_sounds # TODO: Is it possible to make it stick/appear at all desktops? yad --no-buttons --center --borders=22 --title="BATTERY CRITICALLY LOW" --text="SHUTDOWN INITIATED" /usr/sbin/halt -p fi } # run_checked main(){ # Main function: get_battery run_checked exit 0 } # main mainAugust 30, 2022 at 10:02 pm #87857ModeratorBobC
::As I’d said, I was reworking the program last night, and did get that done.
Yes, there were a number of inconsistencies in my program caused by taking different ideas from multiple programs and trying to assemble them into one program, and also make it work.
At lunch today, I incorporated a number of your ideas.
One I didn’t understand was surrounding the variables:
“${BATTERY_LEVEL}”
I hadn’t seen that before. Could you explain the purpose, please?August 30, 2022 at 10:22 pm #87858Member
iznit
August 31, 2022 at 5:45 am #87859ModeratorBobC
::Shell parameter expansion is a new term to me. It does make sense, and I have encountered the problem before where I wanted the value of a variable to be followed immediately by another value. It makes me wonder why all bash programs aren’t written using that syntax. I will have to see if that syntax is also allowed at work (where I am the only programmer that uses the shell tools, so have no mentor or examples).
And yes, sybok, my logic is based on there being 2 levels. One where we need to warn them that the battery is discharging and getting low, and ask them to plug it in. And the second is where the situation is critical, and if they don’t plug it in immediately, we need to shutdown to avoid possible data loss and/or corruption.
In the end, I decided to base it on battery power remaining expressed as time remaining in seconds, and set the warning level at 15 minutes (900 seconds) by default, and the critical level at 5 minutes (300 seconds) by default. I would like to have the user’s selected values maintained and stored somewhere with a little YAD script, I think. The idea was to then retrieve/use them to give user level warnings as well as the root level critical warnings and actions.
Do you think that makes it sense to have 2 separate programs running from cron for that, or do you think it would be better to combine them into one program and run it under root so it is able to shutdown if needed? I’m starting to think that they should be combined and have fewer program files, and only one cron job to run.
Thanks to both of you for adding another bit of syntax knowledge to my storage cells.
September 1, 2022 at 6:01 am #87877Member
sybok
::@BobC: Options to make it more configurable.
That reminds me of a topic with @ModdIt where he/we tried to make ‘/etc/hosts’ per user. Some multi-user issues were encountered. I do not know what is the current status (got out of touch).1) I assume that the suggested “split” would mean the following two items apply:
A) common global configuration (better be placed in a configuration file so that upgrade asks whether to overwrite any local changes)
B) local configuration for non-critical warnings per user that each corresponding user can edit.– How does the script recognize which local/user configuration is to be loaded? Test for user/users that is/are logged in.
– Does remote access e.g. via SSH apply? (How to display the warning in this case?)
– What if multiple users are logged in? E.g. a local user and a remote VNC user; is there any preference or does it run for all logged-in users that have the local configuration file?2) Single script or two scripts?
I believe that a single script is better option.
One can define functions in it and have it structured e.g. as follows:load_ini_file(){ # Load thresholds from current ini file into placeholders thus refreshing them local ini_file ini_file="$*" # Load the thresholds: grep line; awk no. 1: get value (with comment(s)); awk no. 2: strip comment(s), if any; xargs: strip white-spaces <variable to store the value>="$(grep '^[ ]*<threshold name>[ ]*=[ ]*' "${ini_file}" | awk -F= ' { print $2 } ' | awk -F# ' { print $1 } ' | xargs)" } # load_ini_file run_one_check(){ # Run a check based on passed configuration file local ini_file ini_file="$*" if ! [ -f "${ini_file}" ]; then return fi load_ini_file "${ini_file}" # Continue with the check ... } # run_one_check iterate_check(){ # Iterate over configuration files: # 1) Get list of local files: use the global and local variations, e.g. logged in users-array and # /home/${log_user}/.desktop-session/power-check.ini <-- FIXME: user may have its home at a different path! # 2) Use/Add the global file as well, e.g. global_ini='/etc/opt/power_check/power-check.ini' # 3) Iterate over these files for one_user in ${sysusers}; do if [ <user logged in> ]; then run_one_check "<home of user>/.desktop-session/power-check.ini" fi done run_one_check "${global_ini}" } # iterate_check main(){ iterate_check exit 0 } # main main3) Where does it log in the case of multiple users? Should the file be readable by all/other users?
4) BTW, I have discovered a tool ‘shellcheck’ that performs syntax check of your scripts.
It can provide useful hints but sometimes its suggestions to double-quote variables to avoid expansion can cause troubles.I usually output the results of the analysis into a file as follows ‘j=script.sh; shellcheck “$j” > “shell_$j”‘.
-
AuthorPosts
- You must be logged in to reply to this topic.