The PicUp Party Pi

Forums:

Unit

The party pi was based off of my image scrolling pi. A piNOIR camera has been added and takes pictures on a scheduled basis using chrontab. It uses a 3.5 inch display and is housed in a 3D printed case which I will make available on Thingiverse soon. The case takes about 2 hours to print both pieces, though you could print them together, I printed them seperate. I am currently printing a transparent red PET version of the case and will upload pictures if it completes. (note the "if")

Parts

Nice Pi Zero Starter Kit with cables: https://amzn.to/2XbhKvg Full CanaKit Pi Starter Kit: https://amzn.to/2JtFyrc Camera: https://amzn.to/2FNknwD Pi Zero Camera Ribbon Cable: https://amzn.to/2FMH3gm

Concept

The concept is simple. Place the unit somewhere at the event. As images are displayed and people move around the event it will snap random pictures. This allows you to set the unit somewhere and it will do the rest. The idea came to me while I was playing with the picture viewer set up.

Automated Image Uploads

The images are then uploaded via FTP to a website where people can view them using their mobile devices if they so choose. I am using the mobile hotspot on my cell phone for uploading the images. Currently all uploads are done using my home wifi. Images are approximately 500k at 1024x768 resolution, so they do not consume a lot of space at 1 picture every 5 minutes. The piNOIR can take much higher res pictures but I wanted to keep the transfer size at a minimum. While the guests can view the captured images from the event on the upload site, you can allow them to visit your party pi by giving them the URL displayed on the screen.

The cron job

To fire off the image capture every 5 minutes I created a cron job to fire off picup.sh. A simple bash script to push the images to the server via FTP. cron -e will open the editor Add the line: (or change /5 to how many minutes you want between images) */5 * * * * /home/pi/bin/picup.sh # JOB_ID_2

picup.sh

picup.sh is the meat and tators of the image upload processed by crontab. It is a simple bash file so it must be made executable. Create a directory created where the images are staged for upload. (i.e. /home/pi/webcm which is used below) The script uses the existence of a file which is created or deleted from the picup web page, allowing you to pause image taking and uploading from the web interface. You could remove this validation if you simply want it to continuously take pictures with no control.
#picup.sh bash file for image snapshot and upload # File used to determine upload status created from web cmd file. FILE="/home/pi/bin/relay/relayweb/picson" #create a dated filename DATE=$(date +"%Y-%m-%d_%H%M") # If/else to check the enabled/disabled file existence.. (from web cmd interface) is available upload, if not don't do anything if [ -f $FILE ]; #___ Uploads are enabled (found file) then echo "Processing Images" # for pi-camera useraspistill to take the picture. Remark/Unremark based on whether or not you are using picam or usb cam. # Take picture using PI camera raspistill -o /home/pi/webcm/$DATE.png -w 1024 -h 768 # Take picture using USB camera ( fswebcam for a USB camera) #fswebcam -r 1920x1080 --no-banner /home/pi/webcm/$DATE.png # Finally upload the file to your ftp server using curl- this is where you set the FTP server, username and pwd. (note not secure ftp) curl -T /home/pi/webcm/$DATE.png ftp://YOURFTPSERVER/$DATE.png --user FTPUSERNAME:FTPPASSWORD # This is an attempt to set the status image for the web server that it is running cp /home/pi/bin/relay/relayweb/static/picon.png /home/pi/bin/relay/relayweb/static/picstat.png else # __ Uploads are disabled from web page so do nothing. echo "Photo Uploads Disabled" #Another attempt to update the status image on the webpage to show uploads are disabled cp /home/pi/bin/relay/relayweb/static/picoff.png /home/pi/bin/relay/relayweb/static/picstat.png fi

The Display Web Page

Two php Display Examples Viewing the images captured during the event is handled using a simple php web page hosted on a web server from the same directory as the pictures. On load or refresh it will load up all of the images captured into the drop down list. Currently you have to select them and press the button, but I may add a java script loop that will simply display them as the local web page does, using the compiled list of images from php and a cycle loop.

Battery Power

I found this dual set of battery packs for under $20 at my local Sams Club. Since the pi zero does not consume much power, my initial test lasted a good 8 hours or so. This may however diminish after I added the wifi uploads of the pictures and the piNOIR camera unit. I will run a second test to get an approximated time limit the battery will last before needing recharged. This will allow me to place the unit without requiring an electrical outlet.

