From b6fa2d69e7c4fdc6f4b6bf93b6caf0b7fb3eddf2 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Wed, 11 Jun 2025 22:52:30 +0200 Subject: [PATCH] Location Speichern geht? abrufen der gespeicherten location aus properties muss noch gemacht werden --- data/settings.css | 83 +++++++++++++++++++++++++- data/settings.html | 134 +++++++++++++++++++++++++++++++++++++++++- src/databasebackend.h | 42 ++++++++++++- src/master.cpp | 25 +++++--- src/master.h | 3 + src/rfid.h | 5 +- src/webserverrouter.h | 39 +++++++++++- src/wificlass.h | 2 +- 8 files changed, 319 insertions(+), 14 deletions(-) diff --git a/data/settings.css b/data/settings.css index a50ae06..91f7d09 100644 --- a/data/settings.css +++ b/data/settings.css @@ -332,4 +332,85 @@ flex-direction: column; gap: 10px; } - } \ No newline at end of file + } + + .section select { + width: 100%; + padding: 12px 16px; + font-size: 16px; + font-family: inherit; + border: 2px solid #e1e5e9; + border-radius: 8px; + background-color: white; + background-image: url("data:image/svg+xml;charset=US-ASCII,"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 12px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + cursor: pointer; + transition: all 0.3s ease; +} + +.section select:hover { + border-color: #007bff; + box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1); +} + +.section select:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); +} + +.section select:disabled { + background-color: #f8f9fa; + color: #6c757d; + cursor: not-allowed; + opacity: 0.6; + border-color: #dee2e6; +} + +.section select:disabled:hover { + border-color: #dee2e6; + box-shadow: none; +} + +/* Option Styling */ +.section select option { + padding: 8px; + font-size: 16px; + background-color: white; + color: #333; +} + +.section select option:hover { + background-color: #f8f9fa; +} + +.section select option:disabled { + color: #6c757d; + background-color: #f8f9fa; +} + +/* Form Group für bessere Abstände */ +.section .form-group { + margin-bottom: 20px; +} + +.section .form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #333; + font-size: 14px; +} + +/* Responsive Design für kleinere Bildschirme */ +@media (max-width: 768px) { + .section select { + font-size: 16px; /* Verhindert Zoom auf iOS */ + padding: 14px 16px; + } +} \ No newline at end of file diff --git a/data/settings.html b/data/settings.html index 0895255..35bd135 100644 --- a/data/settings.html +++ b/data/settings.html @@ -103,7 +103,7 @@ - +

🏆 Zeiten verwalten

@@ -174,6 +174,26 @@
+
+

📍 Standort

+ +
+
+ + +
+
+ +
+
+
+

🔄 OTA Update

@@ -251,6 +271,7 @@ updateCurrentTimeDisplay(); loadLicence(); loadWifiSettings(); + loadLocations(); }; // Aktuelle Zeit anzeigen (Live-Update) @@ -429,6 +450,7 @@ // Check license level and update OTA button accordingly updateOTAButtonAccess(data.tier || 0); updateWifiButtonAccess(data.tier || 0) + updateLocationAccess(data.tier || 0); }) .catch((error) => console.log("Info konnte nicht geladen werden")); } @@ -494,6 +516,9 @@ showMessage("Bitte WLAN-Namen eingeben", "error"); return; } + if (!confirm("Der Server wird nach dem setzten neu gestartet. Fortsetzten?")) { + return; + } fetch("/api/set-wifi", { method: "POST", @@ -762,6 +787,113 @@ ); } + //location functions + // Locations laden und Dropdown befüllen + function loadLocations() { + fetch("/api/location/") + .then((response) => response.json()) + .then((data) => { + const select = document.getElementById("locationSelect"); + + // Vorhandene Optionen löschen (außer der ersten "Bitte wählen...") + while (select.children.length > 1) { + select.removeChild(select.lastChild); + } + + // Neue Optionen aus Backend-Response hinzufügen + data.forEach((location) => { + const option = document.createElement("option"); + option.value = location.id; + option.textContent = location.name; + select.appendChild(option); + }); + + // Aktuell gespeicherten Standort laden + loadCurrentLocation(); + }) + .catch((error) => { + console.log("Locations konnten nicht geladen werden:", error); + showMessage("Fehler beim Laden der Standorte", "error"); + }); + } + + // Aktuell gespeicherten Standort laden + function loadCurrentLocation() { + fetch("/api/get-location") + .then((response) => response.json()) + .then((data) => { + if (data.locationId) { + document.getElementById("locationSelect").value = data.locationId; + } + }) + .catch((error) => { + console.log("Aktueller Standort konnte nicht geladen werden:", error); + }); + } + + // Location-Zugriff basierend auf Lizenz-Level kontrollieren + function updateLocationAccess(licenseLevel) { + const locationSubmitBtn = document.getElementById("locationSubmitBtn"); + const locationRestrictionNotice = document.getElementById("locationRestrictionNotice"); + const locationCurrentLevelSpan = document.getElementById("currentLocationLicenseLevel"); + const locationSelect = document.getElementById("locationSelect"); + + const level = parseInt(licenseLevel) || 0; + + if (level >= 3) { + // Lizenz Level 3 oder höher - Form aktivieren + locationSubmitBtn.classList.remove("btn-disabled"); + locationSubmitBtn.disabled = false; + locationSelect.disabled = false; + locationRestrictionNotice.style.display = "none"; + } else { + // Lizenz Level unter 3 - Form deaktivieren + locationSubmitBtn.classList.add("btn-disabled"); + locationSubmitBtn.disabled = true; + locationSelect.disabled = true; + locationRestrictionNotice.style.display = "block"; + locationCurrentLevelSpan.textContent = level; + } + } + + // Location Form Handler + document.getElementById("locationForm").addEventListener("submit", function(e) { + e.preventDefault(); + + const locationSubmitBtn = document.getElementById("locationSubmitBtn"); + + // Lizenz-Level prüfen + if (locationSubmitBtn.disabled || locationSubmitBtn.classList.contains("btn-disabled")) { + showMessage("Standort-Konfiguration erfordert Lizenz Level 3 oder höher", "error"); + return; + } + + const locationId = document.getElementById("locationSelect").value; + + if (!locationId) { + showMessage("Bitte einen Standort auswählen", "error"); + return; + } + + // Standort an Backend senden + fetch("/api/set-location", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "locationId=" + encodeURIComponent(locationId), + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + showMessage("Standort erfolgreich gespeichert!", "success"); + } else { + showMessage("Fehler beim Speichern des Standorts", "error"); + } + }) + .catch((error) => showMessage("Verbindungsfehler", "error")); + }); + // Status-Nachricht anzeigen function showMessage(message, type) { const statusDiv = document.getElementById("statusMessage"); diff --git a/src/databasebackend.h b/src/databasebackend.h index 704ab08..fdc9a37 100644 --- a/src/databasebackend.h +++ b/src/databasebackend.h @@ -82,7 +82,7 @@ UserData checkUser(const String& uid) { } //Function to enter user data into the database -bool enterUserData(const String& uid, const String& firstname, const String& lastname, int alter) { +bool enterUserData(const String& uid, const String& firstname, const String& lastname, const String& geburtsdatum, int alter) { if (!backendOnline()) { Serial.println("No internet connection, cannot enter user data."); return false; @@ -98,6 +98,7 @@ bool enterUserData(const String& uid, const String& firstname, const String& las requestDoc["uid"] = uid; requestDoc["vorname"] = firstname; requestDoc["nachname"] = lastname; + requestDoc["geburtsdatum"] = geburtsdatum; requestDoc["alter"] = alter; String requestBody; @@ -116,13 +117,43 @@ bool enterUserData(const String& uid, const String& firstname, const String& las } } +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) + "/api/location/"); + http.addHeader("Authorization", String("Bearer ") + BACKEND_TOKEN); + + 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 +} // Keep this for backward compatibility bool userExists(const String& uid) { return checkUser(uid).exists; } + +//Routes from the Frontend into here and then into DB backend. + void setupBackendRoutes(AsyncWebServer& server) { server.on("/api/health", HTTP_GET, [](AsyncWebServerRequest *request) { @@ -142,6 +173,15 @@ void setupBackendRoutes(AsyncWebServer& server) { // Handle user retrieval logic here }); + //Location routes /api/location/ + server.on("/api/location/", HTTP_GET, [](AsyncWebServerRequest *request){ + + String result; + serializeJson(getAllLocations(), result); + request->send(200, "application/json", result); + }); + + // Add more routes as needed } diff --git a/src/master.cpp b/src/master.cpp index c7dbb71..952ff34 100644 --- a/src/master.cpp +++ b/src/master.cpp @@ -184,24 +184,35 @@ void saveWifiSettings() { preferences.putString("ssid", ssidSTA); preferences.putString("password", passwordSTA); preferences.end(); - Serial.printf("WLAN-Einstellungen gespeichert: SSID=%s, Passwort=%s\n", ssidSTA, passwordSTA); + delay(500); // Warte 2 Sekunden, bevor der Neustart erfolgt + ESP.restart(); // Neustart des ESP32 +} + +void loadLocationSettings() { + preferences.begin("location", true); + masterlocation = preferences.getString("location", ""); + preferences.end(); +} + +void saveLocationSettings() { + preferences.begin("location", false); + preferences.putString("location", masterlocation); + preferences.end(); } void loadWifiSettings() { preferences.begin("wifi", true); - - // 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() { loadLicenceFromPrefs(); String id = getUniqueDeviceID(); diff --git a/src/master.h b/src/master.h index 27943e7..eadfc40 100644 --- a/src/master.h +++ b/src/master.h @@ -50,6 +50,7 @@ int learningStep = 0; // 0=Start1, 1=Stop1, 2=Start2, 3=Stop2 unsigned long maxTimeBeforeReset = 300000; // 5 Minuten default unsigned long maxTimeDisplay = 20000; // 20 Sekunden Standard (in ms) bool wifimodeAP = false; // AP-Modus deaktiviert +String masterlocation; //Function Declarations void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len); @@ -68,6 +69,8 @@ void saveSettings(); void loadSettings(); void loadWifiSettings(); void saveWifiSettings(); +void loadLocationSettings(); +void saveLocationSettings(); void unlearnButton(); int checkLicence(); String getTimerDataJSON(); \ No newline at end of file diff --git a/src/rfid.h b/src/rfid.h index 5a62c8d..d212f83 100644 --- a/src/rfid.h +++ b/src/rfid.h @@ -200,10 +200,11 @@ server.on("/api/users/insert", HTTP_POST, [](AsyncWebServerRequest *request) {}, String uid = doc["uid"] | ""; String vorname = doc["vorname"] | ""; String nachname = doc["nachname"] | ""; + String geburtsdatum = doc["geburtsdatum"] | ""; int alter = doc["alter"] | 0; // Validate the data - if (uid.isEmpty() || vorname.isEmpty() || nachname.isEmpty() || alter <= 0) { + if (uid.isEmpty() || vorname.isEmpty() || nachname.isEmpty() || geburtsdatum.isEmpty() || alter <= 0) { Serial.println("Ungültige Eingabedaten"); response["success"] = false; response["error"] = "Ungültige Eingabedaten"; @@ -215,7 +216,7 @@ server.on("/api/users/insert", HTTP_POST, [](AsyncWebServerRequest *request) {}, Serial.println("Nachname: " + nachname); Serial.println("Alter: " + String(alter)); - bool dbSuccess = enterUserData(uid, vorname, nachname, alter); + bool dbSuccess = enterUserData(uid, vorname, nachname, geburtsdatum, alter); if (dbSuccess) { response["success"] = true; diff --git a/src/webserverrouter.h b/src/webserverrouter.h index c5cfa12..9f73369 100644 --- a/src/webserverrouter.h +++ b/src/webserverrouter.h @@ -199,7 +199,6 @@ void setupRoutes(){ 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\"}"); } @@ -215,6 +214,42 @@ void setupRoutes(){ request->send(200, "application/json", result); }); +server.on("/api/set-location", HTTP_POST, [](AsyncWebServerRequest *request) { + Serial.println("/api/set-location called"); + + String id, name; + + if (request->hasParam("id", true)) { + id = request->getParam("id", true)->value(); + } + if (request->hasParam("name", true)) { + name = request->getParam("name", true)->value(); + } + masterlocation = name; + + saveLocationSettings(); + + // Rückmeldung + DynamicJsonDocument doc(64); + doc["success"] = true; + String result; + serializeJson(doc, result); + request->send(200, "application/json", result); + +}); + + server.on("/api/get-location", HTTP_GET, [](AsyncWebServerRequest *request){ + DynamicJsonDocument doc(128); + doc["locationid"] = masterlocation ? masterlocation : ""; + String result; + serializeJson(doc, result); + request->send(200, "application/json", result); + }); + + + + + // Statische Dateien server.serveStatic("/", SPIFFS, "/"); server.begin(); @@ -222,6 +257,8 @@ void setupRoutes(){ } + + void setupWebSocket() { ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { if (type == WS_EVT_CONNECT) { diff --git a/src/wificlass.h b/src/wificlass.h index 7dbaa78..9a37a67 100644 --- a/src/wificlass.h +++ b/src/wificlass.h @@ -23,7 +23,7 @@ void setupWifi() { Serial.println("Access Point SSID: " + String(ssidSTA)); Serial.println("Access Point PW: " + String(passwordSTA)); -if ((ssidSTA == nullptr) || (passwordSTA == nullptr)) { +if (ssidSTA == nullptr || passwordSTA == nullptr || String(ssidSTA).isEmpty() || String(passwordSTA).isEmpty() ) { Serial.println("Fehler: ssidSTA oder passwordSTA ist null!"); WiFi.mode(WIFI_MODE_AP); WiFi.softAP(ssidAP, passwordAP);