From f429f328902ea2ac75ca17ad0fd3b504c12d8cc1 Mon Sep 17 00:00:00 2001 From: Adam Bissen Date: Sun, 8 Nov 2026 19:28:11 -0600 Subject: [PATCH] Print next race time in local time. --- src/F1_api.h | 67 ++++++++++++++++++++++++++++++++++++++++++---------- src/main.cpp | 10 ++++---- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/F1_api.h b/src/F1_api.h index 0197ca9..3b65938 100644 --- a/src/F1_api.h +++ b/src/F1_api.h @@ -5,10 +5,36 @@ #include "secrets.h" #include "ArduinoJson.h" #include "config.h" +#include "time.h" + +extern "C" char *strptime(const char *__restrict, const char *__restrict, struct tm *__restrict); // Not exposed by headers? + +// Portable replacement for missing timegm() +time_t timegm(struct tm *t) { + int year = t->tm_year + 1900; + int mon = t->tm_mon + 1; // 1 to 12 + int day = t->tm_mday; + + // Shift year/month to make March month 0 (simplifies leap year calculation) + int a = (14 - mon) / 12; + int y = year + 4800 - a; + int m = mon + 12 * a - 3; + + // Calculate Julian Day Number + long jdn = day + (153 * m + 2) / 5 + 365L * y + y / 4 - y / 100 + y / 400 - 32045; + + // Days since Unix epoch (1970-01-01 is JDN 2440588) + long days = jdn - 2440588; + + return (time_t)(days * 86400L + + t->tm_hour * 3600L + + t->tm_min * 60L + + t->tm_sec); +} struct session_info_t { char session_name[50]; - char date_start[32]; + struct tm date_start; char circuit_short_name[50]; uint16_t session_key; uint16_t meeting_key; @@ -118,10 +144,24 @@ int parse_session_json(JsonArray sessions, session_info_t* target, const Semapho JsonObject nextRaceJson = sessions[0]; session_info_t localData; - strptime(); + struct tm utc_tm; + memset(&utc_tm, 0, sizeof(struct tm)); + strptime(nextRaceJson[F("date_start")], "%Y-%m-%dT%H:%M:%S", &utc_tm); + + // 2. Convert UTC struct tm to time_t Unix timestamp + time_t raw_time = timegm(&utc_tm); + + // 3. Convert time_t to local struct tm + struct tm local_tm; + localtime_r(&raw_time, &localData.date_start); + + char buffer[64]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M:%S %p %Z", &local_tm); + Serial.print(""); + Serial.print("Session local: "); + Serial.println(buffer); const char* sessionName = nextRaceJson[F("session_name")]; - const char* dateStart = nextRaceJson[F("date_start")]; const char* circuitName = nextRaceJson[F("circuit_short_name")]; localData.session_key = nextRaceJson[F("session_key")]; @@ -132,15 +172,12 @@ int parse_session_json(JsonArray sessions, session_info_t* target, const Semapho localData.circuit_key = nextRaceJson[F("circuit_key")]; if (localData.circuit_key == 0) return -1; + if (localData.date_start.tm_year == 0) return -1; if (sessionName != NULL) { strlcpy(localData.session_name, sessionName, sizeof(localData.session_name)); cleanToASCIIInPlace(localData.session_name); } else return -1; - if (dateStart != NULL) { - strlcpy(localData.date_start, dateStart, sizeof(localData.date_start)); - cleanToASCIIInPlace(localData.date_start); - } else return -1; if (circuitName != NULL) { strlcpy(localData.circuit_short_name, circuitName, sizeof(localData.circuit_short_name)); cleanToASCIIInPlace(localData.circuit_short_name); @@ -231,11 +268,14 @@ void get_next_race(void *pvParameters) { if (err == DeserializationError::Ok) { JsonArray meetings = json.as(); if (meetings.size() != 0) { - parse_meeting_json(meetings, &race_data.meeting_info, &dataMutex); + if (parse_meeting_json(meetings, &race_data.meeting_info, &dataMutex) != 0) { + Serial.print(""); + Serial.println(F("Failed to parse meeting data.")); + } } } else { Serial.print(""); - Serial.print("Failed to parse JSON response: "); + Serial.print(F("Failed to parse JSON response: ")); Serial.println(err.c_str()); } @@ -275,11 +315,14 @@ void get_next_race(void *pvParameters) { if (err == DeserializationError::Ok) { JsonArray sessions = json.as(); if (sessions.size() != 0) { - parse_session_json(sessions, &race_data.session_info, &dataMutex); + if (parse_session_json(sessions, &race_data.session_info, &dataMutex) != 0) { + Serial.print(""); + Serial.println(F("Failed to parse session data")); + } } } else { Serial.print(""); - Serial.print("Failed to parse JSON response: "); + Serial.print(F("Failed to parse JSON response: ")); Serial.println(err.c_str()); } @@ -297,7 +340,7 @@ void get_next_race(void *pvParameters) { } else { Serial.print(""); - Serial.println("WiFi not connecteFormattedd."); + Serial.println(F("WiFi not connected.")); } vTaskDelay(retryDelay); } diff --git a/src/main.cpp b/src/main.cpp index 58b2d72..30e28f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,7 +56,7 @@ void wifi_connect() { void sync_time() { if (WiFi.status() != WL_CONNECTED) { - Serial.println("Skipping NTP synchronization because WiFi is disconnected."); + Serial.println(F("Skipping NTP synchronization because WiFi is disconnected.")); return; } @@ -71,7 +71,7 @@ void sync_time() { Serial.print("Time synchronized: "); Serial.println(asctime(&timeinfo)); } else { - Serial.println("Failed to synchronize time within 10 seconds."); + Serial.println(F("Failed to synchronize time within 10 seconds.")); } } @@ -115,8 +115,10 @@ void display_loop(void *pvParameters) { Paint_Clear(WHITE); - if (local_data.session_info.date_start[0] != 0) { - Paint_DrawString_EN(10, 22, local_data.session_info.date_start, &Font16, WHITE, BLACK); + if (local_data.session_info.date_start.tm_year != 0) { + char date_buffer[30]; + strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d %I:%M:%S %p %Z", &local_data.session_info.date_start); + Paint_DrawString_EN(10, 22, date_buffer, &Font16, WHITE, BLACK); } Paint_DrawString_EN(10, 0, counterStr, &Font16, WHITE, BLACK);