faster response times
Some checks failed
Build Release / build (push) Failing after 1m18s

This commit is contained in:
2026-08-16 16:55:32 +02:00
parent 3648538cbc
commit fb884acc27
4 changed files with 109 additions and 105 deletions

View File

@@ -15,29 +15,62 @@ namespace {
char pendingCallsign[9] = {0};
bool hasPending = false;
char cachedHex[7] = {0};
Info cached;
constexpr uint8_t CACHE_SIZE = 4;
struct CacheEntry {
char hex[7] = {0};
Info info;
};
CacheEntry cache[CACHE_SIZE];
uint8_t nextCacheSlot = 0;
// Persistent client — avoids repeated TLS handshakes (~0.5-1s each).
WiFiClientSecure persistentClient;
bool clientConfigured = false;
void ensureMutex() {
if (mutex == nullptr) mutex = xSemaphoreCreateMutex();
}
bool httpGetString(WiFiClientSecure& client, const String& url, String& outBody) {
bool httpGetString(const String& url, String& outBody, uint32_t timeoutMs) {
if (!clientConfigured) {
persistentClient.setInsecure();
clientConfigured = true;
}
persistentClient.setTimeout(timeoutMs);
HTTPClient http;
http.setTimeout(5000);
if (!http.begin(client, url)) return false;
http.setTimeout(timeoutMs);
if (!http.begin(persistentClient, url)) return false;
http.setReuse(true);
int code = http.GET();
bool ok = (code == HTTP_CODE_OK);
if (ok) outBody = http.getString();
http.end();
return ok;
}
int findCacheSlot(const char* hex) {
for (uint8_t i = 0; i < CACHE_SIZE; i++) {
if (strcmp(cache[i].hex, hex) == 0) return i;
}
return -1;
}
void storeInCache(const char* hex, const Info& info) {
int slot = findCacheSlot(hex);
if (slot < 0) {
slot = nextCacheSlot;
nextCacheSlot = (nextCacheSlot + 1) % CACHE_SIZE;
}
strncpy(cache[slot].hex, hex, sizeof(cache[slot].hex) - 1);
cache[slot].info = info;
}
}
void request(const char* hex, const char* callsign) {
ensureMutex();
xSemaphoreTake(mutex, portMAX_DELAY);
if (strcmp(cachedHex, hex) != 0 && strcmp(pendingHex, hex) != 0) {
if (findCacheSlot(hex) < 0 && strcmp(pendingHex, hex) != 0) {
strncpy(pendingHex, hex, sizeof(pendingHex) - 1);
strncpy(pendingCallsign, callsign ? callsign : "", sizeof(pendingCallsign) - 1);
hasPending = true;
@@ -49,8 +82,9 @@ Info get(const char* hex) {
ensureMutex();
xSemaphoreTake(mutex, portMAX_DELAY);
Info out;
if (strcmp(cachedHex, hex) == 0) {
out = cached;
int slot = findCacheSlot(hex);
if (slot >= 0) {
out = cache[slot].info;
} else if (strcmp(pendingHex, hex) == 0 && hasPending) {
out.loading = true;
}
@@ -77,15 +111,10 @@ void update() {
Info result;
WiFiClientSecure client;
client.setInsecure();
// Try hexdb.io first (previous source) - but with a shorter
// timeout (3s instead of 5s) so a complete service outage does not
// block the ADS-B query unnecessarily long before the fallback kicks in.
client.setTimeout(3000);
// Model lookup — try hexdb.io first, fallback to adsbdb.com.
// Timeout reduced to 2s per source (was 3-4s) since we chain fallbacks.
String body;
if (httpGetString(client, String("https://hexdb.io/api/v1/aircraft/") + hex, body)) {
if (httpGetString(String("https://hexdb.io/api/v1/aircraft/") + hex, body, 2000)) {
JsonDocument doc;
DeserializationError err = deserializeJson(doc, body);
if (!err) {
@@ -99,13 +128,9 @@ void update() {
}
}
// Fallback: hexdb.io was unreachable/did not deliver a model -
// try adsbdb.com as a second, independent source (different API format,
// but equivalent in content: manufacturer + type via the hex code).
if (!result.model[0]) {
client.setTimeout(4000);
String body2;
if (httpGetString(client, String("https://api.adsbdb.com/v0/aircraft/") + hex, body2)) {
if (httpGetString(String("https://api.adsbdb.com/v0/aircraft/") + hex, body2, 2000)) {
JsonDocument doc2;
DeserializationError err2 = deserializeJson(doc2, body2);
if (!err2) {
@@ -120,25 +145,7 @@ void update() {
}
}
// Flight route (origin/destination airport) - now via a chain of THREE
// independent free sources instead of just one, sorted descending by
// observed hit rate in testing. Previously adsbdb.com alone returned
// "unknown" in about 80% of cases (see Alex' feedback) - the
// three sources draw from different, overlapping but
// not identical community databases, therefore much better
// overall coverage through chaining:
// 1. VRS-Standing-Data-Mirror (adsb.lol) - hourly updated
// mirror of the Virtual Radar Server project, by far the most
// reliable source in testing. Path = first 2 characters of the
// (UPPERCASE - the service is case-sensitive) callsign
// as folder, returns "airport_codes":"ORIG-DEST" (ICAO).
// 2. hexdb.io - own route endpoint (different URL from the
// aircraft endpoint above), returns "route":"ORIG-DEST".
// 3. adsbdb.com callsign endpoint - previous single source, remains
// as last fallback since it occasionally has data that the
// other two do not.
// Only try if a callsign is known at all -
// VFR aircraft without a callsign do not have an evaluable route anyway.
// Route lookup — via chain of three sources, 2s timeout each.
String trimmedCallsign = String(callsign);
trimmedCallsign.trim();
trimmedCallsign.toUpperCase();
@@ -152,11 +159,9 @@ void update() {
};
if (trimmedCallsign.length() >= 2) {
// 1. VRS-Standing-Data-Mirror.
client.setTimeout(4000);
String folder = trimmedCallsign.substring(0, 2);
String body3;
if (httpGetString(client, String("https://vrs-standing-data.adsb.lol/routes/") + folder + "/" + trimmedCallsign + ".json", body3)) {
String folder = trimmedCallsign.substring(0, 2);
if (httpGetString(String("https://vrs-standing-data.adsb.lol/routes/") + folder + "/" + trimmedCallsign + ".json", body3, 2000)) {
JsonDocument doc3;
if (!deserializeJson(doc3, body3)) {
const char* codes = doc3["airport_codes"] | "";
@@ -164,11 +169,9 @@ void update() {
}
}
// 2. hexdb.io route endpoint, if source 1 did not deliver anything.
if (!result.routeOrigin[0] || !result.routeDest[0]) {
client.setTimeout(3000);
String body4;
if (httpGetString(client, String("https://hexdb.io/api/v1/route/icao/") + trimmedCallsign, body4)) {
if (httpGetString(String("https://hexdb.io/api/v1/route/icao/") + trimmedCallsign, body4, 2000)) {
JsonDocument doc4;
if (!deserializeJson(doc4, body4)) {
const char* route = doc4["route"] | "";
@@ -178,11 +181,9 @@ void update() {
}
}
// 3. adsbdb.com callsign endpoint as last fallback.
if (trimmedCallsign.length() > 0 && (!result.routeOrigin[0] || !result.routeDest[0])) {
client.setTimeout(4000);
String body5;
if (httpGetString(client, String("https://api.adsbdb.com/v0/callsign/") + trimmedCallsign, body5)) {
if (httpGetString(String("https://api.adsbdb.com/v0/callsign/") + trimmedCallsign, body5, 2000)) {
JsonDocument doc5;
if (!deserializeJson(doc5, body5)) {
const char* originIcao = doc5["response"]["flightroute"]["origin"]["icao_code"] | "";
@@ -196,10 +197,9 @@ void update() {
}
xSemaphoreTake(mutex, portMAX_DELAY);
strncpy(cachedHex, hex, sizeof(cachedHex) - 1);
cached = result;
storeInCache(hex, result);
hasPending = false;
xSemaphoreGive(mutex);
}
}
}