558 lines
18 KiB
C++
558 lines
18 KiB
C++
#pragma once
|
|
#include <Arduino.h>
|
|
#include <ArduinoJson.h>
|
|
#include <ESPAsyncWebServer.h>
|
|
#include <HTTPClient.h>
|
|
#include <algorithm>
|
|
#include <preferencemanager.h>
|
|
#include <vector>
|
|
|
|
const char *BACKEND_SERVER = "https://ninja.reptilfpv.de";
|
|
extern String
|
|
licence; // Declare licence as an external variable defined elsewhere
|
|
|
|
// Lokale Benutzer-Struktur
|
|
struct LocalUser {
|
|
String uid;
|
|
String name;
|
|
unsigned long timestamp; // Zeitstempel der Erstellung
|
|
};
|
|
|
|
// Lokale Benutzer-Speicherung (geht bei Neustart verloren)
|
|
std::vector<LocalUser> localUsers;
|
|
|
|
bool backendOnline() {
|
|
|
|
Serial.println(licence);
|
|
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
Serial.println("No WiFi connection.");
|
|
return false;
|
|
}
|
|
|
|
HTTPClient http;
|
|
http.begin(String(BACKEND_SERVER) + "/api/v1/private/health");
|
|
http.addHeader("Authorization", String("Bearer ") + licence);
|
|
|
|
int httpCode = http.GET();
|
|
bool isOnline = (httpCode == HTTP_CODE_OK);
|
|
|
|
if (isOnline) {
|
|
Serial.println("Database server connection successful");
|
|
} else {
|
|
Serial.printf("Database server connection failed, error: %d\n", httpCode);
|
|
}
|
|
|
|
http.end();
|
|
return isOnline;
|
|
}
|
|
|
|
struct UserData {
|
|
String uid;
|
|
String firstname;
|
|
String lastname;
|
|
int alter;
|
|
bool exists;
|
|
};
|
|
|
|
// Forward declarations für Leaderboard-Funktionen
|
|
void addLocalTime(String uid, String name, unsigned long timeMs);
|
|
|
|
// Prüft, ob ein Benutzer mit der angegebenen UID in der Datenbank existiert und
|
|
// gibt dessen Daten zurück.
|
|
UserData checkUser(const String &uid) {
|
|
|
|
UserData userData = {"", "", "", 0, false};
|
|
String upperUid = uid;
|
|
upperUid.toUpperCase(); // UID in Großbuchstaben konvertieren
|
|
|
|
// Lokale Benutzer durchsuchen
|
|
for (const auto &user : localUsers) {
|
|
String userUpperUid = user.uid;
|
|
userUpperUid.toUpperCase();
|
|
if (userUpperUid == upperUid) {
|
|
userData.uid = user.uid;
|
|
userData.firstname = user.name;
|
|
userData.lastname = ""; // Nicht mehr verwendet
|
|
userData.alter = 0; // Nicht mehr verwendet
|
|
userData.exists = true;
|
|
|
|
Serial.println("Lokaler Benutzer gefunden: " + user.name);
|
|
return userData;
|
|
}
|
|
}
|
|
|
|
Serial.println("Benutzer mit UID " + uid +
|
|
" nicht in lokaler Datenbank gefunden");
|
|
return userData;
|
|
}
|
|
|
|
// Holt alle Standorte aus der Datenbank und gibt sie als JSON-Dokument zurück.
|
|
JsonDocument getAllLocations() {
|
|
JsonDocument locations; // Allocate memory for the JSON document
|
|
|
|
if (!backendOnline()) {
|
|
Serial.println("No internet connection, cannot fetch locations.");
|
|
return locations; // Return an empty document
|
|
}
|
|
|
|
HTTPClient http;
|
|
http.begin(String(BACKEND_SERVER) + "/v1/private/locations");
|
|
http.addHeader("Authorization", String("Bearer ") + licence);
|
|
|
|
int httpCode = http.GET();
|
|
|
|
if (httpCode == HTTP_CODE_OK) {
|
|
String payload = http.getString();
|
|
DeserializationError error = deserializeJson(locations, payload);
|
|
|
|
if (error) {
|
|
Serial.println("Failed to parse locations JSON.");
|
|
}
|
|
} else {
|
|
Serial.printf("Failed to fetch locations, HTTP code: %d\n", httpCode);
|
|
}
|
|
|
|
http.end();
|
|
return locations; // Return the populated JSON document
|
|
}
|
|
|
|
// Prüft, ob ein Benutzer mit der angegebenen UID existiert
|
|
// (Kompatibilitätsfunktion).
|
|
bool userExists(const String &uid) { return checkUser(uid).exists; }
|
|
|
|
// Fügt einen neuen Benutzer in die Datenbank ein
|
|
bool enterUserData(const String &uid, const String &name) {
|
|
String upperUid = uid;
|
|
upperUid.toUpperCase(); // UID in Großbuchstaben konvertieren
|
|
|
|
// Prüfen ob Benutzer bereits existiert
|
|
for (const auto &user : localUsers) {
|
|
String userUpperUid = user.uid;
|
|
userUpperUid.toUpperCase();
|
|
if (userUpperUid == upperUid) {
|
|
Serial.println("Benutzer mit UID " + upperUid + " existiert bereits!");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Neuen Benutzer erstellen
|
|
LocalUser newUser;
|
|
newUser.uid = upperUid; // UID in Großbuchstaben speichern
|
|
newUser.name = name;
|
|
newUser.timestamp = millis();
|
|
|
|
// Benutzer zum lokalen Array hinzufügen
|
|
localUsers.push_back(newUser);
|
|
|
|
Serial.println("Benutzer lokal gespeichert:");
|
|
Serial.println("UID: " + upperUid);
|
|
Serial.println("Name: " + name);
|
|
Serial.println("Gespeicherte Benutzer: " + String(localUsers.size()));
|
|
|
|
return true;
|
|
}
|
|
|
|
// Gibt alle lokalen Benutzer zurück (für Debugging)
|
|
String getLocalUsersList() {
|
|
String result = "Lokale Benutzer (" + String(localUsers.size()) + "):\n";
|
|
|
|
for (const auto &user : localUsers) {
|
|
result += "- UID: " + user.uid + ", Name: " + user.name +
|
|
", Erstellt: " + String(user.timestamp) + "\n";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Richtet die HTTP-Routen für die Backend-API ein (z.B. Health-Check, User- und
|
|
// Location-Abfragen).
|
|
void setupBackendRoutes(AsyncWebServer &server) {
|
|
|
|
server.on("/api/health", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
DynamicJsonDocument doc(64);
|
|
doc["status"] = backendOnline() ? "connected" : "disconnected";
|
|
String response;
|
|
serializeJson(doc, response);
|
|
request->send(200, "application/json", response);
|
|
});
|
|
|
|
server.on("/api/users", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
// Lokale Benutzer als JSON zurückgeben
|
|
DynamicJsonDocument doc(2048);
|
|
JsonArray usersArray = doc.createNestedArray("users");
|
|
|
|
for (const auto &user : localUsers) {
|
|
JsonObject userObj = usersArray.createNestedObject();
|
|
userObj["uid"] = user.uid;
|
|
userObj["name"] = user.name;
|
|
userObj["timestamp"] = user.timestamp;
|
|
}
|
|
|
|
doc["count"] = localUsers.size();
|
|
|
|
String response;
|
|
serializeJson(doc, response);
|
|
request->send(200, "application/json", response);
|
|
});
|
|
|
|
// Route zum Erstellen eines neuen Benutzers
|
|
server.on(
|
|
"/api/users/insert", HTTP_POST,
|
|
[](AsyncWebServerRequest *request) {
|
|
Serial.println("API: /api/users/insert aufgerufen");
|
|
},
|
|
NULL,
|
|
[](AsyncWebServerRequest *request, uint8_t *data, size_t len,
|
|
size_t index, size_t total) {
|
|
// Diese Funktion wird für den Body aufgerufen
|
|
static String bodyBuffer = "";
|
|
|
|
// Daten anhängen
|
|
for (size_t i = 0; i < len; i++) {
|
|
bodyBuffer += (char)data[i];
|
|
}
|
|
|
|
// Wenn alle Daten empfangen wurden
|
|
if (index + len == total) {
|
|
Serial.println("Request Body empfangen: '" + bodyBuffer + "'");
|
|
|
|
if (bodyBuffer.length() == 0) {
|
|
Serial.println("FEHLER: Request Body ist leer!");
|
|
DynamicJsonDocument response(200);
|
|
response["success"] = false;
|
|
response["error"] = "Request Body ist leer";
|
|
String jsonString;
|
|
serializeJson(response, jsonString);
|
|
request->send(400, "application/json", jsonString);
|
|
bodyBuffer = "";
|
|
return;
|
|
}
|
|
|
|
DynamicJsonDocument doc(512);
|
|
DeserializationError error = deserializeJson(doc, bodyBuffer);
|
|
|
|
if (error) {
|
|
Serial.println("JSON Parse Error: " + String(error.c_str()));
|
|
DynamicJsonDocument response(200);
|
|
response["success"] = false;
|
|
response["error"] = "Invalid JSON: " + String(error.c_str());
|
|
String jsonString;
|
|
serializeJson(response, jsonString);
|
|
request->send(400, "application/json", jsonString);
|
|
bodyBuffer = "";
|
|
return;
|
|
}
|
|
|
|
String uid = doc["uid"].as<String>();
|
|
String name = doc["name"].as<String>();
|
|
|
|
Serial.println("Extrahierte UID: " + uid);
|
|
Serial.println("Extrahierter Name: " + name);
|
|
|
|
if (uid.length() == 0 || name.length() == 0) {
|
|
DynamicJsonDocument response(200);
|
|
response["success"] = false;
|
|
response["error"] = "UID und Name sind erforderlich";
|
|
String jsonString;
|
|
serializeJson(response, jsonString);
|
|
request->send(400, "application/json", jsonString);
|
|
bodyBuffer = "";
|
|
return;
|
|
}
|
|
|
|
// Prüfen ob Benutzer bereits existiert
|
|
bool userExists = false;
|
|
for (const auto &user : localUsers) {
|
|
if (user.uid == uid) {
|
|
userExists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (userExists) {
|
|
DynamicJsonDocument response(200);
|
|
response["success"] = false;
|
|
response["error"] = "Benutzer bereits vorhanden";
|
|
String jsonString;
|
|
serializeJson(response, jsonString);
|
|
request->send(409, "application/json", jsonString);
|
|
bodyBuffer = "";
|
|
return;
|
|
}
|
|
|
|
// Neuen Benutzer direkt in das Array einfügen
|
|
LocalUser newUser;
|
|
newUser.uid = uid;
|
|
newUser.name = name;
|
|
newUser.timestamp = millis();
|
|
|
|
localUsers.push_back(newUser);
|
|
|
|
Serial.println("Benutzer über API eingefügt:");
|
|
Serial.println("UID: " + uid);
|
|
Serial.println("Name: " + name);
|
|
Serial.println("Gespeicherte Benutzer: " + String(localUsers.size()));
|
|
|
|
DynamicJsonDocument response(200);
|
|
response["success"] = true;
|
|
response["message"] = "Benutzer erfolgreich erstellt";
|
|
response["uid"] = uid;
|
|
response["name"] = name;
|
|
|
|
String jsonString;
|
|
serializeJson(response, jsonString);
|
|
request->send(200, "application/json", jsonString);
|
|
|
|
// Buffer zurücksetzen
|
|
bodyBuffer = "";
|
|
}
|
|
});
|
|
|
|
// Debug-Route für lokale Benutzer
|
|
server.on("/api/debug/users", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
String userList = getLocalUsersList();
|
|
request->send(200, "text/plain", userList);
|
|
});
|
|
|
|
// Location routes /api/location/
|
|
server.on("/api/location/", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
String result;
|
|
serializeJson(getAllLocations(), result);
|
|
request->send(200, "application/json", result);
|
|
});
|
|
|
|
server.on("/api/set-local-location", HTTP_POST,
|
|
[](AsyncWebServerRequest *request) {
|
|
Serial.println("/api/set-local-location called");
|
|
String locationId;
|
|
if (request->hasParam("locationId", true)) {
|
|
locationId = request->getParam("locationId", true)->value();
|
|
}
|
|
if (locationId.length() > 0) {
|
|
saveLocationIdToPrefs(locationId);
|
|
DynamicJsonDocument doc(64);
|
|
doc["success"] = true;
|
|
String result;
|
|
serializeJson(doc, result);
|
|
request->send(200, "application/json", result);
|
|
} else {
|
|
request->send(400, "application/json", "{\"success\":false}");
|
|
}
|
|
});
|
|
|
|
server.on("/api/get-local-location", HTTP_GET,
|
|
[](AsyncWebServerRequest *request) {
|
|
String locationId = getLocationIdFromPrefs();
|
|
DynamicJsonDocument doc(64);
|
|
doc["locationId"] = locationId;
|
|
String result;
|
|
serializeJson(doc, result);
|
|
request->send(200, "application/json", result);
|
|
|
|
// Andere Logik wie in getBestLocs
|
|
});
|
|
|
|
// Lokales Leaderboard API (für Hauptseite - 6 Einträge)
|
|
server.on("/api/leaderboard", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
// Sortiere nach Zeit (beste zuerst)
|
|
std::sort(localTimes.begin(), localTimes.end(),
|
|
[](const LocalTime &a, const LocalTime &b) {
|
|
return a.timeMs < b.timeMs;
|
|
});
|
|
|
|
DynamicJsonDocument doc(2048);
|
|
JsonArray leaderboard = doc.createNestedArray("leaderboard");
|
|
|
|
// Nimm die besten 6
|
|
int count = 0;
|
|
for (const auto &time : localTimes) {
|
|
if (count >= 6)
|
|
break;
|
|
|
|
JsonObject entry = leaderboard.createNestedObject();
|
|
entry["rank"] = count + 1;
|
|
entry["name"] = time.name;
|
|
entry["uid"] = time.uid;
|
|
entry["time"] = time.timeMs / 1000.0;
|
|
|
|
// Format time inline
|
|
float seconds = time.timeMs / 1000.0;
|
|
int totalSeconds = (int)seconds;
|
|
int minutes = totalSeconds / 60;
|
|
int remainingSeconds = totalSeconds % 60;
|
|
int milliseconds = (int)((seconds - totalSeconds) * 100);
|
|
|
|
String timeFormatted;
|
|
if (minutes > 0) {
|
|
timeFormatted = String(minutes) + ":" +
|
|
(remainingSeconds < 10 ? "0" : "") +
|
|
String(remainingSeconds) + "." +
|
|
(milliseconds < 10 ? "0" : "") + String(milliseconds);
|
|
} else {
|
|
timeFormatted = String(remainingSeconds) + "." +
|
|
(milliseconds < 10 ? "0" : "") + String(milliseconds);
|
|
}
|
|
entry["timeFormatted"] = timeFormatted;
|
|
|
|
count++;
|
|
}
|
|
|
|
String result;
|
|
serializeJson(doc, result);
|
|
request->send(200, "application/json", result);
|
|
});
|
|
|
|
// Erweiterte Leaderboard API (für Leaderboard-Seite - 10 Einträge)
|
|
server.on(
|
|
"/api/leaderboard-full", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
// Sortiere nach Zeit (beste zuerst)
|
|
std::sort(localTimes.begin(), localTimes.end(),
|
|
[](const LocalTime &a, const LocalTime &b) {
|
|
return a.timeMs < b.timeMs;
|
|
});
|
|
|
|
DynamicJsonDocument doc(2048);
|
|
JsonArray leaderboard = doc.createNestedArray("leaderboard");
|
|
|
|
// Nimm die besten 10
|
|
int count = 0;
|
|
for (const auto &time : localTimes) {
|
|
if (count >= 10)
|
|
break;
|
|
|
|
JsonObject entry = leaderboard.createNestedObject();
|
|
entry["rank"] = count + 1;
|
|
entry["name"] = time.name;
|
|
entry["uid"] = time.uid;
|
|
entry["time"] = time.timeMs / 1000.0;
|
|
|
|
// Format time inline
|
|
float seconds = time.timeMs / 1000.0;
|
|
int totalSeconds = (int)seconds;
|
|
int minutes = totalSeconds / 60;
|
|
int remainingSeconds = totalSeconds % 60;
|
|
int milliseconds = (int)((seconds - totalSeconds) * 100);
|
|
|
|
String timeFormatted;
|
|
if (minutes > 0) {
|
|
timeFormatted =
|
|
String(minutes) + ":" + (remainingSeconds < 10 ? "0" : "") +
|
|
String(remainingSeconds) + "." +
|
|
(milliseconds < 10 ? "0" : "") + String(milliseconds);
|
|
} else {
|
|
timeFormatted = String(remainingSeconds) + "." +
|
|
(milliseconds < 10 ? "0" : "") +
|
|
String(milliseconds);
|
|
}
|
|
|
|
entry["timeFormatted"] = timeFormatted;
|
|
count++;
|
|
}
|
|
|
|
String result;
|
|
serializeJson(doc, result);
|
|
request->send(200, "application/json", result);
|
|
});
|
|
|
|
// Add more routes as needed
|
|
}
|
|
|
|
// Hilfsfunktionen um UID und Status abzufragen (aus communication.h)
|
|
String getStart1UID();
|
|
String getStart2UID();
|
|
bool wasStart1FoundLocally();
|
|
bool wasStart2FoundLocally();
|
|
|
|
// Funktion um Zeit an Online-API zu senden
|
|
void sendTimeToOnlineAPI(int lane, String uid, float timeInSeconds) {
|
|
// Nur senden wenn User online gefunden wurde
|
|
bool wasOnlineFound =
|
|
(lane == 1) ? !wasStart1FoundLocally() : !wasStart2FoundLocally();
|
|
|
|
if (!wasOnlineFound) {
|
|
Serial.println("Zeit nicht gesendet - User wurde lokal gefunden");
|
|
return;
|
|
}
|
|
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
Serial.println("Keine Internetverbindung - Zeit nicht gesendet");
|
|
return;
|
|
}
|
|
|
|
Serial.println("Sende Zeit an Online-API für Lane " + String(lane));
|
|
|
|
HTTPClient http;
|
|
http.begin(String(BACKEND_SERVER) + "/api/v1/private/create-time");
|
|
http.addHeader("Content-Type", "application/json");
|
|
http.addHeader("Authorization", String("Bearer ") + licence);
|
|
|
|
// Zeit in M:SS.mmm Format konvertieren (ohne führende Null bei Minuten)
|
|
int minutes = (int)(timeInSeconds / 60);
|
|
int seconds = (int)timeInSeconds % 60;
|
|
int milliseconds = (int)((timeInSeconds - (int)timeInSeconds) * 1000);
|
|
|
|
String formattedTime =
|
|
String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds) +
|
|
"." + (milliseconds < 10 ? "00" : (milliseconds < 100 ? "0" : "")) +
|
|
String(milliseconds);
|
|
|
|
StaticJsonDocument<200> requestDoc;
|
|
requestDoc["rfiduid"] = uid;
|
|
requestDoc["location_name"] =
|
|
getLocationIdFromPrefs(); // Aus den Einstellungen
|
|
requestDoc["recorded_time"] = formattedTime;
|
|
|
|
String requestBody;
|
|
serializeJson(requestDoc, requestBody);
|
|
|
|
Serial.println("API Request Body: " + requestBody);
|
|
|
|
int httpCode = http.POST(requestBody);
|
|
|
|
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
|
|
String response = http.getString();
|
|
Serial.println("Zeit erfolgreich gesendet: " + response);
|
|
} else {
|
|
Serial.printf("Fehler beim Senden der Zeit: HTTP %d\n", httpCode);
|
|
if (httpCode > 0) {
|
|
String response = http.getString();
|
|
Serial.println("Response: " + response);
|
|
}
|
|
}
|
|
|
|
http.end();
|
|
}
|
|
|
|
// Funktionen für lokales Leaderboard
|
|
void addLocalTime(String uid, String name, unsigned long timeMs) {
|
|
// Prüfe minimale Zeit für Leaderboard-Eintrag
|
|
if (timeMs < minTimeForLeaderboard) {
|
|
Serial.printf(
|
|
"Zeit zu kurz für Leaderboard: %s (%s) - %.2fs (Minimum: %.2fs)\n",
|
|
name.c_str(), uid.c_str(), timeMs / 1000.0,
|
|
minTimeForLeaderboard / 1000.0);
|
|
return; // Zeit wird nicht ins Leaderboard eingetragen
|
|
}
|
|
|
|
LocalTime newTime;
|
|
newTime.uid = uid;
|
|
newTime.name = name;
|
|
newTime.timeMs = timeMs;
|
|
newTime.timestamp = millis();
|
|
|
|
localTimes.push_back(newTime);
|
|
|
|
// Speichere das Leaderboard automatisch
|
|
saveBestTimes();
|
|
|
|
Serial.printf("Lokale Zeit hinzugefügt: %s (%s) - %.2fs\n", name.c_str(),
|
|
uid.c_str(), timeMs / 1000.0);
|
|
}
|
|
|
|
// Leert das lokale Leaderboard
|
|
void clearLocalLeaderboard() {
|
|
localTimes.clear();
|
|
saveBestTimes(); // Speichere das geleerte Leaderboard
|
|
Serial.println("Lokales Leaderboard geleert");
|
|
} |