ZephyrCab - Model Train Physics Simulator Project - Development Blog

Intro to my model trains and their features

A while back in the "1 month just do it" thread, I mentioned the software I am writing. As some of you probably already know, I am a total model train nut. I have a 5x9 HO scale layout and model primarily UP/Southern steam/all things CB&Q/BN. Being interested in computers, I've got all of it DCC-enabled and hooked up to a Linux box running JMRI so I can control the whole thing via IP.

All the trains are independently controllable, and most have sound/lights that are accurate to the prototype. For example, this locomotive is a model of an EMD GP15-1, and has the correct number for its railroad (the Burlington Northern, which has since become the Burlington Northern Santa Fe, or BNSF). It's also got recordings of the correct prime mover sounds, the EMD 645E, which is a 1500HP 12cyl diesel.

And all these functions are controllable from JMRI.


My App - ZephyrCab

JMRI has a WebSockets server, which opens up some really cool web app possibilities. I always thought it would be cool to run the model trains with the same controls as the real ones. Instead of just a single speed knob, you'd have the throttle, brakes, and all the stuff that goes with that. They're very complex machines, especially the braking, so I kind of wanted to cross a train simulator with a model train.

Because of the WebSockets/JSON-friendly nature of JMRI, I chose JavaScript to write it. It also had the bonus of being totally cross-platform, and the idea of running the app from my Nexus tablet while walking around the layout was a huge plus. And so it began. I started writing the app in late May of 2015, with no idea what I was getting into as far as math goes. I started with the lower-level stuff like controlling sounds, which is easier said than done since not every DCC decoder is the same. I had to build a layer-cake style system where there are functions that handle directly talking to the decoder, and everything else is decoder agnostic and just uses those functions. It's evolved a little bit in implentation since then, but the concept is still the same.

The Physics

Turns out train physics are complicated. Really complicated. Developing the program has taught me a lot about JavaScript, but more than anything it's taught me about physics. There's all sorts of physics at work on a train. I had to figure out a way to find the tractive effort of the engine at a given throttle speed. I had to find data on fuel consumption. I had to find data on air reservoir sizes, and I had to use various twisted versions of Boyle's law to create a functioning air pressure simulation system.

Lately...

Lately I've been working on some under-the-hood tweaks for interfacing with the layout, and for "building" your train in the software. Obviously to simulate the physics accurately, the software needs a ridiculous amount of information about the locomotives and rolling stock. It's not enough just to say "it's a 1500hp diesel," it needs way more information than that, and way too much for the user to input manually. To deal with this, I've created a "train builder" that bundles this (and some other things) into a single GUI. It synchronizes with the JMRI roster, so users just add their locomotives using the names from the roster. You can see a demo in this gif.

Just by adding "CBQ2" to the roster, it is able to grab everything you could possibly need to know about that locomotive in the form of a JSON object.

Physics Engine Structure

The physics engine is simple from a programming standpoint. It stores everything in a single list object: train.all. This is an array of objects, and is set up so that the model can be controlled from the object, and the prototype information can also be fetched. For example, running train.all[0].dcc.f.bell.set(true) turns on the bell on the first locomotive. This is thanks to the decoder abstraction layer I've built. Additionally, I can get the PSI of the main air reservoir (updated with each physics engine cycle) like so: train.all[0].prototype.realtime.air.reservoir.main.psi.g. Every 500ms, the physics engine "cycles" through the entire train using a big for loop. This allows you to have as many or as few things on the train as you want, and the physics engine will "just work" regardless.


I'll be posting more updates on development progress here, but do you guys have any questions? I know it's kind of a complex idea with the various pieces that have to fit together to make the finished product, so I'm happy to answer any kind of questions you have. I love constructive criticism, suggestions, or feature requests!

5 Likes

very intriguing!

Being able to operate the model just like a real train sounds like a blast!

