pythonsocketsctypespacket

How can I unpack a F1 23 Telemetry Packet to a dictionary correctly?


For context: I'm trying to adjust the f1_22_telemetry code to the new F1 23 UDP Socket (my repo).
Here is the Documentation by EA: https://answers.ea.com/t5/General-Discussion/F1-23-UDP-Specification/td-p/12632888?attachment-id=704910
Some packets appear to be working fine, but others show weird/wrong values after being converted to a dictionary.

I took Chris Hannam's Code and changed the classes for the Packets with the information in the EA documentation. So the logic should be the same and worked fine with the F1 22 Socket.

Example: PacketSessionData
My Class:

class PacketSessionData(Packet):
    _fields_ = [
        ("header", PacketHeader),
        ("weather", ctypes.c_uint8),
        ("trackTemperature", ctypes.c_int8),
        ("airTemperature", ctypes.c_int8),
        ("totalLaps", ctypes.c_uint8),
        ("trackLength", ctypes.c_uint16),
        ("sessionType", ctypes.c_uint8),
        ("trackId", ctypes.c_int8),
        ("formula", ctypes.c_uint8),
        ("sessionTimeLeft", ctypes.c_uint16),
        ("sessionDuration", ctypes.c_uint16),
        ("pitSpeedLimit", ctypes.c_uint8),
        ("gamePaused", ctypes.c_uint8),
        ("isSpectating", ctypes.c_uint8),
        ("spectatorCarIndex", ctypes.c_uint8),
        ("sliProNativeSupport", ctypes.c_uint8),
        ("numMarshalZones", ctypes.c_uint8),
        ("marshalZones", MarshalZone * 21),
        ("safetyCarStatus", ctypes.c_uint8),
        ("networkGame", ctypes.c_uint8),
        ("numWeatherForecastSamples", ctypes.c_uint8),
        ("forecastAccuracy", ctypes.c_uint8),
        ("aiDifficulty", ctypes.c_uint8),
        ("seasonLinkIdentifier", ctypes.c_uint32),
        ("weekendLinkIdentifier", ctypes.c_uint32),
        ("sessionLinkIdentifier", ctypes.c_uint32),
        ("pitStopWindowIdealLap", ctypes.c_uint8),
        ("pitStopWindowLatestLap", ctypes.c_uint8),
        ("pitStopRejoinPosition", ctypes.c_uint8),
        ("steeringAssist", ctypes.c_uint8),
        ("brakingAssist", ctypes.c_uint8),
        ("gearboxAssist", ctypes.c_uint8),
        ("pitAssist", ctypes.c_uint8),
        ("pitReleaseAssist", ctypes.c_uint8),
        ("ERSAssist", ctypes.c_uint8),
        ("DRSAssist", ctypes.c_uint8),
        ("dynamicRacingLine", ctypes.c_uint8),
        ("dynamicRacingLineType", ctypes.c_uint8),
        ("gameMode", ctypes.c_uint8),
        ("ruleSet", ctypes.c_uint8),
        ("timeOfDay", ctypes.c_uint32),
        ("sessionLength", ctypes.c_uint8),
        ("speedUnitsLeadPlayer", ctypes.c_uint8),
        ("speedUnitsSecondaryPlayer", ctypes.c_uint8),
        ("numSafetyCarPeriods", ctypes.c_uint8),
        ("numRedFlagPeriods", ctypes.c_uint8),
    ]

The Documentation:

struct PacketSessionData
{
    PacketHeader    m_header;                   // Header

