Timer with User Interface control/feedback

The timer is based on Schedex node and applicable for following purposes:

  • Automated adjustable watering/lighting/pumping.
  • Timer-controlled watering/lighting/pumping.

Dark User Interface example Light User Interface example

Features:

  • programmable automated timer (Schedex ) which can be:
  • Enabled or disabled from UI;
  • Or overridden with switch;
  • Active time can be adjusted from UI;
  • Or enable load for timeout defined in UI with countdown feedback;
  • logging, including persistent graphs;
  • Control each circuit separately;
  • Show the status of each circuit on dashboard;
  • Keep/restore important settings and status.

Easy to add:

  • MQTT posting.
  • Local IO control.
  • Active time correction by actual measurements (for instance, adding PID loop for watering correction).
The flow

The valve/switch control script is here.

To multiply control circuits:

  • 1 - Set your schedule in Schedex Timer.
  • 2 - Set persist storage to store and restore last command.
  • 3 - Tune/replace Exec node with your actuator.
  • 4 - Rename UI switch.
  • 5 - In parse_timeout function set the same name to flow.get() as in UI_to_flow_context flow.set().
  • 6 - Connect output of switch2timer function to Schedex timer of each circuit.

It is possible to set separated systems: one for watering, one for pumping and one for lighting. Edit the flow.set/get variable name in UI_to_flow_context and in parse_timeout functions.

Credits: Thank you people helped in the Node-RED forum

