Lokal Leaderboard

This commit is contained in:
Carsten Graf
2025-09-20 19:14:41 +02:00
parent 7e9705902e
commit 9de327bfb3
8 changed files with 419 additions and 36 deletions

View File

@@ -46,6 +46,20 @@ typedef struct {
// MQTT-Server-Instanz
PicoMQTT::Server mqtt;
// Tracking der Quelle für jede Lane
bool start1FoundLocally = false;
bool start2FoundLocally = false;
String start1UID = "";
String start2UID = "";
// Hilfsfunktionen um die Quelle abzufragen
bool wasStart1FoundLocally() { return start1FoundLocally; }
bool wasStart2FoundLocally() { return start2FoundLocally; }
String getStart1UID() { return start1UID; }
String getStart2UID() { return start2UID; }
/**
* Liest eine Button-JSON-Nachricht, extrahiert Typ, MAC und Timestamp,
* prüft die Button-Zuordnung und ruft die entsprechende Handler-Funktion auf.
@@ -275,10 +289,18 @@ void readRFIDfromButton(const char *topic, const char *payload) {
// Check if the buttonmac matches buttonConfigs.start1.mac
if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) {
// Prüfe ob Lane 1 bereit ist
if (timerData1.isRunning || timerData1.isArmed) {
Serial.println("Lane 1 läuft - ignoriere RFID: " + String(uid));
return;
}
// Zuerst lokal suchen (UID in Großbuchstaben konvertieren)
String upperUid = String(uid);
upperUid.toUpperCase();
UserData userData = checkUser(upperUid);
start1FoundLocally = userData.exists; // Merken ob lokal gefunden
start1UID = upperUid; // UID für später speichern
if (!userData.exists) {
// Nicht lokal gefunden - Online-Server fragen
@@ -342,15 +364,17 @@ void readRFIDfromButton(const char *topic, const char *payload) {
// Wenn Benutzer gefunden wurde (lokal oder online)
if (userData.exists) {
// Log user data
Serial.printf("User found for start1: %s\n",
// Bestimme ob lokal oder online gefunden (bereits oben gesetzt)
String source = start1FoundLocally ? "lokal" : "online";
// Log user data mit Quelle
Serial.printf("User %s gefunden für start1: %s\n", source.c_str(),
userData.firstname.c_str());
// Create JSON message to send to the frontend
// Create JSON message to send to the frontend (ohne source)
StaticJsonDocument<128> messageDoc;
messageDoc["name"] =
userData.firstname; // Verwende name statt firstname/lastname
messageDoc["lane"] = "start1"; // Add lane information
messageDoc["name"] = userData.firstname;
messageDoc["lane"] = "start1";
String message;
serializeJson(messageDoc, message);
@@ -361,14 +385,35 @@ void readRFIDfromButton(const char *topic, const char *payload) {
message.c_str());
} else {
Serial.println("User nicht gefunden für UID: " + upperUid);
// Sende UID an Frontend wenn kein User gefunden wurde
StaticJsonDocument<128> messageDoc;
messageDoc["name"] = upperUid; // UID als Name senden
messageDoc["lane"] = "start1";
String message;
serializeJson(messageDoc, message);
// Push die UID an das Frontend
pushUpdateToFrontend(message);
Serial.printf("Sende UID an Frontend für start1: %s\n",
message.c_str());
}
}
// Check if the buttonmac matches buttonConfigs.start2.mac
else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) {
// Prüfe ob Lane 2 bereit ist
if (timerData2.isRunning || timerData2.isArmed) {
Serial.println("Lane 2 nicht bereit - ignoriere RFID: " + String(uid));
return;
}
// Zuerst lokal suchen (UID in Großbuchstaben konvertieren)
String upperUid = String(uid);
upperUid.toUpperCase();
UserData userData = checkUser(upperUid);
start2FoundLocally = userData.exists; // Merken ob lokal gefunden
start2UID = upperUid; // UID für später speichern
if (!userData.exists) {
// Nicht lokal gefunden - Online-Server fragen
@@ -432,15 +477,17 @@ void readRFIDfromButton(const char *topic, const char *payload) {
// Wenn Benutzer gefunden wurde (lokal oder online)
if (userData.exists) {
// Log user data
Serial.printf("User found for start2: %s\n",
// Bestimme ob lokal oder online gefunden (bereits oben gesetzt)
String source = start2FoundLocally ? "lokal" : "online";
// Log user data mit Quelle
Serial.printf("User %s gefunden für start2: %s\n", source.c_str(),
userData.firstname.c_str());
// Create JSON message to send to the frontend
// Create JSON message to send to the frontend (ohne source)
StaticJsonDocument<128> messageDoc;
messageDoc["name"] =
userData.firstname; // Verwende name statt firstname/lastname
messageDoc["lane"] = "start2"; // Add lane information
messageDoc["name"] = userData.firstname;
messageDoc["lane"] = "start2";
String message;
serializeJson(messageDoc, message);
@@ -451,6 +498,19 @@ void readRFIDfromButton(const char *topic, const char *payload) {
message.c_str());
} else {
Serial.println("User nicht gefunden für UID: " + upperUid);
// Sende UID an Frontend wenn kein User gefunden wurde
StaticJsonDocument<128> messageDoc;
messageDoc["name"] = upperUid; // UID als Name senden
messageDoc["lane"] = "start2";
String message;
serializeJson(messageDoc, message);
// Push die UID an das Frontend
pushUpdateToFrontend(message);
Serial.printf("Sende UID an Frontend für start2: %s\n",
message.c_str());
}
} else {
Serial.println("Button MAC does not match start1.mac or start2.mac");

View File

@@ -3,6 +3,7 @@
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <HTTPClient.h>
#include <algorithm>
#include <preferencemanager.h>
#include <vector>
@@ -54,6 +55,9 @@ struct UserData {
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) {
@@ -349,5 +353,135 @@ void setupBackendRoutes(AsyncWebServer &server) {
// Andere Logik wie in getBestLocs
});
// Lokales Leaderboard API
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 5
int count = 0;
for (const auto &time : localTimes) {
if (count >= 5)
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) {
LocalTime newTime;
newTime.uid = uid;
newTime.name = name;
newTime.timeMs = timeMs;
newTime.timestamp = millis();
localTimes.push_back(newTime);
Serial.printf("Lokale Zeit hinzugefügt: %s (%s) - %.2fs\n", name.c_str(),
uid.c_str(), timeMs / 1000.0);
}

View File

@@ -56,6 +56,18 @@ void IndividualMode(const char *action, int press, int lane,
publishLaneStatus(1, "stopped");
Serial.println("Bahn 1 gestoppt - Zeit: " + String(currentTime / 1000.0) +
"s");
// Speichere Zeit lokal wenn User lokal gefunden wurde
if (wasStart1FoundLocally() && getStart1UID().length() > 0) {
// Finde den Namen des lokalen Users
UserData userData = checkUser(getStart1UID());
if (userData.exists) {
addLocalTime(getStart1UID(), userData.firstname, currentTime);
}
} else if (!wasStart1FoundLocally() && getStart1UID().length() > 0) {
// Sende Zeit an Online-API wenn User online gefunden wurde
sendTimeToOnlineAPI(1, getStart1UID(), currentTime / 1000.0);
}
}
}
if (action == "start" && press == 2 && lane == 2) {
@@ -84,6 +96,18 @@ void IndividualMode(const char *action, int press, int lane,
publishLaneStatus(2, "stopped");
Serial.println("Bahn 2 gestoppt - Zeit: " + String(currentTime / 1000.0) +
"s");
// Speichere Zeit lokal wenn User lokal gefunden wurde
if (wasStart2FoundLocally() && getStart2UID().length() > 0) {
// Finde den Namen des lokalen Users
UserData userData = checkUser(getStart2UID());
if (userData.exists) {
addLocalTime(getStart2UID(), userData.firstname, currentTime);
}
} else if (!wasStart2FoundLocally() && getStart2UID().length() > 0) {
// Sende Zeit an Online-API wenn User online gefunden wurde
sendTimeToOnlineAPI(2, getStart2UID(), currentTime / 1000.0);
}
}
}
@@ -330,4 +354,4 @@ String getTimerDataJSON() {
String result;
serializeJson(doc, result);
return result;
}
}

View File

@@ -64,8 +64,20 @@ void setup() {
void loop() {
checkAutoReset();
loopMqttServer(); // MQTT Server in der Loop aufrufen
// MQTT hat höchste Priorität (wird zuerst verarbeitet)
loopMqttServer();
// WebSocket verarbeiten
loopWebSocket();
// RFID Loop nur wenn aktiv (spart CPU-Zyklen)
if (isRFIDReadingActive()) {
loopRFID();
}
// loopBattery(); // Batterie-Loop aufrufen
loopRFID(); // RFID Loop aufrufen
// Kurze Pause um anderen Tasks Zeit zu geben
delay(1);
}

View File

@@ -4,6 +4,7 @@
#include <ESPAsyncWebServer.h>
#include <sys/time.h>
#include <time.h>
#include <vector>
const char *ssidAP;
const char *passwordAP = nullptr;
@@ -24,6 +25,14 @@ struct TimerData1 {
char RFIDUID[32] = "";
};
// Struktur für lokale Zeiten (Leaderboard)
struct LocalTime {
String uid;
String name;
unsigned long timeMs;
unsigned long timestamp;
};
// Timer Struktur für Bahn 2
struct TimerData2 {
unsigned long startTime = 0;
@@ -73,6 +82,9 @@ int laneConfigType = 0; // 0=Identical, 1=Different
int lane1DifficultyType = 0; // 0=Light, 1=Heavy (difficulty)
int lane2DifficultyType = 0; // 0=Light, 1=Heavy (difficulty)
// Lokales Leaderboard
std::vector<LocalTime> localTimes;
// Function Declarations
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len);
void handleLearningMode(const uint8_t *mac);

View File

@@ -19,6 +19,9 @@ bool readingMode = false;
String lastReadUID = "";
unsigned long lastReadTime = 0;
// Hilfsfunktion um Reading-Mode zu prüfen
bool isRFIDReadingActive() { return readingMode; }
// Initialisiert den RFID-Reader
void setupRFID() {
// I2C starten mit korrekten Pins
@@ -54,7 +57,7 @@ bool checkRFID() {
return (versiondata != 0);
}
// Liest RFID-Karte - GANZ EINFACH
// Liest RFID-Karte - NICHT BLOCKIEREND
String readRFIDCard() {
if (!checkRFID()) {
return "";
@@ -63,11 +66,13 @@ String readRFIDCard() {
uint8_t uid[] = {0, 0, 0, 0, 0, 0, 0};
uint8_t uidLength;
// Nicht-blockierender Aufruf mit sehr kurzer Timeout
uint8_t success =
nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength,
50); // 50ms Timeout statt Standard 100ms
if (!success) {
return ""; // Keine Karte
return ""; // Keine Karte oder Timeout
}
// UID zu String
@@ -85,7 +90,7 @@ String readRFIDCard() {
return uidString;
}
// RFID Loop - kontinuierliches Lesen wenn aktiviert
// RFID Loop - kontinuierliches Lesen wenn aktiviert (MQTT-optimiert)
void loopRFID() {
if (!readingMode) {
return; // Lesen nicht aktiviert
@@ -93,13 +98,13 @@ void loopRFID() {
static unsigned long lastCheck = 0;
// Nur alle 200ms prüfen (schneller für bessere Responsivität)
if (millis() - lastCheck < 200) {
// Nur alle 300ms prüfen (weniger belastend für MQTT)
if (millis() - lastCheck < 300) {
return;
}
lastCheck = millis();
// Versuchen zu lesen
// Versuchen zu lesen (mit kurzer Timeout)
String uid = readRFIDCard();
if (uid.length() > 0) {
// Nur neue UIDs oder nach 2 Sekunden Pause