    uint8           m_weather;                  // Weather - 0 = clear, 1 = light cloud, 2 = overcast
                                                // 3 = light rain, 4 = heavy rain, 5 = storm
    int8                m_trackTemperature;     // Track temp. in degrees celsius
    int8                m_airTemperature;       // Air temp. in degrees celsius
    uint8           m_totalLaps;            // Total number of laps in this race
    uint16          m_trackLength;              // Track length in metres
    uint8           m_sessionType;          // 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P
                                                // 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ
                                                // 10 = R, 11 = R2, 12 = R3, 13 = Time Trial
    int8            m_trackId;              // -1 for unknown, see appendix
    uint8           m_formula;                      // Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2,
                                                 // 3 = F1 Generic, 4 = Beta, 5 = Supercars
// 6 = Esports, 7 = F2 2021
    uint16          m_sessionTimeLeft;      // Time left in session in seconds
    uint16          m_sessionDuration;      // Session duration in seconds
    uint8           m_pitSpeedLimit;        // Pit speed limit in kilometres per hour
    uint8           m_gamePaused;                // Whether the game is paused – network game only
    uint8           m_isSpectating;         // Whether the player is spectating
    uint8           m_spectatorCarIndex;    // Index of the car being spectated
    uint8           m_sliProNativeSupport;  // SLI Pro support, 0 = inactive, 1 = active
    uint8           m_numMarshalZones;          // Number of marshal zones to follow
    MarshalZone     m_marshalZones[21];             // List of marshal zones – max 21
    uint8           m_safetyCarStatus;           // 0 = no safety car, 1 = full
                                                 // 2 = virtual, 3 = formation lap
    uint8           m_networkGame;               // 0 = offline, 1 = online
    uint8           m_numWeatherForecastSamples; // Number of weather samples to follow
    WeatherForecastSample m_weatherForecastSamples[56];   // Array of weather forecast samples
    uint8           m_forecastAccuracy;          // 0 = Perfect, 1 = Approximate
    uint8           m_aiDifficulty;              // AI Difficulty rating – 0-110
    uint32          m_seasonLinkIdentifier;      // Identifier for season - persists across saves
    uint32          m_weekendLinkIdentifier;     // Identifier for weekend - persists across saves
    uint32          m_sessionLinkIdentifier;     // Identifier for session - persists across saves
    uint8           m_pitStopWindowIdealLap;     // Ideal lap to pit on for current strategy (player)
    uint8           m_pitStopWindowLatestLap;    // Latest lap to pit on for current strategy (player)
    uint8           m_pitStopRejoinPosition;     // Predicted position to rejoin at (player)
    uint8           m_steeringAssist;            // 0 = off, 1 = on
    uint8           m_brakingAssist;             // 0 = off, 1 = low, 2 = medium, 3 = high
    uint8           m_gearboxAssist;             // 1 = manual, 2 = manual & suggested gear, 3 = auto
    uint8           m_pitAssist;                 // 0 = off, 1 = on
    uint8           m_pitReleaseAssist;          // 0 = off, 1 = on
    uint8           m_ERSAssist;                 // 0 = off, 1 = on
    uint8           m_DRSAssist;                 // 0 = off, 1 = on
    uint8           m_dynamicRacingLine;         // 0 = off, 1 = corners only, 2 = full
    uint8           m_dynamicRacingLineType;     // 0 = 2D, 1 = 3D
    uint8           m_gameMode;                  // Game mode id - see appendix
    uint8           m_ruleSet;                   // Ruleset - see appendix
    uint32          m_timeOfDay;                 // Local time of day - minutes since midnight
    uint8           m_sessionLength;             // 0 = None, 2 = Very Short, 3 = Short, 4 = Medium
// 5 = Medium Long, 6 = Long, 7 = Full
    uint8           m_speedUnitsLeadPlayer;             // 0 = MPH, 1 = KPH
    uint8           m_temperatureUnitsLeadPlayer;       // 0 = Celsius, 1 = Fahrenheit
    uint8           m_speedUnitsSecondaryPlayer;        // 0 = MPH, 1 = KPH
    uint8           m_temperatureUnitsSecondaryPlayer;  // 0 = Celsius, 1 = Fahrenheit
    uint8           m_numSafetyCarPeriods;              // Number of safety cars called during session
    uint8           m_numVirtualSafetyCarPeriods;       // Number of virtual safety cars called
    uint8           m_numRedFlagPeriods;                // Number of red flags called during session
};

When I receive a SessionData-Packet, there are values that should be impossible to get according to the documentation. e.g.:
gearBoxAssist: 33
pitReleaseAssist: 27

So I assume something goes wrong during translating the bytes to the dictionary.


