3

I have a script for my raspberry pi that continually takes readings and saves data to a file. However I know this is dangerous as there is a real risk of the SD card becoming corrupted if the power is pulled while data is being saved.

Is there a way I can have the script terminate itself if the computer is inactive for a set amount of time?

Sorry if this question is vague but I have no idea where to even begin with this so I can't show any code that I have tried.

Pauly D
  • 31
  • 1
  • 3

4 Answers4

1

That is a naive watchdog implementation:

import os
import signal
import threading



class Watchdog():
    def __init__(self, timeout=10):
        self.timeout = timeout
        self._t = None

    def do_expire(self):
        os.kill(os.getpid(),signal.SIGKILL)

    def _expire(self):
        print("\nWatchdog expire")
        self.do_expire()

    def start(self):
        if self._t is None:
            self._t = threading.Timer(self.timeout, self._expire)
            self._t.start()

    def stop(self):
        if self._t is not None:
            self._t.cancel()
            self._t = None

    def refresh(self):
        if self._t is not None:
             self.stop()
             self.start()

Build it by wd = Watchdog() and every time you get something that feed your work call wd.refresh(). If you don't call refresh before timeout ends it will call os.kill(os.getpid(),signal.SIGKILL).

You cannot use just sys.exit() because it raise just a SystemExit exception: use kill works as you want.

Now you can use something to poll the system and use the answer to refresh or not the watch dog. For instance xprintidle tell to you the X idle time, but all depend from what you need to monitoring.

Use example

timeout=10
wd = Watchdog(timeout)
wd.start()
while True:
    a=str(raw_input('Tell me something or I will die in {} seconds: '.format(timeout)))
    wd.refresh()
    print("You wrote '{}'... you win an other cycle".format(a[:-1))
Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
  • The hard part of the problem is how to detect "system activity" and reset the timer. – abarnert Apr 13 '15 at 11:52
  • @abarnert Maybe you are right but OP is not clear about that.... Anyway that answer is useful to cover at least the title :) – Michele d'Amico Apr 13 '15 at 11:57
  • 1
    @abarnert Just to understand: What is the harder part of the question? Type in google *linux detect system inactivity* or guess what the OP want? :) – Michele d'Amico Apr 13 '15 at 12:18
  • 1
    Well, yeah, mindreading is often the hardest part of many problems on SO. :) But if you read the comments on the question, this OP did answer some followups. Obviously it would be better if he edited the actual question into the question (or if he'd written it that way in the first place), but I don't think you need to actually guess anymore. – abarnert Apr 13 '15 at 12:20
  • @Micheled'Amico Thanks for typing this out but it's not working for me :( The script just asks for input and does nothing else, it won't close itslef and I'm not sure how to make it do so – Pauly D Apr 14 '15 at 12:58
  • @PaulyD Sorry I fixed it now – Michele d'Amico Apr 14 '15 at 14:49
  • @Micheled'Amico Still not wuite working properly, when i input some letters it comes up with an error saying "name '[input]' is not defined" – Pauly D Apr 14 '15 at 15:47
  • @PaulyD Sorry I write it for python 3... I'll fix it now to work on python 2.7 – Michele d'Amico Apr 14 '15 at 15:51
1

xprintidle utility, can make this an easy task:

#!/usr/bin/env python
# encoding: utf-8

import subprocess as sp
import threading
import time
import sys

def quit_on_idle(threshold=2000):
    import thread
    # note that sys.stdout.write and .flush should rather be used
    # instead of print
    while True:
        try:
            idle = float(sp.check_output('xprintidle').strip())
            if idle > threshold:
                print 'Inactive for {} seconds'.format(idle / 1000)
                break
        except (ValueError, sp.CalledProcessError) as err:
            print 'An error occured'
            # add your error handling here
        time.sleep(0.2)
    thread.interrupt_main()

try:
    threading.Thread(target=quit_on_idle).start()
    print 'Watchdog has been started in a separate thread'
    time.sleep(1)
    print 'one second passed'
    time.sleep(3)
    print 'This may not run at all, if you left computer idle'
except KeyboardInterrupt:
    print 'Bye'
m.wasowski
  • 6,329
  • 1
  • 23
  • 30
  • Thanks for the help but when I try and run this on my pi I get these errors: Traceback (most recent call last): File "timer.py", line 12, in idle = float(sp.check_output('xprintidle').strip()) File "/usr/lib/python2.7/subprocess.py", line 537, in check_output process = Popen(stdout=PIPE, *popenargs, **kwargs) File "/usr/lib/python2.7/subprocess.py", line 679, in __init__ errread, errwrite) File "/usr/lib/python2.7/subprocess.py", line 1259, in _execute_child raise child_exception OSError: [Errno 2] No such file or directory – Pauly D Apr 14 '15 at 13:01
  • you need to install `xprintidle` first, it is a binary written in C; see https://packages.debian.org/sid/xprintidle – m.wasowski Apr 14 '15 at 13:09
  • OK thanks a lot! That's working now, burt do you know if there's a way to have this timer independent from the rest of the program? – Pauly D Apr 14 '15 at 13:29
  • put it in function and run in a separate thread, see updated version. For more details read the docs about `threading` module. – m.wasowski Apr 14 '15 at 16:41
0

I wrapped xprintidle for python, I'd be keen to see if it does what you want!

https://pypi.python.org/pypi/xprintidle

To install:

pip install xprintidle

To use:

import xprintidle
print xprintidle.idle_time()

Let me know how you go :)

dpn
  • 592
  • 3
  • 9
0

You could call libXss.XScreenSaverQueryInfo() function to get the idle time in the same way as a screen saver does in X. Pure Python pxss.py shows how it can be done. Download it and put it alongside your code:

#!/usr/bin/env python
from __future__ import division
import os
import signal
import time

# $ wget https://raw.githubusercontent.com/mariano/snakefire/9437e59ffe9deff83676c5101a0dbf694ad3344b/snakefire/pxss.py
import pxss

def watchdog(timeout, idle_callback):
    """Call *idle_callback* after *timeout* seconds of inactivity."""
    tracker = pxss.IdleTracker(idle_threshold=timeout*1000)
    while True:
        state_change, wait_time, _ = tracker.check_idle()
        if state_change == "idle":
            idle_callback()
        time.sleep(wait_time / 1000)

def die():
    """thread.interrupt_main() analog."""
    os.kill(os.getpid(), signal.SIGINT)

Example:

import sys
import threading
from itertools import cycle

# exit after 10 seconds of inactivity
t = threading.Thread(target=watchdog, args=[10, die])
t.daemon = True
t.start()

# do whatever you like here that can be interrupted by Ctrl+C
try:
    for c in cycle('/|\-'):
        sys.stderr.write('\b' + c)
        sys.stderr.flush()
        time.sleep(.2)
except KeyboardInterrupt:
    print('bye')
jfs
  • 399,953
  • 195
  • 994
  • 1,670