[{"id":"ee00a260.25be4","type":"tab","label":"2share","disabled":false,"info":""},{"id":"bb9aa72f.f142b8","type":"debug","z":"ee00a260.25be4","name":"watering","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"payload","x":770,"y":300,"wires":[]},{"id":"3c42dbd2.1e05b4","type":"ui_switch","z":"ee00a260.25be4","name":"","label":"Water Lawn","group":"97ade2e4.08d3a","order":2,"width":"5","height":"1","passthru":false,"decouple":"true","topic":"shm/garden/water_lawn/state","style":"","onvalue":"on","onvalueType":"str","onicon":"local_florist","oncolor":"green","offvalue":"off","offvalueType":"str","officon":"local_florist","offcolor":"grey","x":130,"y":520,"wires":[["8a84b5dd.ae4e58"]]},{"id":"6441aba8.d2b1b4","type":"ui_numeric","z":"ee00a260.25be4","name":"","label":"Adjustment, %","group":"97ade2e4.08d3a","order":3,"width":0,"height":0,"passthru":true,"topic":"adjustment","format":"{{value}}","min":0,"max":"300","step":"10","x":260,"y":180,"wires":[["11bb8145.de1bcf","3e2ee852.8125c8"]]},{"id":"6fa30bc9.05bbe4","type":"ui_switch","z":"ee00a260.25be4","name":"enable_water","label":"Enable Watering","group":"97ade2e4.08d3a","order":1,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"shm/garden/water/enable","style":"","onvalue":"on","onvalueType":"str","onicon":"","oncolor":"","offvalue":"off","offvalueType":"str","officon":"","offcolor":"","x":120,"y":280,"wires":[["488908dd.205d88"]]},{"id":"f31149a4.c48a68","type":"ui_text","z":"ee00a260.25be4","group":"97ade2e4.08d3a","order":4,"width":"1","height":"1","name":"counter","label":"","format":"{{msg.payload}}","layout":"row-right","x":360,"y":560,"wires":[]},{"id":"c3fda280.7177b","type":"trigger","z":"ee00a260.25be4","op1":"","op2":"","op1type":"nul","op2type":"pay","duration":"1","extend":false,"units":"min","reset":"","bytopic":"all","name":"","x":140,"y":640,"wires":[["8a84b5dd.ae4e58"]]},{"id":"fd6e73a0.acea9","type":"inject","z":"ee00a260.25be4","name":"water_sch_on","topic":"shm/garden/water/enable","payload":"on","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":100,"y":240,"wires":[["6fa30bc9.05bbe4"]]},{"id":"72ae558b.45668c","type":"exec","z":"ee00a260.25be4","command":"actuators/valve.py 1 ","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":760,"y":520,"wires":[["bb9aa72f.f142b8"],["697816e4.cf9db8"],[]]},{"id":"bc7c106c.5fdf3","type":"inject","z":"ee00a260.25be4","name":"init_off","topic":"","payload":"off","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"0.05","x":370,"y":320,"wires":[["72ae558b.45668c"]]},{"id":"cfe6eb07.872248","type":"persist out","z":"ee00a260.25be4","name":"watering_cor","storageNode":"2c5b61a9.137b0e","x":90,"y":180,"wires":[["6441aba8.d2b1b4"]]},{"id":"11bb8145.de1bcf","type":"persist in","z":"ee00a260.25be4","name":"watering_cor","storageNode":"2c5b61a9.137b0e","x":450,"y":220,"wires":[]},{"id":"3e2ee852.8125c8","type":"trigger","z":"ee00a260.25be4","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"2","extend":true,"units":"min","reset":"","bytopic":"all","name":"delay 2m","x":440,"y":180,"wires":[["3bfb0b7c.5ef504"]]},{"id":"d3f2a255.22a5c","type":"comment","z":"ee00a260.25be4","name":"____________ Watering system description ____________","info":"3 circuits: Lawn, Tree, Stone.\nControls:\n- Enable/disable scheduling.\n- Adjust irrigation time in %.\n- Control each circuit separately with timer or manually.\n\nShow the status of each circuit on dashboard.\nShow watering log in dashboard.\nKeep/restore watering adjustment and watering chart.\n\n100% time and days per circuit:\nLawn - 50 min - Mon, Wed, Fri.\nTrees - 70 min - Tue, Sat.\nStone - 60 min - Thu, Sun.\n\nTODO:\n1 - Revise flow.set() variable name in UI_to_flow_context.\n2 - Adapt number of outputs in calc_off_offset function to your needs.\n","x":450,"y":40,"wires":[]},{"id":"488908dd.205d88","type":"change","z":"ee00a260.25be4","name":"switch2timer","rules":[{"t":"change","p":"payload","pt":"msg","from":"off","fromt":"str","to":"suspended true","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"on","fromt":"str","to":"suspended false","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":150,"y":320,"wires":[["4ff10be1.cd3d94"]]},{"id":"bf519293.47cdb","type":"ui_slider","z":"ee00a260.25be4","name":"timeout","label":"Stop in {{value}} min","group":"97ade2e4.08d3a","order":5,"width":0,"height":0,"passthru":true,"topic":"/shm/garden/timeout","min":0,"max":"90","step":1,"x":340,"y":140,"wires":[["cb50e867.521618"]]},{"id":"cb50e867.521618","type":"function","z":"ee00a260.25be4","name":"UI_to_flow_context","func":"flow.set(\"get_water_timeout\", msg.payload);\nreturn null;","outputs":1,"noerr":0,"x":630,"y":140,"wires":[[]]},{"id":"a63bdebb.14d08","type":"inject","z":"ee00a260.25be4","name":"","topic":"","payload":"10","payloadType":"num","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":110,"y":140,"wires":[["bf519293.47cdb"]]},{"id":"e32111f3.5f18c","type":"trigger","z":"ee00a260.25be4","op1":"","op2":"","op1type":"nul","op2type":"pay","duration":"2","extend":false,"units":"s","reset":"","bytopic":"all","name":"2sec delay","x":550,"y":560,"wires":[["2363bd12.6c9232","72ae558b.45668c","3c42dbd2.1e05b4","b902888f.45f258"]]},{"id":"4ff10be1.cd3d94","type":"schedex","z":"ee00a260.25be4","name":"LawnTimer","suspended":false,"lat":"32","lon":"34","ontime":"goldenHourEnd","ontopic":"shm/garden/water_lawn/state","onpayload":"on","onoffset":"-20","onrandomoffset":0,"offtime":"goldenHourEnd","offtopic":"shm/garden/water_lawn/state","offpayload":"off","offoffset":"30","offrandomoffset":false,"mon":true,"tue":false,"wed":true,"thu":false,"fri":true,"sat":false,"sun":false,"x":360,"y":600,"wires":[["e32111f3.5f18c"]]},{"id":"8a84b5dd.ae4e58","type":"function","z":"ee00a260.25be4","name":"parse_timeout","func":"// Outputs:\n// Counter_to_display, GPIO control via BigTimer, Counter_to_process\n\n// This beautiful expression doesn't work, gets default 10 always.\n// var delay = typeof(context.get('get_water_timeout') == undefined) ? 10 : flow.get('get_water_timeout');\n// This ugly construction works. :(\ntry{\n    var delay = flow.get('get_water_timeout');\n} catch (er) {\n    var delay = 10;\n}\n\nvar state = context.get('state') || 'ignore';\nvar topic = context.get('topic') || msg.topic;\ncontext.set('topic', topic);\nnode.status({fill:\"yellow\",shape:\"ring\",text: delay+','+state+','+msg.payload});\n\n// Block loop of messages when off\nif ('off' == msg.payload && 'off' == state) return [null, null, null];\nnode.status({fill:\"yellow\",shape:\"dot\",text: delay+','+state+','+msg.payload});\n\n// Keep msg for next round\nif ('on' == msg.payload) {\n    // Don't count/display if fully manual\n    var m1 = (delay > 0) ? {payload: delay} : null; \n    // Set delay, start countdown, on load\n    context.set('state', 'on');\n    node.status({fill:\"green\", shape:\"dot\", text: delay+','+context.get('state')+','+msg.payload});\n    return [m1, msg, m1];  \n} else if (('off' == msg.payload) || (parseInt(msg.payload) <= 1)) {\n    // off load, reset clear counter\n    context.set('state', 'off');\n    node.status({fill:\"red\",shape:\"dot\",text: delay+','+context.get('state')+','+msg.payload});\n    return [{payload: ' '}, {payload: 'off'}, null];\n} else if (parseInt(msg.payload) > 1 && context.get('state') == 'on') {\n    // decrement countdown\n    var m1 = {payload: parseInt(msg.payload)-1};\n    node.status({fill:\"blue\",shape:\"dot\",text: delay+'>'+m1.payload+','+state+','+msg.payload});\n    return [m1, null, m1];\n}\n\n// Unexpected state: look for a bug in reached here.\nnode.status({fill:\"grey\",shape:\"ring\",text: delay+','+state+','+msg.payload+'???'});\nreturn [{payload: ' '}, {payload: 'off'}, null];\n","outputs":3,"noerr":0,"x":130,"y":580,"wires":[["f31149a4.c48a68"],["4ff10be1.cd3d94","3c42dbd2.1e05b4"],["c3fda280.7177b"]]},{"id":"298bc26f.bd320e","type":"persist out","z":"ee00a260.25be4","name":"cmd_garden_lawn","storageNode":"fe86bf2f.158b3","x":390,"y":520,"wires":[["e32111f3.5f18c"]]},{"id":"2363bd12.6c9232","type":"persist in","z":"ee00a260.25be4","name":"cmd_garden_lawn","storageNode":"fe86bf2f.158b3","x":750,"y":560,"wires":[]},{"id":"3bfb0b7c.5ef504","type":"function","z":"ee00a260.25be4","name":"calc_off_offset","func":"/*\nPer each watering circuit in dictionary:\nCalculate new offtime from input payload.\n*/\n\nconst MAP = [  // Circuit: total time\n    50, // 'LawnTimer':\n    70, // 'TreeTimer': \n    60, // 'StoneTimer': \n];\nvar res = [];\n\nfor (const value of MAP) {\n    res.push({payload: 'offoffset ' + Math.round(value * parseInt(msg.payload) / 100 - 20)});\n}\n\nreturn res;","outputs":3,"noerr":0,"x":620,"y":180,"wires":[["4ff10be1.cd3d94"],[],[]]},{"id":"7dd7b92d.8645b8","type":"comment","z":"ee00a260.25be4","name":"__________  Circuit specific nodes: duplicate these per circuit  __________","info":"TO DO:\n1 - Set your schedule in Schedex Timer.\n2 - Set persist storage to store and restore last command.\n3 - Tune/replace Exec node with your actuator.\n4 - Rename UI switch.\n5 - In parse_timeout function set the same name to flow.get() as in UI_to_flow_context flow.set().\n6 - Connect output of switch2timer function to Schedex timer of each circuit.\n","x":460,"y":480,"wires":[]},{"id":"fe5728ae.eb12f8","type":"comment","z":"ee00a260.25be4","name":"Connect init_off to each actuator in your scheme","info":"","x":420,"y":280,"wires":[]},{"id":"60e2ab39.9b5004","type":"comment","z":"ee00a260.25be4","name":"___________________ Common part for all circuits ___________________","info":"","x":450,"y":100,"wires":[]},{"id":"71a6bee0.2a10d","type":"comment","z":"ee00a260.25be4","name":"Logging","info":"","x":790,"y":220,"wires":[]},{"id":"697816e4.cf9db8","type":"mqtt out","z":"ee00a260.25be4","name":"","topic":"","qos":"","retain":"","broker":"af8eb750.bd7d38","x":770,"y":340,"wires":[]},{"id":"db907094.b56ba","type":"ui_chart","z":"ee00a260.25be4","name":"","group":"97ade2e4.08d3a","order":6,"width":"6","height":"3","label":"Watering Log","chartType":"line","legend":"true","xformat":"dd HH:mm","interpolate":"step","nodata":"No data","dot":false,"ymin":"0","ymax":"1","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"604800","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":550,"y":400,"wires":[["a50ff488.45b088"],[]]},{"id":"b902888f.45f258","type":"function","z":"ee00a260.25be4","name":"msg2digit","func":"var old = msg.payload;\nnode.status({text: old + '???'});\nif (msg.payload == null) return;\n\nif (msg.payload === true || \n    msg.payload.toLowerCase() == 'open' || \n    msg.payload.toLowerCase() == 'on' ||\n    parseInt(msg.payload) > 0) { msg.payload = 1 }\n    else if (msg.payload === false || \n        msg.payload.toLowerCase() == 'close' || \n        msg.payload.toLowerCase() == 'off' ||\n        parseInt(msg.payload) <= 0) { msg.payload = 0 }\n        else return;\n\nnode.status({text: old + \">>\" + msg.payload});\n\ntry{\n    // Get 2nd word from the end of topic\n    msg.topic = msg.topic.split('/').slice(-2)[0].trim();\n} catch (er) {\n    node.status({text: 'Topic error'});\n    return null;\n}\nnode.status({text: msg.topic + '>>' + old + \",\" + msg.payload});\n\nreturn msg;","outputs":1,"noerr":0,"x":240,"y":400,"wires":[["db907094.b56ba"]]},{"id":"a50ff488.45b088","type":"persist in","z":"ee00a260.25be4","name":"watering","storageNode":"5a252273.7c18ac","x":700,"y":400,"wires":[]},{"id":"e27e688e.50e1c8","type":"persist out","z":"ee00a260.25be4","name":"watering","storageNode":"5a252273.7c18ac","x":400,"y":420,"wires":[["db907094.b56ba"]]},{"id":"c42a5fb9.33de2","type":"comment","z":"ee00a260.25be4","name":"Connect here outputs from all actuators","info":"","x":790,"y":260,"wires":[]},{"id":"35e88818.b547e8","type":"comment","z":"ee00a260.25be4","name":"Log visualization","info":"","x":400,"y":380,"wires":[]},{"id":"97ade2e4.08d3a","type":"ui_group","z":"","name":"Garden: Watering","tab":"11bccf83.c6223","order":1,"disp":true,"width":"6","collapse":false},{"id":"2c5b61a9.137b0e","type":"persist-store","z":"","filename":"watering_cor.json","interval":"3600"},{"id":"fe86bf2f.158b3","type":"persist-store","z":"","filename":"cmd_garden_lawn.json","interval":"600"},{"id":"af8eb750.bd7d38","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"5a252273.7c18ac","type":"persist-store","z":"","filename":"watering.json","interval":"3600"},{"id":"11bccf83.c6223","type":"ui_tab","z":"","name":"2share","icon":"home","order":1}]

Flow Info

Created 6 years, 3 months ago
Updated 5 years, 3 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • change (x1)
  • comment (x7)
  • debug (x1)
  • exec (x1)
  • function (x4)
  • inject (x3)
  • mqtt out (x1)
  • mqtt-broker (x1)
  • trigger (x3)
Other

Tags

  • timer
  • schedule
  • water
  • light
  • pump
  • adjustment
  • correction
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option