Pale blue cloud  

   

   

Workaround for RPi's USB webcam problem

Details

Many people have reported problems with their USB webcams on the Raspberry Pi on various forums. The symptom is that the webcam software 'hangs' after shorter or longer periods of time. When the software is restarted everything is fine again, until the next lock-up.

My RPi exhibits this problem as well. When it occurs, the following message is printed to /var/log/messages:

raspberrypi kernel: [1354871.530013] uvcvideo: Non-zero status (-5) in video completion handler.

And then the webcam stream (created by motion) stops.

This is accompanied by the system load going from normal levels (~ 0.10) to 1, and staying there until motion is restarted:

High load on RPi

My first attempt on a work-around was a Perl script that continuously 'tailed' /var/log/messages, waiting for the above error message to occur and then restart motion. It didn't work well.

 The script that I wrote yesterday works differently: it runs as a daemon and looks at the system load. When the load exceeds a predefined value (0.8) for a predetermined period of time (2 minutes), the motion software is restarted. This seems to work better than the first script (although it was activated only once so far).

A weak point is of course that this script assumes that a high load is an indication of abnormal behaviour of the webcam software. If your system is regularly experiencing high loads for other reasons (for example: video transcoding, or a high load on your website) then it may not work as desired.

Here's the script, motionrestartd:

#!/usr/bin/perl -w
#
#    motionrestartd: a daemon in Perl to restart the 'motion' software
#    on Raspberry Pi when the load exceeds a threshold for a certain time.
#    Copyright (C) 2013 Martijn van den Burg
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

use strict;
use POSIX qw(setsid);

# Load limits:
my $UPPER_LIMIT = 0.8;
my $LOWER_LIMIT = 0.3;
my $MAX_LOAD_TIME_SEC = 120;

# Disable output buffering
$| = 1;

print "Starting $0 and going into the background...";

# go into the background
daemonize();

# do the work
loadmonitor();

#-----------------------------------------------------------------------------
#
#                                 subroutines
#
#-----------------------------------------------------------------------------


#-----------------------------------------------------------------------------
#
# Restart motion software
#
sub restartmotion {
   # Note: print statements suppressed when daemonize() -ed.
   print STDERR "Restarting motion...\n";

   system("/etc/init.d/motion restart");

} # restartmotion

#-----------------------------------------------------------------------------
#
# Daemonize ourself.
# Borrowed from
# http://stackoverflow.com/questions/766397/how-can-i-run-a-perl-script-as-a-system-daemon-in-linux
#
sub daemonize {
   my $pid = fork ();

   if ($pid < 0) {
      die "fork: $!";
   } elsif ($pid) {
      exit 0;
   }
   chdir "/";
   umask 0;
   foreach (0 .. (POSIX::sysconf (&POSIX::_SC_OPEN_MAX) || 1024))
      { POSIX::close $_ }

   # Ignore STDIN, redirect STDOUT and STDERR to /dev/null
   open (STDIN, "</dev/null");
   open (STDOUT, ">/dev/null");
   open (STDERR, ">&STDOUT");

} # daemonize

#-----------------------------------------------------------------------------
#
# Get system load data
#
sub one_minute_load_average {
   open(LOAD, "< /proc/loadavg") or die "Unable to get server load \n";
   my $load_avg = <LOAD>;
   close LOAD;

   my ( $one_min_avg ) = (split /\s/, $load_avg)[0];

   return $one_min_avg;

} # one_minute_load_average

#-----------------------------------------------------------------------------
#
# Monitor system load. Restart 'motion' when needed.
#
sub loadmonitor {
   my $sleepintv_sec = 20;
   my $tracking = 0;
   while (1) {
      my $load = one_minute_load_average();

      if ( $load > $UPPER_LIMIT && $tracking ) {  # Inside a high load situation
         if (time() - $tracking > $MAX_LOAD_TIME_SEC ) {  # High load exceeded time threshold
            print STDOUT "Possible problem detected with motion";  # suppressed when daemonized
            $tracking = 0;   # reset time tracking
            restartmotion(); # here we go
         }
      }
      elsif ( $load > $UPPER_LIMIT && (not $tracking) ) {   # first occurance of high load
         $tracking = time();   # start tracking time/load
      }
      elsif ($load < $LOWER_LIMIT) {  # load has gone done
         $tracking = 0;   # reset tracking time/load
      }
      else {   # load between $UPPER_LIMIT and $LOWER_LIMIT
         # do nothing
      }
      sleep($sleepintv_sec);

   }
} # loadmonitor

As root, save this script in /usr/local/bin and make the script executable:

  1. sudo su -
  2. cp motionrestartd /usr/local/bin
  3. chown root.root /usr/local/bin/motionrestartd
  4. chmod 750 /usr/local/bin/motionrestartd
  5. exit

Note: this script needs to be run as root, assuming that your webcam software is started by root.

Update

  • 9 Mar 2013: bugfix. Added '$tracking = 0;' before restarting motion.

Starting motionrestartd automatically when the RPi boots

Although I appreciate a long uptime like the next Linux geek, there will come the inevitable moment when the RPi is rebooted, either voluntarily (kernel update) or involuntarily (pulling the power supply adapter from the wall socket while vacuuming). When that happens you do not want to have go back to your blog or your little black book to figure out what scripts you need to start in order to get everything running again.

Enter init: the daemon process that starts and stops applications when the RPi starts up or shuts down. Read all about it init.

Configuring the RPi to start the daemon at boot is fairly straightforward.

  1. Create the script (below) and, as root, copy it to /etc/init.d.
  2. Make the script executable: chmod 755 restartmotiond.
  3. 'Register' the script with the init process. This command creates a few symbolic links in the right places, which will trigger the start/stop of the daemon during the boot sequence: update-rc.d motionrestartd defaults

The next time the RPi boots, motionrestartd will be started automatically.

You can also stop/start restartmotiond using the init script:

  • /etc/init.d/motionrestartd stop
  • /etc/init.d/motionrestartd start

Note that the script is not very fancy and that there's no protection against running multiple instances of motionrestartd. Personally I don't see that as a problem, but if you do then you can pimp it to check for a running process or a lock file or something like that.

Init script

#!/bin/sh


### BEGIN INIT INFO
# Provides: motionrestartd
# Required-Start: $local_fs $syslog $remote_fs
# Required-Stop: $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the motionrestartd daemon
# Description: starts motionrestartd using start-stop-daemon
### END INIT INFO

DAEMON=/usr/local/bin/motionrestartd
NAME=motionrestartd
DESC=motion-restart-daemon

test -x $DAEMON || exit 0

set -e

. /lib/lsb/init-functions

case "$1" in
   start)
      echo -n "Starting $DESC: "
      /usr/local/bin/motionrestartd
      echo "$NAME."
      ;;

   stop)
      echo -n "Stopping $DESC: "
      killall motionrestartd
      ;;

   *)
      echo "Usage: $NAME {start|stop}" >&2
      exit 1
      ;;

esac

exit 0

   
© Palebluedot