HaiderZia
HaiderZia

Reputation: 11

How to display unpacked UDP data properly

I'm trying to write a small code which displays data received from a game (F1 2019) over UDP.

The F1 2019 game send out data via the UDP. I have been able to receive the packets and have separated the header and data and now unpacked the data according to the structure in which the data is sent using the rawutil module.

The struct in which the packets are sent can be found here: https://forums.codemasters.com/topic/38920-f1-2019-udp-specification/

I'm only interested in the telemetry packet.

import socket
import cdp
import struct
import array
import rawutil
from pprint import pprint

# settings
ip = '0.0.0.0'
port = 20777

# listen for packets
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
listen_socket.bind((ip, port))

while True:
  # Receiving data
  data, address = listen_socket.recvfrom(65536)
  header = data[:20]
  telemetry = data[20:]

  # decode the header
  packetFormat, = rawutil.unpack('<H', header[:2])
  gameMajorVersion, = rawutil.unpack('<B', header[2:3])
  gameMinorVersion, = rawutil.unpack('<B', header[3:4])
  packetVersion, = rawutil.unpack('<B', header[4:5])
  packetId, = rawutil.unpack('<B', header[5:6])
  sessionUID, = rawutil.unpack('<Q', header[6:14])
  sessionTime, = rawutil.unpack('<f', header[14:18])
  frameIdentifier, = rawutil.unpack('<B', header[18:19])
  playerCarIndex, = rawutil.unpack('<B', header[19:20])

  # print all info (just for now)

##  print('Packet Format : ',packetFormat)
##  print('Game Major Version : ',gameMajorVersion)
##  print('Game Minor Version : ',gameMinorVersion)
##  print('Packet Version : ',packetVersion)
##  print('Packet ID : ', packetId)
##  print('Unique Session ID : ',sessionUID)
##  print('Session Time : ',sessionTime)
##  print('Frame Number : ',frameIdentifier)
##  print('Player Car Index : ',playerCarIndex)
##  print('\n\n')

#start getting the packet data for each packet starting with telemetry data

  if (packetId == 6):
    speed, = rawutil.unpack('<H' , telemetry[2:4])
    throttle, = rawutil.unpack('<f' , telemetry[4:8])
    steer, = rawutil.unpack('<f' , telemetry[8:12])
    brake, = rawutil.unpack('<f' , telemetry[12:16])
    gear, = rawutil.unpack('<b' , telemetry[17:18])
    rpm, = rawutil.unpack('<H' , telemetry[18:20])

    print (speed)

The UDP specification states that the speed of the car is sent in km/h. However when I unpack the packet, the speed is a multiple of 256, so 10 km/h is 2560 for example.

I want to know if I'm unpacking the data in the wrong way? or is it something else that is causing this.

The problem is also with the steering for example. the spec says it should be between -1.0 and 1.0 but the actual values are either very large or very small.

screengrab here: https://i.sstatic.net/vAsQe.jpg

Appreciate any help with this. Thanks.

Upvotes: 1

Views: 2058

Answers (2)

Agit Kaplan
Agit Kaplan

Reputation: 51

Haider I wanted to read car speed from Formula 1 2019 too. I found your question, from your question I had some tips and solved my issue. And now i think i must pay back. The reason you get speed multiplied with 256 is you start from wrong byte and this data is formatted little endian. The code you shared starts from 22nd byte to read speed, if you start it from 23rd byte you will get correct speed data.

Upvotes: 0

Napoleon Borntoparty
Napoleon Borntoparty

Reputation: 1962

I recommend you don't use the unpack method, as with big structures (e.g. MotionPacket has 1343 bytes) your code will immediately get very messy. However, if you desperately want to use it, call unpack only once, such as:

fmt = "<HBBBBQfBB"
size = struct.calcsize(fmt)
arr = struct.unpack("<HBBBBQfBB", header[:size])

Alternatively, have a look at ctypes library, especially ctypes.LittleEndianStructure where you can set the _fields_ attribute to a sequence of ctypes (such as uint8 etc, without having to translate them to relevant symbols as with unpack). https://docs.python.org/3.8/library/ctypes.html#ctypes.LittleEndianStructure

Alternatively alternatively, have a look at namedtuples.

Alternatively alternatively alternatively, there's a bunch of python binary IO libs, such as binio where you can declare a structure of ctypes, as this is a thin wrapper anyway.

To fully answer your question, the structure seems to be:

struct PacketHeader
{
    uint16    m_packetFormat;         // 2019
    uint8     m_gameMajorVersion;     // Game major version - "X.00"
    uint8     m_gameMinorVersion;     // Game minor version - "1.XX"
    uint8     m_packetVersion;        // Version of this packet type, all start from 1
    uint8     m_packetId;             // Identifier for the packet type, see below
    uint64    m_sessionUID;           // Unique identifier for the session
    float     m_sessionTime;          // Session timestamp
    uint      m_frameIdentifier;      // Identifier for the frame the data was retrieved on
    uint8     m_playerCarIndex;       // Index of player's car in the array
};

Meaning that the sequence of symbols for unpack should be: <H4BQfLB, because uint in ctypes is actually uint32, where you had uint8. I also replaced BBBB with 4B. Hope this helps.

Upvotes: 1

Related Questions