Raspberry Pi WebCam with 3G (Solarcam)

From Wurst-Wasser.net
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

The Idea

  • Use solar panel to harvest energy
  • Store the energy in a left over car battery
  • Power a Pi, camera and 3G-Stick Mobile HotSpot[1]
  • Make a nice webcam out of it :)

Shopping List

  • A Pi with camera :)
  • A mobile Hotspot (I used a Huawei E1750C). Because getting an UMTS-Stick getting to work with an Pi sucks. Additionally you can easily join the WiFi network for maintenance)
  • SIM Card
  • (used) Car battery, in my case an 74Ah battery with one dead cell
  • Solar panel (12V, 30W eBay around 40€) NOTE: Might be to small!
  • Solar charger (f.e. "10A 12V/24V PWM solar panel regler solarpanel" @ ebay for around 15€)
  • more to come…

How To

Update your Pi to the latest Linux version

See https://www.datenreise.de/raspberry-pi-raspbian-linux-wheezy-jessie-upgrade/

Scripts

Scripts running on the Pi

cronjobs

Add this to crontab:

# DS18B20
*/15 * * * *	/home/pi/Development/temperature/readTemperature.sh > /dev/null 2>&1
#
# PiCam - Take Snapshot(s)
*/15 * * * *	/home/pi/Development/webcam/takePicture.sh > /dev/null 2>&1
knock.sh
#!/bin/bash

# knock on -heavens- servers door...(bad joke. sorry)

for x in 2323 4242 5555; do nmap -Pn --host_timeout 201 --max-retries 0 -p $x my_fqdn; done

(needs knockd)

takePicture.sh
#!/bin/bash
#set -x
#
# http://www.wurst-wasser.net/wiki/index.php/Raspberry_Pi_WebCam_with_3G
#
# Globals
#
GFOLDERTARGET="/var/www/html/webcam-archive"
GFILELOCK="${GFOLDERTARGET}/takePicture.lck"
GFILELOCKAGEMAX="60"
GTIMESTAMP="`date +'%Y-%m-%d_%H-%M-%S'`" 
GTIMESTAMPDATE="`date +'%Y-%m-%d'`" # Human readable
GTIMESTAMPTIME="`date +'%H:%M:%S'`" # Human readable
GTIMESTAMPMINUTES="`date +'%M'`"
GTIMESTAMPHOURS="`date +'%H'`"
#GTIMESTAMPHOURS="16"
#GTIMESTAMPMINUTES="00"
GFILETARGET="${GFOLDERTARGET}/${GTIMESTAMP}.jpg"
GFILELASTSNAP="/var/www/html/webcam/lastsnap.jpg"
GUPLOADEVERYTIME=0
GBASENAME="${0}"
function log
{
   logger "${GBASENAME}: $1"
   echo $1
} 
if [ "${1}" = "-f" ]; then
  log "Forcing upload."
  GUPLOADEVERYTIME=1
fi
#
#
#
# Main
#
#
# Check locking
if [ -f "${GFILELOCK}" ]; then 
  LOCKFILEAGE="`stat --format=\"%Z\" \"${GFILELOCK}\"`"
  NOW="`date +'%s'`"
  AGE="`expr ${NOW} - ${LOCKFILEAGE}`"
  if [ "${AGE}" -gt "${GFILELOCKAGEMAX}" ]; then
     log "Lockfile is ${AGE} seconds old, removing it."
     rm "${GFILELOCK}"
  else
     log "Lockfile is ${AGE} seconds old, exiting."
     exit 1
  fi