Solution

  • From the documentation:

    Packet Types

    [...] Please note that all values are encoded using Little Endian format. All data is packed.

    Session Packet

    [...] Size: 644 bytes

    Here's a complete definition that matches the documented structure size of 644 bytes.

    The problems were:

    1. Sub-structures missing.
    2. Structures weren't packed.
    3. Four members of PacketSessionData were missing.

    LittleEndianStructure is used in the off-chance portability is needed.

    import ctypes as ct
    
    class PacketHeader(ct.LittleEndianStructure):
        _pack_ = 1
        _fields_ = (('m_packetFormat', ct.c_uint16),
                    ('m_gameYear', ct.c_uint8),
                    ('m_gameMajorVersion', ct.c_uint8),
                    ('m_gameMinorVersion', ct.c_uint8),
                    ('m_packetVersion', ct.c_uint8),
                    ('m_packetId', ct.c_uint8),
                    ('m_sessionUID', ct.c_uint64),
                    ('m_sessionTime', ct.c_float),
                    ('m_frameIdentifier', ct.c_uint32),
                    ('m_overallFrameIdentifier', ct.c_uint32),
                    ('m_playerCarIndex', ct.c_uint8),
                    ('m_secondaryPlayerCarIndex', ct.c_uint8))
    
    class MarshalZone(ct.LittleEndianStructure):
        _pack_ = 1
        _fields_ = (('m_zoneStart', ct.c_float),
                    ('m_zoneFlag', ct.c_int8))
    
    class WeatherForecastSample(ct.LittleEndianStructure):
        _pack_ = 1
        _fields_ = (('m_sessionType', ct.c_uint8),
                    ('m_timeOffset', ct.c_uint8),
                    ('m_weather', ct.c_uint8),
                    ('m_trackTemperature', ct.c_int8),
                    ('m_trackTemperatureChange', ct.c_int8),
                    ('m_airTemperature', ct.c_int8),
                    ('m_airTemperatureChange', ct.c_int8),
                    ('m_rainPercentage', ct.c_uint8))
    
    class PacketSessionData(ct.LittleEndianStructure):
        _pack_ = 1
        _fields_ = (('m_header', PacketHeader),
                    ('m_weather', ct.c_uint8),
                    ('m_trackTemperature', ct.c_int8),
                    ('m_airTemperature', ct.c_int8),
                    ('m_totalLaps', ct.c_uint8),
                    ('m_trackLength', ct.c_uint16),
                    ('m_sessionType', ct.c_uint8),
                    ('m_trackId', ct.c_int8),
                    ('m_formula', ct.c_uint8),
                    ('m_sessionTimeLeft', ct.c_uint16),
                    ('m_sessionDuration', ct.c_uint16),
                    ('m_pitSpeedLimit', ct.c_uint8),
                    ('m_gamePaused', ct.c_uint8),
                    ('m_isSpectating', ct.c_uint8),
                    ('m_spectatorCarIndex', ct.c_uint8),
                    ('m_sliProNativeSupport', ct.c_uint8),
                    ('m_numMarshalZones', ct.c_uint8),
                    ('m_marshalZones', MarshalZone * 21),
                    ('m_safetyCarStatus', ct.c_uint8),
                    ('m_networkGame', ct.c_uint8),
                    ('m_numWeatherForecastSamples', ct.c_uint8),
                    ('m_weatherForecastSamples', WeatherForecastSample * 56),  # missing
                    ('m_forecastAccuracy', ct.c_uint8),
                    ('m_aiDifficulty', ct.c_uint8),
                    ('m_seasonLinkIdentifier', ct.c_uint32),
                    ('m_weekendLinkIdentifier', ct.c_uint32),
                    ('m_sessionLinkIdentifier', ct.c_uint32),
                    ('m_pitStopWindowIdealLap', ct.c_uint8),
                    ('m_pitStopWindowLatestLap', ct.c_uint8),
                    ('m_pitStopRejoinPosition', ct.c_uint8),
                    ('m_steeringAssist', ct.c_uint8),
                    ('m_brakingAssist', ct.c_uint8),
                    ('m_gearboxAssist', ct.c_uint8),
                    ('m_pitAssist', ct.c_uint8),
                    ('m_pitReleaseAssist', ct.c_uint8),
                    ('m_ERSAssist', ct.c_uint8),
                    ('m_DRSAssist', ct.c_uint8),
                    ('m_dynamicRacingLine', ct.c_uint8),
                    ('m_dynamicRacingLineType', ct.c_uint8),
                    ('m_gameMode', ct.c_uint8),
                    ('m_ruleSet', ct.c_uint8),
                    ('m_timeOfDay', ct.c_uint32),
                    ('m_sessionLength', ct.c_uint8),
                    ('m_speedUnitsLeadPlayer', ct.c_uint8),
                    ('m_temperatureUnitsLeadPlayer', ct.c_uint8),  # missing
                    ('m_speedUnitsSecondaryPlayer', ct.c_uint8),
                    ('m_temperatureUnitsSecondaryPlayer', ct.c_uint8),  # missing
                    ('m_numSafetyCarPeriods', ct.c_uint8),
                    ('m_numVirtualSafetyCarPeriods', ct.c_uint8),  # missing
                    ('m_numRedFlagPeriods', ct.c_uint8))
    
    assert ct.sizeof(PacketSessionData) == 644  # for verification