From c35e85790458f91c4fb5cc9c572c9e28dfa3eaab Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Sun, 8 Jun 2025 00:38:00 +0200 Subject: [PATCH] =?UTF-8?q?RFID=20message=20ins=20backend=20geht,=20websoc?= =?UTF-8?q?ket=20f=C3=BCrs=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/index.css | 41 ++++++++++- data/index.html | 58 ++++++++++++++-- data/pictures/favicon.ico | Bin 0 -> 15299 bytes data/rfid.html | 1 + data/settings.html | 1 + src/communication.h | 140 ++++++++++++++++++++++++-------------- src/databasebackend.h | 61 ++++++++++++++--- src/master.cpp | 29 ++++++-- src/rfid.h | 5 +- src/webserverrouter.h | 69 +++++++++++++------ src/wificlass.h | 4 +- 11 files changed, 311 insertions(+), 98 deletions(-) create mode 100644 data/pictures/favicon.ico diff --git a/data/index.css b/data/index.css index 4cad1fc..21ab08e 100644 --- a/data/index.css +++ b/data/index.css @@ -150,6 +150,35 @@ html { color: #fff; } + .swimmer-name { + font-size: clamp(1.5rem, 3.5vw, 2.2rem); + font-weight: bold; + margin-bottom: clamp(15px, 2vh, 25px); + padding: clamp(8px, 1.5vh, 12px) clamp(12px, 2vw, 18px); + background: rgba(255, 255, 255, 0.2); + border-radius: 15px; + border: 2px solid rgba(255, 255, 255, 0.3); + color: #fff; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.4); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(5px); + animation: fadeIn 0.5s ease-in; + text-align: center; + word-wrap: break-word; + line-height: 1.2; + } + + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + .time-display { font-size: clamp(3rem, 9vw, 10rem); font-weight: bold; @@ -280,6 +309,11 @@ html { body { padding: 10px; } + + .swimmer-name { + font-size: clamp(1.2rem, 4vw, 1.8rem); + margin-bottom: clamp(10px, 1.5vh, 20px); + } } @media (max-width: 480px) { @@ -299,4 +333,9 @@ html { .timer-container { padding: 0 2vw; } - } \ No newline at end of file + + .swimmer-name { + font-size: clamp(1rem, 4vw, 1.5rem); + padding: clamp(6px, 1vh, 10px) clamp(8px, 1.5vw, 12px); + } + } \ No newline at end of file diff --git a/data/index.html b/data/index.html index a8c117c..e0d17db 100644 --- a/data/index.html +++ b/data/index.html @@ -1,14 +1,14 @@ - + NinjaCross Timer - + @@ -29,12 +29,14 @@
+

🏊‍♀️ Bahn 1

00.00
Bereit
+

🏊‍♂️ Bahn 2

00.00
Bereit
@@ -64,6 +66,37 @@ let lastSync = Date.now(); let learningMode = false; let learningButton = ""; + let name1 = ""; + let name2 = ""; + const ws = new WebSocket(`ws://${window.location.host}/ws`); + +// Handle WebSocket events +ws.onopen = () => { + console.log("WebSocket connected"); +}; +ws.onclose = () => { + console.log("WebSocket disconnected"); +}; + +ws.onmessage = (event) => { + console.log("WebSocket message received:", event.data); + + try { + const data = JSON.parse(event.data); + + if (data.firstname && data.lastname && data.lane) { + if (data.lane === "start1") { + name1 = `${data.firstname} ${data.lastname}`; + } else if (data.lane === "start2") { + name2 = `${data.firstname} ${data.lastname}`; + } + + updateDisplay(); + } + } catch (error) { + console.error("Error processing WebSocket message:", error); + } +}; function formatTime(seconds) { if (seconds === 0) return "00.00"; @@ -83,7 +116,6 @@ display2 += (now - lastSync) / 1000; } - document.getElementById("time1").textContent = formatTime(display1); const s1 = document.getElementById("status1"); s1.className = `status ${status1}`; @@ -109,6 +141,24 @@ document.getElementById("best2").textContent = best2 > 0 ? formatTime(best2) + "s" : "--.-"; + // Namen anzeigen/verstecken - verbesserte Logik + const name1Element = document.getElementById("name1"); + const name2Element = document.getElementById("name2"); + + if (name1 && name1.trim() !== "") { + name1Element.textContent = name1; + name1Element.style.display = "block"; + } else { + name1Element.style.display = "none"; + } + + if (name2 && name2.trim() !== "") { + name2Element.textContent = name2; + name2Element.style.display = "block"; + } else { + name2Element.style.display = "none"; + } + // Lernmodus const learningDisplay = document.getElementById("learning-display"); if (learningMode) { @@ -139,7 +189,7 @@ ); } - // Sync with backend every 2 seconds + // Sync with backend every 1 second setInterval(syncFromBackend, 1000); // Smooth update every 50ms diff --git a/data/pictures/favicon.ico b/data/pictures/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5f2904b6066949fff4ce6195d013e8a4b74f7fde GIT binary patch literal 15299 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 Df8F^b literal 0 HcmV?d00001 diff --git a/data/rfid.html b/data/rfid.html index 19330bb..9709e92 100644 --- a/data/rfid.html +++ b/data/rfid.html @@ -4,6 +4,7 @@ + RFID Daten Eingabe diff --git a/data/settings.html b/data/settings.html index 8da9d6f..0895255 100644 --- a/data/settings.html +++ b/data/settings.html @@ -4,6 +4,7 @@ + diff --git a/src/communication.h b/src/communication.h index 207824d..6a70efa 100644 --- a/src/communication.h +++ b/src/communication.h @@ -11,6 +11,8 @@ #include "helper.h" #include #include +#include +#include struct TimestampData { uint64_t lastMessageTimestamp; // Timestamp from the device @@ -35,52 +37,6 @@ typedef struct { PicoMQTT::Server mqtt; -void processHeartbeat(const char* topic, const char* payload) { - String macAddress = String(topic).substring(15); - - StaticJsonDocument<200> doc; - DeserializationError error = deserializeJson(doc, payload); - - if (error) { - Serial.printf("JSON parsing failed for MAC %s: %s\n", macAddress.c_str(), error.c_str()); - return; - } - - uint64_t messageTimestamp = doc["timestamp"] | 0; - uint64_t currentLocalTime = getCurrentTimestampMs(); - - // Update timestamps for current device - if (deviceTimestamps.count(macAddress) > 0) { - TimestampData& data = deviceTimestamps[macAddress]; - uint64_t messageDiff = messageTimestamp - data.lastMessageTimestamp; - uint64_t localDiff = currentLocalTime - data.lastLocalTimestamp; - data.drift = localDiff - messageDiff; - } - - // Calculate drift relative to all other devices - Serial.printf("\nDrift analysis for device %s:\n", macAddress.c_str()); - Serial.println("----------------------------------------"); - - for (const auto& device : deviceTimestamps) { - if (device.first != macAddress) { // Skip comparing to self - int64_t timeDiff = messageTimestamp - device.second.lastMessageTimestamp; - int64_t relativeDrift = timeDiff - (currentLocalTime - device.second.lastLocalTimestamp); - - Serial.printf("Relative to %s:\n", device.first.c_str()); - Serial.printf(" Time difference: %lld ms\n", timeDiff); - Serial.printf(" Relative drift: %lld ms\n", relativeDrift); - Serial.println("----------------------------------------"); - } - } - - // Update stored timestamps for current device - deviceTimestamps[macAddress] = { - messageTimestamp, - currentLocalTime, - deviceTimestamps[macAddress].drift - }; -} - void readButtonJSON(const char * topic, const char * payload) { if(strcmp(topic, "aquacross/button/press") == 0){ @@ -133,22 +89,99 @@ void readButtonJSON(const char * topic, const char * payload) { } } +void readRFIDfromButton(const char * topic, const char * payload) { + // Create a JSON document to hold the button press data + StaticJsonDocument<256> doc; + DeserializationError error = deserializeJson(doc, payload); + if (!error) { + const char* mac = doc["buttonmac"] | "unknown"; + const char* uid = doc["uid"] | "unknown"; + + Serial.printf("RFID Read from Button:\n"); + Serial.printf(" Button MAC: %s\n", mac); + Serial.printf(" UID: %s\n", uid); + + // Convert buttonmac to byte array for comparison + auto macBytes = macStringToBytes(mac); + + // Check if the buttonmac matches buttonConfigs.start1.mac + if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0) { + // Fetch user data + UserData userData = checkUser(uid); + if (userData.exists) { + // Log user data + Serial.printf("User found for start1: %s %s, Alter: %d\n", + userData.firstname.c_str(), + userData.lastname.c_str(), + userData.alter); + + // Create JSON message to send to the frontend + StaticJsonDocument<128> messageDoc; + messageDoc["firstname"] = userData.firstname; + messageDoc["lastname"] = userData.lastname; + messageDoc["lane"] = "start1"; // Add lane information + + String message; + serializeJson(messageDoc, message); + + // Push the message to the frontend + pushUpdateToFrontend(message); + Serial.printf("Pushed user data for start1 to frontend: %s\n", message.c_str()); + } else { + Serial.println("User not found for UID: " + String(uid)); + } + } + // Check if the buttonmac matches buttonConfigs.start2.mac + else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0) { + // Fetch user data + UserData userData = checkUser(uid); + if (userData.exists) { + // Log user data + Serial.printf("User found for start2: %s %s, Alter: %d\n", + userData.firstname.c_str(), + userData.lastname.c_str(), + userData.alter); + + // Create JSON message to send to the frontend + StaticJsonDocument<128> messageDoc; + messageDoc["firstname"] = userData.firstname; + messageDoc["lastname"] = userData.lastname; + messageDoc["lane"] = "start2"; // Add lane information + + String message; + serializeJson(messageDoc, message); + + // Push the message to the frontend + pushUpdateToFrontend(message); + Serial.printf("Pushed user data for start2 to frontend: %s\n", message.c_str()); + } else { + Serial.println("User not found for UID: " + String(uid)); + } + } else { + Serial.println("Button MAC does not match start1.mac or start2.mac"); + } + } else { + Serial.println("Failed to parse RFID JSON"); + } +} void setupMqttServer() { // Set up the MQTT server with the desired port // Subscribe to a topic pattern and attach a callback mqtt.subscribe("#", [](const char * topic, const char * payload) { - if (strncmp(topic, "heartbeat/alive/", 15) == 0) { - processHeartbeat(topic, payload); - } else if (strcmp(topic, "aquacross/button/press") == 0) { + //Message received callback + //Serial.printf("Received message on topic '%s': %s\n", topic, payload); + if (strcmp(topic, "aquacross/button/press") == 0) { readButtonJSON(topic, payload); + } else if (strncmp(topic, "aquacross/button/rfid/", 22) == 0) { + readRFIDfromButton(topic, payload); + // Handle RFID read messages + } updateStatusLED(3); }); - - // Add the button subscription - + // Start the MQTT server mqtt.begin(); @@ -168,6 +201,7 @@ void loopMqttServer() { mqtt.publish("sync/time", timeStr); lastPublish = millis(); } + } void sendMQTTMessage(const char * topic, const char * message) { @@ -187,3 +221,5 @@ void sendMQTTJSONMessage(const char * topic, const JsonDocument & doc) { Serial.printf("Published JSON message to topic '%s': %s\n", topic, jsonString.c_str()); } + + diff --git a/src/databasebackend.h b/src/databasebackend.h index b5f9b28..22f1b87 100644 --- a/src/databasebackend.h +++ b/src/databasebackend.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include "master.h" @@ -17,34 +18,76 @@ bool backendOnline() { http.addHeader("Authorization", String("Bearer ") + BACKEND_TOKEN); int httpCode = http.GET(); + bool isOnline = (httpCode == HTTP_CODE_OK); - if (httpCode == HTTP_CODE_OK) { - return true; + if (isOnline) { Serial.println("Database server connection successful"); } else { - return false; Serial.printf("Database server connection failed, error: %d\n", httpCode); } http.end(); + return isOnline; } -bool userExists(const String& userId) { +struct UserData { + String uid; + String firstname; + String lastname; + int alter; + bool exists; +}; + +// UserData checkUser(const String& uid) is defined only once to avoid redefinition errors. +UserData checkUser(const String& uid) { + + UserData userData = {"", "", "", 0, false}; + if (!backendOnline()) { - Serial.println("No internet connection, cannot check user existence."); - return false; + Serial.println("No internet connection, cannot check user."); + return userData; } HTTPClient http; - http.begin(String(BACKEND_SERVER) + "/api/users/" + userId); + http.begin(String(BACKEND_SERVER) + "/api/users/find"); + http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", String("Bearer ") + BACKEND_TOKEN); - //Post request to check if user exists - int httpCode = http.POST(""); + // Create JSON payload + StaticJsonDocument<200> requestDoc; + requestDoc["uid"] = uid; + String requestBody; + serializeJson(requestDoc, requestBody); + + int httpCode = http.POST(requestBody); + + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + StaticJsonDocument<512> responseDoc; + DeserializationError error = deserializeJson(responseDoc, payload); + + if (!error) { + userData.uid = responseDoc["uid"].as(); + userData.firstname = responseDoc["firstname"].as(); + userData.lastname = responseDoc["lastname"].as(); + userData.alter = responseDoc["alter"] | 0; + userData.exists = true; + } + } else { + Serial.printf("User check failed, HTTP code: %d\n", httpCode); + } + + http.end(); + return userData; +} +// Keep this for backward compatibility +bool userExists(const String& uid) { + return checkUser(uid).exists; } void setupBackendRoutes(AsyncWebServer& server) { + server.on("/api/health", HTTP_GET, [](AsyncWebServerRequest *request) { DynamicJsonDocument doc(64); diff --git a/src/master.cpp b/src/master.cpp index 2b60a5b..cf96831 100644 --- a/src/master.cpp +++ b/src/master.cpp @@ -162,10 +162,26 @@ void saveWifiSettings() { } void loadWifiSettings() { - preferences.begin("wifi", true); - ssidSTA = preferences.getString("ssid", "").c_str(); - passwordSTA = preferences.getString("password", "").c_str(); - preferences.end(); + preferences.begin("wifi", true); + + // Speicher freigeben, falls bereits zugewiesen + if (ssidSTA) { + free(ssidSTA); + } + if (passwordSTA) { + free(passwordSTA); + } + + // Neue Werte laden und dynamisch zuweisen + String ssid = preferences.getString("ssid", ""); + String password = preferences.getString("password", ""); + ssidSTA = strdup(ssid.c_str()); + passwordSTA = strdup(password.c_str()); + + preferences.end(); + + // Debug-Ausgabe + Serial.printf("WLAN-Einstellungen geladen: SSID=%s, Passwort=%s\n", ssidSTA, passwordSTA); } int checkLicence() { @@ -179,7 +195,6 @@ String getTimerDataJSON() { DynamicJsonDocument doc(1024); unsigned long currentTime = millis(); - // Bahn 1 if (timerData.isRunning1) { doc["time1"] = (currentTime - timerData.startTime1) / 1000.0; @@ -234,7 +249,7 @@ void setup() { setupTimeAPI(server); setupLicenceAPI(server); setupDebugAPI(server); - setupBackendRoutes(server); + setupBackendRoutes(server);// Speichere WLAN-Einstellungen, falls noch nicht vorhanden // Gespeicherte Daten laden @@ -246,6 +261,7 @@ void setup() { setupOTA(&server); setupRoutes(); + setupWebSocket(); setupLED(); setupMqttServer(); // MQTT Server initialisieren @@ -255,4 +271,5 @@ void setup() { void loop() { checkAutoReset(); loopMqttServer(); // MQTT Server in der Loop aufrufen + loopWebSocket(); } diff --git a/src/rfid.h b/src/rfid.h index ae355b2..a840930 100644 --- a/src/rfid.h +++ b/src/rfid.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include @@ -21,10 +22,6 @@ struct User { unsigned long timestamp; }; -// Array für Benutzer (max 100 Benutzer) -User users[100]; -int userCount = 0; - void setupRFID() { // SPI und RFID initialisieren diff --git a/src/webserverrouter.h b/src/webserverrouter.h index f1bf43b..d3d8cc0 100644 --- a/src/webserverrouter.h +++ b/src/webserverrouter.h @@ -1,17 +1,24 @@ +#pragma once #include #include "master.h" #include +#include #include #include #include #include +#include AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); void setupRoutes(){ - // Web Server Routes + // Web Server Routes + + // Attach WebSocket to the server + server.addHandler(&ws); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.html", "text/html"); @@ -166,34 +173,35 @@ void setupRoutes(){ }); // Setze WLAN-Name und Passwort (POST) - server.on("/api/set-wifi", HTTP_POST, [](AsyncWebServerRequest *request){ + server.on("/api/set-wifi", HTTP_POST, [](AsyncWebServerRequest *request) { Serial.println("/api/set-wifi called"); String ssid, password; if (request->hasParam("ssid", true)) { - ssid = request->getParam("ssid", true)->value(); + ssid = request->getParam("ssid", true)->value(); } if (request->hasParam("password", true)) { - password = request->getParam("password", true)->value(); + password = request->getParam("password", true)->value(); } if (ssid.length() > 0) { - // Hier speichern wir die neuen Werte (z.B. in Preferences oder global) - // Beispiel: strcpy(ssidSTA, ssid.c_str()); - // Beispiel: strcpy(passwordSTA, password.c_str()); - // In deinem Projekt ggf. persistent speichern! - // Hier als global (unsicher, nach Neustart verloren!): - ssidSTA = strdup(ssid.c_str()); - passwordSTA = strdup(password.c_str()); - // Rückmeldung - DynamicJsonDocument doc(64); - doc["success"] = true; - String result; - serializeJson(doc, result); - request->send(200, "application/json", result); - Serial.println("WiFi-Settings updated (nur bis zum Neustart aktiv!)"); + // Speicher freigeben, bevor neue Werte zugewiesen werden + free(ssidSTA); + free(passwordSTA); + + // Neue Werte zuweisen + ssidSTA = strdup(ssid.c_str()); + passwordSTA = strdup(password.c_str()); + + // Rückmeldung + DynamicJsonDocument doc(64); + doc["success"] = true; + String result; + serializeJson(doc, result); + request->send(200, "application/json", result); + Serial.println("WiFi-Settings updated (nur bis zum Neustart aktiv!)"); } else { - request->send(400, "application/json", "{\"success\":false,\"error\":\"SSID fehlt\"}"); + request->send(400, "application/json", "{\"success\":false,\"error\":\"SSID fehlt\"}"); } - }); +}); // Liefert aktuelle WLAN-Einstellungen (GET) server.on("/api/get-wifi", HTTP_GET, [](AsyncWebServerRequest *request){ @@ -210,4 +218,25 @@ void setupRoutes(){ server.begin(); Serial.println("Web Server gestartet"); +} + +void setupWebSocket() { + ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + if (type == WS_EVT_CONNECT) { + Serial.printf("WebSocket client connected: %u\n", client->id()); + } else if (type == WS_EVT_DISCONNECT) { + Serial.printf("WebSocket client disconnected: %u\n", client->id()); + } else if (type == WS_EVT_DATA) { + // Handle incoming WebSocket messages if needed + Serial.printf("WebSocket message received: %s\n", (char *)data); + } + }); +} + +void pushUpdateToFrontend(const String &message) { + ws.textAll(message); // Send the message to all connected clients +} + +void loopWebSocket() { + ws.cleanupClients(); // Clean up disconnected clients } \ No newline at end of file diff --git a/src/wificlass.h b/src/wificlass.h index a06eb80..83ba500 100644 --- a/src/wificlass.h +++ b/src/wificlass.h @@ -13,8 +13,8 @@ String uniqueSSID; const char* ssidAP; const char* passwordAP = nullptr; -const char* ssidSTA = "Obiwlankenobi"; -const char* passwordSTA = "Delfine1!"; +char* ssidSTA = strdup("Obiwlankenobi"); +char* passwordSTA = strdup("Delfine1!"); PrettyOTA OTAUpdates;