Print start time on display. Search by meeting instead of session but get both session and meeting data

This commit is contained in:
bissen21 committed 2026-11-07 17:18:12 -06:00
1 parent 3ec87ada9c
commit c0e71374eb
2 files changed
+191 -25

No files matched your search

+176 -17
View File
@@ -6,7 +6,7 @@
#include "ArduinoJson.h" #include "ArduinoJson.h"
#include "config.h" #include "config.h"
struct race_info_t { struct session_info_t {
char session_name[50]; char session_name[50];
char date_start[32]; char date_start[32];
char circuit_short_name[50]; char circuit_short_name[50];
@@ -15,11 +15,69 @@ struct race_info_t {
uint16_t circuit_key; uint16_t circuit_key;
}; };
extern race_info_t race_data; struct meeting_info_t {
uint16_t circuit_key;
uint16_t meeting_key;
char circuit_image_URL[160];
char country_name[40];
char location[40];
char meeting_name[80];
};
struct data_struct_t {
struct session_info_t session_info;
struct meeting_info_t meeting_info;
};
extern data_struct_t race_data;
extern SemaphoreHandle_t dataMutex; extern SemaphoreHandle_t dataMutex;
static int get_race_query_url(char* target, size_t target_size) { #include <Arduino.h>
if (target == NULL) return -1;
// Modifies the character array directly in-place
void cleanToASCIIInPlace(char* str) {
if (!str) return;
size_t readIdx = 0;
size_t writeIdx = 0;
// Process until the null-terminator
while (str[readIdx] != '\0') {
unsigned char c = str[readIdx];
// Handle common UTF-8 multi-byte sequences for Latin characters
if (c == 0xC3) {
readIdx++; // Move past the prefix byte (0xC3)
unsigned char next = str[readIdx];
switch(next) {
case 0xA3: str[writeIdx++] = 'a'; break; // ã -> a
case 0xA1: str[writeIdx++] = 'a'; break; // á -> a
case 0xA9: str[writeIdx++] = 'e'; break; // é -> e
case 0xAD: str[writeIdx++] = 'i'; break; // í -> i
case 0xB3: str[writeIdx++] = 'o'; break; // ó -> o
case 0xBA: str[writeIdx++] = 'u'; break; // ú -> u
case 0xA7: str[writeIdx++] = 'c'; break; // ç -> c
default: str[writeIdx++] = '?'; break; // Fallback for unhandled symbols
}
readIdx++; // Move past the second UTF-8 byte
}
// Standard printable ASCII filter
else {
if (c < 128) {
str[writeIdx++] = c;
}
readIdx++;
}
}
// Explicitly add the new null-terminator to seal the shorter string
str[writeIdx] = '\0';
}
static int get_future_meetings_url(char* target, size_t target_size) {
if (target == NULL) return -1;
time_t now = time(nullptr); time_t now = time(nullptr);
struct tm timeinfo, futureTime; struct tm timeinfo, futureTime;
@@ -32,24 +90,33 @@ static int get_race_query_url(char* target, size_t target_size) {
char date_buffer[12]; char date_buffer[12];
char fdate_buffer[12]; char fdate_buffer[12];
Serial.print("");
strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", &timeinfo); strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", &timeinfo);
Serial.printf("Current Date: %s\n", date_buffer);
strftime(fdate_buffer, sizeof(fdate_buffer), "%Y-%m-%d", &futureTime); strftime(fdate_buffer, sizeof(fdate_buffer), "%Y-%m-%d", &futureTime);
Serial.printf("Future Date: %s\n", fdate_buffer);
snprintf(target, target_size, "https://api.openf1.org/v1/sessions?date_start>=%s&date_start<=%s&session_type=Race", snprintf(target, target_size, "https://api.openf1.org/v1/meetings?date_end>=%s&date_end<=%s",
date_buffer, date_buffer,
fdate_buffer); fdate_buffer);
Serial.print("");
Serial.println(target);
return 0;
}
static int get_future_sessions_url(char* target, size_t target_size, SemaphoreHandle_t* mutex, uint16_t meeting) {
snprintf(target, target_size, "https://api.openf1.org/v1/sessions?meeting_key=%d&session_type=Race",
meeting);
return 0; return 0;
} }
int parse_session_json(JsonArray sessions, race_info_t session) { int parse_session_json(JsonArray sessions, session_info_t* target, const SemaphoreHandle_t* mutex) {
JsonObject nextRaceJson = sessions[0]; JsonObject nextRaceJson = sessions[0];
race_info_t localData; session_info_t localData;
const char* sessionName = nextRaceJson[F("session_name")]; const char* sessionName = nextRaceJson[F("session_name")];
const char* dateStart = nextRaceJson[F("date_start")]; const char* dateStart = nextRaceJson[F("date_start")];
@@ -66,17 +133,64 @@ int parse_session_json(JsonArray sessions, race_info_t session) {
if (sessionName != NULL) { if (sessionName != NULL) {
strlcpy(localData.session_name, sessionName, sizeof(localData.session_name)); strlcpy(localData.session_name, sessionName, sizeof(localData.session_name));
cleanToASCIIInPlace(localData.session_name);
} else return -1; } else return -1;
if (dateStart != NULL) { if (dateStart != NULL) {
strlcpy(localData.date_start, dateStart, sizeof(localData.date_start)); strlcpy(localData.date_start, dateStart, sizeof(localData.date_start));
cleanToASCIIInPlace(localData.date_start);
} else return -1; } else return -1;
if (circuitName != NULL) { if (circuitName != NULL) {
strlcpy(localData.circuit_short_name, circuitName, sizeof(localData.circuit_short_name)); strlcpy(localData.circuit_short_name, circuitName, sizeof(localData.circuit_short_name));
cleanToASCIIInPlace(localData.circuit_short_name);
} else return -1; } else return -1;
if (xSemaphoreTake(dataMutex, MUTEX_TIMEOUT_TICKS)) { if (xSemaphoreTake(*mutex, MUTEX_TIMEOUT_TICKS)) {
race_data = localData; *target = localData;
xSemaphoreGive(dataMutex); xSemaphoreGive(*mutex);
} else {
Serial.print("");
Serial.println("Unable to acquire mutex, API loop.");
return -1;
}
return 0;
}
int parse_meeting_json(JsonArray sessions, meeting_info_t* target, const SemaphoreHandle_t* mutex) {
JsonObject nextRaceJson = sessions[0];
meeting_info_t localData;
const char* circuit_image_URL = nextRaceJson[F("circuit_image")];
const char* country_name = nextRaceJson[F("country_name")];
const char* location = nextRaceJson[F("location")];
const char* meeting_name = nextRaceJson[F("meeting_name")];
localData.circuit_key = nextRaceJson[F("circuit_key")];
if (localData.circuit_key == 0) return -1;
localData.meeting_key = nextRaceJson[F("meeting_key")];
if (localData.meeting_key == 0) return -1;
if (circuit_image_URL != NULL) {
strlcpy(localData.circuit_image_URL, circuit_image_URL, sizeof(localData.circuit_image_URL));
cleanToASCIIInPlace(localData.circuit_image_URL);
} else return -1;
if (country_name != NULL) {
strlcpy(localData.country_name, country_name, sizeof(localData.country_name));
cleanToASCIIInPlace(localData.country_name);
} else return -1;
if (location != NULL) {
strlcpy(localData.location, location, sizeof(localData.location));
cleanToASCIIInPlace(localData.location);
} else return -1;
if (meeting_name != NULL) {
strlcpy(localData.meeting_name, meeting_name, sizeof(localData.meeting_name));
cleanToASCIIInPlace(localData.meeting_name);
} else return -1;
if (xSemaphoreTake(*mutex, MUTEX_TIMEOUT_TICKS)) {
*target = localData;
xSemaphoreGive(*mutex);
} else { } else {
Serial.print(""); Serial.print("");
Serial.println("Unable to acquire mutex, API loop."); Serial.println("Unable to acquire mutex, API loop.");
@@ -98,7 +212,7 @@ void get_next_race(void *pvParameters) {
http.setCACert(OPENF1_ROOT_CA); http.setCACert(OPENF1_ROOT_CA);
char query_url[103]; char query_url[103];
get_race_query_url(query_url, sizeof(query_url)); get_future_meetings_url(query_url, sizeof(query_url));
const bool requestReady = http.begin(query_url); const bool requestReady = http.begin(query_url);
if (!requestReady) { if (!requestReady) {
@@ -113,9 +227,9 @@ void get_next_race(void *pvParameters) {
JsonDocument json; JsonDocument json;
const DeserializationError err = deserializeJson(json, responseStream); const DeserializationError err = deserializeJson(json, responseStream);
if (err == DeserializationError::Ok) { if (err == DeserializationError::Ok) {
JsonArray sessions = json.as<JsonArray>(); JsonArray meetings = json.as<JsonArray>();
if (sessions.size() != 0) { if (meetings.size() != 0) {
parse_session_json(sessions, race_data); parse_meeting_json(meetings, &race_data.meeting_info, &dataMutex);
} }
} else { } else {
Serial.print(""); Serial.print("");
@@ -134,6 +248,51 @@ void get_next_race(void *pvParameters) {
} }
} }
http.end(); http.end();
HTTPClient http2;
http2.setTimeout(10000);
http2.setCACert(OPENF1_ROOT_CA);
get_future_sessions_url(query_url, sizeof(query_url), &dataMutex, race_data.meeting_info.meeting_key);
Serial.print("");
Serial.println(query_url);
const bool requestReady2 = http2.begin(query_url);
if (!requestReady) {
Serial.print("");
Serial.println("Failed to initialize OpenF1 request.");
} else {
const int httpResponseCode = http2.GET();
if (httpResponseCode > 0) {
Stream& responseStream = http2.getStream();
Serial.printf("HTTP Size: %d\n", http2.getSize());
JsonDocument json;
const DeserializationError err = deserializeJson(json, responseStream);
if (err == DeserializationError::Ok) {
JsonArray sessions = json.as<JsonArray>();
if (sessions.size() != 0) {
parse_session_json(sessions, &race_data.session_info, &dataMutex);
}
} else {
Serial.print("");
Serial.print("Failed to parse JSON response: ");
Serial.println(err.c_str());
}
retryDelay = pdMS_TO_TICKS(900000);
} else {
Serial.print("");
Serial.printf(
"Error on GET: %d (%s)\r\n",
httpResponseCode,
HTTPClient::errorToString(httpResponseCode).c_str());
retryDelay = pdMS_TO_TICKS(10000);
}
}
http.end();
} else { } else {
Serial.print(""); Serial.print("");
Serial.println("WiFi not connecteFormattedd."); Serial.println("WiFi not connecteFormattedd.");
+15 -8
View File
@@ -17,13 +17,13 @@ extern "C" {
} }
static UBYTE displayBuffer[(EPD_4IN2_V2_WIDTH / 8) * EPD_4IN2_V2_HEIGHT]; static UBYTE displayBuffer[(EPD_4IN2_V2_WIDTH / 8) * EPD_4IN2_V2_HEIGHT];
static constexpr UWORD COUNTER_WIDTH = 200; static constexpr UWORD COUNTER_WIDTH = 400;
static constexpr UWORD COUNTER_HEIGHT = 24; static constexpr UWORD COUNTER_HEIGHT = 38;
static constexpr UWORD COUNTER_Y = 20; static constexpr UWORD COUNTER_Y = 20;
static UBYTE counterBuffer[(COUNTER_WIDTH / 8) * COUNTER_HEIGHT]; static UBYTE counterBuffer[(COUNTER_WIDTH / 8) * COUNTER_HEIGHT];
static int counter = 0; static int counter = 0;
race_info_t race_data; data_struct_t race_data;
SemaphoreHandle_t dataMutex; SemaphoreHandle_t dataMutex;
void wifi_connect() { void wifi_connect() {
@@ -81,7 +81,7 @@ bool readLocalTime(struct tm* result) {
} }
void display_loop(void *pvParameters) { void display_loop(void *pvParameters) {
static race_info_t local_data; static data_struct_t local_data;
while (true) { while (true) {
static unsigned long lastRefresh = 0; static unsigned long lastRefresh = 0;
static char counterStr[32]; static char counterStr[32];
@@ -105,15 +105,23 @@ void display_loop(void *pvParameters) {
} }
if (race_data.session_key == 0) { if (local_data.meeting_info.meeting_key == 0) {
snprintf(counterStr, sizeof(counterStr), "Counter: %d", counter); snprintf(counterStr, sizeof(counterStr), "Counter: %d", counter);
Serial.print(""); Serial.print("");
Serial.println(counterStr); Serial.println(counterStr);
} else { } else {
snprintf(counterStr, sizeof(counterStr), "%s", race_data.circuit_short_name); snprintf(counterStr, sizeof(counterStr), "%s", local_data.meeting_info.meeting_name);
} }
Paint_Clear(WHITE); Paint_Clear(WHITE);
if (local_data.session_info.date_start[0] != 0) {
Serial.print("");
Serial.printf("date start: %s\n", local_data.session_info.date_start);
Paint_DrawString_EN(10, 22, local_data.session_info.date_start, &Font16, WHITE, BLACK);
}
Paint_DrawString_EN(10, 0, counterStr, &Font16, WHITE, BLACK); Paint_DrawString_EN(10, 0, counterStr, &Font16, WHITE, BLACK);
EPD_4IN2_V2_PartialDisplay( EPD_4IN2_V2_PartialDisplay(
@@ -149,14 +157,13 @@ void setup() {
Paint_SelectImage(displayBuffer); Paint_SelectImage(displayBuffer);
Paint_Clear(WHITE); Paint_Clear(WHITE);
Paint_DrawString_EN(10, 0, "F1 Display Booting...", &Font16, WHITE, BLACK); Paint_DrawString_EN(10, 0, "F1 Display Booting...", &Font16, WHITE, BLACK);
Paint_DrawString_EN(10, COUNTER_Y, "Counter: 0", &Font16, WHITE, BLACK);
EPD_4IN2_V2_Display(displayBuffer); EPD_4IN2_V2_Display(displayBuffer);
DEV_Delay_ms(500); DEV_Delay_ms(500);
wifi_connect(); wifi_connect();
sync_time(); sync_time();
printf("Begin display loop.\n"); Serial.println("Begin display loop.\n");
Paint_NewImage(counterBuffer, COUNTER_WIDTH, COUNTER_HEIGHT, ROTATE_0, WHITE); Paint_NewImage(counterBuffer, COUNTER_WIDTH, COUNTER_HEIGHT, ROTATE_0, WHITE);
Paint_SelectImage(counterBuffer); Paint_SelectImage(counterBuffer);