I am smoothing out light levels from a sensor in our hallway. Not sure if this is the best way to do it but it works. The following flow also shows how I worked out the best approach by charting the outputs from different smoothing options. Never quite got round to taking that out again.
[{"id":"7f81f115.807e1","type":"subflow","name":"COMMAND output","info":"### Take a COMMAND topic with the action in the payload. Send to DB & MQTT\n\nFeed in a msg with an appropriate COMMAND topic, e,g, COMMAND/SWITCH01\n\nThe payload must contain everything needed to save to MongoDB & everythng\nneeded to process the command elsewhere.\n\n### Input\n- msg with \n - topic set to COMMAND/<switch-name> \n - paload set to command to send (e.g \"On\" or \"Off\")\n\n### Output\n- Each input is sent to MongoDB\n - Once to a collection matching the topic\n - Once to a summary collection \"CurrentSwitchStatus\" with one record for each switch\n- Each input is sent to MQTT on COMMAND/#\n - COMMAND/SWITCHnn message is sent with Retain=true, QoS=1\n - So when NR restarts, it recieves back the last command and replays is\n - This ensures all switches are reset according to the last setting BUT it will override any manual sets\n - Obviously this may result in incorrect settings if a scheduled change event has been missed. (TODO improve scheduler)\n- Output 1 is for debugging the detail output\n- Output 2 is for debugging the summary output\n \n### Expected Input msg Format\n```JSON\n{\n\t\"topic\": \"HARDWARE-IN/TH1/0x3001\",\n\t\"payload\": {\n\t\t\"timestamp\": \"2016-01-09T17:46:17.016Z\",\n\t\t\"topic\": \"TH1/0x3001\",\n\t\t\"inputController\": \"RFX\",\n\t\t\"deviceFamily\": \"TH1\",\n\t\t\"id\": \"0x3001\",\n\t\t\"deviceFeatures\": \"TH\",\n\t\t\n\t\t\"temperature\": { \"value\": 19, \"unit\": \"degC\" },\n\t\t\"humidity\": { \"value\": 50, \"unit\": \"%\", \"status\": \"NORMAL\" },\n\t\t\"status\": { \"rssi\": 4, \"battery\": 9 },\n\t\t\"rssi\": 4,\n\t\t\"battery\": 9,\n\t\t\"Temperature\": 19,\n\t\t\"Humidity\": 50,\n\t\t\"Heat_Index\": 18.3,\n\t\t\"DewPoint\": 8.3\n\t}\n}\n```\n","in":[{"x":100,"y":160,"wires":[{"id":"13c42b13.ec3bd5"}]}],"out":[{"x":644,"y":100,"wires":[{"id":"13c42b13.ec3bd5","port":0}]},{"x":683,"y":209,"wires":[{"id":"13c42b13.ec3bd5","port":1}]}]},{"id":"13c42b13.ec3bd5","type":"function","z":"7f81f115.807e1","name":"Validate Input. 1) Device specific output, 2) Current Status","func":"/***\n * Make sure output message is ready to be recorded in MongoDB\n * INPUT: \n * From MQTT HARDWARE-IN but for remote controls (and smart switches) only\n * OUTPUTS:\n * 1) Device specific, data is sent to both MongoDB and MQTT\n * 2) Current Status. Data is sent to the CurrentSwitchStatus MongoDB collection only\n ***/\n\nif ( !('topic' in msg) || msg.topic === '' ) {\n node.warn( 'No topic! Dropping output' );\n return null;\n}\n\nif ( (msg.topic.split('/'))[0] !== 'COMMAND' ) {\n node.warn( 'Topic is not COMMAND/# Dropping Output' );\n return null;\n}\n\nif ( !('collection' in msg) ) {\n msg.collection = msg.topic; \n}\n\nif ( !('_id' in msg) ) {\n msg._id = new Date();\n}\n\n// Record what triggered this command msg if we can\nif ( !('inputHardware' in msg) ) {\n msg.inputHardware = 'MANUAL_OR_NR';\n}\n\n// -- CHANGED TO A LISTENER ON COMMAND CHANNEL --\n//// Keep current switch status in global memory\n//var cmdStatus = global.get('cmdStatus') || {};\n//cmdStatus[msg.topic] = msg.payload;\n//global.set('cmdStatus', cmdStatus);\n\n// 2nd output is for the Current Switch status\nvar currMsg = {\n 'collection': 'CurrentSwitchStatus',\n '_id': msg.topic,\n 'state': msg.payload,\n 'lastUpdate': msg._id,\n 'inputHardware': msg.inputHardware\n};\n\nreturn [msg, currMsg];","outputs":"2","noerr":0,"x":360,"y":160,"wires":[["6c18bd26.93e744"],[]]},{"id":"6c18bd26.93e744","type":"mqtt out","z":"7f81f115.807e1","name":"COMMAND/SWITCHnn (On/Off) [retained]","topic":"","qos":"1","retain":"true","broker":"3b2726f7.c4d8da","x":794,"y":154,"wires":[]},{"id":"8d3967a.f72c698","type":"comment","z":"7f81f115.807e1","name":"Take a COMMAND topic with the action in the payload. Send to DB & MQTT","info":"### Take a COMMAND topic with the action in the payload. Send to DB & MQTT\n\nFeed in a msg with an appropriate COMMAND topic, e,g, COMMAND/SWITCH01\n\nThe payload must contain everything needed to save to MongoDB & everythng\nneeded to process the command elsewhere.\n\n### Input\n- msg with \n - topic set to COMMAND/<switch-name> \n - paload set to command to send (e.g \"On\" or \"Off\")\n\n### Output\n- Each input is sent to MongoDB\n - Once to a collection matching the topic\n - Once to a summary collection \"CurrentSwitchStatus\" with one record for each switch\n- Each input is sent to MQTT on COMMAND/#\n - COMMAND/SWITCHnn message is sent with Retain=true, QoS=1\n - So when NR restarts, it recieves back the last command and replays is\n - This ensures all switches are reset according to the last setting BUT it will override any manual sets\n - Obviously this may result in incorrect settings if a scheduled change event has been missed. (TODO improve scheduler)\n- Output 1 is for debugging the detail output\n- Output 2 is for debugging the summary output\n \n### Expected Input msg Format\n```JSON\n{\n\t\"topic\": \"HARDWARE-IN/TH1/0x3001\",\n\t\"payload\": {\n\t\t\"timestamp\": \"2016-01-09T17:46:17.016Z\",\n\t\t\"topic\": \"TH1/0x3001\",\n\t\t\"inputController\": \"RFX\",\n\t\t\"deviceFamily\": \"TH1\",\n\t\t\"id\": \"0x3001\",\n\t\t\"deviceFeatures\": \"TH\",\n\t\t\n\t\t\"temperature\": { \"value\": 19, \"unit\": \"degC\" },\n\t\t\"humidity\": { \"value\": 50, \"unit\": \"%\", \"status\": \"NORMAL\" },\n\t\t\"status\": { \"rssi\": 4, \"battery\": 9 },\n\t\t\"rssi\": 4,\n\t\t\"battery\": 9,\n\t\t\"Temperature\": 19,\n\t\t\"Humidity\": 50,\n\t\t\"Heat_Index\": 18.3,\n\t\t\"DewPoint\": 8.3\n\t}\n}\n```\n","x":358.8957824707031,"y":23.888885498046875,"wires":[]},{"id":"e9288d30.e3c06","type":"comment","z":"7f81f115.807e1","name":"TODO: Save command updates to json file","info":"and reload on startup, passing to UI.","x":362,"y":311,"wires":[]},{"id":"a680238d.4782a","type":"comment","z":"a5dcc8f6.5a2338","name":"Switch on in low light but only between 6am and 1am","info":"This monitors light from an ESP8266 based sensor\nplatform containing an I2C light sensor.\n\nThe light output is in Lux.\n\nThe sensor is positioned above a lamp that should\ncome on automatically if the light level falls below\nthat produced by the lamp.\n\nHowever, this should only happen during the day\nand evening, not overnight.","x":411,"y":758,"wires":[]},{"id":"152b0840.102698","type":"function","z":"a5dcc8f6.5a2338","name":"Light & Time control","func":"// Turn on/off a light (by issuing a COMMAND/SWITCHnn MQTT msg)\n// depending on the lux level from a sensor (on/offThresh).\n// Except if the time is between two hours in early AM (start/endTime)\n// Don't repeat send the command (tracks whether light is already on/off)\n//\n// Recommended to feed in Averaged or Low bandwidth filter light\n// levels over about the last 10 minutes. \n// You can use the smooth node for this.\n\n/*\nglobal.set('switchLocations', {\n \"SWITCH02\" : {location: \"HOME/IN/00/HALL\", description: \"Hall standard lamp near kitchen\", type: \"Siemens white remote plug\"},\n});\nglobal.set('lightLevels', {\n \"HOME/IN/00/HALL\": [15,50]\n});\n*/\n\n// Only check during Day time not Night\nvar home = global.get('home');\nvar dayNight = home.daynight || 'NA';\nif ( dayNight === 'Day' ) {\n\n // Which switch are we controlling?\n var mySwitch = 'SWITCH02';\n var ctrlSwitch = (global.get('switchLocations'))[mySwitch];\n var swLocation = ctrlSwitch.location;\n \n // ----------------------------------------------- //\n // SETTINGS\n // ----------------------------------------------- //\n var lightLevels = global.get('lightLevels');\n // What light thresholds do you want?\n onThresh = lightLevels[swLocation][0]; // Turn light on if less than this\n offThresh = lightLevels[swLocation][1]; // Turn light off if greater than this\n // ----------------------------------------------- //\n // The topic is the lamp switch we want to control\n msg.topic = 'COMMAND/' + mySwitch;\n // ----------------------------------------------- //\n\n // Comes in from MQTT in string form\n var val = parseFloat(msg.payload);\n\n node.status({fill:\"green\",shape:\"ring\",text:'Day: Lux=' + val + ', Threshold On=' + onThresh + ' Off=' + offThresh});\n \n // cmdStatus is set by \"COMMAND output\" subflow\n // it keeps track of current command status\n // and is indexed by command (MQTT) topic\n // e.g. cmdStatus = { 'COMMAND/SWITCH02': 'On', 'COMMAND/SWITCH05': 'Off' } \n var cmdStatus = global.get('cmdStatus') || {};\n \n msg.payload = cmdStatus[msg.topic];\n \n // Check light thresholds\n if ( val < onThresh ) {\n \n // Light level below on threshold\n msg.payload = 'On';\n \n } else if ( val > offThresh ) {\n \n // Light level above off threshold\n msg.payload = 'Off';\n \n } // else light within thresholds, do nothing\n \n // If cmd has changed, send msg\n if ( cmdStatus[msg.topic] !== msg.payload ) {\n // NB: cmdStatus is updated in \"COMMAND output\" subflow\n return msg;\n } // otherwise, cmd not changed, nothing to do\n\n} else {\n node.status({fill:\"red\",shape:\"dot\",text:\"Night\"});\n} // -- End of time check -- //\n\n// --- End of Light & Time Control --- //\n\n/*\n// Set the start and end hours (24hr clock) that you want IGNORED\n// No commands issued between these times.\n// NB: Assumes both hours are after midnight. If you need\n// to span midnight, you will need more complex time check.\nvar startIgnore = 0;\nvar endIgnore = 6;\n// ----------------------------------------------- //\n// These DO have to be all new Date()\n// Don't set start/end to nowTime as they would\n// be the SAME data (nowTime would be changed by setHours)\nvar nowTime = new Date();\nvar startTime = new Date();\nvar endTime = new Date();\nstartTime.setHours(startIgnore,0,0,0);\nendTime.setHours(endIgnore,0,0,0);\n\n// Check if we are within the ignore time\nif ( (nowTime > startTime) && (nowTime < endTime ) ) {\n \n // Within the ignore time so do nothing\n\n} else { // Not in ignore time, so process\n \n} // -- End of time check -- //\n\n*/","outputs":"1","noerr":0,"x":540,"y":872,"wires":[["d82db943.105b68"]]},{"id":"e7462437.3acd28","type":"mqtt in","z":"a5dcc8f6.5a2338","name":"Hall Lux","topic":"LIGHT/HOME/IN/00/HALL/D1M02","qos":"2","broker":"3b2726f7.c4d8da","x":160,"y":872,"wires":[["1a6fdb00.289805","f055f8d6.0b6998","41ac59e1.0f0ca8","51207419.66bb1c"]]},{"id":"b07f3bf3.62fb18","type":"comment","z":"a5dcc8f6.5a2338","name":"If avg lux <5 for >10 min: turn on light","info":"","x":250,"y":792,"wires":[]},{"id":"4e96c2fd.cca4ac","type":"comment","z":"a5dcc8f6.5a2338","name":"If avg lux >30 for >10 min: turn off light","info":"","x":554,"y":792,"wires":[]},{"id":"1a6fdb00.289805","type":"smooth","z":"a5dcc8f6.5a2338","name":"Avg last 10, 2dp","action":"mean","count":"10","round":"2","x":320,"y":872,"wires":[["152b0840.102698","70d97026.6ae5f","f0dcb13a.13a88"]]},{"id":"70d97026.6ae5f","type":"debug","z":"a5dcc8f6.5a2338","name":"","active":false,"console":"false","complete":"payload","x":491,"y":832,"wires":[]},{"id":"41ac59e1.0f0ca8","type":"smooth","z":"a5dcc8f6.5a2338","name":"low, smooth 10, 2dp","action":"low","count":"10","round":"2","x":340,"y":1032,"wires":[["2a19f377.03071c"]]},{"id":"85955796.3961b8","type":"ui_chart","z":"a5dcc8f6.5a2338","name":"Hall Light","group":"9f8a5d12.6f72b","order":0,"width":"18","height":"12","label":"Hall Light","chartType":"line","legend":"true","xformat":"%H:%M:%S","interpolate":"linear","nodata":"Waiting for data ...","ymin":"","ymax":"","removeOlder":"2","removeOlderUnit":"3600","x":685,"y":1012,"wires":[[],[]]},{"id":"f055f8d6.0b6998","type":"change","z":"a5dcc8f6.5a2338","name":"LUX","rules":[{"t":"set","p":"topic","pt":"msg","to":"LUX","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":506,"y":992,"wires":[["85955796.3961b8"]]},{"id":"f0dcb13a.13a88","type":"change","z":"a5dcc8f6.5a2338","name":"Avg","rules":[{"t":"set","p":"topic","pt":"msg","to":"Avg","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":506,"y":952,"wires":[["85955796.3961b8"]]},{"id":"2a19f377.03071c","type":"change","z":"a5dcc8f6.5a2338","name":"Low","rules":[{"t":"set","p":"topic","pt":"msg","to":"Low","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":506,"y":1032,"wires":[["85955796.3961b8"]]},{"id":"d82db943.105b68","type":"subflow:7f81f115.807e1","z":"a5dcc8f6.5a2338","name":"","x":758,"y":872,"wires":[[],[]]},{"id":"51207419.66bb1c","type":"smooth","z":"a5dcc8f6.5a2338","name":"low, smooth 5, 2dp","action":"low","count":"5","round":"2","x":330,"y":1072,"wires":[["bb302f88.c4527"]]},{"id":"bb302f88.c4527","type":"change","z":"a5dcc8f6.5a2338","name":"Low5","rules":[{"t":"set","p":"topic","pt":"msg","to":"Low5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":506,"y":1072,"wires":[["85955796.3961b8"]]},{"id":"7c3377d8.b14d08","type":"comment","z":"a5dcc8f6.5a2338","name":"Plot different smoothing","info":"","x":332,"y":978,"wires":[]},{"id":"3b2726f7.c4d8da","type":"mqtt-broker","z":"","broker":"localhost","port":"1883","clientid":"Pi2_NR-Live","usetls":false,"verifyservercert":true,"compatmode":false,"keepalive":"15","cleansession":true,"birthTopic":"DEVICES/PI2NR-LIVE","birthQos":"1","birthRetain":"true","birthPayload":"Online","willTopic":"DEVICES/PI2NR-LIVE","willQos":"1","willRetain":"true","willPayload":"Offline"},{"id":"9f8a5d12.6f72b","type":"ui_group","z":"a5dcc8f6.5a2338","name":"Hall","tab":"2bb8ea8.6805016","disp":true,"width":"18"},{"id":"2bb8ea8.6805016","type":"ui_tab","z":"a5dcc8f6.5a2338","name":"LIGHT","icon":"stars","order":6}]
Sorry, if you import that flow, don't forget to tidy up the MQTT configuration it creates.
I use max/min threshold values from a global variable.