Upload Toggle

To control snapshots and uploads (on/off) a small python script will do the trick. Button from command page will toggle the existence of a file. If it exists then it will upload. If it does not, it will simply skip the entire image process.
#!/usr/bin/python import os pic_file = r"/home/pi/bin/relay/relayweb/picson" if os.path.isfile(pic_file): os.remove(pic_file) else: with open(pic_file,"w") as file: file.write("on")
index.html This index file is the main loader for flask and will be the first thing displayed. This version contains some additional controls which are not used and can be deleted, it was simply recycled from my piacc loader. This should be in the flask templates directory along with the following cmd.html file. This will be displayed on the local screen with rotating images and also acts as the command interface to perform actions on the unit.
{% extends "layout.html" %} {% block content %} <style> .clsver1_2 { float:left; } ::-webkit-scrollbar { display: none; } .BkGrndImage { background-size:contain; height:89vh; width:98%; text-align:center; float:none; background-image:url('/static/1.jpg'); background-repeat:no-repeat; padding:1px; margin:1px; border-radius:12px; } .btnBar { float:none; height:18vh; width:80%; background-repeat:no-repeat; background-size:contain; padding:8px; margin:8px; background-color:#272b33; position:fixed; bottom:0px; border-radius:12px; } .rnd { padding:12px; background-color:#272b33; border-radius:6px; cursor:pointer; } .rnd:hover { background-color:black; } .bl { background-color:purple; } .bR { margin-right:0px; background-size:auto; text-align:center; border-color:black; border-radius:12px; background-image:url('/static/orgBtn.png'); background-color:gray; background-position:center; background-repeat:no-repeat; color:black; float:none; font-size:large; padding-top:18px; cursor:pointer; } .bR:hover { color:red; } .mnuBtn { background-size:auto; text-align:center; text-decoration:none; border-color:black; border-radius:12px; background-image:url('/static/orgBtn.png'); background-color:gray; background-position:center; background-repeat:no-repeat; font-size:large; cursor:pointer; color:black; float:left; padding:4px; margin:4px; box-shadow:2px 2px #888888; } .mnuBtn:hover { color:red; } .btnOff { background-size:cover; width:100%; box-shadown:3px #333 #333; border-radius:12px; background-color:purple; background-position:center; background-repeat:no-repeat; color:black; float:none; font-size:large; padding:8px; background-image:url('/static/rdBtn.png'); display:block; margin-left:auto; margin-right:auto; } .btnOn { background-size:cover; background-image:url('/static/lbBtn.png'); width:100%; box-shadown:3px #333 #333; border-radius:12px; background-color:green; background-position:center; background-repeat:no-repeat; color:black; float:none; font-size:large; padding:8px; display:block; margin-left:auto; margin-right:auto; } .bdv { float:none; background-color:#585a58; text-align:center; align-content:middle; border-radius:0px; width:100%; } .clk { background-image:url("static/heart.png"); background-repeat:no-repeat; background-size:100px 100px; background-position:center bottom; font-size:22px; width:40%; position:absolute; opacity:.7; top:5%; right:12px; border-top-left-radius:0px; border-top-right-radius:0px; border-bottom-left-radius:12px; border-bottom-right-radius:12px; color:yellow; background-color:black; padding:8px; text-align:center; margin:4px; } .td { } </style> <!-- START SCRIPTS ------ --> <script> function startTime() { var today = new Date(); var h = today.getHours(); var m = today.getMinutes(); var s = today.getSeconds(); var mth = today.getMonth(); var yr = today.getFullYear(); var dy = today.getDate(); var bdv = document.getElementById("lightsdiv"); var cnt = document.getElementById("pcnt"); //image cycling if (bdv.style.display === "none") { if (s==59 && cnt.innerHTML !="30") { togglebg(); cnt.innerHTML="30"; } else { cnt.innerHTML="0"; } } m = checkTime(m); s = checkTime(s); var countDownDate = new Date("June 8, 2019 18:00:00").getTime(); var now = new Date().getTime(); var distance = countDownDate - now; var days = Math.floor(distance / (1000 * 60 * 60 * 24)); var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); document.getElementById('clck').innerHTML =days + " days<br>"+hours+" hours<br>"+minutes+" min<br>TO THE BIG DAY!"+ "<br><br><span style=\"color:white\">Our<br> <br>Event</span>"; // (mth + 1) + "/" + ; var t = setTimeout(startTime, 500); } function checkTime(i) { if (i < 10) {i = "0" + i}; // add zero in front of numbers < 10 return i; } function reloadpage() { location.reload(); } function closepage() { window.close(); } function togglediv(pnl) { var x = document.getElementById(pnl); if (x.style.display === "none") { x.style.display = "block"; } else { x.style.display = "none"; } } function toggleweb(pnl) { var p = document.getElementById(pnl); if (p.style.display === "none") { p.style.display = "block"; p.style.position = "absolute"; p.style.top = "1%"; p.style.left = "2%"; } else { p.style.display = "none"; } } function hidediv(pnl) { var x = document.getElementById(pnl); x.style.display = "none"; } function togglemenu() { var mnu = document.getElementById("menudiv"); var lmps = document.getElementById("lightsdiv"); if (mnu.style.display === "none") { mnu.style.display = "block"; lmps.style.display = "none"; } else { mnu.style.display = "none"; } } function tstamp() { var ts = document.getElementById("lblmsg"); msg(Date()); } function msg(m) { var ts = document.getElementById("lblmsg"); ts.innerHTML = m; } function togglebg() { //rotate background images var piclimit=83; var sp = document.getElementById("stp"); var curstp = parseInt(sp.innerHTML); if (curstp => 0) { curstp=parseInt(curstp)+1; }else{ curstp=0; } if (curstp > piclimit){ sp.innerHTML = 0; }else{ sp.innerHTML = curstp; } var bdiv = document.getElementById("bardiv"); bdiv.style.backgroundImage="url('/static/"+curstp+".jpg')"; } function loadwebifrm() { var f = document.getElementsByName("ifrm"); if (f.length > 0) { f[0].src=document.getElementById("srchbox").value; } } </script> <a href="/cmd" style="color:transparent" tabstop="0">Command Page</a> <img id="hdnClockStarter" src="/static/oelogo.png" onload="startTime()" style="height:20px;width:20px;display:none"> <!-- Side Buttons --> <!-- <div class="bR" style="height:50px;width:75px;position:absolute;top:5%;right:76px" onclick="closepage()"> Reset</div> --> <div class="bR" style="visibility:hidden;height:50px;width:75px;position:absolute;top:5%;right:0px" onclick="reloadpage()"> Reset</div> <div class="bR" style="visibility:hidden;height:50px;width:75px;position:absolute;top:20%;right:0px" onclick="togglemenu()"> Menu</div> <div class="bR" style="visibility:hidden;height:50px;width:75px;position:absolute;top:20%;right:0px" onclick="toggleweb('webdiv')"> WWW</div> <div class="bR" style="visibility:hidden;height:50px;width:75px;position:absolute;bottom:25%;right:0px" onclick="togglediv('lightsdiv')"> Lamps</div> <div class="bR" style="visibility:hidden;height:50px;width:75px;position:absolute;bottom:10%;right:0px" onclick="togglebg()"> Next</div> <!-- End Side Buttons --> <!-- Communication & Clock --> <!-- Clock --> <p id="clck" class="clk" OnLoad="startTime()"></p> <!-- Background counter --> <p id="stp" style="display:none">0</p> <p id="lblmsg" style="width:100%;float:left;"></p> <!-- End Comms --> <div id="bardiv" class="BkGrndImage"> <!-- Lamp Buttons --> <form method="post" action="change"> <div id="lightsdiv" style="display:none"> <table id="btntbl" class="btnBar" cellspacing="2px" cellpadding="2px" border="1px"> <tr> <td class="td"> <div class=bdv> <button class="btnOn" type="submit" value="On_1" id="button1" name="button1">Outer Lamps<br>On</button> <button class="btnOff" type="submit" value="Off_1" id="button1" name="button1">Outer Lamps<br>Off</button> </div> </td> <td class="td"> <div class=bdv> <button class="btnOn" type="submit" value="On_2" id="button1" name="button1">Inner Lamps<br>On</button> <button class="btnOff" type="submit" value="Off_2" id="button1" name="button1">Inner Lamps<br>Off</button> </div> </td> <td class="td"> <div class=bdv> <button class="btnOn" type="submit" value="On_3" id="button1" name="button1">Plow Lamp<br>On</button> <button class="btnOff" type="submit" value="Off_3" id="button1" name="button1">Plow Lamp<br>Off</button> </div> </td> <td class="td"> <div class=bdv> <button class="btnOn" type="submit" value="On_4" id="button1" name="button1">All Lamps<br>On</button> <button class="btnOff" type="submit" value="Off_4" id="button1" name="button1">All Lamps<br>Off</button> </div> </td> <tr> </table> </div> </form> <!-- End Lamp Buttons --> </div> <!-- web block --> <!-- iframe display block --> <div id="webdiv" style="display:none;width:90%;height:90%"> <div style="float:left;width:100%;background-color:silver;border-top-left-radius:12px;border-top-right-radius:12px;border-bottom-left-radius:0px;border-bottom-right-radius:0px;"> <button class="rnd" onclick="loadwebifrm()" style="float:right;color:white">Go</button> <input type=text class="rnd" id="srchbox" style="width:20%;float:right;color:white"></input> </div> <iframe name="ifrm" id="ifrm" class="rnd" style="width:100%;height:100%;float:left"></iframe> <!-- iframe display block --> </div> <!--end web block --> <!-- menu block --> <div id="menudiv" style="display:none"> <table id="menutable" class="btnBar" cellspacing="0" cellpadding="0" border="0" style="background-color:#eaeaea;float:left"> <tr><td colspan=3> </td></tr> <tr><td style="width:45%"><!--menu left cell --> <!-- key table --> <table id="locktable" style="float:left;width:50%"> <tr><td class="rnd">0</td><td class="rnd">1</td><td class="rnd">2</td><td class="rnd">3</td></tr> <tr><td class="rnd">4</td><td class="rnd">5</td><td class="rnd">6</td><td class="rnd">7</td></tr> <tr><td class="rnd">8</td><td class="rnd">9</td><td class="rnd">*</td><td class="rnd">!</td></tr> </table> <!-- end key table --> <div style="color:black;float:left;padding:4px;width:45%"><strong>Console Lock</strong><br> <input id="pcode" type="password" style="width:50%"> </div> </td> <td valign="top" style="width:45%;background-image:url('/static/oelogo.png');background-size:auto;background-repeat:no-repeat;color:white;background-color:black;border-radius:12px;text-align:center"><!--menu right cell --> <div style="float:none;padding:4px"><strong>Options</strong><br> <div class="mnuBtn">Theme</div> <div class="mnuBtn">Config</div> </div> </td> </tr> </table> </table> </div> <div id="pcnt" style="display:none"></div> <!-- end menu block --> </div> <!--</form>--> {% endblock %}

Control

Control of the unit is handled by a cmd.html page. This allows a clean reboot and shutdown, the ability to stop and start image capture and uploads, IP and gateway information and forcing a snapshot remotely. While a clean shutdown is key, the pic status keeps it from transmitting images while you are setting it up and waiting for the event to begin. cmd.html
{% extends "layout.html" %} {% block content %} <style> .bkg { height:79vh; width:96%; text-align:center; float:none; background-image:url('/static/bkg8.jpg'); background-repeat:no-repeat; padding:2px; margin:2px; border-radius:12px; } .picstat { width:100%; box-shadown:3px #333 #333; background-size:auto; border-radius:12px; background-color:transparent; background-position:center; background-repeat:no-repeat; color:black; float:none; font-size:large; padding:8px; background-image:url('/static/picstat.png'); display:block; margin-left:auto; margin-right:auto; } .btnBar { float:none; height:20vh; width:90%; background-repeat:no-repeat; background-size:contain; padding:12px; margin:12px; background-color:#272b33; position:fixed; bottom:0px; border-radius:12px; } .btn { width:100%; box-shadown:3px #333 #333; background-size:auto; border-radius:12px; background-color:black; background-position:center; background-repeat:no-repeat; color:black; float:none; font-size:large; padding:8px; background-image:url('/static/orgBtn.jpeg'); display:block; margin-left:auto; margin-right:auto; } .btnOn { background-image:url('/static/lbBtn.jpeg'); width:100%; box-shadown:3px #333 #333; background-size:auto; border-radius:12px; background-color:green; background-position:center; background-repeat:no-repeat; color:black; float:none; font-size:large; padding:8px; display:block; margin-left:auto; margin-right:auto; } .bdv { float:none; background-color:#585a58; text-align:center; align-content:middle; border-radius:0px; width:100%; } .clk { font-size:48px; position:fixed; top:0px; border-radius:12px; color:#43af24; background-color:black; padding:8px; text-align:center; margin:4px; } .td { } </style> <script> function startTime() { var today = new Date(); var h = today.getHours(); var m = today.getMinutes(); var s = today.getSeconds(); var mth = today.getMonth(); var yr = today.getFullYear(); var dy = today.getDay(); m = checkTime(m); s = checkTime(s); document.getElementById('clck').innerHTML = (mth + 1) + "/" + dy + "/" + yr + " " + h + ":" + m + ":" + s; var t = setTimeout(startTime, 500); } function altTime(){ document.getElementById("psimg").alt=today.getHours()+today.getMinutes()+today.getSeconds()); } function checkTime(i) { if (i < 10) {i = "0" + i}; // add zero in front of numbers < 10 return i; } function togglediv() { var x = document.getElementById("btntbl"); if (x.style.display === "none") { x.style.display = "block"; } else { x.style.display = "none"; } //tstamp(); } function tstamp() { var ts = document.getElementById("lblmsg"); msg(Date()); } function msg(m) { var ts = document.getElementById("lblmsg"); ts.innerHTML = m; } function togglebg() { var sp = document.getElementById("stp"); var curstp = parseInt(sp.innerHTML); if (curstp => 0) { curstp=parseInt(curstp)+1; }else{ curstp=0; } if (curstp > 23){ sp.innerHTML = 0; }else{ sp.innerHTML = curstp; } var bdiv = document.getElementById("bardiv"); bdiv.style.backgroundImage="url('/static/bkg"+curstp+".jpg')"; } </script> <meta http-equiv="refresh" content="30; url=/cmd"> <a href="/" style="color:white" tabstop="0">Back</a> <a href="/static/picstat.png"><img src="/static/picstat.png" id="psimg" alt="picstat" border="0"></a> <iframe src="/piup" style="width:50%;height:20%;float:right;border-width:0px"></iframe> <!-- <img id="bgbutton" src="/static/nxt.png" style="float:right;border:0px;width:80px" onclick="togglebg()"> <img id="pwrbutton" src="/static/pwrGrn.png" onload="startTime()" style="float:right;border:0px;width:150px" onclick="togglediv()"> --> <p id="clck" class="clk" style="display:none"></p> <p id="stp" style="display:none">0</p> <form method="post" action="change"> <div id="bardiv" class="bkg" style="visibility:block"> <p id="lblmsg" style="width:100%;float:left;"></p> <table id="btntbl" class="btnBar" cellspacing="2px" cellpadding="2px" border="1px"> <tr> <td class="td"> <div class=bdv> <button class=btnOn type="submit" value="boot" id="button1" name="button1">Reboot<br>Node</button> <button class=btn type="submit" value="down" id="button1" name="button1">Shutdown<br>Node</button> </div> </td> <td class="td"> <div class=bdv> <button class=btnOn class=btn type="submit" value="raspicfg" id="button1" name="button1">Raspi<br>Config</button> <button class=btn type="submit" value="ipcfg" id="button1" name="button1">IP<br>Config</button> </div> </td> <td class="td"> <div class=bdv> <button class=btnOn type="submit" value="snapshot" id="button1" name="button1">Snap<br>Shot</button> <button class=btn type="submit" value="localpic" id="button1" name="button1">local<br>Pic</button> </div> </td> <td class="td"> <div class=bdv> <button class=picstat type="submit" value="picstat" id="button1" name="button1" onload="altTime()">Pics<br>Status</button> <button class=btn type="submit" value="Off_4" id="button1" name="button1">Misc<br>1</button> </div> </td> <tr> </table> </div> </form> {% endblock %}
I have created a new image of the micro SD card and it should be fairly finalized. The next step of the project will be to rebuild the entire project with new and appropriate file names, directory structure, etc. Currently the files are remakes of the relay project and should be rebuilt.
I will be posting up the source code, set up, stl files, etc as time permits and upon project completion.
Files: 
AttachmentSize
Package icon picup_display_examples.zip2.75 KB