Yeah, this idea actually came about during a cab ride on an old Wabash F7 (#1189 up in Monticello, Illinois) and it occurred to me that the sound/lights of the real thing was all there, but it was "too easy" to operate.

Somebody beat me to the idea: Bruce Kingsley built a half-scale replica of a locomotive cab, complete with all the buttons, gauges, and a view from the front of the model via a wireless camera. He's actually a really cool guy, and he's helped me a lot with the physics for mine since I haven't actually had a high-school-level physics class yet.

I like his approach, but not everybody has the room, time, or most of all money, to pull something like that off. So I'm kind of shooting for the next best thing.

2 Likes

My mind is blown here OP. Wow! What a project to take on.

Thanks! The hardest part is that I've never driven a locomotive in real life. So I have to rely purely on data, knowledge, and information from other people. I honestly don't know what I would've done without Bruce Kingsley's pointers; he never shared any finished code with me, but he pointed me in the right direction and answered countless questions. Al Krug's various papers/data tables are excellent too, that's where I found the fuel consumption information I needed, and I'm following his paper on braking to the letter while I get ready to start on the brake system.

Again, if anyone is interested in demos, or has questions, please feel free to ask.

Update:

Well last night I made an interesting discovery. I had to add a few lines to tell the JMRI server that we're "letting go" of a throttle when a locomotive is removed from the train.


A little background:

When you request a throttle, it takes two attributes: a name and a DCC address. The name is abitrary, but you can't have a duplicate throttle name, so my solution is to use a number that's incremented up one for each throttle. It's ugly, but it fixes the problem. When you request a throttle, you send a packet that looks something like this (via WebSockets):

//request throttle for DCC address #1379, and refer to it as "BN #1379"
{"type":"throttle","data":{"throttle":"BN #1379","address":1379}}

There's more information on this protocol in the JMRI docs. Anyway...

So for the throttle name my software would input a simple number, starting at 1 and working its way up as we request more throttles. The problem with this is that when you close the program, it didn't release the throttles. So JMRI was still thinking it had a client with whatever throttle name (1 for example). When you launched the program again, it would start at 1 again, so you'd send an identical throttle request. This would confuse the heck out of JMRI, so it would just throw a massive error to the console, close the WebSockets connection, and start acting weird until you restarted it.

Solution:

The solution was to have ZephyrCab release the throttle whenever an item was removed from the train. It's pretty simple to release a throttle:

//release throttle "BN #1379"
{"type":"throttle","data":{"throttle":"BN #1379","release":null}}

The trick here was implementation. There's no limit to the number of locomotives a user can have one their train, so ZephyrCab needed to be able to know which throttle was which. There was already a low-level JMRI control interface in a .throttle sub-object of every train element. So for example, the first thing on the train would have it at train.all[0].throttle.

I knew from the get-go I wanted a function in every locomotive's .throttle that I could call with no arguments, and it would release the throttle. It would be simple and easy to add to the existing code for removing an item from the train. So I went and modified the jmri.throttle constructor. Note that all this jmri object code is mine; I did not use any of JMRI's JavaScript libraries.

Whenever an item is added to the train, a new jmri.throttle(address, throttleName) is created with that constructor function. So the easy solution was to add this inside the constructor:

//called when removing object from the train; it releases the throttle
this.release = function() {
    releasecmd = '{"type":"throttle","data":{"throttle":"' + throttleName + '","release":null}}';
    sendcmd(releasecmd);
    debugToast("Sent command : " + releasecmd) //this is a debug message system I made. It basically amounts to console messages that can be disabled for better readability.
}

Now, whenever a locomotive is added to the train, there is a release function. This is very easy to call:

train.all[0].throttle.release(); //releases throttle for the first locomotive in the train

With that implemented, all I had to do was have ZephyrCab call that function every time a locomotive was removed. There's a little bit of if/else logic here, but that's just because the function that handles this handles both locomotives and rolling stock, so it only needs to run this particular function if it's removing a locomotive. All I had to add to train.build.remove() was this:

//tell JMRI we're releasing this throttle if it's a locomotive
if (type == "locomotive") {
    train.all[index].throttle.release(); //note that you'll get a message from this if debugToasts are enabled.
}

And like magic, the problem is solved. Whenever you remove an item from the train, its throttle is automagically released.


Is this a good format for posts? Too long? Too short? What should I change? I know you guys probably want to see a demo, and I tried to film one last night but it was after dark and the lighting in the train room is horrid. I also had to deal with the above issue, so I decided to hold off on filming.

Let me know what you think!

1 Like

Hey y'all, just wanted to let you know I've moved the development blog to zephyrcab.tumblr.com (at least for now).

Edit:

If any of you guys use RSS, you can get my blog's RSS feed here:

http://zephyrcab.tumblr.com/rss

Ok then. Best of luck again with this project.