Dashboard slideshow gallery

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
4 Likes

This topic was automatically closed after 60 days. New replies are no longer allowed.