How do I implement an Automated Emergency Suspend or Shutdown?

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.
Viewing 15 posts - 1 through 15 (of 30 total)
  • Author
    Posts
  • #87604
    Moderator
    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?

      #87607
      Moderator
      christophe
        #87608
        Member
        iznit
          #87609
          Moderator
          BobC
            Helpful
            Up
            0
            ::

            The first requires sudo

            I will try the 2nd. Maybe running the cron job as root will make it work.

            Thanks

            #87824
            Moderator
            BobC
              Helpful
              Up
              0
              ::

              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 0
              
              #87825
              Moderator
              christophe
                Helpful
                Up
                0
                ::

                Instead of desktop-session-exit --shutdown
                try poweroff or halt

                Does that work?

                confirmed antiX frugaler, since 2019

                #87826
                Member
                iznit
                  Helpful
                  Up
                  0
                  ::

                  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 yahdahyahdah

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

                  #87827
                  Moderator
                  christophe
                    Helpful
                    Up
                    0
                    ::

                    /full/path/to/desktop-session-exit yahdahyahdah

                    And full path because …

                    I like that. Makes sense to me…

                    confirmed antiX frugaler, since 2019

                    #87835
                    Moderator
                    BobC
                      Helpful
                      Up
                      0
                      ::

                      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 ago by BobC. Reason: had to use /usr/sbin/halt -p
                      #87836
                      Moderator
                      BobC
                        Helpful
                        Up
                        0
                        ::

                        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.

                        #87842
                        Member
                        sybok
                          Helpful
                          Up
                          0
                          ::

                          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
                          
                          main
                          #87857
                          Moderator
                          BobC
                            Helpful
                            Up
                            0
                            ::

                            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?

                            #87858
                            Member
                            iznit
                              Helpful
                              Up
                              0
                              ::

                              ${MYVARIABLE}
                              BobC, this is the syntax for Shell Parameter Expansion

                              #87859
                              Moderator
                              BobC
                                Helpful
                                Up
                                0
                                ::

                                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.

                                #87877
                                Member
                                sybok
                                  Helpful
                                  Up
                                  0
                                  ::

                                  @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
                                  
                                  main

                                  3) 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”‘.

                                Viewing 15 posts - 1 through 15 (of 30 total)
                                • You must be logged in to reply to this topic.