Dashboard slideshow
This flow based on the @Andrei flow :
https://flows.nodered.org/flow/990b338798dc80c829f75677f7176647
creates a slideshow in the Dashboard with pictures from a local directory in your Raspberry pi. The slideshow is controlled by nod-red dashboard buttons.
Features
- You can play and pause the slideshow. Go to next/previous , last/first.
- The time each picture displays is configurable in a delay node.
- You can delete pictures.
[{"id":"59c23b01.d78674","type":"ui_button","z":"a8b1b208.7036f","name":">","group":"d2e998ef.e44f08","order":5,"width":1,"height":1,"passthru":true,"label":"","tooltip":"","color":"","bgcolor":"","icon":"fa-chevron-right","payload":"next","payloadType":"str","topic":"","x":100,"y":1140,"wires":[["12b30e1d.7e5542"]]},{"id":"c24613ec.5dfd1","type":"ui_button","z":"a8b1b208.7036f","name":"<","group":"d2e998ef.e44f08","order":4,"width":1,"height":1,"passthru":false,"label":"","tooltip":"","color":"","bgcolor":"","icon":"fa-chevron-left","payload":"previous","payloadType":"str","topic":"","x":100,"y":1170,"wires":[["12b30e1d.7e5542"]]},{"id":"3b1ae542.01e5ca","type":"ui_button","z":"a8b1b208.7036f","name":"Del","group":"d2e998ef.e44f08","order":7,"width":1,"height":1,"passthru":false,"label":"","tooltip":"","color":"","bgcolor":"red","icon":"delete","payload":"del","payloadType":"str","topic":"","x":100,"y":1110,"wires":[["12b30e1d.7e5542"]]},{"id":"fef5b40f.538648","type":"ui_button","z":"a8b1b208.7036f","name":"<<","group":"d2e998ef.e44f08","order":3,"width":1,"height":1,"passthru":false,"label":"","tooltip":"","color":"","bgcolor":"grey","icon":"fast_rewind","payload":"first","payloadType":"str","topic":"","x":100,"y":1050,"wires":[["12b30e1d.7e5542"]]},{"id":"a1645ac9.756e38","type":"ui_button","z":"a8b1b208.7036f","name":">>","group":"d2e998ef.e44f08","order":6,"width":1,"height":1,"passthru":true,"label":"","tooltip":"","color":"","bgcolor":"grey","icon":"fast_forward","payload":"last","payloadType":"str","topic":"","x":100,"y":1080,"wires":[["12b30e1d.7e5542"]]},{"id":"1913948d.eb97bb","type":"ui_button","z":"a8b1b208.7036f","name":"play","group":"d2e998ef.e44f08","order":8,"width":1,"height":1,"passthru":false,"label":"","tooltip":"","color":"","bgcolor":"","icon":"play_arrow","payload":"play","payloadType":"str","topic":"","x":100,"y":990,"wires":[["12b30e1d.7e5542","e26a4b9c.dcb3d8"]]},{"id":"8dce426b.e5d1e","type":"ui_button","z":"a8b1b208.7036f","name":"pause","group":"d2e998ef.e44f08","order":9,"width":1,"height":1,"passthru":false,"label":"","tooltip":"","color":"","bgcolor":"","icon":"pause","payload":"pause","payloadType":"str","topic":"","x":100,"y":1020,"wires":[["12b30e1d.7e5542","e26a4b9c.dcb3d8"]]},{"id":"d42cdbf4.b3c288","type":"function","z":"a8b1b208.7036f","name":"action case","func":"let action = msg.payload; //what to navigate ?\nlet numSlides = msg.numSlides; //total pictures in the \"files\" array\nlet slideIndex = flow.get(\"slideIndex\") || 0; //index position picture\nlet gateSlide = flow.get(\"gateSlide\")||0; // play or pause\n\n switch (action) {\n case \"play\":\n flow.set (\"gateSlide\", true);\n break;\n case \"pause\":\n flow.set (\"gateSlide\", false);\n break;\n case \"first\": \n slideIndex = 0;//go to first\n break; \n case \"last\":\n slideIndex = numSlides-1;//got to last\n break;\n case \"del\"://**********DELETE PICTURE **********\n msg.fileName = flow.get(\"slides\")[slideIndex]; //Tx: name picture\n node.send([null,{ // 2nd output : \n fileName:msg.fileName, path:msg.path, numSlides:msg.numSlides,slideIndex:slideIndex\n }]); \n return msg\n case \"next\":\n slideIndex++;\n if(gateSlide === true){\n if (slideIndex > numSlides - 1) slideIndex = 0; //repeat if played\n } else {\n if (slideIndex > numSlides - 1) slideIndex = numSlides-1; //stay at last if next\n }\n break;\n case \"previous\": \n slideIndex--;\n if (slideIndex < 0) slideIndex = 0; //stay on first\n break;\n default:\n break;\n }\nmsg.affSlideIndex = slideIndex+1; //Tx N° picture\nflow.set(\"slideIndex\", slideIndex); //save position for next time\nmsg.slideIndex=slideIndex; //Tx position picture\n\n//get name picture from slideIndex position in the \"slides\" array\nmsg.fileName = flow.get(\"slides\")[msg.slideIndex]; //Tx: picture name\n\n//alternative message if Folder is empty\nif (numSlides === 0) {\n msg.fileName = \"Empty Folder ! \";\n msg.affSlideIndex = 0; //Tx N° picture\n}\nnode.status({\"text\" : msg.fileName}); //under the node\n\n\nreturn msg;\n\n\n\n\n","outputs":2,"noerr":0,"x":420,"y":1060,"wires":[["ec22ec57.728cf","d0f9ba93.7ffcb8"],["7c2276ac.1c37b8"]]},{"id":"17020f90.5b295","type":"fs-ops-dir","z":"a8b1b208.7036f","name":"","path":"path","pathType":"msg","filter":"*","filterType":"str","dir":"files","dirType":"msg","x":330,"y":940,"wires":[["a0a2796f.2cfa88"]]},{"id":"a0a2796f.2cfa88","type":"change","z":"a8b1b208.7036f","name":"set array in flow.slides","rules":[{"t":"set","p":"slides","pt":"flow","to":"files","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":940,"wires":[["51a3f66.6f0ab08"]]},{"id":"51a3f66.6f0ab08","type":"function","z":"a8b1b208.7036f","name":"Set numSlides","func":"let totalPic = flow.get(\"slides\").length; //get sise of array\nnode.status({text:totalPic}); // display under node\nmsg.numSlides = totalPic;//Tx: numSlides = total pictures in the files array\nreturn msg;\n\n/* Tx en sortie : \n\n*/","outputs":1,"noerr":0,"x":690,"y":940,"wires":[["d42cdbf4.b3c288"]]},{"id":"9a65fa80.217758","type":"inject","z":"a8b1b208.7036f","name":"Go","topic":"","payload":"Initialize pictures","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"1","x":80,"y":940,"wires":[["61e8a9fd.cc5e48"]]},{"id":"61e8a9fd.cc5e48","type":"function","z":"a8b1b208.7036f","name":"set path","func":"//let path=flow.get('picturePath');\npath = \"/home/pi/Documents/node-red-static/camAlerte\";\n//flow.set ('picturePath',path); //save picturePath : le chemin du dossier des pictures\nmsg.path = path;\nreturn msg;\n","outputs":1,"noerr":0,"x":200,"y":940,"wires":[["17020f90.5b295"]]},{"id":"2e89e1aa.db15fe","type":"link in","z":"a8b1b208.7036f","name":"getPath","links":["271aca69.06a7c6","d4dc51cb.4e0ef","6816661c.dc4008","f8f338.7920fcc8","12b30e1d.7e5542","4616e465.e9e8cc","1a896f6d.7a1971"],"x":115,"y":940,"wires":[["61e8a9fd.cc5e48"]]},{"id":"12b30e1d.7e5542","type":"link out","z":"a8b1b208.7036f","name":"getPath","links":["2e89e1aa.db15fe"],"x":215,"y":1060,"wires":[]},{"id":"ec22ec57.728cf","type":"ui_template","z":"a8b1b208.7036f","group":"d2e998ef.e44f08","name":"img","order":1,"width":10,"height":8,"format":"<!DOCTYPE html>\n<html>\n<head>\n<style>\ndiv.parent {\n position: relative;\n height: 480px ;\n}\ndiv.absolute {\n position: absolute;\n width: 100%;\n bottom: 0px;\n} \n\n</style>\n</head>\n<body>\n <div class=\"parent\">\n <div class=\"absolute\" >\n <img src=\"/camAlerte/{{msg.fileName}}\" alt=\"Camera Picture\" style=\"width:100%\"><br>\n {{msg.fileName}} {{msg.affSlideIndex}}/{{msg.numSlides}} \n </div>\n </div>\n</body>\n</html>\n<!--EXPLICATION : https://www.w3schools.com/cssref/tryit.asp?filename=trycss_position_bottom -->\n\n<!-- ////// SIMPLE VERSION //////////\n<style> \nimg {\n width: 100%;\n height: auto;\n border:1px solid green;\n}\n</style>\n<body>\n<div >\n<img src= \"/camAlerte/{{msg.fileName}}\" alt=\"Camera Picture\" ><br>\n</div>\n</body>\n\n\n\n\n-->\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":580,"y":1040,"wires":[[]]},{"id":"d0f9ba93.7ffcb8","type":"ui_text","z":"a8b1b208.7036f","d":true,"group":"d2e998ef.e44f08","order":12,"width":5,"height":1,"name":"picName/number","label":"{{msg.fileName}}","format":"{{msg.affSlideIndex}}/{{msg.numSlides}}","layout":"row-spread","x":620,"y":1010,"wires":[]},{"id":"7c2276ac.1c37b8","type":"fs-ops-delete","z":"a8b1b208.7036f","name":"del","path":"path","pathType":"msg","filename":"fileName","filenameType":"msg","x":580,"y":1070,"wires":[["ec49c26d.02bc4"]]},{"id":"1a896f6d.7a1971","type":"link out","z":"a8b1b208.7036f","name":"getPath","links":["2e89e1aa.db15fe"],"x":805,"y":1070,"wires":[]},{"id":"ec49c26d.02bc4","type":"function","z":"a8b1b208.7036f","name":"Stay at last","func":"let slideIndex = msg.slideIndex;\nlet numSlides = msg.numSlides;\n if (slideIndex==numSlides-1) {\n msg.payload=\"previous\"\n } \n else { msg.payload=\"refresh\" } \nreturn msg;\n","outputs":1,"noerr":0,"x":710,"y":1070,"wires":[["1a896f6d.7a1971"]]},{"id":"fcf45582.e33608","type":"switch","z":"a8b1b208.7036f","name":"flow gate filter Switch","property":"gateSlide","propertyType":"flow","rules":[{"t":"eq","v":"1","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":500,"y":1210,"wires":[["59c23b01.d78674"]]},{"id":"d89eb0b1.757f1","type":"trigger","z":"a8b1b208.7036f","op1":"","op2":"","op1type":"pay","op2type":"pay","duration":"-2","extend":false,"units":"s","reset":"","bytopic":"all","name":"2s","x":230,"y":1210,"wires":[["40f3dfb2.c4b66"]]},{"id":"416ff6ca.c893f8","type":"inject","z":"a8b1b208.7036f","name":"","topic":"","payload":"Sequencer","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"1","x":100,"y":1210,"wires":[["d89eb0b1.757f1"]]},{"id":"40f3dfb2.c4b66","type":"function","z":"a8b1b208.7036f","name":"Gate","func":"let read = flow.get(\"gateSlide\");\nlet status = read ? \"Opened\" : \"Closed\";\nlet color = read ? \"green\" : \"red\";\nnode.status({fill:color,shape:\"ring\",text:status});\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":1210,"wires":[["fcf45582.e33608"]]},{"id":"e26a4b9c.dcb3d8","type":"ui_text","z":"a8b1b208.7036f","group":"d2e998ef.e44f08","order":10,"width":2,"height":1,"name":"play/pause","label":"{{msg.payload}}","format":"","layout":"row-left","x":250,"y":990,"wires":[]},{"id":"a59cf547.dc4e28","type":"comment","z":"a8b1b208.7036f","name":"Read me","info":"## **Prerequisites**\nThis Flow work in the Raspberry with Raspbian and Node-red installed\n - Requires the installation of contrib node: **fs-ops-dir**.\n - Create folders in Node-RED and populate it with pictures. (The difference with Andrei Flow is that I don't display the images in the background). => **1/**\n - Requires **Context Storage** and **httpStatic** activated change the settings.js file in the raspberry => **2/**.\n\n\n## **How to do prerequisites ?**\n**1/** in the `/home/pi/Documents` Folder : create a new Foder named: `node-red-static`\nin this `/home/pi/Documents/node-red-static` Folder , create a new Folder named: `camAlerte`\n\n**2/** this flow use context storage: in the `/home/pi/.node-red` Folder , open the `settings.js`and verify (around line 222) must be like this : \n```\n // Context Storage\n // The following property can be used to enable context storage. The configuration\n // provided here will enable file-based context that flushes to disk every 30 seconds.\n // Refer to the documentation for further options: https://nodered.org/docs/api/context/\n //\n contextStorage: {\n default: {\n module:\"localfilesystem\",\n\t\t\tconfig: {\n\t\t\t\tdir:\"~/.node-red\",\n\t\t\t\tbase:\"context\",\n\t\t\t\tcache:true,\n\t\t\t\tflushInterval:30\n\t\t\t}\n },\n },\n```\n\n **And** add (modify) a line (around line108) ,like this : \n```\n // When httpAdminRoot is used to move the UI to a different root path, the\n // following property can be used to identify a directory of static content\n // that should be served at http://localhost:1880/.\n\t//httpStatic: '/home/nol/node-red-static/',\n httpStatic: '/home/pi/Documents/node-red-static',\n```\n(of course , you can change the location and the name of the directory)\n## **How it works**\nAfter indicating the folder of images, the **fs-ops-dir** node create an array of pictures name contained in the folder. It only remains to display the desired image by indicating its position in the table. It's a template node that will format a message to display the picture.\n\nBelow the code inside this template node.\n\n`<img src=\"/camAlerte/{{msg.fileName}}\" alt=\"Camera Picture\" style=\"width:100%\"><br>`\n\n\n## **Built and tested With**\n* [Node-RED] - version 1.0.3\n* [Dashboard] - version 2.19.4 \n","x":420,"y":880,"wires":[]},{"id":"50f1a473.e7f02c","type":"comment","z":"a8b1b208.7036f","name":"Your Folder here :","info":"example : \n/home/pi/Documents/node-red-static/camAlerte","x":170,"y":910,"wires":[]},{"id":"d2e998ef.e44f08","type":"ui_group","z":"","name":"Slideshow","tab":"b123e6c8.d3e948","order":1,"disp":true,"width":"10","collapse":false},{"id":"b123e6c8.d3e948","type":"ui_tab","z":"","name":"Slide","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
Prerequisites
This Flow work in the Raspberry with Raspbian and Node-red installed
- Requires the installation of contrib node: fs-ops-dir .
- Create folders in Node-RED and populate it with pictures. (The difference with Andrei Flow is that I don't display the images in the background). => 1/
- Requires Context Storage and httpStatic activated change the settings.js file in the raspberry => 2/ .
How to do prerequisites ?
1/ in the /home/pi/Documents
Folder : create a new Foder named: node-red-static
in this /home/pi/Documents/node-red-static
Folder , create a new Folder named: camAlerte
2/ this flow use context storage: in the /home/pi/.node-red
Folder , open the settings.js
and verify (around line 222) must be like this :
// Context Storage
// The following property can be used to enable context storage. The configuration
// provided here will enable file-based context that flushes to disk every 30 seconds.
// Refer to the documentation for further options: https://nodered.org/docs/api/context/
//
contextStorage: {
default: {
module:"localfilesystem",
config: {
dir:"~/.node-red",
base:"context",
cache:true,
flushInterval:30
}
},
},
And add (modify) a line (around line108) ,like this :
// When httpAdminRoot is used to move the UI to a different root path, the
// following property can be used to identify a directory of static content
// that should be served at http://localhost:1880/.
//httpStatic: '/home/nol/node-red-static/',
httpStatic: '/home/pi/Documents/node-red-static',
(of course , you can change the location and the name of the directory)
How it works
After indicating the folder of images, the fs-ops-dir node create an array of pictures name contained in the folder. It only remains to display the desired image by indicating its position in the table. It's a template node that will format a message to display the picture.
Below the code inside this template node.
<img src="/camAlerte/{{msg.fileName}}" alt="Camera Picture" style="width:100%"><br>
Built and tested With
- [Node-RED] - version 1.0.3
- [Dashboard] - version 2.19.4
- As I am a beginner in programming, there are surely modifications / improvements to be made to the flow. Free to you.
Authors
Based on Andrei Ochmat & reworked by me Chris Sam
License
Free for any kind of use
Acknowledgments
- Possible thanks to the outstanding work of Nick O'Leary and Dave Conway-Jones