v1 #1
@@ -353,7 +353,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
font-size: clamp(3rem, 1.8vw, 1.2rem);
|
font-size: clamp(1.5rem, 3vw, 3rem);
|
||||||
margin: clamp(8px, 1vh, 12px) 0;
|
margin: clamp(8px, 1vh, 12px) 0;
|
||||||
padding: clamp(6px, 1vh, 10px) clamp(12px, 2vw, 18px);
|
padding: clamp(6px, 1vh, 10px) clamp(12px, 2vw, 18px);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -455,6 +455,61 @@ body {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Leaderboard Styles */
|
||||||
|
#leaderboard-container {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: clamp(8px, 1vh, 12px) 0;
|
||||||
|
font-size: clamp(1.1rem, 2.2vw, 1.4rem);
|
||||||
|
font-weight: 600;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
padding: clamp(8px, 1.5vh, 12px) clamp(12px, 2vw, 16px);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .rank {
|
||||||
|
color: #ffd700;
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 30px;
|
||||||
|
font-size: clamp(1.2rem, 2.4vw, 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .name {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 15px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .time {
|
||||||
|
color: #00ff88;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
min-width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-times {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: clamp(0.9rem, 1.8vw, 1.1rem);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.learning-mode {
|
.learning-mode {
|
||||||
background: rgba(245, 157, 15, 0.2);
|
background: rgba(245, 157, 15, 0.2);
|
||||||
border: 2px solid #f59d0f;
|
border: 2px solid #f59d0f;
|
||||||
|
|||||||
107
data/index.html
107
data/index.html
@@ -72,14 +72,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="best-times">
|
<div class="best-times">
|
||||||
<h3>🏆 Deine Bestzeiten heute</h3>
|
<h3>🏆 Lokales Leaderboard</h3>
|
||||||
<div class="best-time-row">
|
<div id="leaderboard-container">
|
||||||
<span>Bahn 1:</span>
|
<div class="leaderboard-entry">
|
||||||
<span id="best1">--.-</span>
|
<span class="rank">1.</span>
|
||||||
</div>
|
<span class="name">Max Mustermann</span>
|
||||||
<div class="best-time-row">
|
<span class="time">23.45</span>
|
||||||
<span>Bahn 2:</span>
|
</div>
|
||||||
<span id="best2">--.-</span>
|
<div class="leaderboard-entry">
|
||||||
|
<span class="rank">2.</span>
|
||||||
|
<span class="name">Anna Schmidt</span>
|
||||||
|
<span class="time">24.67</span>
|
||||||
|
</div>
|
||||||
|
<div class="leaderboard-entry">
|
||||||
|
<span class="rank">3.</span>
|
||||||
|
<span class="name">Tom Weber</span>
|
||||||
|
<span class="time">25.89</span>
|
||||||
|
</div>
|
||||||
|
<div class="leaderboard-entry">
|
||||||
|
<span class="rank">4.</span>
|
||||||
|
<span class="name">Lisa Müller</span>
|
||||||
|
<span class="time">26.12</span>
|
||||||
|
</div>
|
||||||
|
<div class="leaderboard-entry">
|
||||||
|
<span class="rank">5.</span>
|
||||||
|
<span class="name">Paul Fischer</span>
|
||||||
|
<span class="time">27.34</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -96,6 +115,7 @@
|
|||||||
let learningButton = "";
|
let learningButton = "";
|
||||||
let name1 = "";
|
let name1 = "";
|
||||||
let name2 = "";
|
let name2 = "";
|
||||||
|
let leaderboardData = [];
|
||||||
|
|
||||||
// Lane Configuration
|
// Lane Configuration
|
||||||
let laneConfigType = 0; // 0=Identical, 1=Different
|
let laneConfigType = 0; // 0=Identical, 1=Different
|
||||||
@@ -336,7 +356,67 @@
|
|||||||
|
|
||||||
function formatTime(seconds) {
|
function formatTime(seconds) {
|
||||||
if (seconds === 0) return "00.00";
|
if (seconds === 0) return "00.00";
|
||||||
return seconds.toFixed(2);
|
|
||||||
|
const totalSeconds = Math.floor(seconds);
|
||||||
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
|
const remainingSeconds = totalSeconds % 60;
|
||||||
|
const milliseconds = Math.floor((seconds - totalSeconds) * 100);
|
||||||
|
|
||||||
|
// Zeige Minuten nur wenn über 60 Sekunden
|
||||||
|
if (totalSeconds >= 60) {
|
||||||
|
return `${minutes.toString().padStart(2, "0")}:${remainingSeconds
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}.${milliseconds.toString().padStart(2, "0")}`;
|
||||||
|
} else {
|
||||||
|
return `${remainingSeconds.toString().padStart(2, "0")}.${milliseconds
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaderboard Funktionen
|
||||||
|
async function loadLeaderboard() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/leaderboard");
|
||||||
|
const data = await response.json();
|
||||||
|
leaderboardData = data.leaderboard || [];
|
||||||
|
updateLeaderboardDisplay();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fehler beim Laden des Leaderboards:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLeaderboardDisplay() {
|
||||||
|
const container = document.getElementById("leaderboard-container");
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
if (leaderboardData.length === 0) {
|
||||||
|
container.innerHTML =
|
||||||
|
'<div class="no-times">Noch keine Zeiten erfasst</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
leaderboardData.forEach((entry, index) => {
|
||||||
|
const entryDiv = document.createElement("div");
|
||||||
|
entryDiv.className = "leaderboard-entry";
|
||||||
|
|
||||||
|
const rankSpan = document.createElement("span");
|
||||||
|
rankSpan.className = "rank";
|
||||||
|
rankSpan.textContent = entry.rank + ".";
|
||||||
|
|
||||||
|
const nameSpan = document.createElement("span");
|
||||||
|
nameSpan.className = "name";
|
||||||
|
nameSpan.textContent = entry.name;
|
||||||
|
|
||||||
|
const timeSpan = document.createElement("span");
|
||||||
|
timeSpan.className = "time";
|
||||||
|
timeSpan.textContent = entry.timeFormatted;
|
||||||
|
|
||||||
|
entryDiv.appendChild(rankSpan);
|
||||||
|
entryDiv.appendChild(nameSpan);
|
||||||
|
entryDiv.appendChild(timeSpan);
|
||||||
|
container.appendChild(entryDiv);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDisplay() {
|
function updateDisplay() {
|
||||||
@@ -411,10 +491,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("best1").textContent =
|
// Leaderboard wird separat geladen
|
||||||
best1 > 0 ? formatTime(best1) + "s" : "--.-";
|
|
||||||
document.getElementById("best2").textContent =
|
|
||||||
best2 > 0 ? formatTime(best2) + "s" : "--.-";
|
|
||||||
|
|
||||||
// Namen anzeigen/verstecken
|
// Namen anzeigen/verstecken
|
||||||
const name1Element = document.getElementById("name1");
|
const name1Element = document.getElementById("name1");
|
||||||
@@ -528,6 +605,10 @@
|
|||||||
// Initial load
|
// Initial load
|
||||||
syncFromBackend();
|
syncFromBackend();
|
||||||
loadLaneConfig();
|
loadLaneConfig();
|
||||||
|
loadLeaderboard();
|
||||||
|
|
||||||
|
// Leaderboard alle 5 Sekunden aktualisieren
|
||||||
|
setInterval(loadLeaderboard, 5000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ typedef struct {
|
|||||||
// MQTT-Server-Instanz
|
// MQTT-Server-Instanz
|
||||||
PicoMQTT::Server mqtt;
|
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,
|
* Liest eine Button-JSON-Nachricht, extrahiert Typ, MAC und Timestamp,
|
||||||
* prüft die Button-Zuordnung und ruft die entsprechende Handler-Funktion auf.
|
* 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
|
// Check if the buttonmac matches buttonConfigs.start1.mac
|
||||||
if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) {
|
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)
|
// Zuerst lokal suchen (UID in Großbuchstaben konvertieren)
|
||||||
String upperUid = String(uid);
|
String upperUid = String(uid);
|
||||||
upperUid.toUpperCase();
|
upperUid.toUpperCase();
|
||||||
UserData userData = checkUser(upperUid);
|
UserData userData = checkUser(upperUid);
|
||||||
|
start1FoundLocally = userData.exists; // Merken ob lokal gefunden
|
||||||
|
start1UID = upperUid; // UID für später speichern
|
||||||
|
|
||||||
if (!userData.exists) {
|
if (!userData.exists) {
|
||||||
// Nicht lokal gefunden - Online-Server fragen
|
// 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)
|
// Wenn Benutzer gefunden wurde (lokal oder online)
|
||||||
if (userData.exists) {
|
if (userData.exists) {
|
||||||
// Log user data
|
// Bestimme ob lokal oder online gefunden (bereits oben gesetzt)
|
||||||
Serial.printf("User found for start1: %s\n",
|
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());
|
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;
|
StaticJsonDocument<128> messageDoc;
|
||||||
messageDoc["name"] =
|
messageDoc["name"] = userData.firstname;
|
||||||
userData.firstname; // Verwende name statt firstname/lastname
|
messageDoc["lane"] = "start1";
|
||||||
messageDoc["lane"] = "start1"; // Add lane information
|
|
||||||
|
|
||||||
String message;
|
String message;
|
||||||
serializeJson(messageDoc, message);
|
serializeJson(messageDoc, message);
|
||||||
@@ -361,14 +385,35 @@ void readRFIDfromButton(const char *topic, const char *payload) {
|
|||||||
message.c_str());
|
message.c_str());
|
||||||
} else {
|
} else {
|
||||||
Serial.println("User nicht gefunden für UID: " + upperUid);
|
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
|
// Check if the buttonmac matches buttonConfigs.start2.mac
|
||||||
else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) {
|
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)
|
// Zuerst lokal suchen (UID in Großbuchstaben konvertieren)
|
||||||
String upperUid = String(uid);
|
String upperUid = String(uid);
|
||||||
upperUid.toUpperCase();
|
upperUid.toUpperCase();
|
||||||
UserData userData = checkUser(upperUid);
|
UserData userData = checkUser(upperUid);
|
||||||
|
start2FoundLocally = userData.exists; // Merken ob lokal gefunden
|
||||||
|
start2UID = upperUid; // UID für später speichern
|
||||||
|
|
||||||
if (!userData.exists) {
|
if (!userData.exists) {
|
||||||
// Nicht lokal gefunden - Online-Server fragen
|
// 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)
|
// Wenn Benutzer gefunden wurde (lokal oder online)
|
||||||
if (userData.exists) {
|
if (userData.exists) {
|
||||||
// Log user data
|
// Bestimme ob lokal oder online gefunden (bereits oben gesetzt)
|
||||||
Serial.printf("User found for start2: %s\n",
|
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());
|
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;
|
StaticJsonDocument<128> messageDoc;
|
||||||
messageDoc["name"] =
|
messageDoc["name"] = userData.firstname;
|
||||||
userData.firstname; // Verwende name statt firstname/lastname
|
messageDoc["lane"] = "start2";
|
||||||
messageDoc["lane"] = "start2"; // Add lane information
|
|
||||||
|
|
||||||
String message;
|
String message;
|
||||||
serializeJson(messageDoc, message);
|
serializeJson(messageDoc, message);
|
||||||
@@ -451,6 +498,19 @@ void readRFIDfromButton(const char *topic, const char *payload) {
|
|||||||
message.c_str());
|
message.c_str());
|
||||||
} else {
|
} else {
|
||||||
Serial.println("User nicht gefunden für UID: " + upperUid);
|
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 {
|
} else {
|
||||||
Serial.println("Button MAC does not match start1.mac or start2.mac");
|
Serial.println("Button MAC does not match start1.mac or start2.mac");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <preferencemanager.h>
|
#include <preferencemanager.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -54,6 +55,9 @@ struct UserData {
|
|||||||
bool exists;
|
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
|
// Prüft, ob ein Benutzer mit der angegebenen UID in der Datenbank existiert und
|
||||||
// gibt dessen Daten zurück.
|
// gibt dessen Daten zurück.
|
||||||
UserData checkUser(const String &uid) {
|
UserData checkUser(const String &uid) {
|
||||||
@@ -349,5 +353,135 @@ void setupBackendRoutes(AsyncWebServer &server) {
|
|||||||
// Andere Logik wie in getBestLocs
|
// 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
|
// 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,18 @@ void IndividualMode(const char *action, int press, int lane,
|
|||||||
publishLaneStatus(1, "stopped");
|
publishLaneStatus(1, "stopped");
|
||||||
Serial.println("Bahn 1 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
Serial.println("Bahn 1 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
||||||
"s");
|
"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) {
|
if (action == "start" && press == 2 && lane == 2) {
|
||||||
@@ -84,6 +96,18 @@ void IndividualMode(const char *action, int press, int lane,
|
|||||||
publishLaneStatus(2, "stopped");
|
publishLaneStatus(2, "stopped");
|
||||||
Serial.println("Bahn 2 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
Serial.println("Bahn 2 gestoppt - Zeit: " + String(currentTime / 1000.0) +
|
||||||
"s");
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,8 +64,20 @@ void setup() {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
checkAutoReset();
|
checkAutoReset();
|
||||||
loopMqttServer(); // MQTT Server in der Loop aufrufen
|
|
||||||
|
// MQTT hat höchste Priorität (wird zuerst verarbeitet)
|
||||||
|
loopMqttServer();
|
||||||
|
|
||||||
|
// WebSocket verarbeiten
|
||||||
loopWebSocket();
|
loopWebSocket();
|
||||||
|
|
||||||
|
// RFID Loop nur wenn aktiv (spart CPU-Zyklen)
|
||||||
|
if (isRFIDReadingActive()) {
|
||||||
|
loopRFID();
|
||||||
|
}
|
||||||
|
|
||||||
// loopBattery(); // Batterie-Loop aufrufen
|
// loopBattery(); // Batterie-Loop aufrufen
|
||||||
loopRFID(); // RFID Loop aufrufen
|
|
||||||
|
// Kurze Pause um anderen Tasks Zeit zu geben
|
||||||
|
delay(1);
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/master.h
12
src/master.h
@@ -4,6 +4,7 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
const char *ssidAP;
|
const char *ssidAP;
|
||||||
const char *passwordAP = nullptr;
|
const char *passwordAP = nullptr;
|
||||||
@@ -24,6 +25,14 @@ struct TimerData1 {
|
|||||||
char RFIDUID[32] = "";
|
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
|
// Timer Struktur für Bahn 2
|
||||||
struct TimerData2 {
|
struct TimerData2 {
|
||||||
unsigned long startTime = 0;
|
unsigned long startTime = 0;
|
||||||
@@ -73,6 +82,9 @@ int laneConfigType = 0; // 0=Identical, 1=Different
|
|||||||
int lane1DifficultyType = 0; // 0=Light, 1=Heavy (difficulty)
|
int lane1DifficultyType = 0; // 0=Light, 1=Heavy (difficulty)
|
||||||
int lane2DifficultyType = 0; // 0=Light, 1=Heavy (difficulty)
|
int lane2DifficultyType = 0; // 0=Light, 1=Heavy (difficulty)
|
||||||
|
|
||||||
|
// Lokales Leaderboard
|
||||||
|
std::vector<LocalTime> localTimes;
|
||||||
|
|
||||||
// Function Declarations
|
// Function Declarations
|
||||||
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len);
|
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len);
|
||||||
void handleLearningMode(const uint8_t *mac);
|
void handleLearningMode(const uint8_t *mac);
|
||||||
|
|||||||
19
src/rfid.h
19
src/rfid.h
@@ -19,6 +19,9 @@ bool readingMode = false;
|
|||||||
String lastReadUID = "";
|
String lastReadUID = "";
|
||||||
unsigned long lastReadTime = 0;
|
unsigned long lastReadTime = 0;
|
||||||
|
|
||||||
|
// Hilfsfunktion um Reading-Mode zu prüfen
|
||||||
|
bool isRFIDReadingActive() { return readingMode; }
|
||||||
|
|
||||||
// Initialisiert den RFID-Reader
|
// Initialisiert den RFID-Reader
|
||||||
void setupRFID() {
|
void setupRFID() {
|
||||||
// I2C starten mit korrekten Pins
|
// I2C starten mit korrekten Pins
|
||||||
@@ -54,7 +57,7 @@ bool checkRFID() {
|
|||||||
return (versiondata != 0);
|
return (versiondata != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Liest RFID-Karte - GANZ EINFACH
|
// Liest RFID-Karte - NICHT BLOCKIEREND
|
||||||
String readRFIDCard() {
|
String readRFIDCard() {
|
||||||
if (!checkRFID()) {
|
if (!checkRFID()) {
|
||||||
return "";
|
return "";
|
||||||
@@ -63,11 +66,13 @@ String readRFIDCard() {
|
|||||||
uint8_t uid[] = {0, 0, 0, 0, 0, 0, 0};
|
uint8_t uid[] = {0, 0, 0, 0, 0, 0, 0};
|
||||||
uint8_t uidLength;
|
uint8_t uidLength;
|
||||||
|
|
||||||
|
// Nicht-blockierender Aufruf mit sehr kurzer Timeout
|
||||||
uint8_t success =
|
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) {
|
if (!success) {
|
||||||
return ""; // Keine Karte
|
return ""; // Keine Karte oder Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// UID zu String
|
// UID zu String
|
||||||
@@ -85,7 +90,7 @@ String readRFIDCard() {
|
|||||||
return uidString;
|
return uidString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFID Loop - kontinuierliches Lesen wenn aktiviert
|
// RFID Loop - kontinuierliches Lesen wenn aktiviert (MQTT-optimiert)
|
||||||
void loopRFID() {
|
void loopRFID() {
|
||||||
if (!readingMode) {
|
if (!readingMode) {
|
||||||
return; // Lesen nicht aktiviert
|
return; // Lesen nicht aktiviert
|
||||||
@@ -93,13 +98,13 @@ void loopRFID() {
|
|||||||
|
|
||||||
static unsigned long lastCheck = 0;
|
static unsigned long lastCheck = 0;
|
||||||
|
|
||||||
// Nur alle 200ms prüfen (schneller für bessere Responsivität)
|
// Nur alle 300ms prüfen (weniger belastend für MQTT)
|
||||||
if (millis() - lastCheck < 200) {
|
if (millis() - lastCheck < 300) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastCheck = millis();
|
lastCheck = millis();
|
||||||
|
|
||||||
// Versuchen zu lesen
|
// Versuchen zu lesen (mit kurzer Timeout)
|
||||||
String uid = readRFIDCard();
|
String uid = readRFIDCard();
|
||||||
if (uid.length() > 0) {
|
if (uid.length() > 0) {
|
||||||
// Nur neue UIDs oder nach 2 Sekunden Pause
|
// Nur neue UIDs oder nach 2 Sekunden Pause
|
||||||
|
|||||||
Reference in New Issue
Block a user