Project – Race simulator
Some information
Every so often I like to race in my simulator. And, from time to time, I race together with some friends. We organize a weekend where we train on Friday and race a 24-hour race from Saturday until Sunday. One of us will be racing for 2 hours and then, during a pit-stop, we swap places. The game is streamed live to another monitor or TV and the others usually sit in another room watching the other race.
While racing you want to be able to focus on the task at hand: be consistent, be quick and keep it on the track. That’s where the others come in. While one is racing, the others can keep a look out for various things. Oncoming trafic, backmarkers, tires that are worn out, fuel level, etc. While racing, you are able to see all this information, but the information is usually tucked away inside several menu’s. There is also a whole plethora of information that is simply not accessible trough the GUI.
The “problem”
We used to ask the current driver to pull up the information, but this causes unwanted distraction. Not only that, all three of the “benched” drivers wanted this information. The other issue was that if we asked the information from the driver, it would almost instantly be obsolete. Tire wear could look good, but one lockup could change the whole scenario. We needed a solution.
Challenges
Maybe the biggest challenge would be that we use not one, but multiple different simulation software. We decided to focus on two for now: IRacing and ProjectCars2. We wanted to be able to view the data as real-time as possible on any device we saw fit. This meant that we needed to build something that was able to handle multiple “sources” as data input and be able to display it into something that would be viewable on multiple different devices.
The DataProvider
Because we were focusing on two different applications, we first needed to check how we would be getting the data.
IRacing
IRacing has a really big community behind it and this was working towards our advantage. One of the community members created a SDK for IRacing(irsdk). This was a Python based solution we could build on to fetch information directly from IRacing.
Project Cars 2
PC2 was a bit more labor intensive. It does not directly expose any sort of API, but rather it will store all it’s current race data in mapped memory. This was something we could read and interpret.
From file
It’s a bit difficult to develop while sitting behind a simulator, so we created a tool that could do two things:
- It could read the data real-time and store it to a file (for both IRacing and PC2).
- It could write the data to Mapped memory the same way PC2 did.
This enabled us to do two things:
- We could write previous data back to MMAP to imitate a running PC2 session.
- Because we could define the structure of the file we write to, we can also create a connector that can read this file. Because of this, both IRacing and PC2 data would be stored the same way. This would eliminate the need to implement some sort of IRacing imitator.
Connectors
To be able to handle different sources, we created connectors for each possible data-source. These connectors all have the same “Get” method and would return all possible data in a simple dictionary, with the first key being the connector type (IR, PC)
Providing the data
To create a generic solution, we opted to create a python solution that would do the following:
- It would startup a WebSocked listener where other applications can subscribe to to get real-time updates
- Then it would check what simulator is running and startup an update loop for the desired connector.
- Every “tick” it would call the “get” function from the loaded connector and return the raw data (dictionary) provided.
The Middleman
We created a NodeJS server that would be doing three things
- Subscribe trough a websocked to the DataProvider
- When it receives an update with data, map it from the simulator to a generalized data structure
- Setup it’s own websocked where clients can subscribe to and send any updates to the clients.
The main reason we opted to do the mapping in the Middleman is to remove unwanted processing from the DataProvider. It’s sole role should be fetching and providing the data. This also allowed us to change any incorrect mapping without needing to redeploy the DataProvider.
Furthermore, in a later stage, we would be able to split up the data into specific parts. Depending on the page the client currently is, we would most of the time only need a subset of data. The client would then subscribe to a specific “page update” (for example a page with a tire overview) and we would only need to send the data the client actually needs. When the client would change page (for example the map), it would unsubscribe from it’s current page and subscribe to the new page.
The Client
As the client we created an Angular application. This would subscribe to the middleman and watch for any updates that were send it’s way, depending on it’s current page. This meant that implementing any new page was just a matter of checking what data we need for the current page, make it available inside the Middleman and subscribing to it.
Last thoughts
This project was from before when I started this website, thus I don’t have any screenshots on hand. Nonetheless, this was easily one of the more fun projects that I’ve done. For one, it involved something I really like to do when not working (simracing) and I embarked on this together with a friend. We spend several evenings researching the ways we would be able to get the data, deciding on the data structure and the infrastructure of the entire application stack.
This project also created several side projects (like writing MMAP to file and re-writing the file to a MMAP) and allowed me to learn a lot about several things like threading in Pyhing, MMAP and pub-sub principles.
10/10 that I would do this again.
