[mythtv-users] how to make idle frontend exit?

Michael Watson michael at thewatsonfamily.id.au
Sun Jul 8 23:48:08 UTC 2012


On 8/07/2012 9:39 PM, Petr Stehlik wrote:
> Hi all,
>
> in the new 0.25 mythfrontend there's an idle timer. How can I set it up
> so that the final action is not the current "turn off screen" but "exit
> the frontend" or "run this sleep script"?
>
> Thanks,
>
> Petr
>
>
>
I have written the attached script to do just that, its also designed to 
shutdown a slave backend / frontend when idle as well.
For a frontend run via crontab every 10 minutes or so, use command line 
options "mysleepcommand -f" or "mysleepcommand -f -j" if you are running 
mythjobqueue as well as mythfrontend.  On a frontend machine it uses 
xscreensaver to determine whether the frontend is idle, so clearly you 
need to install/configure xscreensaver.

You need to set the hostname of your MBE in XML_STATS_URL on line 7, and 
adjust FRONTEND_IDLE to your liking (Remember this is the No of seconds 
xscreensaver is active before shutting down).

For a slave backend, set the script to run via the "Slave Backend -> 
Sleep Command", you will need command line options like "mysleepcommand 
-b -d -f" for a SBE with FE, or "mysleepcommand -b -d" for just a SBE.  
One Ubuntu 10.04 you will need to add "-u1" to the command, otherwise 
shutdown will be blocked.

You need to add something like this to "/etc/sudoers" so the script has 
access to shutdown and query xscreensaver
%mythtv ALL=(ALL) NOPASSWD: /sbin/shutdown, /usr/bin/xscreensaver-command


Shutdown is blocked if:
      uptime is less than ten minutes.
     Additional users are logged in
     Frontend is in mythbrowser, ripcd, and mythmusic
     Transcoding or Commercial flagging
     Currently recording (or about to start recording) on local Backend

Regards
Michael Watson

-------------- next part --------------
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
MIN_UPTIME=10
FRONTEND_IDLE=300				# Seconds to Wait to assume Frontend is Idle
FRONTEND_CMD="mythfrontend.real"                # MythFrontend Executable
XML_STATS_URL = "http://myth01:6544"		# MBE XML Status Hostname:PortNo
SHUTDOWN_CMD = "sudo /sbin/shutdown -P now"	# Command to Shutdown System
MYTHJOBS = "mythcommflag mythtranscode"		# Executables used by MythTV Jobs
FRONTEND_LOCATIONS = "playlistview playlisteditorview mythbrowser ripcd visualizerview" # Block Frontend Idle if in these locations

logMask = 'general'
logLevel = 'info'
logPath = '/var/log/mythtv'

import socket, urllib, commands, os, time, datetime, sys, string
import xml.etree.ElementTree as xml
from MythTV import MythBE, MythDB, MythError, MythDBError, MythFEError, MythLog
from optparse import OptionParser

# Initial Defaul Settings and Global Variable Declartion
HOSTNAME = "localhost"
LEVEL = None
ISBACKEND = False
ISFRONTEND = False
JOBQUEUE = False
SLEEPTIME = 300
NOUSERS = None

DB = None
LOG = None
BACKEND = None
FRONTEND = None

class VERBOSE_LEVEL:
    DEFAULT = 0
    VERBOSE = 1
    DEBUG = 2
    
def verbose(level, *messages):
    if (LEVEL >= level):
	# print ''.join(map(str,messages))
	LOG(LOG.GENERAL, LOG.INFO, ''.join(map(str,messages)))

def setuplog():
    global DB, LOG, FRONTEND, BACKEND

    try:
	DB = MythDB()
    except MythDBError:
	print "Problem Connecting to Database"
	sys.exit(1)

    try:
	BACKEND = MythBE()
    except MythBEError:
	print "Problem Connecting to Master Backend"
	sys.exit(1)

#    if ISFRONTEND == True:
#	try:
#	    FRONTEND = DB.getFrontend(HOSTNAME)
#	except MythFEError:
#	    print "Problem Connecting to Frontend"
#	    sys.exit(1)


    LOG = MythLog(module='MythSleep', db=DB)
    LOG._setmask(logMask)
    LOG._setlevel(logLevel)
    LOG._setfileobject(open('/var/tmp/mysleepcommand.log', 'a'))
#    LOG._setpath(logPath)


