A lot of the time we find ourselves with tons of data available to us but we only care about seeing a particular subset, for example only the points that have recently changed or only the points that are turned on. Regardless of the application there can be a lot of value in using dynamic lists that re-sort themselves on the fly.
Here’s an example of a Node-RED flow that dynamically changes pump information being displayed in a groov View screen based on which were most recently toggled:
In this example the pump buttons on the left are static, but as they are turned on and off the most recently activated pump always appears at the top of the list on the right.
So here you can see pump #5 was turned on almost 6 minutes after pump #8, so #5 shows up at the top of the list, where pump #8 shows up at the bottom. Any pump that gets turned off is removed from the list on the right (which is why 1, 4, 6, and 9 are not listed at all).
This starts with three lists in a groov data store: the button boolean list and the two dynamic display lists – one for the pump index number and another for string status, which I am simply using for the time the button was turned on:
These are displayed with 10 button gadgets and 20 individual text area gadgets in groov View, then the buttons are read in as one table into Node-RED where they are checked for changes (report by exception / “RBE” node), then the code to process them begins.
The main logic here is to check to see if “on” buttons have a timestamp saved yet (did they just get turned on?), and check to see if “off” buttons have a timestamp that needs clearing (did they just get turned off?)
Once this is known, either save the timestamp using a new Date()
JavaScript date object, or set the timestamp = 0 to mark which pumps are turned off.
This time list is actually a list of pairs [ index , timestamp ]
so that in the next node we can sort by timestamp to get the most recent values at the top, and also sort the associated index at the same time so that the list of pump numbers is correct relative to the new time-based ordering.
Once the list is reordered we go through the list and add each new value to an array of “write objects” that uses dynamic node settings to update the data store tag values.
Here is the groov View page and a back-up of the flow: NR-flow groov-page.zip (3.4 KB)
Here is the Node-RED flow raw text:
[{"id":"4e48abcf.651304","type":"function","z":"e02f10b4.235de","name":"save button press time","func":"var count = 10; // number if items (for timestamp list length)\n\n// grab list of timestamps, initialize the list if it doesn't exist yet (if it is empty):\nvar timestamps = flow.get('btn_times') || [];\nif (timestamps.length < 1)\n for(i = 0; i < count; i++) timestamps.push([i, 0]); // push pairs [ index , time ]\n\nfor(i = 0; i < msg.payload.length; i++) {\n if(msg.payload[i] && (timestamps[i][1] === 0)) // button ON, previously OFF:\n timestamps[i] = [ i, (new Date().getTime()) ]; // save [ index, time ]\n\n else if (!msg.payload[i] && (timestamps[i][1] > 0)) // button OFF, previously ON:\n timestamps[i] = [ i, 0 ]; // save [ index, 0 ]\n}\n\nflow.set('btn_times', timestamps); // save the list\nreturn { payload : timestamps }; // also return it for sorting & displaying","outputs":1,"noerr":0,"x":660,"y":620,"wires":[["db108e6c.a0dd2"]]},{"id":"d315cd1c.b2ff2","type":"rbe","z":"e02f10b4.235de","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":470,"y":640,"wires":[["4e48abcf.651304"]]},{"id":"db108e6c.a0dd2","type":"function","z":"e02f10b4.235de","name":"order data by time","func":"timestamps = msg.payload;\ntimestamps.sort(function(a, b){ return b[1] - a[1] }); // sort by timestamp, not index.\n\n/* \"timestamps\" is a two-dimensional array like [ [0, time], [1, time], ... [10, time] ]\n This way when it is sorted by decreasing time we already have each associated index\n relative to the new order, since the [index, time] pair gets sorted together.\n Note that timestamps[i] = [index, time]; THUS timestamps[i][0] = index;\n AND timestamps[i][1] = time; */\n\nobjects = []; // create dynamically determined write objects to be displayed:\nfor(i = 0; i < timestamps.length; i++) {\n // if timestamp > 0 then the button is ON; display most recently activated buttons at the top:\n if(timestamps[i][1] > 0) {\n objects.push({ // get the \"i\"th index for the dynamic number:\n tagName : 'dynamic_number', // \"dynamic_number\" is the displayed pump #\n tableStartIndex : i,\n values : timestamps[i][0] + 1 // value is index number for this timestamp, offset +1\n });\n // get the \"i\"th activation time for dynamic text:\n myTime = new Date(timestamps[i][1]).toString();\n objects.push({\n tagName : 'dynamic_text',\n tableStartIndex : i,\n values : myTime.substring(4, myTime.length-15)\n });\n }\n \n // otherwise the button is off; clear this entry in the number and text tables:\n else {\n objects.push({\n tagName : 'dynamic_number',\n tableStartIndex : i,\n values : 0 // show pump # \"0\" for disabled pumps.\n });\n objects.push({\n tagName : 'dynamic_text',\n tableStartIndex : i,\n values : \"Pump disabled.\"\n });\n }\n}\ntimestamps.sort(function(a, b){ return a[0] - b[0] }); // resort by index.\n\nreturn { payload : objects }; // return the write objects to be displayed in groov","outputs":1,"noerr":0,"x":670,"y":660,"wires":[["2b63e375.1ea93c"]]},{"id":"573f5769.632008","type":"groov-read-ds","z":"e02f10b4.235de","dataStore":"18a28a95.517b05","tagName":"dynamic_buttons","tableStartIndex":"","tableLength":"10","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"read","x":350,"y":640,"wires":[["d315cd1c.b2ff2"]]},{"id":"2b63e375.1ea93c","type":"split","z":"e02f10b4.235de","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":850,"y":640,"wires":[["c5a66611.15c8c8"]]},{"id":"8184e55f.a7f8b8","type":"inject","z":"e02f10b4.235de","name":"0.5 s","topic":"","payload":"","payloadType":"date","repeat":".5","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":640,"wires":[["573f5769.632008"]]},{"id":"c5a66611.15c8c8","type":"groov-write-ds","z":"e02f10b4.235de","dataStore":"18a28a95.517b05","tagName":"","tableStartIndex":"","value":"payload.values","valueType":"msg","name":"output","x":970,"y":640,"wires":[[]]},{"id":"18a28a95.517b05","type":"groov-data-store","z":"","project":"","dsName":"NodeRED"}]
Feel free to use and modify this project as much as you’d like, and please post any questions or comments you have.
And as always, happy coding.