fi
#
#
# Lock
echo $$ > "${GFILELOCK}"
#
# timeout should not be 0, since images will be to dark and are noisy
#raspistill --nopreview -t 1 -w 1024 -h 768 -o -
# 
# Since my cam is mounted wrong, I prefer to flip it :) (and flop it)
#raspistill --nopreview -t 2 -w 1024 -h 768 -o - | convert - -flip -flop -
#raspistill --nopreview -w 1024 -h 768 -o - | convert - -flip -flop -
raspistill --nopreview -w 1024 -h 768 -o - | convert - -flip -flop - > "${GFOLDERTARGET}/lastsnap.tmp"
#
# Bild verkleinern...
mogrify -quality "80%" -interlace Plane "${GFOLDERTARGET}/lastsnap.tmp"
#mogrify -stroke white -pointsize 10 -gravity SouthEast -draw "text 0,0 '${GTIMESTAMPHR}'" "${GFOLDERTARGET}/lastsnap.tmp"
#mogrify -stroke white -pointsize 10 -gravity SouthWest -draw "text 0,0 '${GTIMESTAMPDATE}'" "${GFOLDERTARGET}/lastsnap.tmp" 
#mogrify -stroke white -pointsize 10 -gravity SouthEast -draw "text 0,0 '${GTIMESTAMPTIME}'" "${GFOLDERTARGET}/lastsnap.tmp"
mogrify -stroke black -pointsize 10 -gravity NorthWest -draw "text 0,0 '${GTIMESTAMPDATE}'" "${GFOLDERTARGET}/lastsnap.tmp"
mogrify -stroke black -pointsize 10 -gravity NorthEast -draw "text 0,0 '${GTIMESTAMPTIME}'" "${GFOLDERTARGET}/lastsnap.tmp"
#
# Save the taken picture to the archive, give the file a nice timestamped name
mv "${GFOLDERTARGET}/lastsnap.tmp" "${GFILETARGET}"
#
# Copy the latest image to the "current view" - folder
cp "${GFILETARGET}" "${GFILELASTSNAP}"
#
#raspistill -t 0 -w 1024 -h 768 -o -
#
# Make a upload to our webserver
if [ "${GTIMESTAMPHOURS}" -ge 0 -a "${GTIMESTAMPHOURS}" -le 23 -o "${GUPLOADEVERYTIME}" -eq 1 ]; then # Between 0 and 0 o'clock
  if [ "${GTIMESTAMPMINUTES}" -eq 0 -o "${GTIMESTAMPMINUTES}" -eq 15 -o "${GTIMESTAMPMINUTES}" -eq 30  -o "${GTIMESTAMPMINUTES}" -eq 45 -o "${GUPLOADEVERYTIME}" -eq 1 ]; then
     log "Uploading picture..."
     ~/Development/webcam/knock.sh 
     #   scp "${GFILETARGET}" dt:tmp/
     sftp dt:tmp/ <<< "put ${GFILETARGET}"
  fi
fi
#
# Cleanup: Delete all files older than n*24 hours
find "${GFOLDERTARGET}" -ctime +1 -delete
#
# Unlock
rm "${GFILELOCK}"
#
# Exit
exit 0
readTemperature.sh
#!/bin/bash
#set -x
#
# Script: readTemperature.sh
# Author: Heiko Kretschmer
# Purpose: Reading the temperature and generating a nice plot
#
#

#
# Globals
#
GDEVICEID="10-000802ae4298"
#GDEVICEID="10-000802ad5087"
GDEVICESPATH="/sys/bus/w1/devices"
GDEVICEVALUEFILE="w1_slave"
GTIMESTAMPFORMAT="%Y-%m-%d_%H:%M"
GTIMESTAMP="`date +${GTIMESTAMPFORMAT}`"
GTIMESTAMPTIME="`date '+%H:%M'`"
GTIMESTAMPDATE="`date '+%Y-%m-%d'`" # ISO 8601 date format
GTIMESTAMPDATEHUMANREADABLE="`date '+%A, %Y-%m-%d'`"
GFOLDERBASE="/home/pi/Development/temperature"
GFOLDERLOGS="${GFOLDERBASE}/logs"
GFOLDERGRAPHS="${GFOLDERBASE}/graphs"
GFOLDERTMP="${GFOLDERBASE}/tmp"
GFILELOG="${GFOLDERLOGS}/readTemperature_${GTIMESTAMPDATE}.log"
GFILEGRAPH="${GFOLDERGRAPHS}/readTemperature_${GTIMESTAMPDATE}.svg"
GFILEPLOTCOMMANDS="${GFOLDERTMP}/readTemperature-plot.cmd"
GTIMESTAMPMINUTES="`date +'%M'`"
GTIMESTAMPHOURS="`date +'%H'`" 
GUPLOADEVERYTIME=0
GBASENAME="${0}"
function log
{
  logger "${GBASENAME}: $1"
  echo $1
}

if [ "${1}" = "-f" ]; then
  log "Forcing upload."
  GUPLOADEVERYTIME=1
fi


#
# Main
# 

# Init
test ! -d "${GFOLDERLOGS}" && mkdir "${GFOLDERLOGS}"
test ! -d "${GFOLDERGRAPHS}" && mkdir "${GFOLDERGRAPHS}"
test ! -d "${GFOLDERTMP}" && mkdir "${GFOLDERTMP}"

