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
+
+ 🔒 Standort-Konfiguration ist nur mit Lizenz Level 3 oder höher verfügbar. Aktuelle Lizenz: Level 0
+
+
+
+
🔄 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);