Hi as said in the title Im trying to receive UDP packets from game (f1 codemasters game) with ESP32, and then show some of that info in the nextion screen.
So i have a "list" of points.
1-reveiving and extracto info from udp packets.
2-show some info in nextion screen.
point one was easy, i have a packet.h with the udp especification of f1 22 (https://github.com/hotlaps/f1-game-udp-specs/tree/main/2022)
Then, with wifiudp, i receive and parse packets.
int packetSize = Udp.parsePacket();
if (packetSize > 0) {
// read the packet into packetBufffer
int packet_length = Udp.read(packetBuffer, sizeof(packetBuffer));
if (packet_length >= sizeof(PacketHeader)) {
struct PacketHeader * header = (struct PacketHeader * ) & packetBuffer;
if (header -> m_packetFormat == 2022) {
uint8_t myCar = header -> m_playerCarIndex;
switch (header -> m_packetId) {
case 2: {
struct PacketLapData * p;
p = (struct PacketLapData * ) packetBuffer;
struct LapData * lapdata = & (p -> m_lapData[myCar]);
lastLapTimeMS = lapdata -> m_lastLapTimeInMS;
currentLapTimeMS = lapdata -> m_currentLapTimeInMS;
sector1TimeMS = lapdata -> m_sector1TimeInMS;
sector2TimeMS = lapdata -> m_sector2TimeInMS;
lapDistance = lapdata -> m_lapDistance;
carPosition = lapdata -> m_carPosition;
currentLapNum = lapdata -> m_currentLapNum;
lapDelta = lastLapTimeMS - best;
if (lapDelta < 0) {
best = currentLapTimeMS;
}
}
break;
Also i have a rotary encoder to switch between pages.
The second part is driving me crazy.
I have configured baud=115200 on nextion editor. I have put
Serial2.begin(115200)
On setup().
And Im "writing" text like this:
unsigned int minutes = currentLapTimeMS / 60000;
unsigned int seconds = (int)(currentLapTimeMS % 60000)/1000;
unsigned int milliseconds = ((currentLapTimeMS % 60000)%1000);
Serial2.print(F("laptime.txt=\""));
Serial2.print(seconds);
Serial2.print(F("\""));
Serial2.print(F("\xFF\xFF\xFF"));
When printing "minutes" its alright. but, when printing seconds, it breaks (i have no tried to print milliseconds)
The textfield shows the correct value, then the next... but after some time, it stucks, and then after another few seconds, shows the correct value.
The game its configured with 60Hz UDP rate speed, but i have tried the lower value (10Hz) and nothing changes.
Its a speed problem? nextion/serial Can support a refresh rate to be able to change the text every millisecond? or every second?
And its also one textfield, in that screen will be 11, and a progressbar. If one textfield lags... its going to be a nightmare.
Any hints, or ideas?, im stuck...
edit:
This is all the code from the main program:
#include <WiFiUdp.h>
#include "NewEncoder.h"
#include <WiFi.h>
#include "Packets.h"
//Includes relacionados con el uso de la pantalla nextion
#include <SPI.h>
//Creamos objeto UDP
WiFiUDP Udp;
//Definición de constantes
#define MODE_LONG_RANGE_MODE 0x80
#define UDP_TX_PACKET_MAX_SIZE 6250
//Constantes UDP
const uint16_t F1_UDP_PORT = 20777;
uint8_t packetBuffer[UDP_TX_PACKET_MAX_SIZE];
//Datos conexión Wifi
const char * ssid = "xxxxxxxxxxx";
const char * password = "xxxxxxxxxx";
//Variables
int current = 0;
float best = 0;
//Variables nextio
void handleEncoder(void *pvParameters);
void ESP_ISR callBack(NewEncoder *encPtr, const volatile NewEncoder::EncoderState *state, void *uPtr);
QueueHandle_t encoderQueue;
volatile int16_t prevEncoderValue;
void setup() {
Serial.begin(9600);
Serial2.begin(115200);
//Conectamos a la wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("\nConectando");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
//Mostramos la IP
Serial.println("\nConectado a la wifi");
Serial.print("Dirección IP: ");
Serial.println(WiFi.localIP());
BaseType_t success = xTaskCreatePinnedToCore(handleEncoder, "Handle Encoder", 1900, NULL, 2, NULL, 1);
if (!success) {
printf("Failed to create handleEncoder task. Aborting.\n");
while (1) {
yield();
}
}
//Iniciamos la recepción UDP
Udp.begin(F1_UDP_PORT);
}
void handleEncoder(void *pvParameters) {
NewEncoder::EncoderState currentEncoderstate;
int16_t currentValue;
encoderQueue = xQueueCreate(1, sizeof(NewEncoder::EncoderState));
if (encoderQueue == nullptr) {
printf("Failed to create encoderQueue. Aborting\n");
vTaskDelete(nullptr);
}
NewEncoder *encoder1 = new NewEncoder(26, 25, 0, 3, 0, FULL_PULSE);
if (encoder1 == nullptr) {
printf("Failed to allocate NewEncoder object. Aborting.\n");
vTaskDelete(nullptr);
}
if (!encoder1->begin()) {
printf("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting.\n");
delete encoder1;
vTaskDelete(nullptr);
}
encoder1->getState(currentEncoderstate);
prevEncoderValue = currentEncoderstate.currentValue;
printf("Encoder Successfully Started at value = %d\n", prevEncoderValue);
encoder1->attachCallback(callBack);
for (;;) {
xQueueReceive(encoderQueue, ¤tEncoderstate, portMAX_DELAY);
currentValue = currentEncoderstate.currentValue;
if (currentValue != prevEncoderValue) {
prevEncoderValue = currentValue;
current = currentValue;
} else {
}
}
vTaskDelete(nullptr);
}
void ESP_ISR callBack(NewEncoder*encPtr, const volatile NewEncoder::EncoderState *state, void *uPtr) {
BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
xQueueOverwriteFromISR(encoderQueue, (void * )state, &pxHigherPriorityTaskWoken);
if (pxHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
void loop() {
switch (current) {
case 0: {
uint32_t lastLapTimeMS;
uint32_t currentLapTimeMS;
float lapDistance;
uint16_t sector1TimeMS;
uint16_t sector2TimeMS;
uint8_t carPosition;
uint8_t currentLapNum;
uint32_t lapDelta;
uint16_t carSpeed;
int8_t gear;
uint8_t revLightsPercent;
uint8_t tempRuedaDelanteIzq;
uint8_t tempRuedaDetrasIzq;
uint8_t tempRuedaDelanteDer;
uint8_t tempRuedaDetrasDer;
float vueltasGasolina;
float porcentaje;
uint16_t trackLength;
struct * bestLap;
struct * lastLap;
float fuelLaps;
int packetSize = Udp.parsePacket();
if (packetSize > 0) {
// read the packet into packetBufffer
int packet_length = Udp.read(packetBuffer, sizeof(packetBuffer));
if (packet_length >= sizeof(PacketHeader)) {
struct PacketHeader * header = (struct PacketHeader * ) & packetBuffer;
if (header -> m_packetFormat == 2022) {
uint8_t myCar = header -> m_playerCarIndex;
switch (header -> m_packetId) {
case 2: {
struct PacketLapData * p;
p = (struct PacketLapData * ) packetBuffer;
struct LapData * lapdata = & (p -> m_lapData[myCar]);
lastLapTimeMS = lapdata -> m_lastLapTimeInMS;
currentLapTimeMS = lapdata -> m_currentLapTimeInMS;
sector1TimeMS = lapdata -> m_sector1TimeInMS;
sector2TimeMS = lapdata -> m_sector2TimeInMS;
lapDistance = lapdata -> m_lapDistance;
carPosition = lapdata -> m_carPosition;
currentLapNum = lapdata -> m_currentLapNum;
lapDelta = lastLapTimeMS - best;
if (lapDelta < 0) {
best = currentLapTimeMS;
}
}
break;
case 6: {
struct PacketCarTelemetryData * p;
p = (struct PacketCarTelemetryData * ) packetBuffer;
struct CarTelemetryData * telemetry = & (p -> m_carTelemetryData[myCar]);
carSpeed = telemetry -> m_speed;
gear = telemetry -> m_gear;
revLightsPercent = telemetry -> m_revLightsPercent;
tempRuedaDelanteIzq = telemetry -> m_tyresSurfaceTemperature[2];
tempRuedaDetrasIzq = telemetry -> m_tyresSurfaceTemperature[0];
tempRuedaDelanteDer = telemetry -> m_tyresSurfaceTemperature[3];
tempRuedaDetrasDer = telemetry -> m_tyresSurfaceTemperature[1];
}
break;
case 7: {
struct PacketCarStatusData * p;
p = (struct PacketCarStatusData * ) packetBuffer;
struct CarStatusData * status = & (p -> m_carStatusData[myCar]);
fuelLaps = status -> m_fuelRemainingLaps;
float ersStoreEnergy = status -> m_ersStoreEnergy;
float ersDeployedThisLap = status -> m_ersDeployedThisLap;
float ersHarvestedThisLapMGUK = status -> m_ersHarvestedThisLapMGUK;
float ersHarvestedThisLapMGUH = status -> m_ersHarvestedThisLapMGUH;
vueltasGasolina = status -> m_fuelRemainingLaps;
porcentaje = (ersStoreEnergy - ersDeployedThisLap + ersHarvestedThisLapMGUK + ersHarvestedThisLapMGUH) / ersStoreEnergy * 100;
if (porcentaje > 100) {
porcentaje = 100;
}
}
break;
default:
break;
}
unsigned int minutes = currentLapTimeMS / 60000;
unsigned int seconds = (int)(currentLapTimeMS % 60000)/1000;
unsigned int milliseconds = ((currentLapTimeMS % 60000)%1000);
Serial2.print(F("laptime.txt=\""));
Serial2.print(seconds);
Serial2.print(F("\""));
Serial2.print(F("\xFF\xFF\xFF"));
}
}
}
}
break;
default:
break;
}
}
And this is packet.h
#pragma pack(push)
#pragma pack(1)
struct PacketHeader
{
uint16_t m_packetFormat; // 2022
uint8_t m_gameMajorVersion; // Game major version - "X.00"
uint8_t m_gameMinorVersion; // Game minor version - "1.XX"
uint8_t m_packetVersion; // Version of this packet type, all start from 1
uint8_t m_packetId; // Identifier for the packet type, see below
uint64_t m_sessionUID; // Unique identifier for the session
float m_sessionTime; // Session timestamp
uint32_t m_frameIdentifier; // Identifier for the frame the data was retrieved on
uint8_t m_playerCarIndex; // Index of player's car in the array
uint8_t m_secondaryPlayerCarIndex; // Index of secondary player's car in the array (splitscreen)
// 255 if no second player
};
struct LapData
{
uint32_t m_lastLapTimeInMS; // Last lap time in milliseconds
uint32_t m_currentLapTimeInMS; // Current time around the lap in milliseconds
uint16_t m_sector1TimeInMS; // Sector 1 time in milliseconds
uint16_t m_sector2TimeInMS; // Sector 2 time in milliseconds
float m_lapDistance; // Distance vehicle is around current lap in metres – could
// be negative if line hasn’t been crossed yet
float m_totalDistance; // Total distance travelled in session in metres – could
// be negative if line hasn’t been crossed yet
float m_safetyCarDelta; // Delta in seconds for safety car
uint8_t m_carPosition; // Car race position
uint8_t m_currentLapNum; // Current lap number
uint8_t m_pitStatus; // 0 = none, 1 = pitting, 2 = in pit area
uint8_t m_numPitStops; // Number of pit stops taken in this race
uint8_t m_sector; // 0 = sector1, 1 = sector2, 2 = sector3
uint8_t m_currentLapInvalid; // Current lap invalid - 0 = valid, 1 = invalid
uint8_t m_penalties; // Accumulated time penalties in seconds to be added
uint8_t m_warnings; // Accumulated number of warnings issued
uint8_t m_numUnservedDriveThroughPens; // Num drive through pens left to serve
uint8_t m_numUnservedStopGoPens; // Num stop go pens left to serve
uint8_t m_gridPosition; // Grid position the vehicle started the race in
uint8_t m_driverStatus; // Status of driver - 0 = in garage, 1 = flying lap
// 2 = in lap, 3 = out lap, 4 = on track
uint8_t m_resultStatus; // Result status - 0 = invalid, 1 = inactive, 2 = active
// 3 = finished, 4 = didnotfinish, 5 = disqualified
// 6 = not classified, 7 = retired
uint8_t m_pitLaneTimerActive; // Pit lane timing, 0 = inactive, 1 = active
uint16_t m_pitLaneTimeInLaneInMS; // If active, the current time spent in the pit lane in ms
uint16_t m_pitStopTimerInMS; // Time of the actual pit stop in ms
uint8_t m_pitStopShouldServePen; // Whether the car should serve a penalty at this stop
};
struct PacketLapData
{
PacketHeader m_header; // Header
LapData m_lapData[22]; // Lap data for all cars on track
uint8_t m_timeTrialPBCarIdx; // Index of Personal Best car in time trial (255 if invalid)
uint8_t m_timeTrialRivalCarIdx; // Index of Rival car in time trial (255 if invalid)
};
struct CarDamageData
{
float m_tyresWear[4]; // Tyre wear (percentage)
uint8_t m_tyresDamage[4]; // Tyre damage (percentage)
uint8_t m_brakesDamage[4]; // Brakes damage (percentage)
uint8_t m_frontLeftWingDamage; // Front left wing damage (percentage)
uint8_t m_frontRightWingDamage; // Front right wing damage (percentage)
uint8_t m_rearWingDamage; // Rear wing damage (percentage)
uint8_t m_floorDamage; // Floor damage (percentage)
uint8_t m_diffuserDamage; // Diffuser damage (percentage)
uint8_t m_sidepodDamage; // Sidepod damage (percentage)
uint8_t m_drsFault; // Indicator for DRS fault, 0 = OK, 1 = fault
uint8_t m_ersFault; // Indicator for ERS fault, 0 = OK, 1 = fault
uint8_t m_gearBoxDamage; // Gear box damage (percentage)
uint8_t m_engineDamage; // Engine damage (percentage)
uint8_t m_engineMGUHWear; // Engine wear MGU-H (percentage)
uint8_t m_engineESWear; // Engine wear ES (percentage)
uint8_t m_engineCEWear; // Engine wear CE (percentage)
uint8_t m_engineICEWear; // Engine wear ICE (percentage)
uint8_t m_engineMGUKWear; // Engine wear MGU-K (percentage)
uint8_t m_engineTCWear; // Engine wear TC (percentage)
uint8_t m_engineBlown; // Engine blown, 0 = OK, 1 = fault
uint8_t m_engineSeized; // Engine seized, 0 = OK, 1 = fault
};
struct PacketCarDamageData
{
PacketHeader m_header; // Header
CarDamageData m_carDamageData[22];
};
struct CarTelemetryData
{
uint16_t m_speed; // Speed of car in kilometres per hour
float m_throttle; // Amount of throttle applied (0.0 to 1.0)
float m_steer; // Steering (-1.0 (full lock left) to 1.0 (full lock right))
float m_brake; // Amount of brake applied (0.0 to 1.0)
uint8_t m_clutch; // Amount of clutch applied (0 to 100)
int8_t m_gear; // Gear selected (1-8, N=0, R=-1)
uint16_t m_engineRPM; // Engine RPM
uint8_t m_drs; // 0 = off, 1 = on
uint8_t m_revLightsPercent; // Rev lights indicator (percentage)
uint16_t m_revLightsBitValue; // Rev lights (bit 0 = leftmost LED, bit 14 = rightmost LED)
uint16_t m_brakesTemperature[4]; // Brakes temperature (celsius)
uint8_t m_tyresSurfaceTemperature[4]; // Tyres surface temperature (celsius)
uint8_t m_tyresInnerTemperature[4]; // Tyres inner temperature (celsius)
uint16_t m_engineTemperature; // Engine temperature (celsius)
float m_tyresPressure[4]; // Tyres pressure (PSI)
uint8_t m_surfaceType[4]; // Driving surface, see appendices
};
struct PacketCarTelemetryData
{
PacketHeader m_header; // Header
CarTelemetryData m_carTelemetryData[22];
uint8_t m_mfdPanelIndex; // Index of MFD panel open - 255 = MFD closed
// Single player, race – 0 = Car setup, 1 = Pits
// 2 = Damage, 3 = Engine, 4 = Temperatures
// May vary depending on game mode
uint8_t m_mfdPanelIndexSecondaryPlayer; // See above
int8_t m_suggestedGear; // Suggested gear for the player (1-8)
// 0 if no gear suggested
};
struct CarSetupData
{
uint8_t m_frontWing; // Front wing aero
uint8_t m_rearWing; // Rear wing aero
uint8_t m_onThrottle; // Differential adjustment on throttle (percentage)
uint8_t m_offThrottle; // Differential adjustment off throttle (percentage)
float m_frontCamber; // Front camber angle (suspension geometry)
float m_rearCamber; // Rear camber angle (suspension geometry)
float m_frontToe; // Front toe angle (suspension geometry)
float m_rearToe; // Rear toe angle (suspension geometry)
uint8_t m_frontSuspension; // Front suspension
uint8_t m_rearSuspension; // Rear suspension
uint8_t m_frontAntiRollBar; // Front anti-roll bar
uint8_t m_rearAntiRollBar; // Front anti-roll bar
uint8_t m_frontSuspensionHeight; // Front ride height
uint8_t m_rearSuspensionHeight; // Rear ride height
uint8_t m_brakePressure; // Brake pressure (percentage)
uint8_t m_brakeBias; // Brake bias (percentage)
float m_rearLeftTyrePressure; // Rear left tyre pressure (PSI)
float m_rearRightTyrePressure; // Rear right tyre pressure (PSI)
float m_frontLeftTyrePressure; // Front left tyre pressure (PSI)
float m_frontRightTyrePressure; // Front right tyre pressure (PSI)
uint8_t m_ballast; // Ballast
float m_fuelLoad; // Fuel load
};
struct PacketCarSetupData
{
PacketHeader m_header; // Header
CarSetupData m_carSetups[22];
};
struct CarStatusData
{
uint8_t m_tractionControl; // Traction control - 0 = off, 1 = medium, 2 = full
uint8_t m_antiLockBrakes; // 0 (off) - 1 (on)
uint8_t m_fuelMix; // Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max
uint8_t m_frontBrakeBias; // Front brake bias (percentage)
uint8_t m_pitLimiterStatus; // Pit limiter status - 0 = off, 1 = on
float m_fuelInTank; // Current fuel mass
float m_fuelCapacity; // Fuel capacity
float m_fuelRemainingLaps; // Fuel remaining in terms of laps (value on MFD)
uint16_t m_maxRPM; // Cars max RPM, point of rev limiter
uint16_t m_idleRPM; // Cars idle RPM
uint8_t m_maxGears; // Maximum number of gears
uint8_t m_drsAllowed; // 0 = not allowed, 1 = allowed
uint16_t m_drsActivationDistance; // 0 = DRS not available, non-zero - DRS will be available
// in [X] metres
uint8_t m_actualTyreCompound; // F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1
// 7 = inter, 8 = wet
// F1 Classic - 9 = dry, 10 = wet
// F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard
// 15 = wet
uint8_t m_visualTyreCompound; // F1 visual (can be different from actual compound)
// 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet
// F1 Classic – same as above
// F2 ‘19, 15 = wet, 19 – super soft, 20 = soft
// 21 = medium , 22 = hard
uint8_t m_tyresAgeLaps; // Age in laps of the current set of tyres
int8_t m_vehicleFiaFlags; // -1 = invalid/unknown, 0 = none, 1 = green
// 2 = blue, 3 = yellow, 4 = red
float m_ersStoreEnergy; // ERS energy store in Joules
uint8_t m_ersDeployMode; // ERS deployment mode, 0 = none, 1 = medium
// 2 = hotlap, 3 = overtake
float m_ersHarvestedThisLapMGUK; // ERS energy harvested this lap by MGU-K
float m_ersHarvestedThisLapMGUH; // ERS energy harvested this lap by MGU-H
float m_ersDeployedThisLap; // ERS energy deployed this lap
uint8_t m_networkPaused; // Whether the car is paused in a network game
};
struct PacketCarStatusData
{
PacketHeader m_header; // Header
CarStatusData m_carStatusData[22];
};
#pragma pack(pop)
The encoder part, its using a task, so it could run in the second core of esp32, avoiding interferences between main code and encoder interrupts. (if anyone has a better approach, im ok with erasing all and starting from scratch)
The only code i cant put is the tft code (for the nextion screen) but, its onle pages, textboxes and a bunch of images.