Attached is my latest traffic light FSM version. Cars have priority what makes a long pedestrian red time of 9 seconds. If a pedestrian appears in that time, he might press his request button to stop the cars prematurely. To give some immendiate feedback, the pushbutton is a illuminated model what indicates pending requests to its user.
The request button is a digital input. A preprocessing flow copies its signal to a flow context input mirrow. The FSM shows on how to poll any input signal equal to the timer signal.
[{"id":"8e2b8fd0f926c128","type":"tab","label":"Event based FSM","disabled":false,"info":"This is a state machine example for Node Red. Graphical FSM representation is done using Mermaid code what is supported up from NodeRed V3.1. To be recognized, the mermaid code needs to be included in three tilde characters followed by the keyword mermaid. This tilde character is only available at the key beside backspace and can be accessed by pressing the shift key only. \n\nTo get a good FSM design, never use composite states, nor fork and join. \n\n```mermaid\nstateDiagram-v2\n [*] --> red\n red --> red_yellow : time\n red_yellow --> green : time\n green --> yellow : time\n yellow --> red : time\n```\n","env":[]},{"id":"aacb7c81f89be079","type":"tab","label":"Blink FSM","disabled":false,"info":"","env":[]},{"id":"9fe5fb9ec0c0b8a7","type":"group","z":"8e2b8fd0f926c128","name":"Cars traffic lights output viusalisation","style":{"label":true},"nodes":["7d74c23801848b0c","ed91409803b1598b","c6f26b4859613480","c80b8337ad33fa72"],"x":934,"y":439,"w":312,"h":262},{"id":"7a1dfe750951b882","type":"group","z":"8e2b8fd0f926c128","name":"Pedestrians traffic lights output visualisation","style":{"label":true},"nodes":["f29beac28fd80910","5e75e673a6b2c1b4","d913a633711cfeb3","094fc31f60cfbbb7"],"x":934,"y":139,"w":312,"h":282},{"id":"9d1731a61c2227ee","type":"group","z":"8e2b8fd0f926c128","name":"Same than timers, all HW input signals communicate with FSM using a flow scoped context variable","style":{"label":true},"nodes":["d589e831a5039c8d","96cd8e84e48efe34","486562621d485e60"],"x":94,"y":579,"w":692,"h":82},{"id":"c447cdfa25107a3d","type":"group","z":"8e2b8fd0f926c128","name":"Auxilliary timer loop to escape NodeReds synchronous msg flow design. Normally one timer per FSM is sufficient","style":{"label":true},"nodes":["bde8bee28d5bb264","85f024e2a41bebd6","4857cb7c9b528ce5","8a9bbbc89d6f0d5d","4bc2d01449a13ffa","320f9f9bbd242a6b"],"x":94,"y":739,"w":1072,"h":82},{"id":"106b9e284a8b6d25","type":"junction","z":"aacb7c81f89be079","x":640,"y":180,"wires":[["b84fef40d0752775"]]},{"id":"7d74c23801848b0c","type":"function","z":"8e2b8fd0f926c128","g":"9fe5fb9ec0c0b8a7","name":"Cars RED light","func":"if ((msg.payload == 0) || (msg.payload == 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 }\nreturn;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1040,"y":540,"wires":[[]]},{"id":"ed91409803b1598b","type":"function","z":"8e2b8fd0f926c128","g":"9fe5fb9ec0c0b8a7","name":"Cars YELLOW light","func":"if ((msg.payload == 1) || (msg.payload == 3))\n {msg.payload = 1;\n node.status({fill:\"yellow\",shape:\"dot\",text:\"Yellow ON\"});\n }\nelse\n {msg.payload = 0;\n node.status({});}\nreturn;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1050,"y":600,"wires":[[]]},{"id":"c6f26b4859613480","type":"function","z":"8e2b8fd0f926c128","g":"9fe5fb9ec0c0b8a7","name":"Cars GREEN light","func":"if (msg.payload == 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 }\nreturn;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1050,"y":660,"wires":[[]]},{"id":"c18b4c9bd8d583b2","type":"function","z":"8e2b8fd0f926c128","name":"Traffic Lights State Machine","func":"// FSM implemented by switch / case \n\nvar pRed;\nvar pGrn;\nvar pWit;\n\nswitch (msg.payload)\n{\n case 0: // state red\n if (flow.get(\"Timer\")== 0) // wait for red time elapsed \n { pRed = {payload:\"on\"}; // switch pedestrian red off\n pGrn = {payload:\"off\"}; // switch Pedestrians Green on\n flow.set(\"Timer\", 1000); // start red_yellow time\n msg.payload = 1; // next state red_yellow \n }\n break;\n \n case 1: // state red_yellow\n if (flow.get(\"Timer\")==0) // wait for red_yellow time elapsed \n {flow.set(\"Timer\",9000) // start green time\n msg.payload = 2; // next state green\n }\n break;\n \n case 2: // state green\n if (flow.get(\"Button\") == 0) // wait for pedestrians request button\n {if (flow.get(\"Timer\")==0) // wait for green time elapsed \n {flow.set(\"Timer\", 1000); // start yellow time\n msg.payload = 3; // next state yellow\n }\n } \n else\n { pWit = {payload:\"on\"}; // confirm pedestrians request\n flow.set(\"Timer\", 1000); // start yellow time\n msg.payload = 3; // next state yellow\n }\n break;\n \n case 3: // state yellow\n if (flow.get(\"Timer\")==0) // wait for yellow time elapsed \n { pRed = {payload:\"off\"}; // switch Pedestrians Green off\n pGrn = {payload:\"on\"}; // switch Pedestrians Green on\n pWit = {payload:\"off\"}; // no more pedestrians request\n flow.set(\"Timer\",4000); // start red time\n msg.payload = 0; // next state red\n }\n break;\n\n case 4: {} // breakpoint state, does nothing\n break;\n}\n\nnode.status({text:\"State = \" + msg.payload}); // display state information\nreturn [pRed,pGrn,pWit,msg];\n","outputs":4,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":380,"wires":[["f29beac28fd80910"],["5e75e673a6b2c1b4"],["094fc31f60cfbbb7"],["7d74c23801848b0c","ed91409803b1598b","c6f26b4859613480","546deffe9af46b93"]]},{"id":"e8c94c4ffb118d09","type":"inject","z":"8e2b8fd0f926c128","name":"Pon-Reset","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0","topic":"","payload":"true","payloadType":"bool","x":190,"y":320,"wires":[["0a058b7e346b5361"]]},{"id":"0a058b7e346b5361","type":"function","z":"8e2b8fd0f926c128","name":"FSM Init","func":"var pRed;\n\npRed = {payload:\"on\"};\nmsg.payload = 0;\nreturn [pRed,msg]\n","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":320,"wires":[["f29beac28fd80910"],["c18b4c9bd8d583b2"]],"info":"Every FSM needs to be initialized at power up. This requires to set the state information what is depicted by a solid black start point in the Mermaid FSM diagram. \n\nFor the first state entry, Mealy Outputs needs to be initialized as well while Moore outputs rely on the state information iteself. Therefore this init funktion uses two outputs. One for the state machine what passes the initial state information, another for each output what passes the first event for the output signal.\n"},{"id":"f29beac28fd80910","type":"function","z":"8e2b8fd0f926c128","g":"7a1dfe750951b882","name":"Pedestrians RED","func":"if (msg.payload ===\"on\")\n {msg.payload = 1;\n node.status({fill:\"red\",shape:\"dot\",text:\"Red ON\"});\n }\nelse\n {msg.payload = 0;\n node.status({});\n }\nreturn;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1050,"y":240,"wires":[[]]},{"id":"5e75e673a6b2c1b4","type":"function","z":"8e2b8fd0f926c128","g":"7a1dfe750951b882","name":"Pedestrians GREEN","func":"if (msg.payload ===\"on\")\n {msg.payload = 1;\n node.status({fill:\"green\",shape:\"dot\",text:\"Green ON\"});\n }\nelse\n {msg.payload = 0;\n node.status({});\n }\nreturn;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":300,"wires":[[]]},{"id":"d913a633711cfeb3","type":"comment","z":"8e2b8fd0f926c128","g":"7a1dfe750951b882","name":"This is a Mealy FSM architecture","info":"The Mealy architecture should be always prefered for software FSM designs to keep the number of required states essential. Mealy output behaviour is independend from state information and might be combined with other combinatorical signals. This allows to keep the number of states essential. **Mealy outputs connect to the transition events instead of states. **\n\nFirst step for system design is to determine the number and names of input and output signals of the project. For any given outputs, there is the next step to try the the signal generation by using a boolean equation from inputs. \n\nIf not possible, the problem is sequential for what we have to introduce a state. It is the job of experienced humans to reqcognize the correct number of FSM and its states and give them approprate names.\n","x":1090,"y":180,"wires":[]},{"id":"c80b8337ad33fa72","type":"comment","z":"8e2b8fd0f926c128","g":"9fe5fb9ec0c0b8a7","name":"This is a Moore FSM architecture","info":"Outputs for Moore FSM always depend from the state information. The Moore style output decoder is included only for information. Personally I prefere the Mealy style for FSM designs.\n","x":1090,"y":480,"wires":[]},{"id":"bde8bee28d5bb264","type":"delay","z":"8e2b8fd0f926c128","g":"c447cdfa25107a3d","name":"Async FSM Timer","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":510,"y":780,"wires":[["8a9bbbc89d6f0d5d"]]},{"id":"85f024e2a41bebd6","type":"inject","z":"8e2b8fd0f926c128","g":"c447cdfa25107a3d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0","topic":"","payload":"true","payloadType":"bool","x":190,"y":780,"wires":[["320f9f9bbd242a6b"]]},{"id":"4857cb7c9b528ce5","type":"function","z":"8e2b8fd0f926c128","g":"c447cdfa25107a3d","name":"TestTimer","func":"if (flow.get(\"Timer\") == 0) return [{delay:\"stopped\"}, null] // wait for FSM\nelse return [null, {delay:flow.get(\"Timer\")}]; // start new time\n","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":780,"wires":[["4bc2d01449a13ffa"],["bde8bee28d5bb264"]]},{"id":"8a9bbbc89d6f0d5d","type":"function","z":"8e2b8fd0f926c128","g":"c447cdfa25107a3d","name":"TimeElapsed","func":"flow.set(\"Timer\", 0); // time elapsed, handshake for FSM \nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":780,"wires":[["4857cb7c9b528ce5"]]},{"id":"4bc2d01449a13ffa","type":"delay","z":"8e2b8fd0f926c128","g":"c447cdfa25107a3d","name":"Timer Resolution","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1050,"y":780,"wires":[["4857cb7c9b528ce5"]],"info":"Node Reds runtime will be blocked if using 100% of the available computing time for polling in the loops. Therefore we slowdown the loops to allow cooperative thread switching.\n\nFor 100mS delay in the timer loop, the maximum jitter will be 100mS. "},{"id":"320f9f9bbd242a6b","type":"change","z":"8e2b8fd0f926c128","g":"c447cdfa25107a3d","name":"Timer Init","rules":[{"t":"set","p":"delay","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":780,"wires":[["bde8bee28d5bb264"]]},{"id":"546deffe9af46b93","type":"delay","z":"8e2b8fd0f926c128","name":"FSM cycle time","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":480,"y":400,"wires":[["c18b4c9bd8d583b2"]],"info":"Node Reds runtime will be blocked if using 100% of the available computing time for polling in the loops. Therefore we slowdown the loops to allow cooperative thread switching.\n\nFor 100mS delay in the FSM loop, timer resolution will be 100mS.\n"},{"id":"d589e831a5039c8d","type":"inject","z":"8e2b8fd0f926c128","g":"9d1731a61c2227ee","name":"Pedestrians Request Button","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"global","x":260,"y":620,"wires":[["96cd8e84e48efe34"]]},{"id":"96cd8e84e48efe34","type":"trigger","z":"8e2b8fd0f926c128","g":"9d1731a61c2227ee","name":"","op1":"","op2":"","op1type":"num","op2type":"num","duration":"200","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":500,"y":620,"wires":[["486562621d485e60"]]},{"id":"486562621d485e60","type":"function","z":"8e2b8fd0f926c128","g":"9d1731a61c2227ee","name":"Input Mirror","func":"if (msg.payload == 1) flow.set(\"Button\", 1); \nif (msg.payload == 0) flow.set(\"Button\", 0);\nreturn null;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":620,"wires":[[]]},{"id":"094fc31f60cfbbb7","type":"function","z":"8e2b8fd0f926c128","g":"7a1dfe750951b882","name":"Request Pending","func":"if (msg.payload ===\"on\")\n {msg.payload = 1;\n node.status({fill:\"white\",shape:\"dot\",text:\"Button iluminated\"});\n }\nelse\n {msg.payload = 0;\n node.status({});\n }\nreturn;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1050,"y":380,"wires":[[]]},{"id":"7334cf2d08ec61be","type":"function","z":"aacb7c81f89be079","name":"Blink State Machine","func":"// Blinker FSM using asynchronous timer system\n\nvar pYel; // parameter port for yellow output\n\nswitch (msg.payload) // pop state info as parameter from stack\n{ case 0: // state OFF\n if (flow.get(\"GlobalTimer\") == 0) // wait for OFF time elapsed\n { pYel = {payload:\"on\"}; // switch output for yellow light on\n flow.set(\"GlobalTimer\", 400); // start on time\n msg.payload = 1; // next state ON\n } \n break;\n\n case 1: // state ON\n if (flow.get(\"GlobalTimer\") == 0) // wait for ON time elapsed\n { pYel = {payload:\"off\"}; // switch output for yellow light off\n flow.set(\"GlobalTimer\" , 600) // start off time\n msg.payload = 0; // next state OFF\n }\n break;\n}\n\nnode.status({text:\"State = \" + msg.payload}); // display state information\nreturn [pYel,msg];\n","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":140,"wires":[["8b99ba6916511706"],["106b9e284a8b6d25"]]},{"id":"83b08ea7f44c0d4b","type":"inject","z":"aacb7c81f89be079","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0","topic":"","payload":"true","payloadType":"bool","x":110,"y":140,"wires":[["7dc2e27aea626843"]]},{"id":"8b99ba6916511706","type":"function","z":"aacb7c81f89be079","name":"YellowBlinker","func":"if (msg.payload ===\"on\") node.status({fill:\"yellow\",shape:\"dot\",text:\"Yellow ON\"});\nelse node.status({});\nreturn;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":140,"wires":[[]]},{"id":"b84fef40d0752775","type":"delay","z":"aacb7c81f89be079","name":"Speed Limit","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":270,"y":180,"wires":[["7334cf2d08ec61be"]],"info":"Node Reds runtime will be blocked if using 100% of the available computing time for polling in the loops. Therefore we slowdown the loops to allow cooperative thread switching.\n\nFor 100mS delay in the FSM loop, timer resolution will be 100mS.\n"},{"id":"7dc2e27aea626843","type":"change","z":"aacb7c81f89be079","name":"FSM Init","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":260,"y":140,"wires":[["7334cf2d08ec61be"]]},{"id":"11f4989c0dba9de8","type":"delay","z":"aacb7c81f89be079","name":"Async FSM Timer","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":430,"y":300,"wires":[["407daca816bddd54"]]},{"id":"1ff5cd5581ca6ad5","type":"inject","z":"aacb7c81f89be079","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0","topic":"","payload":"true","payloadType":"bool","x":110,"y":300,"wires":[["a8e06c1a18428622"]]},{"id":"bf02810cef9dfd9b","type":"function","z":"aacb7c81f89be079","name":"TestTimer","func":"if (flow.get(\"GlobalTimer\") == 0) return [{delay:\"stopped\"}, null] // wait for FSM\nelse return [null, {delay:flow.get(\"GlobalTimer\")}]; // start new time\n","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":300,"wires":[["d4379218167a7106"],["11f4989c0dba9de8"]]},{"id":"407daca816bddd54","type":"function","z":"aacb7c81f89be079","name":"TimeElapsed","func":"flow.set(\"GlobalTimer\", 0); // time elapsed, handshake for FSM \nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":300,"wires":[["bf02810cef9dfd9b"]]},{"id":"d4379218167a7106","type":"delay","z":"aacb7c81f89be079","name":"Speed Limit","pauseType":"delay","timeout":"100","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":950,"y":300,"wires":[["bf02810cef9dfd9b"]],"info":"Node Reds runtime will be blocked if using 100% of the available computing time for polling in the loops. Therefore we slowdown the loops to allow cooperative thread switching.\n\nFor 100mS delay in the timer loop, the maximum jitter will be 100mS. "},{"id":"a8e06c1a18428622","type":"change","z":"aacb7c81f89be079","name":"Timer Init","rules":[{"t":"set","p":"delay","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":300,"wires":[["11f4989c0dba9de8"]]}]
Unfortunately, the timer loop has a major design flaw. While any time is running, the blocking NodeRed Timer node does not allow to start another time before the previous elapsed. As a consequence, pedestrians might request its green priority but waiting time is not really shortend. (This is what pedestrians always notice while waiting) . Therefore the auxilliary timer loop needs improvement. Obviously its possible and should be prefered to derive time from system UTC by using something like a cron job. Mainly, the FSM code already shows the equal processing of times and input signals what was not possible in other FSM examples of this thread.