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.