# Get the temperature
VALUERAW="`cat \"${GDEVICESPATH}/${GDEVICEID}/${GDEVICEVALUEFILE}\" | grep t= | cut -d= -f2`"
VALUE="`echo \"scale = 3; ${VALUERAW} / 1000\" | bc`"
#echo Temperature: ${VALUE} 

# Write it into the log (one logfile per day to get nice graphs)
echo -e "${GTIMESTAMPTIME}\t${VALUE}" >> "${GFILELOG}"

# Generate the graph
test -f "${GFILEPLOTCOMMANDS}" && rm "${GFILEPLOTCOMMANDS}"
echo "reset" >> "${GFILEPLOTCOMMANDS}"
echo "set key inside right top vertical Right noreverse enhanced autotitles columnhead nobox" >> "${GFILEPLOTCOMMANDS}"

# Set time format for X axis
echo "set timefmt \"%H:%M\"" >> "${GFILEPLOTCOMMANDS}"
echo "set xdata time" >> "${GFILEPLOTCOMMANDS}"
echo "set format x \"%H:%M\"" >> "${GFILEPLOTCOMMANDS}"

# Setup line style (#1) for the temperature line
echo "set style line 1 lc rgb '#8b1a0e' pt 1 ps 1 lt 1 lw 2" >> "${GFILEPLOTCOMMANDS}" # http://www.gnuplotting.org/tag/grid/
echo "set style data lines" >> "${GFILEPLOTCOMMANDS}"

# Set X tics (one tic per hour, rotate that tick-labels by 90 deg and move em a bit)
echo "set xtics \"01:00\" rotate by 90 offset 0,-2 out nomirror" >> "${GFILEPLOTCOMMANDS}"

# Setup Grid (with line style #12)
echo "set style line 12 lc rgb '#E0E0E0' lt 0 lw 1" >> "${GFILEPLOTCOMMANDS}" # http://www.gnuplotting.org/tag/grid/
echo "set grid back ls 12" >> "${GFILEPLOTCOMMANDS}" # http://www.gnuplotting.org/tag/grid/

# Setup Title
echo "set title \"Temperature on ${GTIMESTAMPDATEHUMANREADABLE}\"" >> "${GFILEPLOTCOMMANDS}"

# Label X and Y Axis
echo "set ylabel \"°C\"" >> "${GFILEPLOTCOMMANDS}"
echo "set xlabel \"Time\" offset 0,-0.5" >> "${GFILEPLOTCOMMANDS}" 

# Setup Y range
echo "set yrange [ 0.000 : ] noreverse nowriteback" >> "${GFILEPLOTCOMMANDS}"
 
# Set output file type to svg and plot it into file
echo "set term svg size 640,480" >> "${GFILEPLOTCOMMANDS}"
echo "set output \"${GFILEGRAPH}\"" >> "${GFILEPLOTCOMMANDS}"
echo "plot \"${GFILELOG}\" using 1:2 title 'Temperature' with l ls 1" >> "${GFILEPLOTCOMMANDS}"
cat "${GFILEPLOTCOMMANDS}" | gnuplot 

# Copy this to Webserver
if [ "${GTIMESTAMPMINUTES}" -eq 0 -o "${GTIMESTAMPMINUTES}" -eq 30 -o "${GUPLOADEVERYTIME}" -eq 1 ]; then
  log "Uploading hourly graph..."
#   scp "${GFILEGRAPH}" dt:tmp/
  ~/Development/webcam/knock.sh 
  sftp dt:tmp/ <<< "put ${GFILEGRAPH}"
  rm "${GFILEGRAPH}"
fi
#
# EOF

Scripts running on the web server

cronjobs
# m h  dom mon dow   command
02,17,32,47 * * * *	~/bin/movePictures.sh >> /dev/null 2>&1
#
# Create a daily time-lapse
50 23 * * *    ~/bin/mergeAllPicturesInOneAnimatedGIF.sh >> /dev/null 2>&1
movePictures.sh

(called by crond)

#!/bin/bash
#set -x

# movePictures.sh
# Author: Heiko
# Purpose: Move files into place (Webserver), set lastsnap and delete old files... 

# Globals
GFOLDERWWW="/var/www/html/solarcam"
GFOLDERWWWIMAGES="${GFOLDERWWW}/webcam-archive"
GFILEWWWLASTSNAP="${GFOLDERWWW}/webcam/lastsnap.jpg"
GFILEWWWLASTGRAPH="${GFOLDERWWW}/webcam/lastgraph.svg"
GHOME="/home/solarcam" 

