On off switch loop

Hello.
Total noob here.
I want to make an automation for a fogger.
-1 slider for on in hours minutes seconds
-2 slider for off in hours minutes seconds

i have managed to do one with no UI using trigger and delay node.But is not based on UI input.
But i want to be able to change the timing without change the code by hand.
Can someone help me with this task.

Maybe you could use this new node [ANNOUNCE] node-red-contrib-ui-countdown-timer-switch

Thank you.
I'll take look but seems like this one can't run (loop) forever.
Also just find out that my current flow stops after 24 h :frowning:

(It would help if you did mention that in the original question.)

You could try this flow:

[{"id":"d3399d9e.0ff088","type":"subflow","name":"timers","info":"This subflow/node facilitates the setting\nof multiple timers in an easy manner.\n\n# Input Message to set timer\n\nThe subflow expects an input `msg` which\ncontains the following to set a timer:\n\n+ a `msg.payload` which will be send once the timer has finished\n+ a `msg.duration` either in the format of a number in seconds or a string in the \"hh:mm:ss\" format\n+ an optional `msg.topic` which will also be send on completion\n\n# Other Control Messages\n\nThere are also a number of other control\nmessages which the subflow accept:\n\n+ a `msg.payload` of `reset` will reset all running timers\n+ a `msg.payload` of `debug` will send an array of all set timers to the secon debug output.\n+ a `msg` with a duration in seconds as a number or a string in the hh:mm:ss format with `msg.delete` set to true will delete the first set timer with a matching duration\n\n# Configuration Settings\n\nThe subflow although offers several menu options which can be adjusted:\n\n+ there is a setting which determines if a new timer should be appended to the list of existing timers and run in parallel or if every new timer should replace the previous\n+ a setting for the output format of the duration and the time left to-go which affects both the status display and the debug messages\n+ a setting to enable/disable the progress status being shown on the node status\n+ a setting which when enabled sends a debug message every second to the second output\n\n# Debug Message Format\nThe node sends a debug message to its second output on several events or in addition every second if configured. Those events are:\n\n+ When a new timer is set\n+ When a timer completes\n+ When a timer is deleted\n+ When the timers are reset\n\nThe format of this message is an array which\nincludes one object for every timer running\nthat includes useful information about each\ntimer in this format:\n```\n{\n    \"payload\":\"ON\",\n    \"duration\":\"00:00:25\",\n    \"due\":1607330155654,\n    \"toGo\":\"00:00:25\",\n    \"topic\":\"Test\"\n}\n```\nthe `toGo` and `duration` properties will be in the format configured in the nodes settings.","category":"","in":[{"x":100,"y":140,"wires":[{"id":"fe62abda.f8f7a"}]}],"out":[{"x":840,"y":140,"wires":[{"id":"c50c6881.b2fdf","port":0}]},{"x":840,"y":200,"wires":[{"id":"12c839f6.01eed6","port":0},{"id":"595b735c.08ff54","port":1}]}],"env":[{"name":"on_input","type":"str","value":"append","ui":{"label":{"en-US":"On new input"},"type":"select","opts":{"opts":[{"l":{"en-US":"append to list of current timers"},"v":"append"},{"l":{"en-US":"replace current timer"},"v":"replace"}]}}},{"name":"time_format","type":"str","value":"hhmmss","ui":{"label":{"en-US":"Time format of output/status"},"type":"select","opts":{"opts":[{"l":{"en-US":"hh:mm:ss"},"v":"hhmmss"},{"l":{"en-US":"seconds"},"v":"seconds"}]}}},{"name":"show_progress","type":"bool","value":"true","ui":{"label":{"en-US":"show progress in status"},"type":"checkbox"}},{"name":"send_progress","type":"bool","value":"false","ui":{"label":{"en-US":"send progress to second output"},"type":"checkbox"}}],"color":"#FDF0C2","icon":"node-red/timer.svg","status":{"x":840,"y":80,"wires":[{"id":"415b9d3f.6e5754","port":0}]}},{"id":"fe326ef6.0184b","type":"function","z":"d3399d9e.0ff088","name":"tick","func":"const timers = flow.get(\"timers\") || [];\nif (msg.delete === true || timers.length > 0) {\n    return msg;\n}\nconst start = Date.now();\nconst startCorrected = (Math.ceil(start / 1000)) * 1000;\nfunction initial() {\n    initialTimeout = startCorrected - start;\n    setTimeout(()=>{\n        tick(1000);\n        node.send({payload:\"tick\"});\n    },initialTimeout);\n}\nfunction tick(timeout) {\n    setTimeout(()=>{\n        if (flow.get(\"timers\").length > 0) {\n            const newStart = Date.now();\n            const offset = (newStart-startCorrected) % 10;\n            const newTimeout = (offset > 0) ? 1000-(offset) : 1000;\n            tick(newTimeout);\n            node.send({payload:\"tick\"});\n        }\n    },timeout)\n}\ninitial();\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":550,"y":140,"wires":[["c50c6881.b2fdf","415b9d3f.6e5754"]]},{"id":"c50c6881.b2fdf","type":"function","z":"d3399d9e.0ff088","name":"check timers","func":"const now = Date.now();\nlet timers = flow.get(\"timers\") || [];\nif (msg.payload === \"tick\") {\n    if (timers.length === 0) { return null; } \n    let done = [];\n    timers.forEach(timer => {\n        if (timer.due < now) {\n            let outputMsg = {};\n            outputMsg.payload = timer.payload;\n            if (timer.hasOwnProperty(\"topic\")) { outputMsg.topic = timer.topic }\n            node.send([outputMsg, null]);\n            done.push(timer);\n        }\n    });\n    if (done.length !== 0) {\n        done.forEach(item => {\n            let toDelete = timers.indexOf(item);\n            timers.splice(toDelete, 1);\n        });\n        node.send([null,{payload:timers}]);\n    }\n    timers.forEach((timer, index) => {\n        timers[index].toGo = Math.round((timer.due - now) / 1000);\n    });\n    if (env.get(\"send_progress\") === true && done.length === 0 ) {\n        node.send([null,{payload:timers}]);\n    }\n} else if (msg.delete) {\n    for(i=0;i<timers.length;i++) {\n        if (timers[i].duration === msg.payload) {\n            timers.splice(i, 1);\n            node.send([null,{payload:timers}]);\n            break;\n        }\n    }\n} else {\n    let newTimer = {\n        payload: msg.payload,\n        duration: msg.duration,\n        due: now + (msg.duration * 1000),\n        toGo: msg.duration\n    };\n    if (msg.hasOwnProperty(\"topic\")) { newTimer.topic = msg.topic }\n    if (env.get(\"on_input\") === \"append\") {\n        timers.push(newTimer);\n    } else {\n        timers = [newTimer];\n    }\n    node.send([null,{payload:timers}]);\n}\nflow.set(\"timers\",timers);\nreturn null;","outputs":2,"noerr":0,"initialize":"","finalize":"","x":710,"y":140,"wires":[[],["12c839f6.01eed6"]]},{"id":"415b9d3f.6e5754","type":"function","z":"d3399d9e.0ff088","name":"set status","func":"function convertFormat(input){\n    let hoursRaw = input / 3600;\n    hoursRaw = hoursRaw.toString().split(\".\");\n    let hours = hoursRaw[0];\n    let minutesRaw = (input / 60) - (Number(hours) * 60);\n    minutesRaw = minutesRaw.toString().split(\".\");\n    let minutes = minutesRaw[0];\n    let seconds = (input - (Number(hours) * 3600) - (Number(minutes) * 60)).toString();\n    if (hours.length === 1) { hours = \"0\" + hours; }\n    if (minutes.length === 1) { minutes = \"0\" + minutes; }\n    if (seconds.length === 1) { seconds = \"0\" + seconds; }\n    const output = `${hours}:${minutes}:${seconds}`;\n    return output;\n}\nconst timers = flow.get(\"timers\") || [];\nif (env.get(\"show_progress\") === true) {\n    if (timers.length !== 0) {\n        const now = Date.now();\n        let newPayload = \"\";\n        timers.forEach(timer => {\n            let toGo = Math.round((timer.due - now) / 1000);\n            let addPayload = \"\";\n            if (env.get(\"time_format\") === \"seconds\") {\n                addPayload = `${toGo}s of ${timer.duration}s to go`;\n            } else {\n                let newDuration = convertFormat(timer.duration);\n                let newToGo = convertFormat(toGo);\n                addPayload = `${newToGo} of ${newDuration} to go`;\n            }\n            if (newPayload.length !== 0) { newPayload += \", \"; }\n            newPayload += addPayload;\n        });\n        node.send({payload:newPayload});\n    } else {\n        node.send({payload:\"no timer\"});\n    }\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":700,"y":80,"wires":[[]]},{"id":"fe62abda.f8f7a","type":"switch","z":"d3399d9e.0ff088","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"reset","vt":"str"},{"t":"eq","v":"debug","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":210,"y":140,"wires":[["1e5213c7.d9c46c"],["1461b44c.50eae4"],["595b735c.08ff54"]]},{"id":"1e5213c7.d9c46c","type":"change","z":"d3399d9e.0ff088","name":"reset","rules":[{"t":"set","p":"timers","pt":"flow","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":80,"wires":[["415b9d3f.6e5754","1461b44c.50eae4"]]},{"id":"595b735c.08ff54","type":"function","z":"d3399d9e.0ff088","name":"error checking","func":"let newMsg = {};\nif (typeof msg.duration === \"string\" && msg.duration.match(/[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}/g)) {\n    msg.duration = msg.duration.split(\":\");\n    msg.duration = (Number(msg.duration[0]) * 3600) + (Number(msg.duration[1]) * 60) + Number(msg.duration[2]);\n}\nif (msg.delete === true && typeof msg.payload === \"string\" && msg.payload.match(/[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}/g)) {\n    msg.payload = msg.payload.split(\":\");\n    msg.payload = (Number(msg.payload[0]) * 3600) + (Number(msg.payload[1]) * 60) + Number(msg.payload[2]);\n}\nif (msg.delete !== true) {\n    if (msg.duration === undefined || typeof msg.duration !== \"number\" || msg.duration <= 0) {\n        newMsg.payload = \"input message needs a msg.duration property which is either of type number or a string in the hh:mm:ss second format\";\n        node.warn(newMsg.payload);\n        return [null, newMsg];\n    } else if (msg.payload === undefined) {\n        newMsg.payload = \"msg.payload needs to be defined\";\n        node.warn(newMsg.payload);\n        return [null, newMsg];\n    }\n} else if (typeof msg.payload !== \"number\" || msg.payload <= 0) {\n    newMsg.payload - \"if msg.delete is true msg.payload needs to be of type number and coresponding to the duration of the timer to be deleted\";\n    node.warn(newMsg.payload);\n    return [null, newMsg];\n}\nreturn [msg, null];","outputs":2,"noerr":0,"initialize":"","finalize":"","x":380,"y":140,"wires":[["fe326ef6.0184b"],[]]},{"id":"12c839f6.01eed6","type":"function","z":"d3399d9e.0ff088","name":"format output","func":"function convertFormat(input){\n    let hoursRaw = input / 3600;\n    hoursRaw = hoursRaw.toString().split(\".\");\n    let hours = hoursRaw[0];\n    let minutesRaw = (input / 60) - (Number(hours) * 60);\n    minutesRaw = minutesRaw.toString().split(\".\");\n    let minutes = minutesRaw[0];\n    let seconds = (input - (Number(hours) * 3600) - (Number(minutes) * 60)).toString();\n    if (hours.length === 1) { hours = \"0\" + hours; }\n    if (minutes.length === 1) { minutes = \"0\" + minutes; }\n    if (seconds.length === 1) { seconds = \"0\" + seconds; }\n    const output = `${hours}:${minutes}:${seconds}`;\n    return output;\n}\nif(env.get(\"time_format\") === \"hhmmss\") {\n    let newMsg = RED.util.cloneMessage(msg);\n    newMsg.payload.forEach(timer => {\n        timer.duration = convertFormat(timer.duration);\n        timer.toGo = convertFormat(timer.toGo);\n    });\n    return newMsg;\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":700,"y":200,"wires":[[]]},{"id":"8370617b.6272a","type":"inject","z":"d3399d9e.0ff088","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":80,"wires":[["1e5213c7.d9c46c"]]},{"id":"1461b44c.50eae4","type":"change","z":"d3399d9e.0ff088","name":"timers","rules":[{"t":"set","p":"payload","pt":"msg","to":"timers","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":200,"wires":[["12c839f6.01eed6"]]},{"id":"3d3c122c.20f306","type":"subflow:d3399d9e.0ff088","z":"ba5b6f93.bba11","name":"","env":[{"name":"on_input","value":"replace","type":"str"},{"name":"send_progress","type":"bool","value":"true"}],"x":650,"y":400,"wires":[["4f797877.32172"],["b3ad6bcf.eb652"]]},{"id":"55b1fed.567d3","type":"ui_form","z":"ba5b6f93.bba11","name":"","label":"","group":"5b1b349e.248d7c","order":1,"width":0,"height":0,"options":[{"label":"Interval 1","value":"inter1","type":"text","required":true,"rows":null},{"label":"Interval 2","value":"inter2","type":"text","required":true,"rows":null},{"label":"First State","value":"first","type":"text","required":true,"rows":null},{"label":"Second State","value":"second","type":"text","required":true,"rows":null}],"formValue":{"inter1":"","inter2":"","first":"","second":""},"payload":"","submit":"Start","cancel":"","topic":"","x":270,"y":400,"wires":[["c9a169c1.2797a8"]]},{"id":"c9a169c1.2797a8","type":"change","z":"ba5b6f93.bba11","name":"","rules":[{"t":"set","p":"duration","pt":"msg","to":"payload.inter1","tot":"msg"},{"t":"set","p":"payload.next","pt":"msg","to":"inter2","tot":"str"},{"t":"set","p":"state","pt":"msg","to":"payload.first","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":400,"wires":[["3d3c122c.20f306","4385be53.09c9b8"]]},{"id":"891a724f.d13d58","type":"ui_text","z":"ba5b6f93.bba11","group":"5b1b349e.248d7c","order":2,"width":0,"height":0,"name":"","label":"current","format":"{{msg.payload}}","layout":"col-center","x":1000,"y":460,"wires":[]},{"id":"b3ad6bcf.eb652","type":"change","z":"ba5b6f93.bba11","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$exists(msg.payload[0]) ? msg.payload[0].toGo & \" of \" & msg.payload[0].duration & \" left\" : \"not running\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":820,"y":460,"wires":[["891a724f.d13d58"]]},{"id":"4f797877.32172","type":"change","z":"ba5b6f93.bba11","name":"","rules":[{"t":"set","p":"state","pt":"msg","to":"msg.payload.next = \"inter2\" ? msg.payload.second : msg.payload.first","tot":"jsonata"},{"t":"set","p":"duration","pt":"msg","to":"msg.payload.next = \"inter2\" ? msg.payload.inter2 : msg.payload.inter1\t","tot":"jsonata"},{"t":"set","p":"payload.next","pt":"msg","to":"msg.payload.next = \"inter1\" ? \"inter2\" : \"inter1\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":820,"y":400,"wires":[["3d3c122c.20f306","4385be53.09c9b8"]]},{"id":"c0071f78.de6258","type":"ui_button","z":"ba5b6f93.bba11","name":"","group":"5b1b349e.248d7c","order":4,"width":0,"height":0,"passthru":false,"label":"Stop","tooltip":"","color":"","bgcolor":"","icon":"","payload":"reset","payloadType":"str","topic":"","x":490,"y":460,"wires":[["3d3c122c.20f306"]]},{"id":"4385be53.09c9b8","type":"ui_text","z":"ba5b6f93.bba11","group":"5b1b349e.248d7c","order":3,"width":0,"height":0,"name":"","label":"state","format":"{{msg.state}}","layout":"col-center","x":730,"y":340,"wires":[]},{"id":"5b1b349e.248d7c","type":"ui_group","name":"Interval Loop","tab":"93e51f49.a58cd8","order":3,"disp":true,"width":"6","collapse":false},{"id":"93e51f49.a58cd8","type":"ui_tab","name":"Übersicht","icon":"info","order":1,"disabled":false,"hidden":false}]

It uses a subflow I build a while ago that allows for multiple timer that can send progress updates while they are running and accepts hh:mm:ss inputs. This way it’s easily combinable with dashboard nodes as in the flow above that I quickly threw together.
The flow above has two intervals and two states that can all be entered in a dashboard form. The times need to be in hh:mm:ss format and the states are strings. It enters the first state when you click start and changes to the second state after the duration of the first interval. It than goes back to that initial first state after the duration of the second interval and starts over.
This is of course a very basic example. It uses some jsonata in the change nodes for the loop logic to iterate between the two intervals.
It will run forever until you click stop.
Maybe you can adapt this to your needs or this gives you some ideas.

Johannes

I know :frowning:

Thank you very much :+1:

JGKK thanks again.
But no mater how much i try to understand how to use it i am hopeless :slight_smile:
I opened node red and home assistant 1 week ago for the first time :slight_smile:
So if any extra help for me it will be appreciated.

Have you watched the video guides Node red essentials? If not then spend an hour or so watching them, then some things may become clearer.

on point comment.
My bad that i was looking (do the need) for a shortcut :+1:

If you have some specific questions about the sample flow above I am also happy to help.

My comment wasn't meant as any sort of criticism, but rather that you may learn things that would help to get you going with node-red. By all means then come back and ask about specific things that need further clarification.