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</text>\n  <text x=\"240\" y=\"280\" fill=\"white\">P1</text>\n  <text x=\"240\" y=\"220\" fill=\"white\">P2</text>\n  <text x=\"240\" y=\"80\" fill=\"white\">P3</text>\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</svg>","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.