# Move the image in place
mv "${GHOME}/tmp/"*.jpg "${GFOLDERWWWIMAGES}/"
mv "${GHOME}/tmp/"*.svg "${GFOLDERWWWIMAGES}/"

# Make the newest "lastsnap"
FILENEWEST="`ls -Art \"${GFOLDERWWWIMAGES}\" | grep jpg | tail -1`"
cp -p "${GFOLDERWWWIMAGES}/${FILENEWEST}" "${GFILEWWWLASTSNAP}"
FILENEWEST="`ls -Art \"${GFOLDERWWWIMAGES}\" | grep svg |  tail -1`"
cp -p "${GFOLDERWWWIMAGES}/${FILENEWEST}" "${GFILEWWWLASTGRAPH}"

# Cleanup: Delete all files older than n*24hrs
find "${GFOLDERWWWIMAGES}" -ctime +30 -delete

# EOF
mergeAllPicturesInOneAnimatedGIF.sh

(called by crond)

#!/bin/bash
set -x
# 
# Script: mergeAllPicturesInOneAnimatedGIF.sh
# Author: Heiko Kretschmer
# Purpose: Merge all .jpg into one animated .gif
#
# History:
# 2014-03-30,	09:52, h:	Creation. Note. default ticks in -delay is n/100 of a second
# 2018-03-31,   08:11, h:	Moved to dt. Make daily GIF.
# 

# Globals
#
GFOLDERSOURCE="/var/www/html/solarcam/webcam-archive"
GSUFFIXSOURCE="jpg"
GTIMESTAMP="`date +'%Y-%m-%d'`"
#GTIMESTAMP="2018-03-29"
#echo "${GTIMESTAMP}"
#exit 0
GFILETARGET="/var/www/html/solarcam/webcam-archive/${GTIMESTAMP}.gif"

# (Un)Main
#
convert -limit area 0 -delay 25 -loop 1 "${GFOLDERSOURCE}/${GTIMESTAMP}*.${GSUFFIXSOURCE}" "${GFILETARGET}"
index.php

(place in your web server directory)

<?php
?>
<!doctype html public "-//W3C//DTD HTML 4.0 //EN"> 
<html> 
 <head> 
       <title>Webcam History</title> 
       <META HTTP-EQUIV="refresh" CONTENT="3600">
 </head> 
 <body style="background-color:#EEEEEE">
 
       <H1>Webcam Live Image</H1> 
       <img src="./webcam/lastsnap.jpg" />        
       <img src="./webcam/lastgraph.svg" />        
 
       <H1>Webcam History</H1> 
 	 
 <?php 

	$imagepath = "./webcam-archive"; 
	$images = scandir($imagepath); 
	$images = array_reverse($images); // newest on top
	foreach($images as $curimg)
	{ 
		if (substr($curimg, 0, 1)!="." && filesize("$imagepath/$curimg")>10000 &&
                    ( strstr($curimg, ".swf") || strstr($curimg, ".avi") || strstr($curimg, ".mpg") || strstr($curimg, ".jpg") || strstr($curimg, ".gif")  ) )
		{
			/* Link to Viewing-Page */
			$curimgencoded=urlencode($curimg);
	  		echo "<a href='./imageviewer.php?filename=$curimgencoded' alt='Show $curimg'  />$curimg<br>\n";

			/* Direct Link to Image File */
 			if (0)
			{
  				#echo "<H2>$curimg</H2>\n";
	  			#echo "<img src='$imagepath/$curimg' alt='$curimg' /><br>$curimg<br>\n";
	  			echo "<a href='$imagepath/$curimg' alt='$curimg' />$curimg<br>\n";
				#echo "<hr>\n";
			}
		}
	}; 

 ?> 
 
	<hr>
	<br/>
	<a href='./index.php'>Back!</a>

 </body> 
 </html>
 
imageviewer.php

(place in your web server directory)

