TelemetryPC3

Responsive image
Yesterday in this blog post I anticipated this new project, but what is it? what is it for?
This is a really simple telemetry software that display the telemetry data captured by RST_UDP.

Before to go into the details, I want you to know that you can get the source code on GitHub:
https://github.com/CodeSailer/TelemetryPC3

What is does?

It load the data from the text file created by RST_UDP, and display a graph that show the telemetry.
To create the graph I used OxyPlot, which is very simple to use.

How to use it

Before use it you need a telemetry file, to make that open RST_UDP and start Project CARS 3(or 2) then do some laps(any mode).
Now you’ll find a file called telemetry_[n].txt inside the RST_UDP directory.
Open it with this software.
On the left you have the graph.
On the right you have the Settings that allow you to change which lap to analyze, you can write all to show all the datas without filter them by lap.
Then you have the times of the Sector 1, Sector 2, Sector 3 and the lap time.
Last, you have the min and max values of the g-forces.

The code

Now you know how to use this software, so let me explain the code a bit.
The first thing I did was create all the data series and the the plot model for OxyPlot, then I created 3 list for the g-forces(you’ll see later why):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
PlotModel myModel = new PlotModel { };

LineSeries speedSeries = new LineSeries() { Title = "speed"};
LineSeries tFLSeries = new LineSeries() { Title = "t° FL" };
LineSeries tFRSeries = new LineSeries() { Title = "t° FR" };
LineSeries tRLSeries = new LineSeries() { Title = "t° RL" };
LineSeries tRRSeries = new LineSeries() { Title = "t° RR" };
LineSeries throttleSeries = new LineSeries() { Title = "throttle" };
LineSeries brakeSeries = new LineSeries() { Title = "brake" };
LineSeries steeringSeries = new LineSeries() { Title = "steering" };
string telemetryFileName;

List<float> gforce_x = new List<float>();
List<float> gforce_y = new List<float>();
List<float> gforce_z = new List<float>();

I added the string telemetryFile inside the function Form1() to be able to open multiple window without the necessity to reload each time the telemetry file, then I just set the color of each series:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public Form1( string telemetryFile = null)
    {
        if(telemetryFile != null)
        {
            telemetryFileName = telemetryFile;
        }
        InitializeComponent();
        
        speedSeries.Color = OxyColors.Blue;
        tFLSeries.Color = OxyColors.Orange;
        tFRSeries.Color = OxyColors.DarkOrange;
        tRLSeries.Color = OxyColors.Purple;
        tRRSeries.Color = OxyColors.DarkViolet;
        throttleSeries.Color = OxyColors.Green;
        brakeSeries.Color = OxyColors.Red;
        steeringSeries.Color = OxyColors.Black;
    }

When you click Open in the menu it clear the plot and it load the new selected file.
To clear the plot I did:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public void ClearPlot()
    {
        // reset plot
        myModel.InvalidatePlot(true);
        myModel.Series.Clear();
        //reset series data
        speedSeries.Points.Clear();
        tFLSeries.Points.Clear();
        tFRSeries.Points.Clear();
        tRLSeries.Points.Clear();
        tRRSeries.Points.Clear();
        throttleSeries.Points.Clear();
        brakeSeries.Points.Clear();
        steeringSeries.Points.Clear();
    }

To load the telemetry file I did:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public void openTelemetryFile(string fileName, string lapNumber)
    {
        List<string> tmp = File.ReadAllLines(fileName).ToList();
        for (int i = 0; i < tmp.Count; i++)
        {
            string[] line_part = tmp[i].Split(' ');
            if (lapNumber != "all")
            {
                if (line_part[0] == lapNumber)
                {
                    speedSeries.Points.Add(new DataPoint(i, double.Parse(line_part[1]) * 3.6));
                    s1time.Text = line_part[2];
                    s2time.Text = line_part[3];
                    s3time.Text = line_part[4];
                    laptime.Text = line_part[5];
                    tFLSeries.Points.Add(new DataPoint(i, double.Parse(line_part[6])+(-273.15)));
                    tFRSeries.Points.Add(new DataPoint(i, double.Parse(line_part[7]) + (-273.15)));
                    tRLSeries.Points.Add(new DataPoint(i, double.Parse(line_part[8]) + (-273.15)));
                    tRRSeries.Points.Add(new DataPoint(i, double.Parse(line_part[9]) + (-273.15)));

                    throttleSeries.Points.Add(new DataPoint(i, double.Parse(line_part[10])/255*100));
                    brakeSeries.Points.Add(new DataPoint(i, double.Parse(line_part[11])/255*100));
                    steeringSeries.Points.Add(new DataPoint(i, double.Parse(line_part[12])));
                    gforce_x.Add(float.Parse(line_part[13]) / 10);
                    gforce_y.Add(float.Parse(line_part[14]) / 10);
                    gforce_z.Add(float.Parse(line_part[15]) / 10);
                }
            }
            else
            {
                speedSeries.Points.Add(new DataPoint(i, double.Parse(line_part[1]) * 3.6));
                s1time.Text = line_part[2];
                s2time.Text = line_part[3];
                s3time.Text = line_part[4];
                laptime.Text = line_part[5];
                tFLSeries.Points.Add(new DataPoint(i, double.Parse(line_part[6]) + (-273.15)));
                tFRSeries.Points.Add(new DataPoint(i, double.Parse(line_part[7]) + (-273.15)));
                tRLSeries.Points.Add(new DataPoint(i, double.Parse(line_part[8]) + (-273.15)));
                tRRSeries.Points.Add(new DataPoint(i, double.Parse(line_part[9]) + (-273.15)));

                throttleSeries.Points.Add(new DataPoint(i, double.Parse(line_part[10]) / 255 * 100));
                brakeSeries.Points.Add(new DataPoint(i, double.Parse(line_part[11])/255*100));
                steeringSeries.Points.Add(new DataPoint(i, double.Parse(line_part[12])));
                gforce_x.Add(float.Parse(line_part[13]) / 10);
                gforce_y.Add(float.Parse(line_part[14]) / 10);
                gforce_z.Add(float.Parse(line_part[15]) / 10);
            }

        }
        myModel.Series.Add(speedSeries);
        myModel.Series.Add(tFLSeries);
        myModel.Series.Add(tFRSeries);
        myModel.Series.Add(tRLSeries);
        myModel.Series.Add(tRRSeries);
        myModel.Series.Add(throttleSeries);
        myModel.Series.Add(brakeSeries);
        myModel.Series.Add(steeringSeries);
        if (gforce_x.Count() != 0)
        {
            gforce_x_min.Text = gforce_x.Min().ToString("0.0");
            gforce_x_max.Text = gforce_x.Max().ToString("0.0");
            gforce_y_min.Text = gforce_y.Min().ToString("0.0");
            gforce_y_max.Text = gforce_y.Max().ToString("0.0");
            gforce_z_min.Text = gforce_z.Min().ToString("0.0");
            gforce_z_max.Text = gforce_z.Max().ToString("0.0");
        }
        this.plotView1.Model = myModel;
    }

