Time based - sliding window - simple stats

Not tonight, I have to be up early in the morning. In the meantime wikipedia may be helpful, Low-pass filter - Wikipedia, if you can try and get the gist of what it is talking about without getting bogged down in the maths. The function above approximates to a Resistor Capacitor filter as shown in the diagrams there.

1 Like

Just for completeness, here is a subflow which input data and outputs:

{"count":3,
"min":0,
"max":9,
"sum":9,
"median":0,
"avg_qty":3,
"avg_time":4.5}

it use a sliding windows.
You can adjust the time threshold in the subflow node properties.

[{"id":"364e384bacbf752f","type":"subflow","name":"timeWeightedAverage","info":"","category":"","in":[{"x":120,"y":280,"wires":[{"id":"440e4364ed92206b"}]}],"out":[{"x":760,"y":280,"wires":[{"id":"64c35f5cfe2c4613","port":0}]}],"env":[{"name":"threshold_s","type":"num","value":"60","ui":{"icon":"font-awesome/fa-clock-o","type":"input","opts":{"types":["num"]}}}],"meta":{"author":"aikitori@github.com","license":"MIT"},"color":"#3FADB5","icon":"font-awesome/fa-clock-o"},{"id":"440e4364ed92206b","type":"function","z":"364e384bacbf752f","name":"store","func":"var sliding_windows_size = 10\n\n\n// Limit array just by size\nfunction limit_array_size (arr,size) {\n        if (arr.length > size) {\n            arr.shift()\n        }\n        return arr\n}\n\n// Limit array by age\nfunction removeOlderThan(timestamps, data, threshold) {\n    const currentTime = Date.now();\n    for (let i = timestamps.length - 1; i >= 0; i--) {\n        if (currentTime - timestamps[i] > threshold) {\n            timestamps.splice(i, 1);\n            data.splice(i, 1);\n        }\n    }\n}\n\nvar data = []\nvar timestamps = []\n\ndata = context.get('data') || []\ntimestamps = context.get('timestamps') || []\n\nif (msg.topic == 'dump') {    \n    msg.payload = {}\n    msg.payload[\"data\"] = data\n    msg.payload[\"timestamps\"]= timestamps\n\n    context.set('data', [])\n    context.set('timestamps', [])\n\n} else {\n    let payload = msg.payload\n    let now = Date.now()\n    data.push(payload)\n    timestamps.push(now)\n\n    const threshold_s = env.get(\"threshold_s\");\n    const threshold_ms = threshold_s * 1000\n\n    removeOlderThan(timestamps, data, threshold_ms);\n\n    context.set('data',data)\n    context.set('timestamps',timestamps)\n\n    msg.payload = {}\n    msg.payload[\"data\"] = data\n    msg.payload[\"timestamps\"] = timestamps\n\n}\nreturn msg;\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":280,"wires":[["64c35f5cfe2c4613"]]},{"id":"64c35f5cfe2c4613","type":"function","z":"364e384bacbf752f","name":"calculate stuff","func":"function timeWeightedAverage(data, timestamps) {\n    if (data.length !== timestamps.length) {\n        node.warn(\"array lenght is not equal\");\n    }\n    \n    let total_weighted_sum = 0.0;\n    let total_duration = 0.0;\n    let total_area = 0.0\n    \n    for (let i = 1; i < data.length; i++) {\n        let d_t = timestamps[i] - timestamps[i - 1];\n        let v_1 = data[i -1]\n        let v_2 = data[i]\n        let area = ((v_1+v_2))/2 * d_t\n        total_area += area\n        const duration = timestamps[i] - timestamps[i - 1];\n        total_duration += duration;\n    }\n    \n    const time_weighted_avg = total_area / total_duration;\n    return time_weighted_avg;\n}\n\n\nfunction findMedian(arr) {\n    arr.sort((a, b) => a - b);\n    const middleIndex = Math.floor(arr.length / 2);\n\n    if (arr.length % 2 === 0) {\n        return (arr[middleIndex - 1] + arr[middleIndex]) / 2;\n    } else {\n        return arr[middleIndex];\n    }\n}\n\nlet new_payload = {}\nconst data = msg.payload.data\nconst timestamps = msg.payload.timestamps\n\n\n//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max\nconst min = Math.min(...data)\nconst max = Math.max(...data)\n\n// https://stackoverflow.com/a/10624256\nconst sum = data.reduce((a, b) => a + b, 0);\n\n\nconst avg_qty = (sum / data.length) || 0;\nlet avg_time = timeWeightedAverage(data, timestamps)\n\n\nnew_payload[\"count\"] = timestamps.length \nnew_payload[\"min\"] = min\nnew_payload[\"max\"] = max\nnew_payload[\"sum\"] = sum\nnew_payload[\"median\"] = findMedian(data)\nnew_payload[\"avg_qty\"] = avg_qty\nnew_payload[\"avg_time\"] = avg_time || avg_qty\n\n\nmsg.payload = new_payload\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":280,"wires":[[]]},{"id":"62c4c2b01cca3e59","type":"subflow:364e384bacbf752f","z":"8eabc3a82cb1c5d6","name":"","x":620,"y":680,"wires":[["21e13998d15101d8"]]},{"id":"064548962ef80966","type":"inject","z":"8eabc3a82cb1c5d6","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$floor($random() * 10)\t\t","payloadType":"jsonata","x":390,"y":680,"wires":[["62c4c2b01cca3e59"]]},{"id":"21e13998d15101d8","type":"debug","z":"8eabc3a82cb1c5d6","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":810,"y":680,"wires":[]}]

@Colin the RC filter looks also interesting, thank you for the hint!

@kitori - I forgot to come back and say thank you to you and everyone who helped me! Thank you!