I was asked the other day about how to create a Finite State Machine (FSM) with different delays between various transitions. Here is a simple example, based on the UK traffic light sequence, that makes use of the msg.delay option in the Delay node.
Here's a screenshot of the flow.
[{"id":"be3ab1ef.3f0a68","type":"tab","label":"Traffic Lights with variable delay","disabled":false,"info":"","env":[]},{"id":"743e732.c12228c","type":"function","z":"be3ab1ef.3f0a68","name":"Decode RED light","func":"var fsm_state = flow.get(\"state_counter\");\n\nif (fsm_state === 0 || fsm_state == 1)\n {msg.payload = 1;\n node.status({fill:\"red\",shape:\"dot\",text:\"Red ON\"});\n }\n \nelse\n {msg.payload = 0;\n node.status({});\n }\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1010,"y":120,"wires":[[]]},{"id":"778d9f53.8e5f7","type":"function","z":"be3ab1ef.3f0a68","name":"Decode YELLOW light","func":"var fsm_state = flow.get(\"state_counter\");\n\nif (fsm_state == 1 || fsm_state == 3)\n {msg.payload = 1;\n node.status({fill:\"yellow\",shape:\"dot\",text:\"Yellow ON\"});\n }\nelse\n {msg.payload = 0;\n node.status({});}\n\nreturn msg;","outputs":1,"noerr":0,"x":1020,"y":180,"wires":[[]]},{"id":"c8a94827.acb49","type":"function","z":"be3ab1ef.3f0a68","name":"Decode GREEN light","func":"var fsm_state = flow.get(\"state_counter\");\n\nif (fsm_state == 2)\n {msg.payload = 1;\n node.status({fill:\"green\",shape:\"dot\",text:\"Green ON\"});\n }\nelse\n {msg.payload = 0;\n node.status({});\n }\n\nreturn msg;","outputs":1,"noerr":0,"x":1020,"y":240,"wires":[[]]},{"id":"28cc6bcb.920094","type":"function","z":"be3ab1ef.3f0a68","name":"Finite State Machine","func":"// Change the values of delay to suit your application\n\n\n// Here is the classical way of coding a state machine.\n// It uses a 'case construct' to check the current state and then set the next state.\n\nvar fsm_state = flow.get(\"state_counter\") || 0;\n\nvar defaultDelay = flow.get(\"defaultDelay\");\n\nswitch (fsm_state)\n {\n case 0:\n fsm_state = 1; // Next state\n node.send( {payload:fsm_state, delay:5000}); // delay is in milliseconds\n break;\n \n case 1:\n fsm_state = 2;\n node.send( {payload:fsm_state, delay:defaultDelay});\n break;\n \n case 2:\n fsm_state = 3;\n node.send( {payload:fsm_state, delay:defaultDelay});\n break;\n \n case 3:\n fsm_state = 0;\n node.send( {payload:fsm_state, delay:defaultDelay});\n break;\n }\n \nflow.set(\"state_counter\", fsm_state);\n\nnode.status({text:\"State counter = \" + fsm_state});\n\nreturn null;","outputs":"1","timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":180,"wires":[["743e732.c12228c","778d9f53.8e5f7","c8a94827.acb49","82ab245c.6aa488"]]},{"id":"77e8a5e.82598dc","type":"comment","z":"be3ab1ef.3f0a68","name":"Place specific delays in here","info":"","x":720,"y":140,"wires":[]},{"id":"634074c5.d267d4","type":"inject","z":"be3ab1ef.3f0a68","name":"Toggle button","props":[{"p":"payload"},{"p":"delay","v":"1000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":170,"y":180,"wires":[["ee02a850.8eee8","82ab245c.6aa488"]]},{"id":"ee02a850.8eee8","type":"function","z":"be3ab1ef.3f0a68","name":"Status indicator","func":"var status = flow.get(\"status\") || \"stopped\";\n\nif (status == \"stopped\") {\n flow.set(\"status\", \"running\");\n node.status({text:\"State = running\"});\n}\nelse {\n flow.set(\"status\", \"stopped\");\n node.status({text:\"State = STOPPED\"});\n}\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":380,"y":300,"wires":[[]]},{"id":"3361daf1.f94576","type":"function","z":"be3ab1ef.3f0a68","name":"Indicator and Gate","func":"var status = flow.get(\"status\") || \"stopped\";\nif (status == \"running\") {\n node.status({text:\"State = running\"});\n return msg;\n}\nelse {\n node.status({text:\"State = STOPPED\"});\n return null;\n}\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":180,"wires":[["28cc6bcb.920094"]]},{"id":"82ab245c.6aa488","type":"delay","z":"be3ab1ef.3f0a68","name":"","pauseType":"delayv","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":360,"y":180,"wires":[["3361daf1.f94576"]]},{"id":"98ee2ab4.5b3e38","type":"comment","z":"be3ab1ef.3f0a68","name":"Override delay with msg.delay","info":"","x":420,"y":240,"wires":[]},{"id":"203dae68.e2be8a","type":"inject","z":"be3ab1ef.3f0a68","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":150,"y":100,"wires":[["fd179166.5703b"]]},{"id":"fd179166.5703b","type":"function","z":"be3ab1ef.3f0a68","name":"Set default delay to 1 second","func":"flow.set(\"defaultDelay\", 1000);\n\nreturn null;","outputs":0,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":100,"wires":[]},{"id":"a373a8b72097daad","type":"comment","z":"be3ab1ef.3f0a68","name":"Connect to MQTT-Out node or GPIO pin","info":"","x":1300,"y":120,"wires":[]},{"id":"5732ad4983cbe4dd","type":"comment","z":"be3ab1ef.3f0a68","name":"Start/Stop button","info":"","x":160,"y":220,"wires":[]},{"id":"8d023d6015ef7f2d","type":"comment","z":"be3ab1ef.3f0a68","name":"Connect to MQTT-Out node or GPIO pin","info":"","x":1300,"y":180,"wires":[]},{"id":"cc463dec8a1978aa","type":"comment","z":"be3ab1ef.3f0a68","name":"Connect to MQTT-Out node or GPIO pin","info":"","x":1300,"y":240,"wires":[]}]
The 'case construct' is the place where you can define the delay for each transition.
In this example, I have set the delay from state-0 to state-1 as 5 secs, while the delay from state-1 to state-2 is using the defaultDelay (set in the start-up to 1 sec).
This is the setting needed in the Delay node to override the normal delay.
I hope someone finds this useful?