Physical dashboard for race games

I love playing race games and at some point I got the idea to create a functioning dashboard for these games with Arduino and Processing (speedometer, RPM, etc.). After some searching on the internet I found out that games by Codemasters (F1 2012, F1 2013, GRID, GRID2) allow you to send the in-game data to external programs via UDP. This means that you can read the game data, like the cars speed, with processing. With use of Firmata you can then create a dashboard. The code is below but I’ll show what I’ve done first.

So this is how it works:
First you need to modify the configuration file of the game in order to receive the data. Head to …/Documents/My Games/F1 2012/hardwaresettings (or any other Codemasters game) and open hardware_settings_config.xml. At the bottom of the file make sure to change the motion entry to this: . What this does is it creates a UDP server in the game with an IP address and a port and it transmits extradata to this server.

Now head over to processing and make sure you have the UDP library installed (sketch >> import library >> add library >> search for UDP). For testing purposes I’ll use the ControlP5 library in processing as well because it allows me to create dials etc. It’s not required but makes life a lot easier.

Now to the fun part. In the Setup we first create a canvas (set size and background) and a connection to the arduino, which is running standardFirmata. Next we’ll create 3 outputs on the screen with ControlP5, the speedometer, RPM and gear indicators. Finally we start the UDP connection on the port and IP that we configured in the game configuration file (IP: 127.0.0.1 and port 20777). If you want you can turn on the log function of the UDP receiver by setting it to true but there’s not much interesting stuff there.

The draw function will be empty because everything is being handled by the UDP receiver. This function receives an array of bytes from the game and stores this array in the data variable. However, everything in the game is stored in 4 byte floats. This means that we have to read 4 bytes at a time and combine these to a float. The function fullOutput() will output all the received data from the game and will show the starting position of the bytes in the array. After some testing I found that the speed, for example, is stored in bytes 28, 29, 30 and 31. By combining these 4 bytes with the function Float.intBitsToFloat we get the speed as a float. This number can be send to the speedometer we created with ControlP5. Furthermore we can map the number and send it to an Arduino in order to create a working speedometer with a servo. My Servo can handle 180 degrees so I map the cars speed to a 0->180 scale.

I’ve kept it quite simple here but you can go crazy with this code. You could wire up a 7 segment display and show the current gear or lap or maybe you can hook up some LEDs to create an RPM indicator. The game outputs 38 different values so there is enough data available. Please post your examples here for inspiration!

Special thanks to Robert Gray, who did something similar in C# and has been an inspiration for this project.

Processing:

import controlP5.*;
import hypermedia.net.*;
import processing.serial.*;
import cc.arduino.*;

Arduino arduino;
int arduinoPos = 0;

ControlP5 cp5;
UDP udpRX;

String ip="127.0.0.1";
int portRX=20777;

int pos;

void setup(){
  size(280,280);
  background(255);
  
  // Arduino connection and Servo output
  arduino = new Arduino(this, Arduino.list()[0], 57600);  // Your offset may vary
  arduino.pinMode(9,5);
  
  // Create some dials and gauges on screen
  cp5 = new ControlP5(this);

  cp5.addSlider("rpm")
    .setSize(200,50)
    .setPosition(10,10)
    .setValue(0)
    .setRange(0,19000);
     
  cp5.addNumberbox("gear")
    .setSize(50,50)
    .setPosition(220,10)
    .setValue(0);
    
  cp5.addKnob("speed")
    .setRadius(100)
    .setPosition(10,70)
    .setValue(0)
    .setRange(0,350);
      
      
  // Create new object for receiving 
  udpRX=new UDP(this,portRX,ip);
  udpRX.log(false);
  udpRX.listen(true);
}


void draw(){
// Nothing happens here
}

void receive(byte[] data, String ip, int portRX){
  
  // Function to output all the game data received
  // fullOutput(data);
  
  // Time elapsed since game start
  pos = 0;
  float tTime = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24));
  
  // Lap time
  pos = 4;
  float lapTime = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24));
  
  // Speed, *3.6 for Km/h
  pos = 28;
  float speed = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24))*3.6;
    
  // Gear, neutral = 0
  pos = 132;
  float gear = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24));
  
  // Current lap, starts at 0
  pos = 144;
  float cLap = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24));
  
  // RPM, requires *10 for realistic values
  pos = 148;
  float rpm = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24))*10;

  // Debug the received values
  gameDataOutput(tTime, lapTime, speed, gear, cLap, rpm);
  
  // Output the values to the dashboard
  cp5.getController("rpm").setValue(rpm);
  cp5.getController("gear").setValue(gear);
  cp5.getController("speed").setValue(speed);
  
  // Send the speed to the Servo
  arduinoPos = (int)map(speed, 0, 350, 1, 180); // Note that I've set the max speed to 350, you might have to change this for other games
  arduino.servoWrite(9, 180-arduinoPos);
}

void gameDataOutput(float tTime, float lapTime, float speed, float gear, float cLap, float rpm){
  println("Total time: " + tTime);
  println("Lap time: " + lapTime);
  println("Speed: " + speed);
  println("Gear: " + gear);
  println("Current lap: " + cLap);
  println("RPM: " + rpm);
}

// Function that outputs all the received game data
void fullOutput(byte[] data){
  
  // Loop all the received bytes
  for(int i=0; i <= data.length-1; i++){
    
    // Values consist of 4 bytes
    if(i % 4 == 0){
      
      // Combine 4 bytes to the value
      float val = Float.intBitsToFloat((data[i] & 0xff) | ((data[i+1] & 0xff) << 8) | ((data[i+2] & 0xff) << 16) | ((data[i+3] & 0xff) << 24));
      
      // Output the 'raw' value
      println("Value received at position " + i + " = " + val);
      
    }
  }
}

And here is a screencast of the Processing sketch in action. Note that I've commented out the Arduino stuff here since it's a screencast..

congratulations
very interesting project.
xsim I use for the same purpose. I manufacture a tachometer and minisimulador.
but its application is very interesting. I will be attentive to your updates on this.
sorry for my English: D

Thanks for the code! Couldn't have completed my project without it.

Took foooreever to figure out though, since I know next to nothing about binary or UDP. Found that I also needed the hypermedia.net library, which was a pain to find.

I went without firmata, so I could use more RPM LEDs and have a 7 segment gear display on the MEGA (firmata was limited to the base 13 pins).

Here's my video in a custom sim.

Thanks for posting!

Hello
I d like to create a simulator. So I need to modify the program but I don't know how. I have an arduino mega and 6 NEMA stepper motor. I need the following informations, to "apply" them on the stepper motors:

  • Suspension position front right/left
  • Suspension position rear right/left
  • G force longitudinal/latitudinal

Can you help me??
Greetings

Hello
can you please tell me where you found the positions?
For example:

// Gear, neutral = 0
pos = 132;
float gear = Float.intBitsToFloat((data[pos] & 0xff) | ((data[pos+1] & 0xff) << 8) | ((data[pos+2] & 0xff) << 16) | ((data[pos+3] & 0xff) << 24));

from where do you know that the Gear position is 132???
thx

from where do you know that the Gear position is 132???

The smiley face told him/her!

Now, maybe you can see why we insist on code tags!

PaulS:
The smiley face told him/her!

Now, maybe you can see why we insist on code tags!

hahahaha maybe