Let me explain a bit more.

1
2
3
4
5
6
List<string> tmp = File.ReadAllLines(fileName).ToList();
for (int i = 0; i < tmp.Count; i++)
{
    string[] line_part = tmp[i].Split(' ');
    //...
}

This load the telemetry file, and through the for loop it read it line by line.
The first thing that it does is split each line by spaces, it does like this because the telemetry file is written like this:

lap speed s1->s3 laptime t°Fl->RR throttle brake g-forceXYZ
0     1    2->4     5       6->9     10      11      12->14

The first part of the line is the lap number(line_part[0]), then the speed(line_part[1]), and so on.
Then it filter the data by lap number and it will display only the data that match the lap number setted in the textbox. After that it add the data to the series:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
speedSeries.Points.Add(new DataPoint(i, double.Parse(line_part[1]) * 3.6));
s1time.Text = line_part[2];
s2time.Text = line_part[3];
s3time.Text = line_part[4];
laptime.Text = line_part[5];
tFLSeries.Points.Add(new DataPoint(i, double.Parse(line_part[6]) + (-273.15)));
tFRSeries.Points.Add(new DataPoint(i, double.Parse(line_part[7]) + (-273.15)));
tRLSeries.Points.Add(new DataPoint(i, double.Parse(line_part[8]) + (-273.15)));
tRRSeries.Points.Add(new DataPoint(i, double.Parse(line_part[9]) + (-273.15)));

throttleSeries.Points.Add(new DataPoint(i, double.Parse(line_part[10]) / 255 * 100));
brakeSeries.Points.Add(new DataPoint(i, double.Parse(line_part[11])/255*100));
steeringSeries.Points.Add(new DataPoint(i, double.Parse(line_part[12])));
gforce_x.Add(float.Parse(line_part[13]) / 10);
gforce_y.Add(float.Parse(line_part[14]) / 10);
gforce_z.Add(float.Parse(line_part[15]) / 10);

Now it add the series to the plot model:

1
2
3
4
5
6
7
8
myModel.Series.Add(speedSeries);
myModel.Series.Add(tFLSeries);
myModel.Series.Add(tFRSeries);
myModel.Series.Add(tRLSeries);
myModel.Series.Add(tRRSeries);
myModel.Series.Add(throttleSeries);
myModel.Series.Add(brakeSeries);
myModel.Series.Add(steeringSeries);

Then it update the textbox of the g-forces:

1
2
3
4
5
6
7
8
9
if (gforce_x.Count() != 0)
{
    gforce_x_min.Text = gforce_x.Min().ToString("0.0");
    gforce_x_max.Text = gforce_x.Max().ToString("0.0");
    gforce_y_min.Text = gforce_y.Min().ToString("0.0");
    gforce_y_max.Text = gforce_y.Max().ToString("0.0");
    gforce_z_min.Text = gforce_z.Min().ToString("0.0");
    gforce_z_max.Text = gforce_z.Max().ToString("0.0");
}

I’ve used a list for store the g-forces so I was able to call the Min() and the Max() functions to show their values.

The las thing that it does is set the plotmodel to the plotview:

1
this.plotView1.Model = myModel;

Conclusion

I hope you found useful or, at least, interesting.
You can find all the source on GitHub on the link above, with the very permissive MIT license.
You can contact me on Twitter: @CodeSailer