Media Center Auto Shutdown and RTC Wakeup Based on tvheadend Recording Schedule
I created a script that runs via cron job that will power off my media center if it is not in use, but will program the real time clock (RTC) on the motherboard to wake up the system in time to run a scheduled recording, and/or to update the EPG data, and from that derive potential new or changed auto-recordings. To determine if the system is currently not in use, I
- Check if the monitor is off
- Check if any audio is playing, e.g., Spotify or KODI is playing anything
- Check if the wireless keyboard is connected
- Check if tvheadend is currently recording something
Also, the shutdown processing can be blocked by creating a flag file. If that file exists, no shutdown will happen.
The wake-up time is either the next recording time plus some allowance for boot time, or every 24 hours, whatever comes first. This makes sure that at least once a day the EPG is updated and tvheadend can update its auto-recording-schedule.
As a result, I reduce power consumption of the media center considerably.
You may also be interested in my next blog post: CEC-like Power Features with Non-CEC-Equipment
Motivation
My brand-new x86-based Media Center is not really energy hungry, but still a bit more than my previous, Raspberry Pi 4 based incarnation. But it has a built-in RTC and the ability to wake up/boot based on an alarm programmed into that RTC. While my Raspberry Pi 4 was running 24/7 to record TV shows based on automatic recordings in tvheadend, I wanted to use the new capabilities of the new platform to improve on that and have the media center only running when needed. This will reduce power consumption way below what the old solution needed.
Concept
The general approach is to
- create a script (decided to use a simple shell script with bash) that
- runs regularly on a cron schedule
- and checks if the media center is currently idle and ready for shutdown (see below)
- and if so, determines the next wakeup time,
- programs the RTC alarm to that wakeup time
- and powers off the media center.
- The RTC alarm powers the PC back on in due time.
“Idle and ready for shutdown” means:
- Process not blocked
By creating a given file, I can suppress the shutdown. This is to allow remote system maintenance via SSH , or to have the system up when I want to access recordings remotely via VPN, e.g., if I am travelling. - System is up for a minimum amount of time
If the PC just booted, I need to give tvheadend some time to update the EPG over the air, and process it for potential new auto recordings. Therefore, after a reboot, the system will not shut down before enough time has passed. - Monitor is off
If the monitor is on, this usually means that I am actively using the PC, e.g., for web browsing, watching media or listening to music. Obviously I do not want the system to shut down while I am doing that. - No audio is playing
If any application is playing audio, like Spotify, KODI or the web browser, this would mean that I am listening to music, web radio or something with the monitor off. I still want the machine to keep running, for obvious reasons. - Wireless keyboard is not connected
I was hoping that I could use my wireless keyboard as a very simple override-device, like having the keyboard’s power switch on to avoid any PC shutdown. Unfortunately, the wireless keyboard goes into some sleep state when no key was pressed for a while, which from the receiver side is indistinguishable from the power switch being off. Still, for a few minutes, until the keyboard goes to sleep, the mechanism will work – better than nothing. So I kept it. - No tvheadend recording is currently running
Obviously I want the system to stay on until the recording is done.
tvheadend status can be queried via API. - The next recording is not due in the near future
If a recording is due within the next few minutes, do not bother to shut down and reboot.
The next wakeup time is determined with the following logic:
- If no recording is planned, wake up in 24 hours.
- If the next recording is scheduled in more than 24 hours, wake up in 24 hours.
- If the next recording is scheduled in less then 24 hours, wake up at that time, minus a bit of allowance for booting.
This ensures that the PC wakes up at least every 24 hours. This is necessary to keep the EPG up to date and check if new broadcasts came up that match an auto record pattern.
Remains one caveat to be covered: Imagine playing some music with monitor off, and you need to pause output for a short while, to answer the bell or so. Now, the second after you paused output, the script runs. It would shut down the PC… Inconvenient! Solution: I require the script to decide a shutdown is due in two consecutive runs before it actually shuts down the machine. If I set the cron job to run the script every 10 minutes, I have these 10 minutes minimum as a grace period.
The resulting shell-script will follow in the end. Now follow a few things that are not 100% obvious and in some cases took me a while to work out. But first:
Credits
The starting point for this is a script shared by Mr Rooster in the tvheadend forum. Thank you very much for sharing this!
Implementation
Process System Uptime
The uptime command gives system uptime. The standard output is human readable, but difficult to interpret for the script. With uptime -s however you get the boot time in a standardised format. I convert this into a timestamp via date, and then subtract it from the current time’s timestamp. Result is the uptime in seconds.
1 2 |
on_time=`date --date="$(uptime -s)" +%s` up_since=$((`date +%s`-$on_time)) |
Check if Monitor is On or Off
That was a tricky one. Many posts on the internet suggest using xrandr -q, xset -q, udevadm monitor –property or some other methods, but none worked for me. This may be due to the fact that my monitor is connected to the HDMI output of the PC via a HDMI-to-DVI-D-converter. However, even this setup offers an I²C-connection, and I finally found this post making use of this connection. It uses ddcutil detect to query the monitor, and the output will say “Invalid display” if the monitor is off, or “Display 1” if it is on (and some more information). For this to work, with Debian bookworm the following steps are necessary:
sudo apt install ddcutil
In /etc/modules-load.d/modules.conf add the line
i2c_dev
This loads the kernel-module i2c_dev at boot, which is needed by ddcutil. Now to check the monitor state is done by these lines:
1 2 |
ddcutil detect 2> /dev/null | grep -q 'Display 1' monitor_off=$? |
This runs ddcutil detect, discards any error messages (you get some if ddcutil is not run in root context), and uses grep to check for the words “Display 1”. If the words are present, grep will return exit code 0, otherwise something non-zero (usually 1). The variable monitor_off stores this result and can be used for later checking.
Check if Any Audio is Playing
Any application that plays audio, registers an input sink with pulse audio, the audio infrastructure currently favored by Debian. If this audio sink has state “running”, it actively plays audio. So here’s how I check if audio is playing currently:
1 2 |
pacmd list-sink-inputs | grep -q "state: RUNNING" no_audio_playing=$? |
Again, I use grep to check for expected output. The output state: RUNNING is only present if a) at least one input sink is registered and b) at least one sink is actively playing back audio.
Check if the Wireless Keyboard is Connected
I use a Logitech K400 Plus (be aware of potential security issues!), and for interfacing with such keyboards, and for customizing their features, there is solaar. This software has a GUI module which allows you to interact with it nicely from the desktop, but you also can use it from the command line to gain info about the devices connected. In my case I have only one device connected, which allows me just to check for the line “device is disconnected”. If you have multiple devices connected, you may need to test more intelligently. It is really a pity that I cannot distinguish between keyboard on sleep and keyboard switched off, otherwise this would have been the perfect manual override switch…
You will need to install solaar with sudo apt install solaar. The check is quite simple and again uses the grep-logic:
1 2 |
solaar show | grep -q "device is offline" keyboard_off=$? |
Check if tvheadend is Recording Right Now
I use the tvheadend API to query the status, using the endpoint grid_upcoming. Then grep-test for the text “sched_status”:”recording”, – if it is present, a recording is in progress. Please note that you need to provide username and password if your tvheadend requires authentication.
1 2 |
curl -s "http://localhost:9981/api/dvr/entry/grid_upcoming?limit=99999" -u "tvheadenduser:password" --digest | grep -q '"sched_status":"recording",' no_record=$? |
Get Recordings and Find the Next Upcoming
I use the same endpoint to get all recordings, cut out the timestamps, sort them and pick the closest one:
1 |
next_recording=`curl -s "http://localhost:9981/api/dvr/entry/grid_upcoming?limit=99999" -u "tvheadenduser:password" --digest | tr , '\n' | grep start_real | sed "s/.*start_real.:\([0-9]*\).*/\1/" | sort -n | head -1` |
Now the logic is done to determine if there’s an upcoming recording, if it is more than 24 hours in the future etc. – look into the script, it is straightforward. Note that you can configure the allowance for boot process in the variable PRE_SCHEDULE. MIN_GAP contains the time period that needs to be exceeded by the next recording in order for the shutdown to happen. If the next recording is due earlier, the PC keeps running.
Ensure a Minimum Grace Period
I found it very difficult to have my script leave a message for the next run of the same script. I was hoping I could use some environment variable, but they are well protected between shell instances. So I decided to simply write a file. If this file exists, it indicates that the last script run decided that the PC should be shut down, but did not yet do so. If the next iteration of my script is still of the opinion that the PC should be shut down and it finds the file, it actually will do the shutdown. If that next run decided that no shutdown is due anymore, it will remove the file and not do a shutdown. The flag file name can be configured in UPCOMING_SHUTDOWN_FLAGFILE.
This method is far from elegant – if you know a better way, please write a comment below!
RTC Alarm Programming and Shutdown
First, you need to ensure that the user which runs the script is allowed to sudo the necessary commands, i.e. rtcwake and poweroff, without giving a password. For this, create a file in /etc/sudoers.d – e.g., named the-user. It needs to contain this line:
the-user ALL= NOPASSWD: /usr/sbin/poweroff, /usr/sbin/rtcwake
The RTC programming command is
rtcwake -m no -t <Wakeup-Timetsamp>
-m no tells the RTC to target for “normal” state, i.e. the fully booted OS, CPU not in any sleep state. After that, the system is shut down using the poweroff command.
cron Setup
I decided to run the script every 10 minutes – for this, create a file, e.g., crontab.user, which contains the line
*/10 * * * * /home/the-user/auto_shutdown.sh
Activate this file using the command (in the relevant user’s context)
crontab crontab.user
You can check success using
crontab -l
Logging
Each run creates a logfile that you can configure in the script via variable LOGFILE. If the script decides that it will shut down the PC, it will copy the current logfile to the file configured via LASTLOGFILE variable. This allows you to check the details of the last shutdown.
Discarded Ideas
While working out this solution, I went down a few roads that turned out to be to narrow, but which I will keep here for documentation purposes. Mainly these were, instead of checking if any audio is playing, to check the following:
- Spotify is not playing anything
I have the Spotify fat client installed and use it to listen to music or audio plays. I may have the monitor off while I do so, but still want the system to stay on.
Spotify play status can be queried via dbus. - KODI is not playing anything
For playing back my music library, I typically use KODI. Same as with Spotify: Monitor might be off, but I want the system on.
KODI play status can be queried via JSON-RPC API.
Check if Spotify is Currently Playing
That was surprisingly easy – an RPC call via dbus yields “Playing” or “Paused” depending on status. These lines use a similar grep-logic as with monitor on/off checking:
1 2 |
dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:org.mpris.MediaPlayer2.Player string:PlaybackStatus 2> /dev/null | grep -q '"Playing"' spotify_off=$? |
If Spotify is not running at all, the command will also yield a non-zero exit code, so this case is covered.
Check if KODI is Currently Playing
KODI can expose a JSON-RPC API – for this you need to go to the KODI settings, “Service” section, and under “Control” activate “Allow remote control via HTTP”. I chose to switch off authentication, since the PC runs in a well secured home network. You may want to choose a username/password, which you then would need to add to the curl command below with the option -u “username:password”.
To check if KODI plays something, you query the API for any active player, using the API-method Player.GetActivePlayers. If nothing plays, the result is empty, otherwise you get one or more player-IDs. I use sed to cut out the relevant part of the JSON response, and then the same grep logic as above:
1 2 |
curl -g --data-binary '{"jsonrpc": "2.0", "method": "Player.GetActivePlayers", "params": {}, "id": 0}' --header 'content-type: application/json;' http://127.0.0.1:8080/jsonrpc 2> /dev/null | sed 's/.*result":\[\(.*\)\].*/\1/' | grep -q "playerid" kodi_off=$? |
Result and Final Words
I’m very happy to have this in place now, because I have always been very unhappy with my media center running 24/7, eating up power mostly unneeded. This script makes my media center much more eco-friendly!
You may have other requirements to determine if the PC may be shut down – I hope my examples give enough ideas and guidance for you to adjust the script to your needs.
Finally: Do not miss my next, related, blog post: CEC-like Power Features with Non-CEC-Equipment
Here’s the script – for download and as a listing (make sure to adjust the paths, and to replace the credentials for tvheadend API access):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
#!/bin/bash # This script will shut down the media center PC when idle, and schedule it to wake up via RTC to meet any planned tvheadend recording. # # Conditions for shutdown: # - Blocking file BLOCKFILE does not exist - the files allows to block auto shutdown completely # - Enough time (MIN_UPTIME seconds) has passed since last boot to allow tvheadend to update EPG/Autorec's # - Monitor must be off (i.e. nobody currently actively using the PC) # - No audio is playing (i.e. nobody is listening to Spotify, KODI, web radio etc. with monitor off) # - No recording is in progress currently # - Next planned recording is not in near future (within the next MIN_GAP seconds) # - The previous script run already determined shutdown state, and left the file UPCOMING_SHUTDOWN_FLAGFILE as indicator. # # Wakeup by RTC is scheduled for: # - Next recording time minus PRE_SCHEDULE seconds if recording is planned within the next 24 hours # - In 24 hours if no recording is due earlier - this is to allow tvheadend to get EPG updates and schedule Autorec's # # Logfile of last shutdown check goes into LOGFILE # Logfile that caused shutdown is copied into LASTLOGFILE # Script should be run via cron # # Prerequisites: # - User that runs the script needs passwordless sudo capabilities for commands "poweroff" and "rtcwake" # - ddcutil installed and i2c_dev kernel module loaded # - solaar installed # # V1 by Hauke, Jan 11th 2024, https://projects.webvoss.de/2024/01/11/media-center-auto-shutdown-and-rtc-wakeup-based-on-tvheadend-recording-schedule/ # Inspired by Mr Rooster in tvheadend forum (https://tvheadend.org/boards/4/topics/27066) ### CONFIG ### # Logfile for last shutdown check LOGFILE="/home/the-user/autoshutdown/shutdown_check.log" # Logfile of run that caused last shutdown LASTLOGFILE="/home/the-user/autoshutdown/last_autoshutdown.log" # Blocking file to avoid shutdown process completely BLOCKFILE="/home/the-user/autoshutdown/no-shutdown.flag" # If the script identifies that the system should shut down, it will not do immediatly. It will first # create this file. Only if this file exists, the actual shutdown will happen. This will make sure that at least # once the script running interval will pass before a shutdown happens. The file will be deleted if the reason for # shutdown does no longer exist on second run, and no shutdown will happen in that case. UPCOMING_SHUTDOWN_FLAGFILE="/home/the-user/autoshutdown/upcoming-shutdown.flag" # Number of seconds the system needs to be up before a shutdown will happen (to allow tvheadend to scan EPG and update autorecs) MIN_UPTIME=1800 # Minimum time in seconds until next recording for processing shutdown - if gap is smaller, no shutdown , but wait for recording MIN_GAP=1800 # Seconds to boot before scheduled recording time PRE_SCHEDULE=120 ### END CONFIG ### echo "Auto-Shutdown check starts... ($(date))" > $LOGFILE if [ -f "$BLOCKFILE" ]; then echo "Blocking file $BLOCKFILE found - will not process shutdown!" >> $LOGFILE rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null else # Get uptime in minutes on_time=`date --date="$(uptime -s)" +%s` up_since=$((`date +%s`-$on_time)) if [ $up_since -gt $MIN_UPTIME ]; then ## Only restart if the system was up for at least 30 minutes to give tvheadend enough time for EPG update and autorec update # Get status of monitor: is it switched on? 0 = monitor is on ddcutil detect 2> /dev/null | grep -q 'Display 1' monitor_off=$? if [ $monitor_off != 0 ]; then # If monitor is on (= 0), assume someone is using the computer and do not shut down # Check if any audio output is going on (assume that music is playing with monitor off --> do not shut down) # 0 = some audio playing pacmd list-sink-inputs | grep -q "state: RUNNING" no_audio_playing=$? if [ $no_audio_playing != 0 ]; then # Only shut down if no audio is playing (else assume someone listens to music with monitor off) # Check if the wireless keyboard is connected. Unfortunately keyboard at sleep yields same result... solaar show | grep -q "device is offline" keyboard_off=$? if [ $keyboard_off -eq 0 ]; then # only shut down if keyboard is not connected (if it is connected, assume user is active) # Check for active recordings curl -s "http://localhost:9981/api/dvr/entry/grid_upcoming?limit=99999" -u "tvheadenduser:password" --digest | grep -q '"sched_status":"recording",' no_record=$? if [ $no_record != 0 ]; then # Not recording, can we shutdown? if [ -f "$UPCOMING_SHUTDOWN_FLAGFILE" ]; then # Check if at last script run shutdown condition existed - only then shut down. next_recording=`curl -s "http://localhost:9981/api/dvr/entry/grid_upcoming?limit=99999" -u "tvheadenduser:password" --digest | tr , '\n' | grep start_real | sed "s/.*start_real.:\([0-9]*\).*/\1/" | sort -n | head -1` # If there are no recordings we should wake up tomorrow if [ "$next_recording" = "" ]; then echo "No recordings, wake up tomorrow." >> $LOGFILE next_recording=`date --date "tomorrow" +%s` else echo Next recording: `date --date="@$next_recording"` >> $LOGFILE fi gap=$(($next_recording-`date +%s`)) if [ $gap -gt $MIN_GAP ]; then # The gap to the next recording is more than minimum gap, so lets shutdown if [ $gap -gt 86400 ]; then # Wake up at least once a day to update EPG and identify new autorecordings echo "Next recording more than one day in the future - wake up tomorrow." >> $LOGFILE next_recording=`date --date "tomorrow" +%s` fi # Set the wakeup before the next recording according to pre-schedule config wakeup=$((next_recording-PRE_SCHEDULE)) wakeup_date=`date --date="@$wakeup"` echo "Waking up at: $wakeup_date" >> $LOGFILE # Program RTC /usr/bin/sudo /usr/sbin/rtcwake -m no -t $wakeup >> $LOGFILE # Save current logile for review after reboot cp $LOGFILE $LASTLOGFILE # remove flag file, no longer needed rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null # ...and shutdown. /usr/bin/sudo /sbin/poweroff fi else # First time shutdown reason was detected - do not shut down, set flag for next script run echo "Would shut down, but will wait for another cycle." >> $LOGFILE touch $UPCOMING_SHUTDOWN_FLAGFILE fi else echo "Still recording. Not shutting down." >> $LOGFILE rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null fi else echo "Wireless keyboard connected, no shutdown." >> $LOGFILE rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null fi else echo "Audio is playing, no shutdown." >> $LOGFILE rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null fi else echo "Monitor is on, will not shut down." >> $LOGFILE rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null fi else echo "System is up less than $(($MIN_UPTIME/60)) minutes - no shutdown." >> $LOGFILE rm $UPCOMING_SHUTDOWN_FLAGFILE 2> /dev/null fi fi |