Results 1 to 5 of 5

Thread: Capturing and reading F1 UDP telemetry

  1. #1
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Capturing and reading F1 UDP telemetry

    Hi all!

    I am back again with a new project.

    The concept:
    I am making an overlay for a F1 game to use on my Twitch channel. The game has a UDP telemetry feature that can be activated to provide live game information to racing gear equipment and monitoring apps that players can use. The data is transmitted locally, or to an IP, and a port that is provided to the game by the user.
    2017-11-30 18_28_39-F1 2017.jpg

    Here is the information that I have about the data being transmitted:

    UDP Packet Structure:
    The data is sent as raw data in the UDP packet, converted to a char array, with packing enabled (no padding to align different sized types). To decode this into something usable it should be a case of casting the packet data back to the UDPPacket struct (or another structure with the same layout). The layout of the UDP data is as follows:

    Qt Code:
    1. // Packet size – 1289 bytes
    2.  
    3. struct UDPPacket
    4. {
    5. float m_time;
    6. float m_lapTime;
    7. float m_lapDistance;
    8. float m_totalDistance;
    9. float m_x; // World space position
    10. float m_y; // World space position
    11. float m_z; // World space position
    12. float m_speed; // Speed of car in MPH
    13. ...
    14. float m_yd; // World space forward direction
    15. float m_zd; // World space forward direction
    16. float m_susp_pos[4]; // Note: All wheel arrays have the order:
    17. float m_susp_vel[4]; // RL, RR, FL, FR
    18. float m_wheel_speed[4];
    19. ...
    20. float m_gforce_lat;
    21. float m_gforce_lon;
    22. ...
    23. byte m_rev_lights_percent; // NEW: rev lights indicator (percentage)
    24. byte m_is_spectating; // NEW: whether the player is spectating
    25. byte m_spectator_car_index; // NEW: index of the car being spectated
    26.  
    27.  
    28. // Car data
    29. byte m_num_cars; // number of cars in data
    30. byte m_player_car_index; // index of player's car in the array
    31. ...
    32. CarUDPData m_car_data[20]; // data for all cars on track
    33. float m_ang_acc_y; // NEW (v1.8) angular acceleration y-component
    34. float m_ang_acc_z; // NEW (v1.8) angular acceleration z-component
    35. };
    36.  
    37. struct CarUDPData
    38. {
    39. float m_worldPosition[3]; // world co-ordinates of vehicle
    40. float m_lastLapTime;
    41. byte m_driverId;
    42. byte m_teamId;
    43. ...
    44. byte m_currentLapInvalid; // current lap invalid - 0 = valid, 1 = invalid
    45. byte m_penalties; // NEW: accumulated time penalties in seconds to be added
    46. };
    To copy to clipboard, switch view to plain text mode 

    Adititional data:
    Qt Code:
    1. Track and Team IDs
    2. ID Track
    3. 0 Melbourne
    4. 1 Sepang
    5. 2 Shanghai
    6. ...
    7. 23 Texas Short
    8. 24 Suzuka Short
    9.  
    10. Team Team ID
    11. Mercedes 4
    12. Redbull 0
    13. ...
    14. Ferrari 1
    15. Sauber 5
    16.  
    17. Classic Team Team ID
    18. Williams 1992 0
    19. ...
    20. Redbull 2010 11
    21. McLaren 1991 12
    22.  
    23. Driver ID
    24. Lewis Hamilton 9
    25. Valtteri Bottas 15
    26. ...
    27. Gert Waldmuller 33
    28. Julian Quesada 34
    To copy to clipboard, switch view to plain text mode 

    The code:
    To receive this data, I have used the following code that can be found here:
    Qt Code:
    1. // receiver.h
    2.  
    3. #ifndef RECEIVER_H
    4. #define RECEIVER_H
    5. #include <QWidget>
    6.  
    7. class QLabel;
    8. class QUdpSocket;
    9. class QAction;
    10.  
    11. class Receiver : public QWidget
    12. {
    13. Q_OBJECT
    14.  
    15. public:
    16. Receiver(QWidget *parent = 0);
    17.  
    18. private slots:
    19. void processPendingDatagrams();
    20.  
    21. private:
    22. QLabel *statusLabel;
    23. QPushButton *quitButton;
    24. QUdpSocket *udpSocket;
    25. };
    26.  
    27. #endif
    To copy to clipboard, switch view to plain text mode 

    Qt Code:
    1. // receiver.cpp
    2.  
    3. #include <QtWidgets>
    4. #include <QtNetwork>
    5. #include "receiver.h"
    6.  
    7. Receiver::Receiver(QWidget *parent)
    8. : QWidget(parent)
    9. {
    10. statusLabel = new QLabel(tr("Listening for broadcasted messages"));
    11. statusLabel->setWordWrap(true);
    12.  
    13. quitButton = new QPushButton(tr("&Quit"));
    14.  
    15. udpSocket = new QUdpSocket(this);
    16. udpSocket->bind(45454, QUdpSocket::ShareAddress);
    17.  
    18. connect(udpSocket, SIGNAL(readyRead()),
    19. this, SLOT(processPendingDatagrams()));
    20. connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));
    21.  
    22. QHBoxLayout *buttonLayout = new QHBoxLayout;
    23. buttonLayout->addStretch(1);
    24. buttonLayout->addWidget(quitButton);
    25. buttonLayout->addStretch(1);
    26.  
    27. QVBoxLayout *mainLayout = new QVBoxLayout;
    28. mainLayout->addWidget(statusLabel);
    29. mainLayout->addLayout(buttonLayout);
    30. setLayout(mainLayout);
    31.  
    32. setWindowTitle(tr("Broadcast Receiver"));
    33. }
    34.  
    35. void Receiver::processPendingDatagrams()
    36. {
    37. while (udpSocket->hasPendingDatagrams()) {
    38. QByteArray datagram;
    39. datagram.resize(udpSocket->pendingDatagramSize());
    40. udpSocket->readDatagram(datagram.data(), datagram.size());
    41. statusLabel->setText(tr("Received datagram: \"%1\"")
    42. .arg(datagram.data()));
    43. }
    44. }
    To copy to clipboard, switch view to plain text mode 

    The current situation:
    I was successfully able to connect to the UDP telemetry but the data was just not readable. I could tell that it was the correct data because different kinds of data show up for different things I did in the game. However, it is still unreadable.
    Here is a video and a snapshot of how the output looked like:

    vlcsnap-2017-11-30-18h13m11s798.jpg


    Processing the data:
    I assume that this reading issue has to do with how the data was processed and I can see a couple of problems in my early attempt at capturing it.
    The first is casting. I assume that the data can't just be directly read as a series of integer values. I have to create a similar structure to the one that they were sent out as. However, looking at the data structure, I see two different structs, struct UDPPacket and struct CarUDPDat. How can I tell which is which when I am casting? It is hard to believe that the data for CarUDPData will find it's way automatically into it's place in UDPPacket, or can it? How do I go about executing such a casting method in my code?
    The second is endiness. If I understood it correctly, the data is being sent in little endian and I have to make changes to my reading function so that it adheres to that order if it isn't already so.

    Providing the data to the interface:
    If I were to create those two structs, and have their values made available to the qml side of the app in order to render different graphics with them, I assume it would be best to have a model class to pass along the data and to keep the view updated. However, there will always be only one instance of each struct at a time which makes me wonder if an object oriented approach is a proper one here. That is, a class created for each of the two structs which holds all the necessary values.
    If I were to skip the object oriented approach to the UDP structures, maybe I can have them as private members of the receiver class, or a separate class, and create public member functions that return values of specific variables that are requested.

    The interactive overlay:
    A last thing I want to mention is that if possible, I would want make this overlay controlled by viewers watching the stream by allowing them to change between different telemetry layouts. This can be done through installing a Twitch stream extension on my channel that transmitts user clicks/picks on the video player to my overlay app. This in turn changes which view is the active one.
    The app is added to OBS, which is used for streaming the game, as a window capture with an added effect of removing a specified color and replacing it with transparency where the underlying capture, the F1 game capture, is displayed.
    2017-11-30 18_49_51-Monitor _ Restream.io.jpg

    More about Twitch extensions: Twitch TV Extensions
    An example of such an app/overlay: Gamepadviewer.com

    I would appreciate some advice on the approach to choose here as well as how to handle the reading/casting of the UDP telemetry data to make usable and readable for my application.
    I hope that I am making some sense here with my descriptions. All help, tips and advice is very appreciated. Thanks.

  2. #2
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Capturing and reading F1 UDP telemetry

    Update:

    I was able to read the data after casting it using this method:

    Qt Code:
    1. UDPPacket* updPacket = reinterpret_cast<UDPPacket*>(datagram.data());
    To copy to clipboard, switch view to plain text mode 

    and then outputting it to the console using:

    Qt Code:
    1. qDebug() << "Time: " << updPacket->m_time;
    2. qDebug() << "Lap time: " << updPacket->m_lapTime;
    3. qDebug() << "Lap distance: " << updPacket->m_lapDistance;
    4. qDebug() << "Total distance: " << updPacket->m_totalDistance;
    5. qDebug() << "World space position X: " << updPacket->m_x;
    6. qDebug() << "World space position Y: " << updPacket->m_y;
    7. qDebug() << "World space position Z: " << updPacket->m_z;
    8. ...
    To copy to clipboard, switch view to plain text mode 

    Below is a sample of the output I got while driving:

    Qt Code:
    1. Time: 247.21
    2. Lap time: 3.21761
    3. Lap distance: 256.501
    4. Total distance: 5557.78
    5. World space position X: -297.622
    6. World space position Y: 1.87141
    7. World space position Z: 287.999
    8. Speed: 71.6476
    9. World space velocity X: -51.4009
    10. World space velocity Y: 0.235372
    11. World space velocity Y: -49.9126
    12. World space right direction X: 0.696872
    13. World space right direction Y: 0.000541129
    14. World space right direction Z: -0.717195
    15. World space forward direction X: -0.717193
    16. World space forward direction Y: 0.00341658
    17. World space forward direction Z: -0.696867
    18. Susp. position RL: 17.8514
    19. Susp. position RR: 17.1895
    20. Susp. position FL: 13.5203
    21. Susp. position FR: 13.2659
    22. Susp. velocity RL: -10.5386
    23. Susp. velocity RR: -3.86364
    24. Susp. velocity FL: -18.3728
    25. Susp. velocity FR: -4.74988
    26. Susp. speed RL: 71.6071
    27. Susp. speed RR: 71.6319
    28. Susp. speed FL: 71.7294
    29. Susp. speed FR: 71.7293
    30. Throttle: 0
    31. Steer: 0
    32. Brake: 0
    33. Clutch: 0
    34. Gear: 8
    35. G. force latitude: -0.0710501
    36. G. force longitude: -0.792866
    37. Lap: 1
    38. Engine rate: 10318.3
    39. SLI Pro native support: 0
    40. Car position: 1
    41. Kers level: 400000
    42. Kers max level: 400000
    43. DRS: 0
    44. Traction control: 0.5
    45. Anti-lock brakes: 1
    46. Fuel in tank: 10
    47. Fuel capacity: 105
    48. In pits: 0
    49. Sector: 0
    50. Sector 1 time: 0
    51. Sector 2 time: 0
    52. Brakes temperature RL: 19.8746
    53. Brakes temperature RR: 19.8746
    54. Brakes temperature FL: 19.8746
    55. Brakes temperature FR: 19.8746
    56. Tyres pressure RL: 21.5
    57. Tyres pressure RR: 21.5
    58. Tyres pressure FL: 23
    59. Tyres pressure FR: 23
    60. Team ID: 4
    61. Total laps: 1
    62. Track size: 5301.28
    63. Last lap time: 96.2424
    64. Max rpm: 13600
    65. Idle rpm: 4300
    66. Max gears: 9
    67. Session type: 0
    To copy to clipboard, switch view to plain text mode 

    It seems to match what I was doing in the game.
    Now, I will try to find a way to handle those structs and make them available to the qml view.

  3. #3
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Capturing and reading F1 UDP telemetry

    Any ideas on how to deal with the captured data?

    I am thinking of going with the following approach:

    Two C++ classes, Telemetry and TelemetryData. Telemetry has a TelemetryData private member instance. Telemetry captures the UDP data and sends it to TelemetryData by calling a function of TelemetryData setUDPPacket(Type udp packet data). This function would take either a QByteArray of the UDP data and then cast it internally to the packet struct to make it readable, or it will take a pointer to a struct that it has already been cast as.

    In other words:
    Option A:
    Telemetry class captures data.
    Calls function of it's private instance of TelemetryData.
    The function setUDPPacket(QByteArray udpPacketData) takes a QByteArray and casts it as a struct to an instance of said struct to make the data available.

    Option B:
    Telemetry class captures data.
    Calls function setUDPPacket(UDPPacket *udpPacketData) takes a pointer to a UDPPacket struct that the Telemetry class has cast the QByteArray as.
    TelemetryData copies the udpPacketData to it's instance of the UDPPacket struct to make the data available.

    main.cpp registers the class Telemetry and sets an instance of both Telemetry and TelemetryData as context properties. TelemetryData is registered through a pointer that is retrieved from Telemetry's function getTelemetryData() which returns a pointer of it's instance of TelemetryData.
    This makes the TelemetryData class accessible by the QML side.

    The TelemetryData class has get functions for all the variables in the struct UDPPacket such as getSpeed(), getLap() and so on. Those functions are called in the QML side to display the current UDP data.
    To make this possible, the functions are made available through the Q_PROPERTY macro or the Q_GADGET macro.

    The last problem that I will solve is how to update the view. One way is to make variables notify on change through the Q_GADGET or Q_PROPERTY macro as in Q_PROPERTY(float lapTime READ getLapTime NOTIFY lapTimeChanged).

    The problem here is that the struct UDPPacket data is replaced with new data at once. This means that I have to create xChanged() for all the struct variable and call all of them every time the struct data is replaced when a new UDPPacket arrives.

    A better method in my opinion would be to skip the NOTIFY method and all the xChanged signals and just make the QML side refreshes all it's data at a rate of sixty times every second, that is because the UDP data is sent at 60 Hz.

    Any thoughts on this approach? And any tips on making it as lightweight as possible?

  4. #4
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Capturing and reading F1 UDP telemetry

    UDP data is sent at 60 Hz.

    Any thoughts on this approach? And any tips on making it as lightweight as possible?
    If what you are doing with this data is mostly displaying it to the user, then I would throw most of it away. A UI that changes at 60 Hz is ridiculous for displaying to a user. 1 - 2 Hz is more reasonable. You could average values at 60 Hz, but update the display with the average no more than once or twice a second. Humans can't comprehend and act on anything at a faster rate than that.

    One thing you should be careful about - if the data being sent is newly allocated memory for each call, then be sure you properly delete it when you are done, otherwise your program will quickly come to a grinding halt as it runs out of resources.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  5. #5
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Capturing and reading F1 UDP telemetry

    Thanks for the help, I have now achieved the goal. I can update this post later with my source code for those looking for a similar solution.

Similar Threads

  1. capturing keystrokes help
    By davinciomare in forum Newbie
    Replies: 1
    Last Post: 4th October 2016, 18:02
  2. Capturing Video
    By ashkan in forum Newbie
    Replies: 0
    Last Post: 4th February 2009, 12:15
  3. Capturing close
    By steg90 in forum Qt Programming
    Replies: 1
    Last Post: 25th September 2007, 12:22
  4. Keyboard Capturing
    By ToddAtWSU in forum Qt Programming
    Replies: 2
    Last Post: 29th June 2007, 07:37
  5. Mouse Capturing
    By ToddAtWSU in forum Qt Programming
    Replies: 1
    Last Post: 24th August 2006, 20:37

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.