On off switch loop

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