Controlling model train

Node-Red, Raspberry Pi 4 and 0, Esp32, MQTT.
In the last months, a lot of spare time, I build my 40 year old model train set and tried to control it with Node-Red.
First step: Manually controlling with the Dashboard.
Second step: I build a SVG layout on the screen to mouse-control the turnouts
Third step: With the node "finite state machine" it is possible to run a program for the train.

If anyone is interested I will publish the code and the hardware in this topic.

My excuses for the use of some dutch names in the code and nodes. "Wissels " are turnouts.
To control the train you have to click on the light-yellow squares and the train will stop on that square.
As I am new to this forum I am not sure this is the right way to publish the code.

The code:
[{"id":"21b58389.efd5bc","type":"mqtt out","z":"32882b31.635ce4","name":"","topic":"Trein0","qos":"0","retain":"true","broker":"f35948f7.ab6ed8","x":970,"y":260,"wires":},{"id":"b0f3c08e.355bb","type":"ui_switch","z":"32882b31.635ce4","name":"","label":"Switch","tooltip":"","group":"e3f528f9.334568","order":2,"width":0,"height":0,"passthru":false,"decouple":"false","topic":"Trein0/Schakelaar","style":"","onvalue":"1_Aan","onvalueType":"str","onicon":"","oncolor":"","offvalue":"1_Uit","offvalueType":"str","officon":"","offcolor":"","x":230,"y":200,"wires":[["d7c79cab.501d3"]]},{"id":"2ab0b378.c2c13c","type":"ui_switch","z":"32882b31.635ce4","name":"","label":"Direction","tooltip":"","group":"e3f528f9.334568","order":6,"width":0,"height":0,"passthru":false,"decouple":"false","topic":"Trein0/richting","style":"","onvalue":"Achter-uit","onvalueType":"str","onicon":"","oncolor":"","offvalue":"Voor-uit","offvalueType":"str","officon":"","offcolor":"","x":240,"y":160,"wires":[["9c7fa46d.f04438","d7c79cab.501d3"]]},{"id":"40f7c3b6.2902fc","type":"ui_slider","z":"32882b31.635ce4","name":"PWMdc-Trein","label":"pwmdcslider","tooltip":"","group":"e3f528f9.334568","order":8,"width":0,"height":0,"passthru":true,"outs":"all","topic":"Trein0/pwm","min":"0","max":"99","step":1,"x":260,"y":240,"wires":[["d7c79cab.501d3"]]},{"id":"46d54459.dd2e0c","type":"mqtt in","z":"32882b31.635ce4","name":"","topic":"Trein0","qos":"0","datatype":"auto","broker":"f35948f7.ab6ed8","x":1130,"y":260,"wires":[["c4816b85.835d68"]]},{"id":"c4816b85.835d68","type":"ui_text_input","z":"32882b31.635ce4","name":"Msg Train","label":"Msg Trein","tooltip":"","group":"e769ca0d.2e2108","order":2,"width":4,"height":2,"passthru":true,"mode":"text","delay":300,"topic":"","x":1140,"y":180,"wires":[]},{"id":"1a84fe14.697c52","type":"mqtt out","z":"32882b31.635ce4","name":"Wissels","topic":"Wissels","qos":"0","retain":"","broker":"f35948f7.ab6ed8","x":980,"y":320,"wires":},{"id":"b03d6efb.4c985","type":"mqtt in","z":"32882b31.635ce4","name":"","topic":"Wissels","qos":"0","datatype":"auto","broker":"f35948f7.ab6ed8","x":1130,"y":320,"wires":[["5349b878.7c6d48"]]},{"id":"9c7fa46d.f04438","type":"ui_text_input","z":"32882b31.635ce4","name":"","label":"Direction","tooltip":"","group":"e3f528f9.334568","order":4,"width":0,"height":0,"passthru":true,"mode":"text","delay":300,"topic":"","x":500,"y":160,"wires":[]},{"id":"8a9ec74e.2d98e8","type":"ui_button","z":"32882b31.635ce4","name":"75%","group":"e3f528f9.334568","order":14,"width":0,"height":0,"passthru":false,"label":"75% button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"75","payloadType":"num","topic":"Trein0/pwm","x":231,"y":320,"wires":[["d7c79cab.501d3"]]},{"id":"b96d63b.e3e5ba","type":"ui_button","z":"32882b31.635ce4","name":"100%","group":"e3f528f9.334568","order":12,"width":0,"height":0,"passthru":false,"label":"100% button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"100","payloadType":"num","topic":"Trein0/pwm","x":230,"y":280,"wires":[["d7c79cab.501d3"]]},{"id":"e31392c4.5472d","type":"ui_button","z":"32882b31.635ce4","name":"50%","group":"e3f528f9.334568","order":16,"width":0,"height":0,"passthru":false,"label":"50% button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"50","payloadType":"num","topic":"Trein0/pwm","x":231,"y":360,"wires":[["d7c79cab.501d3"]]},{"id":"c0134917.bbae08","type":"mqtt in","z":"32882b31.635ce4","name":"","topic":"Sensoren","qos":"0","datatype":"auto","broker":"f35948f7.ab6ed8","x":160,"y":520,"wires":[["f5e5ef8c.75189"]]},{"id":"5349b878.7c6d48","type":"ui_text_input","z":"32882b31.635ce4","name":"Msg TurnOuts","label":"Msg Wissels","tooltip":"","group":"e769ca0d.2e2108","order":8,"width":4,"height":2,"passthru":true,"mode":"text","delay":300,"topic":"","x":1160,"y":400,"wires":[]},{"id":"cb2e82b1.1d465","type":"ui_text_input","z":"32882b31.635ce4","name":"Msg Retour","label":"Msg Retour","tooltip":"","group":"e769ca0d.2e2108","order":11,"width":4,"height":2,"passthru":true,"mode":"text","delay":300,"topic":"","x":390,"y":580,"wires":[]},{"id":"a82cab42.2ddf28","type":"ui_button","z":"32882b31.635ce4","name":"40%","group":"e3f528f9.334568","order":18,"width":0,"height":0,"passthru":false,"label":"40% button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"40","payloadType":"num","topic":"Trein0/pwm","x":230,"y":398,"wires":[["d7c79cab.501d3"]]},{"id":"f5e5ef8c.75189","type":"rbe","z":"32882b31.635ce4","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":210,"y":580,"wires":[["cb2e82b1.1d465","7ba40ddb.3b7eb4","b75f6161.df9f6"]]},{"id":"7492e853.427198","type":"ui_slider","z":"32882b31.635ce4","name":"pwmFreqslider","label":"pwmFreqslider","tooltip":"","group":"e3f528f9.334568","order":10,"width":0,"height":0,"passthru":true,"outs":"all","topic":"Trein0/pwmfreq","min":"300","max":"1000","step":1,"x":260,"y":120,"wires":[["6d9a2c52.142554","d7c79cab.501d3"]]},{"id":"31c83962.4d33f6","type":"ui_text","z":"32882b31.635ce4","group":"e769ca0d.2e2108","order":6,"width":0,"height":0,"name":"pwmFreq","label":"pwmFreq","format":"{{msg.payload}}","layout":"row-spread","x":600,"y":120,"wires":},{"id":"6d9a2c52.142554","type":"function","z":"32882b31.635ce4","name":"-200","func":"msg.payload = (msg.payload -200)\nreturn msg;","outputs":1,"noerr":0,"x":450,"y":120,"wires":[["31c83962.4d33f6"]]},{"id":"84f27159.2d5ba","type":"change","z":"32882b31.635ce4","name":"Wissel -->","rules":[{"t":"set","p":"payload","pt":"msg","to":"{"rect":{"kleur1":"green","kleur2":"red"}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":580,"y":640,"wires":[["93e7613b.e1139"]]},{"id":"7ba40ddb.3b7eb4","type":"function","z":"32882b31.635ce4","name":"Wissel kleur","func":"if(msg.payload == 'Wisselrecht')\n{\n var msg1 = {payload : 'p1'};\n global.set("stand", "Wisselrecht");\n}\nif(msg.payload == 'Wisselbuig')\n{\n var msg2 = {payload : 'p2'};\n global.set("stand", "Wisselbuig");\n}\nreturn [ msg1, msg2 ]\n","outputs":2,"noerr":0,"x":230,"y":640,"wires":[["f2b3c4e6.a09ae8"],["c6cbdabe.7ec648"]]},{"id":"c6cbdabe.7ec648","type":"change","z":"32882b31.635ce4","name":"Topic databind","rules":[{"t":"set","p":"topic","pt":"msg","to":"databind","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":680,"wires":[["6e625b58.170c74"]]},{"id":"6e625b58.170c74","type":"change","z":"32882b31.635ce4","name":"Wissel />","rules":[{"t":"set","p":"payload","pt":"msg","to":"{"rect":{"kleur1":"red","kleur2":"green"}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":580,"y":680,"wires":[["93e7613b.e1139"]]},{"id":"f2b3c4e6.a09ae8","type":"change","z":"32882b31.635ce4","name":"Topic databind","rules":[{"t":"set","p":"topic","pt":"msg","to":"databind","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":640,"wires":[["84f27159.2d5ba"]]},{"id":"93e7613b.e1139","type":"ui_svg_graphics","z":"32882b31.635ce4","group":"2b3cd5f6.3d089a","order":2,"width":10,"height":7,"svgString":"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" height="300" width="500">\n\n <text x="230" y="160" fill="white">Station\n <text x="240" y="280" fill="white">P1\n <text x="240" y="220" fill="white">P2\n <text x="240" y="80" fill="white">P3\n \n <rect id="vak1A" x="60" y="40" height="20" width="170" fill="black" stroke="white" stroke-width="2" />\n <rect id="vak1B" x="232" y="40" height="20" width="36" fill="lightgreen" stroke="white" stroke-width="2" />\n <rect id="vak1C" x="270" y="40" height="20" width="170" fill="black" stroke="white" stroke-width="2" />\n \n <rect id="vak2A" x="60" y="240" height="20" width="170" fill="black" stroke="white" stroke-width="2" />\n <rect id="vak2B" x="232" y="240" height="20" width="36" fill="lightgreen" stroke="white" stroke-width="2" />\n <rect id="vak2C" x="270" y="240" height="20" width="90" fill="black" stroke="white" stroke-width="2" />\n <rect id="vak2D" x="360" y="240" height="20" width="80" fill="black" stroke="white" stroke-width="2" />\n \n <rect id="vak3" x="440" y="40" height="220" width="20" fill="black" stroke="white" stroke-width="2" />\n <rect id="vak4" x="40" y="40" height="220" width="20" fill="black" stroke="white" stroke-width="2" />\n \n <rect id="vak5A" x="140" y="180" height="20" width="90" fill="black" stroke="white" stroke-width="2" />\n <rect id="vak5B" x="232" y="180" height="20" width="36" fill="lightgreen" stroke="white" stroke-width="2" />\n <rect id="vak5C" x="270" y="180" height="20" width="90" fill="black" stroke="white" stroke-width="2" />\n \n <polygon id= "W1" fill="darkgreen" points="360,180 440,240 415,240 360,200" stroke="white" stroke-width="2" />\n <polygon id= "W2" fill="black" points="140,200 85,240 60,240 140,180" stroke="white" stroke-width="2" />\n \n","clickableShapes":[{"targetId":"#vak2D","action":"click","payload":"#vak2D","payloadType":"str","topic":"control"},{"targetId":"#vak1B","action":"click","payload":"#vak1B","payloadType":"str","topic":"blok2"},{"targetId":"#vak5B","action":"click","payload":"#vak5B","payloadType":"str","topic":"station"},{"targetId":"#vak2B","action":"click","payload":"#vak2B","payloadType":"str","topic":"blok1"}],"smilAnimations":,"bindings":[{"selector":"#vak2D","bindSource":"payload.rect.kleur1","bindType":"attr","attribute":"fill"},{"selector":"#W1","bindSource":"payload.rect.kleur2","bindType":"attr","attribute":"fill"}],"showCoordinates":true,"autoFormatAfterEdit":true,"outputField":"payload","editorUrl":"http://drawsvg.org/drawsvg.html","directory":"","name":"SVG Test","x":740,"y":640,"wires":[["41b0c185.9e6fa","e4d76a23.9480d8"]]},{"id":"41b0c185.9e6fa","type":"function","z":"32882b31.635ce4","name":"Mouse Click","func":"var wisselstand = global.get("stand") || 0;\nvar muis = msg.payload\n\nif (wisselstand === 'Wisselrecht' && muis === '#vak2D')\n{\n msg = {payload : "Spoor2"};\n \n}\nif (wisselstand === 'Wisselbuig' && muis === '#vak2D')\n{\n msg = {payload : "Spoor1"};\n \n}\n\nnode.status({fill:"blue", shape:"ring", text:"stand "+wisselstand});\nreturn msg","outputs":1,"noerr":0,"x":930,"y":640,"wires":[["1a84fe14.697c52"]]},{"id":"e4d76a23.9480d8","type":"dsm","z":"32882b31.635ce4","name":"train","sm_config":"{\n "stateOutput": "payload",\n "globalOutput": "trein_state",\n "currentState": "gestopt",\n "states": {\n "gestopt": {\n "start": "rijden"\n },\n "rijden": {\n "blok1": "blok1",\n "blok2": "blok2",\n "station": "station",\n "stop": "gestopt"\n },\n "station": {\n "Poort2": "gestopt",\n "stop": "gestopt"\n },\n "blok1": {\n "Poort1": "gestopt",\n "stop": "gestopt"\n },\n "blok2": {\n "Poort3": "gestopt",\n "stop": "gestopt"\n }\n }\n}","x":610,"y":380,"wires":[["2946a6c4.80691a","4f31c4c0.184f0c"]]},{"id":"2946a6c4.80691a","type":"function","z":"32882b31.635ce4","name":"State","func":"var wisselstand = global.get("stand") || 0;\nif(msg.payload === 'gestopt')\n{\n msg1 = {payload : '0'};\n msg2 = {payload : "none"};\n}\nif(msg.payload === 'rijden')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : "none"};\n}\nif(msg.payload === 'station' && wisselstand === 'Wisselrecht')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : 'Spoor2'};\n}\nif(msg.payload === 'station' && wisselstand === 'Wisselbuig')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : "none"};\n} \nif(msg.payload === 'blok1' && wisselstand === 'Wisselrecht')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : 'none'};\n}\nif(msg.payload === 'blok1' && wisselstand === 'Wisselbuig')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : "Spoor1"};\n} \nif(msg.payload === 'blok2' && wisselstand === 'Wisselrecht')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : 'none'};\n}\nif(msg.payload === 'blok2' && wisselstand === 'Wisselbuig')\n{\n msg1 = {payload : '50'};\n msg2 = {payload : "Spoor1"};\n} \n\nreturn [ msg1, msg2 ]\n","outputs":2,"noerr":0,"x":750,"y":380,"wires":[["21b58389.efd5bc"],["1a84fe14.697c52"]]},{"id":"b75f6161.df9f6","type":"change","z":"32882b31.635ce4","name":"topic poort","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":520,"wires":[["e4d76a23.9480d8"]]},{"id":"d7c79cab.501d3","type":"delay","z":"32882b31.635ce4","name":"","pauseType":"delay","timeout":"25","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":510,"y":260,"wires":[["21b58389.efd5bc"]]},{"id":"655dd6e8.c77cf8","type":"ui_button","z":"32882b31.635ce4","name":"Stop","group":"2b3cd5f6.3d089a","order":5,"width":0,"height":0,"passthru":false,"label":"Stop","tooltip":"","color":"","bgcolor":"red","icon":"","payload":"aa","payloadType":"str","topic":"stop","x":430,"y":360,"wires":[["e4d76a23.9480d8"]]},{"id":"6d1ef18f.a2433","type":"ui_button","z":"32882b31.635ce4","name":"Start","group":"2b3cd5f6.3d089a","order":4,"width":0,"height":0,"passthru":false,"label":"Start","tooltip":"","color":"","bgcolor":"","icon":"","payload":"cc","payloadType":"str","topic":"start","x":430,"y":400,"wires":[["e4d76a23.9480d8"]]},{"id":"4f31c4c0.184f0c","type":"ui_text","z":"32882b31.635ce4","group":"2b3cd5f6.3d089a","order":3,"width":0,"height":0,"name":"State","label":"State: ","format":"{{msg.payload}}","layout":"row-left","x":750,"y":440,"wires":},{"id":"f35948f7.ab6ed8","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"Trein/pwm","birthQos":"0","birthRetain":"false","birthPayload":"0","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"e3f528f9.334568","type":"ui_group","z":"","name":"Train "0"","tab":"1fb219e1.ba02f6","order":4,"disp":true,"width":"4","collapse":false},{"id":"e769ca0d.2e2108","type":"ui_group","z":"","name":"All messages","tab":"1fb219e1.ba02f6","order":3,"disp":true,"width":"4","collapse":false},{"id":"2b3cd5f6.3d089a","type":"ui_group","z":"","name":"Auto","tab":"1fb219e1.ba02f6","order":4,"disp":true,"width":10,"collapse":false},{"id":"1fb219e1.ba02f6","type":"ui_tab","z":"","name":"Trein Control","icon":"dashboard","disabled":false,"hidden":false}]

12 Likes

Definitely publish. Though it has been many a decade since I last had a train set (though actually I still have the remains of it in the loft now I think about it!), we all love to see how people have used Node-RED. It is great to have inspirations for others too.

Yes please!

I did a similar thing with a group of students a few years ago.
We used PIC microcontrollers talking to signals and points using a CAN bus.
We also used PWM to control the speed and direction of the train.

If I had the time to "do it again today" I think I'd use Wemos D1s (ESP8266) and Node-RED as I could control everything via WiFi using MQTT.

I like your idea of using SVG - I'm sure Bart @BartButenaers will think it's a great application for SVG.

Note:
You can write finite state machines using JavaScript (as well as using the FSM nodes).

Here's a link to a slot racing car project I did with my IoT students.

3 Likes

Can't wait to see it running, to be honest.
That is the kind of stuff for which we have developed the SVG node...

Since I have been young (long time ago), I did some googling about the model railroad control. And seems that there is already nice software available, like e.g. mentioned on this page:

Would be very cool if model trains could controlled via the Node-RED dashboard...

P.S. seems that the RocRail model railroad software is already using Node-RED...

This topic was automatically closed after 60 days. New replies are no longer allowed.