Print start time on display. Search by meeting instead of session but get both session and meeting data
This commit is contained in:
1 parent
3ec87ada9c
commit
c0e71374eb
2 files changed
+191
-25
No files matched your search
+176
-17
@@ -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
@@ -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);
|
||||||
|
|||||||
Reference in new issue
Block a user