Pi Temporal Camera

A flow to automatically create a time-lapse gif from the previous day. Using your Pi camera and the raspistill command in an exec node, pictures are taken every four minutes (or whenever you want). At the end of the day, or when you get enough images, a bash script is called with an exec node. Using ffmpeg, the pictures are assembled into an animated gif to be displayed on your dashboard.

Requires node-red-dashboard.

Requires a Pi camera.

Requires ffmpeg.

Requires a short bash script I have up on Github.

See full project and example pictures at https://github.com/WillWelker/pi-temporal-camera.

[{"id":"3bce27d.6ce10d8","type":"ui_template","z":"546bb50e.78adfc","group":"d28118de.c73bd8","name":"Current Image","order":2,"width":"12","height":"10","format":"<img name=\"main\" id=\"main\" border=\"0\" width=\"480\" height=\"460\" src=\"../seq/{{msg.payload}}.jpg\">\n<br>\nCTRL+ to zoom in, CTRL- to zoom out, F11 for full screen","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":1040,"y":160,"wires":[[]]},{"id":"b301d941.5c8d78","type":"ui_template","z":"546bb50e.78adfc","group":"b67801e.531e","name":"show gif","order":1,"width":"12","height":"10","format":"\n<img src=\"../gif/{{msg.payload}}.gif\">\n<h3>\n   <i class=\"fa fa-download\" aria-hidden=\"true\"></i>\n   <a href=\"../gif/{{ msg.payload }}.gif\" download=\"time-shift.gif\">Download This GIF </a>|\n   <i class=\"fa fa-cogs\" aria-hidden=\"true\"></i>\n   <a href=\"../\" target=\"_blank\"> Node Settings</a>|\n   <i class=\"fa fa-refresh\" aria-hidden=\"true\"></i>\n   <a href=\"../ui\"> Reload Page</a>\n      \n</h3>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":1040,"y":420,"wires":[[]]},{"id":"c533d93a.4c4848","type":"inject","z":"546bb50e.78adfc","name":"record stop time","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"00 11 * * *","once":false,"onceDelay":0.1,"x":150,"y":460,"wires":[["5857388d.669678"]]},{"id":"61b6fdcd.919f04","type":"exec","z":"546bb50e.78adfc","command":"/home/pi/.node-red/make-gif.sh","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Make gif","x":620,"y":460,"wires":[["548e48c7.cafa98","78ff2e1b.7b49c","efd04b84.d93478"],[],[]]},{"id":"5857388d.669678","type":"function","z":"546bb50e.78adfc","name":"gif name","func":"//Using a random number,\n//a unique name is stored in a global variable\n//Using context store, this value is stored in\n//the file system to survive a reboot.\n//This name is also passed to the exec node.\n//The bash script uses it to name the final gif.\n\nvar x = Math.floor((Math.random() * 1000) + 1);\nvar gifName = \"img\" + x.toString();\nglobal.set(\"gifName\", gifName);\nmsg.payload =  gifName;\nreturn msg;\n\n//this node will throw an error if\n//the context sorage is not enabled \n//in settings.js.\n//Without context storage, you could remove\n//the \"default\" argument in the global.set line.\n","outputs":1,"noerr":0,"x":460,"y":520,"wires":[["6bb644a9.20e0cc","61b6fdcd.919f04"]]},{"id":"548e48c7.cafa98","type":"function","z":"546bb50e.78adfc","name":"name gif","func":"//After the gif script has completed,\n//the global variable is used to pass on the \n//gif name to the template node for display.\n//In the event of a reboot, the context sorage\n//needs to be enabled in settings.js so this\n//gets stored in the file system rather than\n//getting lost.\n\nvar msg2={};\nvar gifName = global.get(\"gifName\");\nmsg.payload = gifName;\n\n\nreturn msg;","outputs":1,"noerr":0,"x":860,"y":340,"wires":[["b301d941.5c8d78"]]},{"id":"4c554974.f69318","type":"inject","z":"546bb50e.78adfc","name":"every minute","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":620,"wires":[["9df55c4c.8b05c"]]},{"id":"f709d0b0.9d255","type":"exec","z":"546bb50e.78adfc","command":"ls  /home/pi/.node-red/static/seq/  -1 | wc -l","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Count images","x":640,"y":620,"wires":[["6923e886.ba6df8","737d4ac0.bf6e64"],[],[]]},{"id":"6923e886.ba6df8","type":"ui_text","z":"546bb50e.78adfc","group":"b67801e.531e","order":2,"width":"5","height":"1","name":"Image Count","label":"Image count for next GIF --","format":"{{msg.payload}}","layout":"row-spread","x":1050,"y":660,"wires":[]},{"id":"ad29d171.0af1c","type":"inject","z":"546bb50e.78adfc","name":"On Start","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":"1","x":180,"y":360,"wires":[["548e48c7.cafa98"]]},{"id":"932be71b.6e0dc8","type":"comment","z":"546bb50e.78adfc","name":"Set Stop Time to record gif","info":"Set the inject node to the time you want \nrecording to stop.\nThe exec node will run an exec command to to ceate \nan animated gif from all the images that have been \nstored. It will also remove the original jpg images.\n","x":130,"y":420,"wires":[]},{"id":"cccbc6f.588cf38","type":"comment","z":"546bb50e.78adfc","name":"Push gif name","info":"In the event of a reboot a little extra push\nis needed to inject the gif name into the \ntemplate node.\nThis Inject node fires once on startup.","x":170,"y":320,"wires":[]},{"id":"66c87ff4.b753d","type":"comment","z":"546bb50e.78adfc","name":"Latest image","info":"This template node has the HTML code to display\nthe latest image from the motion program.\n","x":1030,"y":125,"wires":[]},{"id":"1ffdb497.6e9d0b","type":"comment","z":"546bb50e.78adfc","name":"Show GIF","info":"This template node has the HTML code to show\nthe animated gif.\nIt also contains code for a download link\nto save the current gif.","x":1040,"y":386,"wires":[]},{"id":"f57c7781.3d1538","type":"comment","z":"546bb50e.78adfc","name":"make-gif.sh","info":"This exec node runs the shell script that\ndoes all the work with ffmpeg.\nThe time stamp from the inject node is passed\ninto the exec node which uses it to name\nthe gif that results.\nWhen complete, it passes a message on to the\n'name gif' function node.  This node ignores\nany content of the message.  It is just triggered \nby the event to pass on the gif name it get from \nglobal storageto the template node.\nThis can take a few minutes to run depending on how\nmany images you have.\n\nYou can get make-gif.sh at:\nhttps://github.com/WillWelker/pi-temporal-camera/tree/master/bash\nThe script should be placed in your /home/pi/.node-red/ directory.\nYou can make the script file executable with:\nsudo chmod +x make-gif.sh \nor from the Pi Desktop environment,\nyou can right click the file > properties > Permissions, set\nexecutable to 'anyone'.","x":630,"y":419,"wires":[]},{"id":"c9ea73de.d71ed","type":"comment","z":"546bb50e.78adfc","name":"count images","info":"This exec node runs a command to count the\nimages in /home/pi/.node-re/static/seq/\n\nls  /home/pi/.node-red/static/seq/  -1 | wc -l\n\n\n\n\n","x":630,"y":579,"wires":[]},{"id":"a8b20f6c.824df","type":"ui_button","z":"546bb50e.78adfc","name":"","group":"b67801e.531e","order":3,"width":"4","height":"1","passthru":false,"label":"Make GIF now","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":160,"y":540,"wires":[["5857388d.669678"]]},{"id":"5be3c4a.515f73c","type":"comment","z":"546bb50e.78adfc","name":"Dashboard button","info":"Dashboard button to 'make gif now' with \nthe images currently in the seq directory.","x":150,"y":500,"wires":[]},{"id":"a256b17d.975eb","type":"ui_text","z":"546bb50e.78adfc","group":"b67801e.531e","order":4,"width":"3","height":"1","name":"Status","label":"Status:","format":"{{msg.payload}}","layout":"row-spread","x":1030,"y":540,"wires":[]},{"id":"78ff2e1b.7b49c","type":"change","z":"546bb50e.78adfc","name":"Done","rules":[{"t":"set","p":"payload","pt":"msg","to":"Done.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":500,"wires":[["a256b17d.975eb"]]},{"id":"6bb644a9.20e0cc","type":"change","z":"546bb50e.78adfc","name":"Working","rules":[{"t":"set","p":"payload","pt":"msg","to":"Working...","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":460,"wires":[["a256b17d.975eb"]]},{"id":"546b61e.d452ca","type":"comment","z":"546bb50e.78adfc","name":"Status","info":"These two change nodes set the text output to 'Working...'\nat the beginning of the gif process and then 'Done.'\nat the the end of the proccess.","x":850,"y":420,"wires":[]},{"id":"2fc32348.f6a5dc","type":"ui_template","z":"546bb50e.78adfc","group":"d28118de.c73bd8","name":"Style for links","order":1,"width":"0","height":"0","format":"<style>\n/* unvisited link */\na:link {\n  color: blue;\n}\n\n/* visited link */\na:visited {\n  color: green;\n}\n\n/* mouse over link */\na:hover {\n  color: hotpink;\n}\n\n/* selected link */\na:active {\n  color: yellow;\n}\n</style>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":1040,"y":80,"wires":[[]]},{"id":"6586629d.a4953c","type":"inject","z":"546bb50e.78adfc","name":"Take Picture","topic":"","payload":"","payloadType":"date","repeat":"480","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":120,"wires":[["1ccf9674.b66bca"]]},{"id":"13cf5762.20f3b9","type":"exec","z":"546bb50e.78adfc","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Take Picture","x":650,"y":220,"wires":[["5480c276.6d48dc","cb610c96.c9fbf"],[],[]]},{"id":"1ccf9674.b66bca","type":"function","z":"546bb50e.78adfc","name":"image name","func":"//creat new message object for second output\n//increase the outputs to 2 with the option below\nvar msg2 = {};\n\n//build the raspistill comand to take a picture\nvar imgPath = \"raspistill -o /home/pi/.node-red/static/seq/\" + msg.payload + \".jpg -w 640 -h 480\";\n//put the full raspistill comand into msg2.payload\nmsg2.payload = imgPath;\n\n\n//the first output will send msg\n//the second output will send msg2\nreturn [ msg, msg2 ];\n","outputs":2,"noerr":0,"x":430,"y":120,"wires":[["320e366b.bc000a"],["13cf5762.20f3b9","61fe26ed.d63ec8"]]},{"id":"8e63a1a7.4fbcb","type":"comment","z":"546bb50e.78adfc","name":"exec raspistill","info":"This exec node runs the raspisill command\npassed from the previous function node,","x":650,"y":179,"wires":[]},{"id":"320e366b.bc000a","type":"delay","z":"546bb50e.78adfc","name":"","pauseType":"delay","timeout":"7","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":640,"y":80,"wires":[["3bce27d.6ce10d8"]]},{"id":"92422f97.8b6d9","type":"comment","z":"546bb50e.78adfc","name":"Display last","info":"Wait a few seconds for raspistill to \ncomplete, then send the image name to \nthe template node.","x":650,"y":45,"wires":[]},{"id":"8f9190ed.73c64","type":"ui_button","z":"546bb50e.78adfc","name":"","group":"d28118de.c73bd8","order":3,"width":"3","height":"1","passthru":false,"label":"Shutdown Pi","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":170,"y":900,"wires":[["c28fb5cc.bfb6f8","e14291e6.2fc24"]]},{"id":"e14291e6.2fc24","type":"change","z":"546bb50e.78adfc","name":"Shutdown","rules":[{"t":"set","p":"payload","pt":"msg","to":"Shutting Down Now...","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":920,"wires":[["9407c88b.a2f1c8"]]},{"id":"c28fb5cc.bfb6f8","type":"exec","z":"546bb50e.78adfc","command":"sudo shutdown ","addpay":false,"append":"-t now","useSpawn":"false","timer":"","oldrc":false,"name":"Shutdown","x":620,"y":880,"wires":[[],[],[]]},{"id":"9407c88b.a2f1c8","type":"ui_text","z":"546bb50e.78adfc","group":"d28118de.c73bd8","order":4,"width":"3","height":"1","name":"Pi:","label":"Pi:","format":"{{msg.payload}}","layout":"row-spread","x":1030,"y":940,"wires":[]},{"id":"a3b1c49b.dc57b8","type":"inject","z":"546bb50e.78adfc","name":"Pi On","topic":"","payload":"On","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"1","x":190,"y":940,"wires":[["9407c88b.a2f1c8"]]},{"id":"7dcc4358.6a7e3c","type":"exec","z":"546bb50e.78adfc","command":"rm -f /home/pi/.node-red/static/seq/*.jpg","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Clear Images","x":630,"y":720,"wires":[["ad0e9637.5b99e8"],[],[]]},{"id":"c59d137c.9f9c1","type":"ui_button","z":"546bb50e.78adfc","name":"","group":"b67801e.531e","order":5,"width":"3","height":"1","passthru":false,"label":"Clear Images","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":170,"y":700,"wires":[["7dcc4358.6a7e3c"]]},{"id":"1a9311a.eb667ee","type":"ui_button","z":"546bb50e.78adfc","name":"","group":"d28118de.c73bd8","order":5,"width":"3","height":"1","passthru":false,"label":"Take Picture","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":170,"y":160,"wires":[["39078c9d.12c744"]]},{"id":"1a4e69b5.959586","type":"link in","z":"546bb50e.78adfc","name":"Count","links":["9df55c4c.8b05c","cb610c96.c9fbf","d4688286.42d81","efd04b84.d93478"],"x":505,"y":620,"wires":[["f709d0b0.9d255"]]},{"id":"cb610c96.c9fbf","type":"link out","z":"546bb50e.78adfc","name":"To Count Images","links":["1a4e69b5.959586"],"x":815,"y":200,"wires":[]},{"id":"efd04b84.d93478","type":"link out","z":"546bb50e.78adfc","name":"To Count Images","links":["1a4e69b5.959586"],"x":755,"y":440,"wires":[]},{"id":"9df55c4c.8b05c","type":"link out","z":"546bb50e.78adfc","name":"To Count Images","links":["1a4e69b5.959586"],"x":355,"y":620,"wires":[]},{"id":"d4688286.42d81","type":"link out","z":"546bb50e.78adfc","name":"To Count Images","links":["1a4e69b5.959586"],"x":995,"y":700,"wires":[]},{"id":"bc3e1400.dc2b28","type":"comment","z":"546bb50e.78adfc","name":"Links","info":"Several precesses need to trigger the \nCount Images node when compleate.\nTo clean things up I made a number\nof link-to nodes all connect to a\nsingle link-from node at the Count Images\nexec node.","x":430,"y":600,"wires":[]},{"id":"595df1f7.e7a55","type":"comment","z":"546bb50e.78adfc","name":"CSS Style","info":"This template node contains a little CSS\nto style my Lnik text.\n","x":1020,"y":40,"wires":[]},{"id":"7bd2632b.73ea6c","type":"inject","z":"546bb50e.78adfc","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":180,"y":740,"wires":[["7dcc4358.6a7e3c"]]},{"id":"ad0e9637.5b99e8","type":"delay","z":"546bb50e.78adfc","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":860,"y":720,"wires":[["d4688286.42d81"]]},{"id":"663ce98f.f38cf8","type":"ui_text","z":"546bb50e.78adfc","group":"d28118de.c73bd8","order":6,"width":"2","height":"1","name":"Picture Status","label":"","format":"{{msg.payload}}","layout":"row-spread","x":1040,"y":300,"wires":[]},{"id":"61fe26ed.d63ec8","type":"change","z":"546bb50e.78adfc","name":"Working","rules":[{"t":"set","p":"payload","pt":"msg","to":"Working...","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":300,"wires":[["663ce98f.f38cf8"]]},{"id":"5480c276.6d48dc","type":"trigger","z":"546bb50e.78adfc","op1":"Done","op2":"<i class=\"fa fa-camera\" aria-hidden=\"true\"></i>","op1type":"str","op2type":"str","duration":"5","extend":false,"units":"s","reset":"","bytopic":"all","name":"Done","x":850,"y":240,"wires":[["663ce98f.f38cf8"]]},{"id":"39078c9d.12c744","type":"change","z":"546bb50e.78adfc","name":"timestamp","rules":[{"t":"set","p":"payload","pt":"msg","to":"","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":240,"wires":[["1ccf9674.b66bca"]]},{"id":"e3e28fd.1f6f77","type":"comment","z":"546bb50e.78adfc","name":"change node","info":"Sets msg.paylod to timestamp","x":330,"y":200,"wires":[]},{"id":"c6d1001a.9a279","type":"exec","z":"546bb50e.78adfc","command":"sudo reboot","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Reboot","x":620,"y":980,"wires":[[],[],[]]},{"id":"3c39ae96.e73442","type":"inject","z":"546bb50e.78adfc","name":"Reboot Pi","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":180,"y":980,"wires":[["c6d1001a.9a279"]]},{"id":"737d4ac0.bf6e64","type":"switch","z":"546bb50e.78adfc","name":"100","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"100","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":850,"y":600,"wires":[["5857388d.669678"]]},{"id":"d50da188.23e3c","type":"comment","z":"546bb50e.78adfc","name":"Reboot or Shutdown","info":"Some nodes to easily reboot or shutdown the Pi.\n","x":410,"y":840,"wires":[]},{"id":"8539906a.4823a","type":"comment","z":"546bb50e.78adfc","name":"Max Image","info":"Set max image count.\nIf exeeded, Make-gif will execute.\nTrying to process too many images\ncan stll the Pi.","x":860,"y":540,"wires":[]},{"id":"d28118de.c73bd8","type":"ui_group","z":"","name":"Latest Image","tab":"96a299bc.6e1788","disp":true,"width":"12","collapse":false},{"id":"b67801e.531e","type":"ui_group","z":"","name":"Time Shift","tab":"96a299bc.6e1788","order":2,"disp":true,"width":"12","collapse":false},{"id":"96a299bc.6e1788","type":"ui_tab","z":"","name":"Main","icon":"dashboard"}]

Flow Info

Created 6 years, 11 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • change (x5)
  • comment (x15)
  • delay (x2)
  • exec (x6)
  • function (x3)
  • inject (x7)
  • link in (x1)
  • link out (x4)
  • switch (x1)
  • trigger (x1)
Other
  • ui_button (x4)
  • ui_group (x2)
  • ui_tab (x1)
  • ui_template (x3)
  • ui_text (x4)

Tags

  • timelapse
  • ffmpeg
  • bash
  • script
  • camera
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option