<?php
 ?>
 <!doctype html public "-//W3C//DTD HTML 4.0 //EN"> 
 <html> 
 <head> 
       <title>Webcam History</title> 
       <META HTTP-EQUIV="refresh" CONTENT="60">
 </head> 
 <body style="background-color:#EEEEEE">
 	 
 <?php 

	/* Chosen Image */
    $filename=htmlspecialchars($_GET["filename"]);
    echo("<H1>Webcam Archive Image: $filename</H1>\n"); 
	//echo("<H2>Showing " . $filename . "</H2><br/>\n");
	$imagepath = "./webcam-archive"; 

	/* Alle Bilder im Archiv finden (Non-Bilder wegfiltern) */
	$images = scandir($imagepath); // Alle Bilder im Ordner
    #echo("images:\n");
    #print_r($images);
    #echo("---\n");
	$imagesfiltered=array();
	foreach($images as $curimg) // filter alles weg, was kein Bild ist!
	{ 
		#echo("Checking: $curimg<br/>\n");
		if (substr($curimg, 0, 1)!="." && filesize("$imagepath/$curimg")>10000 &&
                    ( strstr($curimg, ".swf") || strstr($curimg, ".avi") || strstr($curimg, ".mpg") || strstr($curimg, ".jpg") ) )
		{
			array_push($imagesfiltered, $curimg);
		}
	}
    #echo("imagesfiltered:\n");
	#print_r($imagesfiltered);
	#echo("---\n");

	/* Finde das vorherige und das nachfolgende Bild */
	$idx=array_search($filename, $imagesfiltered);
	#echo("idx: $idx<br/>\n");

	/* Previous Image-Link erzeugen */
	$previouslink="";
	if ($idx>0)
	{
		$previousimage=$imagesfiltered[$idx-1];
		$previousimageencoded=urlencode($previousimage);
		$previouslink="<a href='./imageviewer.php?filename=$previousimageencoded' alt='Show previous image'>< Previous Image <</a>\n";
	}
	
	/* Next Image-Link erzeugen */
	$nextlink="";
    if ($idx<count($imagesfiltered)-1)
	{
		$nextimage=$imagesfiltered[$idx+1];
		$nextimageencoded=urlencode($nextimage);
		$nextlink="<a href='./imageviewer.php?filename=$nextimageencoded' alt='Show next image'>> Next Image ></a>\n";
	}
	
	/* Obere Navigation */
	echo("<p align='center'>\n");
		//echo("$previouslink");
		//if (strlen($previouslink) > 1) { echo($previouslink); }
		if (strlen($previouslink) > 1 ) { echo($previouslink); } 
		echo("     "); // http://de.wikihow.com/Leerzeichen-in-HTML-einfügen
		if (strlen($nextlink) > 1) { echo($nextlink); }
	echo("</p>\n");	

	/* Das Bild */
	echo("<p align='center'>\n");
	echo("<img src='" . $imagepath . "/" . $filename . "' alt='$filename' />\n");
	echo("</p>\n");

	/* Untere Navigation */
	echo("<p align='center'>\n");
		if (strlen($previouslink) > 1 ) { echo($previouslink); } 
		echo("     "); // http://de.wikihow.com/Leerzeichen-in-HTML-einfügen
		if (strlen($nextlink) > 1) { echo($nextlink); }
	echo("</p>\n");	

	if (0)
	foreach($images as $curimg)
	{ 
		if (substr($curimg, 0, 1)!="." && filesize("$imagepath/$curimg")>10000 &&
                    ( strstr($curimg, ".swf") || strstr($curimg, ".avi") || strstr($curimg, ".mpg") || strstr($curimg, ".jpg") ) )
		{
			#echo "<H2>$curimg</H2>\n";
	  		#echo "<img src='$imagepath/$curimg' alt='$curimg' /><br>$curimg<br>\n";
	  		echo "<a href='$imagepath/$curimg' alt='$curimg' />$curimg<br>\n";
			#echo "<hr>\n";
		}
	}; 

 ?> 
 
	<hr>
	<br/>
	<a href='./index.php'>Back!</a>
 
 </body> 
 </html>

Photos

The finished product looks like this:
RaspberryPiSolarCam01.jpg RaspberryPiSolarCam02.jpg RaspberryPiSolarCam03.jpg


Tips

  • Remember that this Pi is in the wild and easily stolen or all the data read
  • Don't use your usual passwords for this project
  • Don't store keys on the Pi
  • Let your master server fetch the data (so no private keys are stored on the Pi) or use a chroot environment
  • Create a special user to access the data from remote, or even better, get it per HTTP and use .htaccess for privacy (and less 3G-traffic ;) )
  • If your WiFi connection breaks regularly, see Raspberry Pi WiFi issue (Solarcam)

?


  1. Since setting up the 3G-Stick was a pain in...