def checktuner():
# Return True if All Tuners are Idle

    tuneridle = True
    if ISBACKEND is True:
        xmldata = xml.parse(urllib.urlopen("%s/Status/xml" % XML_STATS_URL))
	rootElement = xmldata.getroot()
	now = datetime.datetime.now()
	for encoders in rootElement.getiterator('Encoders'):
	    for encoder in encoders.getiterator('Encoder'):
		if encoder.attrib.get('hostname') == HOSTNAME:
		    if int(encoder.attrib.get('state')) > 0:
			for program in encoder.getiterator('Program'):
			    title = program.attrib.get('title')
			    break
			verbose(VERBOSE_LEVEL.DEBUG, 'Encoder %d is Recording %s' % (int(encoder.attrib.get('id')), title))
			tuneridle = False
			break

	if tuneridle is True:
	    for schedule in rootElement.getiterator('Scheduled'):
		for program in schedule.getiterator('Program'):
		    title = program.attrib.get('title')
		    if program.attrib.get('hostname') == HOSTNAME:
			for channel in program.getiterator('Channel'):
			    recorder = int(channel.attrib.get('inputId'))
			for recording in program.getiterator('Recording'):
                	    start = recording.attrib.get('recStartTs')
                	    recstart = datetime.datetime.strptime(start, "%Y-%m-%dT%H:%M:%S")
            		if int(((recstart - now).seconds) / 60) <= 15:
            		    verbose(VERBOSE_LEVEL.DEBUG, 'Next Recording Starts in %d Minutes.  Using Tuner ID: %d' % ((((recstart - now).seconds) / 60), recorder))
                	    tuneridle = False
            		break

    if tuneridle is True:
	verbose(VERBOSE_LEVEL.DEBUG, 'Tuners Are Idle')

    return tuneridle

def checkusers():
# Return True if No additional Users are logged in
    idle = True
    nousers = int(commands.getstatusoutput("who -q | grep users | awk -F\"=\" '{ print $2 }'")[-1])
    if nousers > NOUSERS:
	idle = False

    verbose(VERBOSE_LEVEL.DEBUG, 'Users: %s  No. Users: %d' % (idle, nousers))
    return idle


def checkuptime():
# Return True if uptime is > MIN_UPTIME

    try:
	f = open("/proc/uptime")
	contents = f.read().split()
    except:
	print "Problem Reading UpTime"
	sys.exit(1)

    total_minutes = float(contents[0])
    total_minutes = int(total_minutes / 60)
    if total_minutes > MIN_UPTIME:
	idle = True
    else:
	idle = False
	
    verbose(VERBOSE_LEVEL.DEBUG, 'Uptime: %s' % idle)
    return idle
    
def getpid(process):
    pid=commands.getoutput("ps -ef | grep %s | grep -v grep" % process)
    if pid == "":
	return 0
    else:
	pid=commands.getoutput("ps -ef | grep %s | grep -v grep | awk '{ print $2 }'" % process)
	pidlist=pid.splitlines()
	return int(pidlist[0])

def checkjobs():
# Return True if No Transcode or Commflag Jobs are Active
    idle=True

    if JOBQUEUE is True:
	for progname in string.split(MYTHJOBS):
	    job_pid=getpid(progname)
	    if job_pid > 0:
		idle=False
		break

    verbose(VERBOSE_LEVEL.DEBUG, 'Jobs: %s' % idle)
    return idle

def checkscreensaver(mintime):
# Retrun True if Screen Saver Active Time > FRONTEND_IDLE
    idle = False
    saveruserlist=commands.getoutput("ps -ef | grep xscreensaver | grep -v grep | awk '{ print $1 }'")
    saveruser=saveruserlist.splitlines()[0]
    status = commands.getoutput("export DISPLAY=:0.0; sudo -u %s xscreensaver-command -time" % saveruser)
    verbose(VERBOSE_LEVEL.DEBUG, status)
    if status != "no saver status on root window":
	savermode = str.split(status)[3]
	if savermode == "blanked":
	    idledate = "%s %s %s %s" % (str.split(status)[7], str.split(status)[6], str.split(status)[9], str.split(status)[8])
	    idletime = time.strptime(idledate, "%d %b %Y %H:%M:%S")
	    localtime = time.localtime()
	    diff=datetime.datetime(*localtime[:6]) - datetime.datetime(*idletime[:6])
	    verbose(VERBOSE_LEVEL.DEBUG, 'Screensaver Active %d Secs' % diff.seconds)
	    if diff.seconds > mintime:
		idle=True

    verbose(VERBOSE_LEVEL.DEBUG, 'ScreenSaver: %s' % idle)
    return idle

def checkfrontend():
    if getpid("mythfrontend.real") == 0:
	# IF Frontend Not Running, Return True
	return True
	
    # backend = MythBE()
    # db = MythDB()

    frontend = DB.getFrontend(HOSTNAME)
    location = frontend.sendQuery('Location')
    # print "Frontend Location: %s" % location
    
    if location in FRONTEND_LOCATIONS:
	# If we are in one of these location, assume Frontend is not idle
	return False

    if str.split(location)[0] == "Playback":
	# If we are in Playback and screen saver is active, playback is paused, triple idle time
	idle = checkscreensaver(FRONTEND_IDLE * 3)
	return idle

    idle = checkscreensaver(FRONTEND_IDLE)
    return idle

