I got a bit bored this evening and had a go at writing a Node-RED flow to simulate your gates.
Most of the Node-RED flow is concerned with running the simulation. I've annotated the flow so you can see where the finite state machine (FSM) is located which does all the clever stuff.
There's quite a bit of code to interlock the push buttons so the user can't do 'silly things'.
Last of all... I think the simulation looks best when you use the Dark theme.
Please ask if you have any questions about the code. I dare say the code can be simplified.
[{"id":"d0383a44.bb6f98","type":"inject","z":"b9712f38.cc3d58","name":"Clock Pulse Generator","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"1","payloadType":"num","x":190,"y":260,"wires":[["7ba8c160.c38be"]]},{"id":"ff4aca1c.1649a8","type":"ui_slider","z":"b9712f38.cc3d58","name":"Left-hand gate","label":"","tooltip":"","group":"173d5c59.768484","order":2,"width":"6","height":"1","passthru":true,"outs":"all","topic":"","min":0,"max":"100","step":"10","x":780,"y":280,"wires":[["1892f88e.72326f","f795e3b4.d9a9c"]]},{"id":"1892f88e.72326f","type":"function","z":"b9712f38.cc3d58","name":"Simulate CLOSED limit switch","func":"if (msg.payload >= 90) {\n msg.background = \"red\";\n flow.set(\"closed_limit_switch\",\"activated\");\n return msg;\n}\nelse {\n msg.background = \"#097479\";\n flow.set(\"closed_limit_switch\",\"inactive\");\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1030,"y":260,"wires":[["a1111341.3f4538"]]},{"id":"22d44bc2.91d3f4","type":"inject","z":"b9712f38.cc3d58","name":"Set initial conditions","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":180,"y":80,"wires":[["2a70b209.4e23c6"]]},{"id":"2a70b209.4e23c6","type":"function","z":"b9712f38.cc3d58","name":"Set initail conditions","func":"flow.set(\"position\",0);\n\nflow.set(\"gate_fsm_state_register\",\"stopped\");\n\nflow.set(\"button_pressed\",\"stop\");\n\n// flow.set(\"simulation_status\",\"stopped\");\n\nmsg.payload = 100;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":420,"y":80,"wires":[["ff4aca1c.1649a8","72893f84.1fafa8","3c5014dc.9fe7a4"]]},{"id":"d20a6897.b9b85","type":"ui_template","z":"b9712f38.cc3d58","group":"173d5c59.768484","name":"Fix the width of the dashboard to 800px","order":1,"width":0,"height":0,"format":"<style>\n.masonry-container {\n width: 800px;\n}\n</style>\n<style>\nbutton{\n text-transform: none !important;\n}\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":880,"y":40,"wires":[[]]},{"id":"c723b57f.ba441","type":"ui_button","z":"b9712f38.cc3d58","name":"Open limit switch","group":"b830026.09b9e8","order":3,"width":"3","height":"1","passthru":false,"label":"Open Limit Switch","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"open_limit_switch","payloadType":"str","topic":"","x":1290,"y":300,"wires":[[]]},{"id":"a1111341.3f4538","type":"ui_button","z":"b9712f38.cc3d58","name":"Closed limit switch","group":"b830026.09b9e8","order":1,"width":"3","height":"1","passthru":false,"label":"Closed Limit Switch","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"closed_limit_switch","payloadType":"str","topic":"","x":1290,"y":260,"wires":[[]]},{"id":"f795e3b4.d9a9c","type":"function","z":"b9712f38.cc3d58","name":"Simulate OPENED limit switch","func":"if (msg.payload <= 20) {\n msg.background = \"red\";\n flow.set(\"opened_limit_switch\",\"activated\");\n return msg;\n}\nelse {\n msg.background = \"#097479\";\n flow.set(\"opened_limit_switch\",\"inactive\");\n return msg;\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1030,"y":300,"wires":[["c723b57f.ba441"]]},{"id":"7ba8c160.c38be","type":"function","z":"b9712f38.cc3d58","name":"gate_fsm","func":"var fsm_state = flow.get(\"gate_fsm_state_register\") || \"stopped\";\n\nvar closed_limit_switch = flow.get(\"closed_limit_switch\") || \"activated\";\nvar opened_limit_switch = flow.get(\"opened_limit_switch\") || \"activated\";\n\nvar button_pressed = flow.get(\"button_pressed\") || \"stopped\";\n\nswitch (fsm_state)\n {\n case \"stopped\":\n if ( (button_pressed == \"open\") && (opened_limit_switch == \"inactive\") ) {\n fsm_state = \"opening\";\n node.status({text:\"State register = Opening\"});\n node.send( {payload:\"opening\"} );\n }\n else if ( (button_pressed == \"close\") && (closed_limit_switch == \"inactive\") ) {\n fsm_state = \"closing\";\n node.status({text:\"State register = Closing\"});\n node.send( {payload:\"closing\"} );\n }\n break;\n \n case \"opening\":\n if ( (button_pressed == \"stop\") || (opened_limit_switch == \"activated\") ) {\n fsm_state = \"stopped\";\n node.status({text:\"State register = Stopped\"});\n node.send( [{payload:\"stopped\"}] );\n flow.set(\"button_pressed\",\"stop\");\n }\n else { \n fsm_state = \"opening\";\n node.status({text:\"State register = Opening\"});\n node.send( {payload:\"opening\"} );\n }\n break;\n \n case \"closing\":\n if ( (button_pressed == \"stop\") || (closed_limit_switch == \"activated\") ) {\n fsm_state = \"stopped\";\n node.status({text:\"State register = stopped\"});\n node.send( [{payload:\"stopped\"}] );\n flow.set(\"button_pressed\",\"stop\");\n }\n else {\n fsm_state = \"closing\";\n node.status({text:\"State register = Closing\"});\n node.send( {payload:\"closing\"} );\n }\n break;\n }\n\nflow.set(\"gate_fsm_state_register\", fsm_state);\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":240,"y":320,"wires":[["658e87aa.a6e538","42c4a527.320d1c","3c5014dc.9fe7a4"]]},{"id":"72893f84.1fafa8","type":"ui_slider","z":"b9712f38.cc3d58","name":"Right-hand gate","label":"","tooltip":"","group":"173d5c59.768484","order":3,"width":"6","height":"1","passthru":true,"outs":"all","topic":"","min":"100","max":"0","step":"10","x":780,"y":360,"wires":[[]]},{"id":"e85840f.82c754","type":"ui_button","z":"b9712f38.cc3d58","name":"Motor-left state","group":"b830026.09b9e8","order":2,"width":"3","height":"1","passthru":false,"label":"{{msg.topic}}","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"","payloadType":"str","topic":"","x":780,"y":440,"wires":[[]]},{"id":"a84bfea6.58983","type":"ui_button","z":"b9712f38.cc3d58","name":"Motor-right state","group":"b830026.09b9e8","order":2,"width":"3","height":"1","passthru":false,"label":"{{msg.topic}}","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"","payloadType":"str","topic":"","x":790,"y":480,"wires":[[]]},{"id":"d80acb77.2220c8","type":"ui_button","z":"b9712f38.cc3d58","name":"Open","group":"876a6d6.be7489","order":2,"width":"3","height":"1","passthru":false,"label":"Open","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"btn_open","payloadType":"str","topic":"","x":170,"y":640,"wires":[["47128f2a.e3958"]]},{"id":"de7e2620.b2f658","type":"ui_button","z":"b9712f38.cc3d58","name":"Close","group":"876a6d6.be7489","order":3,"width":"3","height":"1","passthru":false,"label":"Close","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"btn_close","payloadType":"str","topic":"","x":170,"y":700,"wires":[["5cef6460.dcfb2c"]]},{"id":"8bf888fb.f8e3b8","type":"ui_button","z":"b9712f38.cc3d58","name":"STOP","group":"876a6d6.be7489","order":5,"width":"3","height":"1","passthru":false,"label":"STOP","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"btn_stop","payloadType":"str","topic":"","x":170,"y":760,"wires":[["51dc7dba.5978e4"]]},{"id":"47128f2a.e3958","type":"function","z":"b9712f38.cc3d58","name":"","func":"var opened_limit_switch = flow.get(\"opened_limit_switch\") || \"activated\";\n\nif (opened_limit_switch == \"inactive\") {\n var button_pressed = flow.get(\"button_pressed\") || \"stop\";\n if (button_pressed == \"close\") {\n flow.set(\"button_pressed\", \"stop\");\n }\n else {\n flow.set(\"button_pressed\",\"open\");\n }\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":640,"wires":[["3c5014dc.9fe7a4"]]},{"id":"5cef6460.dcfb2c","type":"function","z":"b9712f38.cc3d58","name":"","func":"var closed_limit_switch = flow.get(\"closed_limit_switch\") || \"activated\";\n\nif (closed_limit_switch == \"inactive\") {\n var button_pressed = flow.get(\"button_pressed\") || \"stop\";\n if (button_pressed == \"open\") {\n flow.set(\"button_pressed\", \"stop\");\n }\n else {\n flow.set(\"button_pressed\",\"close\");\n }\n}\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":700,"wires":[["3c5014dc.9fe7a4"]]},{"id":"51dc7dba.5978e4","type":"function","z":"b9712f38.cc3d58","name":"","func":"flow.set(\"button_pressed\",\"stop\");\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":760,"wires":[["3c5014dc.9fe7a4"]]},{"id":"658e87aa.a6e538","type":"function","z":"b9712f38.cc3d58","name":"Decode FSM outputs for gate movement","func":"var position = flow.get(\"position\") || 100;\n\nif (msg.payload == \"opening\") { \n // Increment gate position\n if (position >= 10) {\n position = position - 10;\n flow.set(\"position\",position);\n \n node.status({text:\"Position =\" + position});\n \n msg.payload = position;\n return msg;\n }\n}\nelse if (msg.payload == \"closing\") {\n // Decrement gate position\n if (position <= 90) {\n position = position + 10;\n flow.set(\"position\",position);\n \n node.status({text:\"Position =\" + position});\n \n msg.payload = position;\n return msg;\n }\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":500,"y":320,"wires":[["ff4aca1c.1649a8","72893f84.1fafa8"]]},{"id":"3c5014dc.9fe7a4","type":"function","z":"b9712f38.cc3d58","name":"","func":"var button_pressed = flow.get(\"button_pressed\") || \"stop\";\n\nif (button_pressed == \"open\") {\n node.send( [{background:\"red\"},{background:\"#097479\"},{background:\"#097479\"}] );\n}\nelse if (button_pressed == \"close\") {\n node.send( [{background: \"#097479\"},{background:\"red\"},{background: \"#097479\"}] );\n}\nelse if (button_pressed == \"stop\") {\n node.send( [{background: \"#097479\"},{background: \"#097479\"},{background:\"red\"}] );\n}\n","outputs":3,"noerr":0,"initialize":"","finalize":"","x":590,"y":700,"wires":[["d80acb77.2220c8"],["de7e2620.b2f658"],["8bf888fb.f8e3b8"]]},{"id":"42c4a527.320d1c","type":"function","z":"b9712f38.cc3d58","name":"Decode FSM outputs for the motors","func":"if (msg.payload == \"stopped\") { \n msg.topic = \"Motor is stopped\";\n msg.background = \"#097479\";\n return msg;\n\n}\nelse {\n msg.topic =\"Motor is running\";\n msg.background = \"red\";\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":510,"y":460,"wires":[["e85840f.82c754","a84bfea6.58983"]]},{"id":"927029c7.a456e8","type":"comment","z":"b9712f38.cc3d58","name":"I think the dashboard looks best in Dark mode","info":"","x":890,"y":100,"wires":[]},{"id":"c7205543.b79dd","type":"comment","z":"b9712f38.cc3d58","name":"Readme for... FSM details","info":"This is the Finite State machine.\n\nIt has three states... \"stopped\", \"opening\" and \"closing\".\n\nIt can move between the states depending on the limit switches and/or the push buttons. ","x":290,"y":380,"wires":[]},{"id":"8bbc7156.ee8e18","type":"comment","z":"b9712f38.cc3d58","name":"Direct movement is prohibited","info":"Direct movement is prohibited\n\nThe code in the three function blocks prevents direct movement\nfrom Open to Close or Close to Open as this could damage the motors.\n\nIf the user did try to do this, the system goes into the STOPPED mode. \n\nThe function blocks also prevent a user from trying to...\n Open the gates if they are already OPEN\n Close the gates if they are already CLOSED\n\n","x":340,"y":600,"wires":[]},{"id":"173d5c59.768484","type":"ui_group","z":"","name":"Simulated movement of a pair of gates","tab":"58343703.f0abb8","order":1,"disp":true,"width":"12","collapse":false},{"id":"b830026.09b9e8","type":"ui_group","z":"","name":"Gate sensors and state of the motor","tab":"58343703.f0abb8","order":2,"disp":true,"width":"12","collapse":false},{"id":"876a6d6.be7489","type":"ui_group","z":"","name":"Simulation control buttons","tab":"58343703.f0abb8","order":3,"disp":true,"width":"12","collapse":false},{"id":"58343703.f0abb8","type":"ui_tab","z":"","name":"Gate simulation","icon":"dashboard","order":41,"disabled":false,"hidden":false}]