def shutdown_frontend():
    verbose(VERBOSE_LEVEL.VERBOSE, 'Shutting Down Frontend')
#    db = MythDB()
    frontend = DB.getFrontend(HOSTNAME)
    location = frontend.sendQuery('Location')
    if str.split(location)[0] == "Playback":
	frontend.key['enter']
	frontend.jump['mainmenu']
	location = frontend.sendQuery('Location')
	
    if location != "mainmenu":
	frontend.jump['mainmenu']

    # Shutdown Frontend
    frontend.key['escape']
    frontend.key['down']
    frontend.key['enter']
    time.sleep(5)

    
def shutdown_system():
    verbose(VERBOSE_LEVEL.VERBOSE, 'Shutting Down System')
    result = commands.getstatusoutput(SHUTDOWN_CMD)


def idlechecks():

#    print "Uptime  : ", checkuptime()
#    print "Users   : ", checkusers()
#    print "Jobs    : ", checkjobs()
#    print "Tuner   : ", checktuner()
#    print "FrontEnd: ", checkfrontend()

    idle = False
    if checkuptime():
	if checkusers():
	    if checkjobs():
		if checktuner():
		    if checkfrontend():
			idle = True
			pid=getpid(FRONTEND_CMD)
			if pid > 0:
			    shutdown_frontend()
			shutdown_system()

    if idle == False:
	verbose(VERBOSE_LEVEL.DEFAULT, 'System Active.  Shutdown Aborted')


def run_daemon():
    
    # fork once
    try:
        pid = os.fork()
        if pid:
            # parent, exit
            sys.exit(0)
    except OSError, e:
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed fork to background.")
        sys.exit(1)

    os.chdir("/")
    os.setsid()
    os.umask(0)

    # fork twice
    try:
        pid = os.fork()
        if pid:
            # parent, exit
            sys.exit(0)
    except OSError, e:
        verbose(VERBOSE_LEVEL.DEFAULT, "Daemon failed fork to background.")
        sys.exit(1)

    setuplog()
    # backend = MythBE()
    current_uptime = old_uptime = BACKEND.getUptime()
    while current_uptime >= old_uptime:
	old_uptime = current_uptime
	verbose(VERBOSE_LEVEL.DEBUG, 'Backend Uptime: %s' % current_uptime)
	# print "Uptime: ", current_uptime
	try:
	    # uptime = MYTH_BE.getUptime()
	    # print uptime
	    idlechecks()
	except MythError, e:
	    # MythError
	    # wait for bindings to automatically reconnect
	    print "Exception Error"
	    pass
	
	time.sleep(SLEEPTIME)
	current_uptime = BACKEND.getUptime()
        
    verbose(VERBOSE_LEVEL.DEBUG, 'Daemon Exiting')


if __name__ == '__main__':
    parser = OptionParser(usage="usage: %prog [options]")
    parser.set_defaults(verbose_level=VERBOSE_LEVEL.DEFAULT)
    parser.add_option('-v', '--verbose', action='store', type='int', dest='verbose_level', default=VERBOSE_LEVEL.DEFAULT, help='Verbosity level')
    parser.add_option('-d', '--daemonize', action='store_true', dest='daemon', default=False, help='Daemonize and manage mythjobqueue')
    parser.add_option('-b', '--backend', action='store_true', dest='backend', default=False, help='Host is Backend')
    parser.add_option('-f', '--frontend', action='store_true', dest='frontend', default=False, help='Host is Frontned')
    parser.add_option('-j', '--jobqueue', action='store_true', dest='jobqueue', default=False, help='Host Runs JobQueue')
    parser.add_option('-s', '--sleep', action='store', type='int', dest='sleeptime', default=SLEEPTIME, help='Seconds between Idle Checks')
    parser.add_option('-u', '--users', action='store', type='int', dest='nousers', default=0, help='No Users Normally logged in')
    opts, args = parser.parse_args()
    
    # If we are a Backend, perform JobQueue checks as well
    if opts.backend is True:
	JOBQUEUE = True
    else:
	JOBQUEUE = opts.jobqueue
	
    LEVEL = opts.verbose_level
    ISBACKEND = opts.backend
    ISFRONTEND = opts.frontend
    HOSTNAME = socket.gethostname()
    SLEEPTIME = opts.sleeptime
    NOUSERS = opts.nousers

    if opts.daemon:
	run_daemon()
	sys.exit(0)
    else:
	setuplog()
	idlechecks()



More information about the mythtv-users mailing list