Implement DatabaseBackend, Verschiedenes

This commit is contained in:
Carsten Graf
2025-06-06 22:07:47 +02:00
parent 4f4d0757d5
commit a1434347d8
10 changed files with 256 additions and 681 deletions

View File

@@ -62,11 +62,11 @@ void readButtonJSON(const char * topic, const char * payload) {
// Button-Zuordnung prüfen und entsprechende Aktion ausführen
if (memcmp(macBytes.data(), buttonConfigs.start1.mac, 6) == 0 && (pressType == 2)) {
handleStart1();
} else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0 && (pressType == 2)) {
} else if (memcmp(macBytes.data(), buttonConfigs.stop1.mac, 6) == 0 && (pressType == 1)) {
handleStop1();
} else if (memcmp(macBytes.data(), buttonConfigs.start2.mac, 6) == 0 && (pressType == 2)) {
handleStart2();
} else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0 && (pressType == 2)) {
} else if (memcmp(macBytes.data(), buttonConfigs.stop2.mac, 6) == 0 && (pressType == 1)) {
handleStop2();
}

67
src/databasebackend.h Normal file
View File

@@ -0,0 +1,67 @@
#include <Arduino.h>
#include <HTTPClient.h>
#include "master.h"
const char* BACKEND_SERVER = "http://db.reptilfpv.de:3000";
const char* BACKEND_TOKEN = "a4514dc0-15f5-4299-8826-fffb3139d39c";
bool backendOnline() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("No WiFi connection.");
return false;
}
HTTPClient http;
http.begin(String(BACKEND_SERVER) + "/api/health");
http.addHeader("Authorization", String("Bearer ") + BACKEND_TOKEN);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
return true;
Serial.println("Database server connection successful");
} else {
return false;
Serial.printf("Database server connection failed, error: %d\n", httpCode);
}
http.end();
}
bool userExists(const String& userId) {
if (!backendOnline()) {
Serial.println("No internet connection, cannot check user existence.");
return false;
}
HTTPClient http;
http.begin(String(BACKEND_SERVER) + "/api/users/" + userId);
http.addHeader("Authorization", String("Bearer ") + BACKEND_TOKEN);
//Post request to check if user exists
int httpCode = http.POST("");
}
void setupBackendRoutes(AsyncWebServer& server) {
server.on("/api/health", HTTP_GET, [](AsyncWebServerRequest *request) {
DynamicJsonDocument doc(64);
doc["status"] = backendOnline() ? "connected" : "disconnected";
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
});
server.on("/api/users", HTTP_GET, [](AsyncWebServerRequest *request) {
if (!backendOnline()) {
request->send(503, "application/json", "{\"error\":\"Database not connected\"}");
return;
}
// Handle user retrieval logic here
});
// Add more routes as needed
}

View File

@@ -18,14 +18,12 @@
#include <wificlass.h>
#include <webserverrouter.h>
#include <communication.h>
#include <databasebackend.h>
const char* firmwareversion = "1.0.0"; // Version der Firmware
void handleStart1() {
if (!timerData.isRunning1 && timerData.isReady1) {
timerData.isReady1 = false; // Setze auf "Nicht bereit" bis Stopp
@@ -232,10 +230,11 @@ void setup() {
return;
}
//setup external libararies
//setup API libararies
setupTimeAPI(server);
setupLicenceAPI(server);
setupDebugAPI(server);
setupBackendRoutes(server);
// Gespeicherte Daten laden

View File

@@ -1,548 +1,151 @@
#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <MFRC522.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <WiFiClientSecure.h>
// RFID Configuration
#define RST_PIN 22 // Reset pin
#define SS_PIN 21 // SDA pin
#define SCK_PIN 18 // SCK pin
#define MOSI_PIN 23 // MOSI pin
#define MISO_PIN 19 // MISO pin
// RFID Konfiguration
#define SS_PIN 21
#define RST_PIN 22
MFRC522 mfrc522(SS_PIN, RST_PIN);
// Webserver auf Port 80
WebServer server(80);
// PostgreSQL API Configuration
// Replace with your database API endpoint (e.g., Supabase, Railway, Render, etc.)
const char* DB_API_BASE_URL = "https://your-database-api.com/api";
const char* DB_API_KEY = "YOUR_API_KEY"; // If using Supabase or similar service
// Struktur für Benutzerdaten
struct User {
String uid;
String vorname;
String nachname;
int alter;
unsigned long timestamp;
};
// Server Configuration
MFRC522 rfid(SS_PIN, RST_PIN);
// Array für Benutzer (max 100 Benutzer)
User users[100];
int userCount = 0;
// HTTP clients
HTTPClient httpClient;
WiFiClientSecure secureClient;
void setupRFID() {
// Database table structure
// SPI und RFID initialisieren
SPI.begin();
mfrc522.PCD_Init();
// Gespeicherte Daten laden
loadUsersFromFile();
// Route Definitionen
setupRoutes(AsyncWebServer& server);
// CORS headers
void setCORSHeaders(AsyncWebServerResponse* response) {
response->addHeader("Access-Control-Allow-Origin", "*");
response->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, apikey");
response->addHeader("Access-Control-Max-Age", "86400");
}
// Initialize RFID
void initRFID() {
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN);
rfid.PCD_Init();
// Check if RFID reader is connected
byte version = rfid.PCD_ReadRegister(rfid.VersionReg);
if (version == 0x00 || version == 0xFF) {
Serial.println("WARNING: RFID reader not found!");
void setupRoutes() {
// CORS Header für alle Anfragen
server.onNotFound([]() {
if (server.method() == HTTP_OPTIONS) {
server.sendHeader("Access-Control-Allow-Origin", "*");
server.sendHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
server.sendHeader("Access-Control-Allow-Headers", "Content-Type");
server.send(200);
} else {
Serial.print("RFID reader found, version: 0x");
Serial.println(version, HEX);
server.send(404, "text/plain", "Not Found");
}
});
// API: RFID UID lesen
server.on("/api/rfid/read", HTTP_POST, []() {
server.sendHeader("Access-Control-Allow-Origin", "*");
server.sendHeader("Content-Type", "application/json");
Serial.println("RFID Reader initialized");
}
// Initialize WiFi
void initWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
String uid = readRFIDCard();
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(1000);
Serial.print(".");
attempts++;
}
DynamicJsonDocument doc(200);
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.print("WiFi connected! IP address: ");
Serial.println(WiFi.localIP());
if (uid != "") {
doc["success"] = true;
doc["uid"] = uid;
Serial.println("UID gelesen: " + uid);
} else {
Serial.println();
Serial.println("WiFi connection failed!");
doc["success"] = false;
doc["error"] = "Keine RFID Karte gefunden";
}
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
});
// API: Neuen Benutzer erstellen
server.on("/api/users", HTTP_POST, []() {
server.sendHeader("Access-Control-Allow-Origin", "*");
server.sendHeader("Content-Type", "application/json");
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, server.arg("plain"));
if (error) {
DynamicJsonDocument errorDoc(200);
errorDoc["success"] = false;
errorDoc["error"] = "Ungültige JSON Daten";
String response;
serializeJson(errorDoc, response);
server.send(400, "application/json", response);
return;
}
// Daten aus JSON extrahieren
String uid = doc["uid"].as<String>();
String vorname = doc["vorname"].as<String>();
String nachname = doc["nachname"].as<String>();
int alter = doc["alter"].as<int>();
// Prüfen ob UID bereits existiert
// Benutzer hinzufügen
// Erfolgreiche Antwort
DynamicJsonDocument successDoc(200);
successDoc["success"] = true;
successDoc["message"] = "Benutzer erfolgreich gespeichert";
String response;
serializeJson(successDoc, response);
server.send(201, "application/json", response);
Serial.println("Neuer Benutzer: " + vorname + " " + nachname + " (UID: " + uid + ")");
});
}
// Test database connection
bool testDatabaseConnection() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected");
return false;
}
HTTPClient http;
http.begin(DB_API_BASE_URL);
// Add API key header if using services like Supabase
if (strlen(DB_API_KEY) > 0) {
http.addHeader("apikey", DB_API_KEY);
http.addHeader("Authorization", String("Bearer ") + DB_API_KEY);
}
int httpResponseCode = http.GET();
bool success = (httpResponseCode > 0 && httpResponseCode < 400);
if (success) {
Serial.println("✅ Database connection successful");
} else {
Serial.print("❌ Database connection failed. HTTP code: ");
Serial.println(httpResponseCode);
if (httpResponseCode > 0) {
Serial.println("Response: " + http.getString());
}
}
http.end();
return success;
}
// Read RFID UID
String readRFIDUID() {
// Check for new cards
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
return "";
}
String uid = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (uid.length() > 0) uid += ":";
if (rfid.uid.uidByte[i] < 0x10) uid += "0";
uid += String(rfid.uid.uidByte[i], HEX);
}
uid.toUpperCase();
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
return uid;
}
// Make HTTP request to database API
DynamicJsonDocument makeDBRequest(const String& method, const String& endpoint, const String& payload = "") {
DynamicJsonDocument response(4096);
if (WiFi.status() != WL_CONNECTED) {
response["success"] = false;
response["error"] = "WiFi not connected";
return response;
}
HTTPClient http;
String url = String(DB_API_BASE_URL) + endpoint;
http.begin(url);
// Set headers
http.addHeader("Content-Type", "application/json");
if (strlen(DB_API_KEY) > 0) {
http.addHeader("apikey", DB_API_KEY);
http.addHeader("Authorization", String("Bearer ") + DB_API_KEY);
}
int httpResponseCode;
if (method == "GET") {
httpResponseCode = http.GET();
} else if (method == "POST") {
httpResponseCode = http.POST(payload);
} else if (method == "DELETE") {
httpResponseCode = http.sendRequest("DELETE", payload);
} else {
response["success"] = false;
response["error"] = "Unsupported HTTP method";
http.end();
return response;
}
if (httpResponseCode > 0) {
String responseString = http.getString();
if (httpResponseCode >= 200 && httpResponseCode < 300) {
// Parse successful response
DeserializationError error = deserializeJson(response, responseString);
if (error) {
response.clear();
response["success"] = false;
response["error"] = "Failed to parse response JSON";
response["raw_response"] = responseString;
} else {
// Add success flag if not present
if (!response.containsKey("success")) {
response["success"] = true;
}
}
} else {
// Handle HTTP errors
response["success"] = false;
response["error"] = "HTTP Error " + String(httpResponseCode);
response["raw_response"] = responseString;
}
} else {
response["success"] = false;
response["error"] = "HTTP request failed: " + String(httpResponseCode);
}
http.end();
return response;
}
// Get all users from database
DynamicJsonDocument getAllUsers(int limit = 50) {
String endpoint = "/rfid_users?select=*&order=created_at.desc";
if (limit > 0) {
endpoint += "&limit=" + String(limit);
}
return makeDBRequest("GET", endpoint);
}
// Create new user in database
DynamicJsonDocument createUser(const String& uid, const String& vorname, const String& nachname, int alter) {
DynamicJsonDocument payload(512);
payload["uid"] = uid;
payload["vorname"] = vorname;
payload["nachname"] = nachname;
payload["alter"] = alter;
String payloadString;
serializeJson(payload, payloadString);
return makeDBRequest("POST", "/rfid_users", payloadString);
}
// Delete user from database
DynamicJsonDocument deleteUser(const String& uid) {
String endpoint = "/rfid_users?uid=eq." + uid;
return makeDBRequest("DELETE", endpoint);
}
// Check if user exists
bool userExists(const String& uid) {
String endpoint = "/rfid_users?uid=eq." + uid + "&select=uid";
DynamicJsonDocument result = makeDBRequest("GET", endpoint);
if (result["success"].as<bool>() && result.containsKey("data")) {
JsonArray data = result["data"].as<JsonArray>();
return data.size() > 0;
} else if (result.containsKey("uid")) {
// Direct response format (some APIs return object directly)
return true;
}
return false;
}
void setupRFID(AsyncWebServer& server) {
Serial.begin(115200);
Serial.println("\n=== ESP32 RFID Server with PostgreSQL ===");
// Initialize components
initRFID();
// Test database connection
delay(2000); // Wait for WiFi to stabilize
testDatabaseConnection();
// Configure time for timestamps
configTime(0, 0, "pool.ntp.org");
// CORS preflight handler
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->method() == HTTP_OPTIONS) {
AsyncWebServerResponse *response = request->beginResponse(200);
setCORSHeaders(response);
request->send(response);
} else {
request->send(404, "application/json", "{\"error\":\"Not Found\"}");
}
});
// Health check endpoint
server.on("/api/health", HTTP_GET, [](AsyncWebServerRequest *request) {
DynamicJsonDocument response(512);
response["status"] = "OK";
response["wifi_connected"] = (WiFi.status() == WL_CONNECTED);
response["wifi_ip"] = WiFi.localIP().toString();
response["free_heap"] = ESP.getFreeHeap();
response["rfid_status"] = "connected";
response["database"] = "postgresql";
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(200, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
});
// Get all users
server.on("/api/users", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("GET /api/users - Loading users from database");
int limit = 50;
if (request->hasParam("limit")) {
limit = request->getParam("limit")->value().toInt();
}
DynamicJsonDocument dbResponse = getAllUsers(limit);
DynamicJsonDocument response(8192);
if (dbResponse["success"].as<bool>()) {
response["success"] = true;
// Handle different response formats
if (dbResponse.containsKey("data")) {
response["data"] = dbResponse["data"];
response["count"] = dbResponse["data"].as<JsonArray>().size();
} else {
// Direct array response
response["data"] = dbResponse.as<JsonArray>();
response["count"] = dbResponse.as<JsonArray>().size();
}
} else {
response["success"] = false;
response["error"] = dbResponse["error"];
response["data"] = JsonArray();
response["count"] = 0;
}
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(200, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
});
// Create new user
server.on("/api/users", HTTP_POST, [](AsyncWebServerRequest *request) {
// This will be handled in the body handler
}, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// Parse JSON body
DynamicJsonDocument requestDoc(1024);
DeserializationError error = deserializeJson(requestDoc, (char*)data, len);
DynamicJsonDocument response(1024);
if (error) {
response["success"] = false;
response["error"] = "Invalid JSON";
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(400, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
return;
}
// Validate required fields
if (!requestDoc.containsKey("uid") || !requestDoc.containsKey("vorname") ||
!requestDoc.containsKey("nachname") || !requestDoc.containsKey("alter")) {
response["success"] = false;
response["error"] = "Missing required fields: uid, vorname, nachname, alter";
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(400, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
return;
}
String uid = requestDoc["uid"].as<String>();
String vorname = requestDoc["vorname"].as<String>();
String nachname = requestDoc["nachname"].as<String>();
int alter = requestDoc["alter"].as<int>();
Serial.println("POST /api/users - Creating user: " + uid);
// Check if user already exists
if (userExists(uid)) {
response["success"] = false;
response["error"] = "User with this UID already exists";
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(409, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
return;
}
// Create user in database
DynamicJsonDocument dbResponse = createUser(uid, vorname, nachname, alter);
if (dbResponse["success"].as<bool>()) {
response["success"] = true;
response["message"] = "User created successfully";
// Include user data in response
if (dbResponse.containsKey("data")) {
response["data"] = dbResponse["data"];
} else {
DynamicJsonDocument userData(512);
userData["uid"] = uid;
userData["vorname"] = vorname;
userData["nachname"] = nachname;
userData["alter"] = alter;
response["data"] = userData;
}
Serial.println("✅ User created successfully: " + uid);
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(201, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
} else {
response["success"] = false;
response["error"] = dbResponse["error"];
Serial.println("❌ Failed to create user: " + dbResponse["error"].as<String>());
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(500, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
}
});
// Delete user by UID
server.on("/api/users/*", HTTP_DELETE, [](AsyncWebServerRequest *request) {
String pathInfo = request->url();
String uid = pathInfo.substring(pathInfo.lastIndexOf('/') + 1);
// URL decode the UID
uid.replace("%3A", ":");
uid.toUpperCase();
Serial.println("DELETE /api/users - Deleting user: " + uid);
DynamicJsonDocument response(512);
// Check if user exists
if (!userExists(uid)) {
response["success"] = false;
response["error"] = "User not found";
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(404, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
return;
}
// Delete user from database
DynamicJsonDocument dbResponse = deleteUser(uid);
if (dbResponse["success"].as<bool>()) {
response["success"] = true;
response["message"] = "User deleted successfully";
Serial.println("✅ User deleted successfully: " + uid);
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(200, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
} else {
response["success"] = false;
response["error"] = dbResponse["error"];
Serial.println("❌ Failed to delete user: " + dbResponse["error"].as<String>());
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(500, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
}
});
// Read RFID UID endpoint
server.on("/api/rfid/read", HTTP_POST, [](AsyncWebServerRequest *request) {
DynamicJsonDocument response(512);
Serial.println("POST /api/rfid/read - Reading RFID card");
// Try to read RFID for up to 5 seconds
unsigned long startTime = millis();
String uid = "";
while (millis() - startTime < 5000) { // 5 second timeout
uid = readRFIDUID();
if (uid.length() > 0) {
break;
}
delay(100);
}
if (uid.length() > 0) {
response["success"] = true;
response["uid"] = uid;
response["message"] = "UID read successfully";
Serial.println("✅ RFID UID read: " + uid);
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(200, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
} else {
response["success"] = false;
response["error"] = "No RFID card detected";
response["message"] = "Please place an RFID card near the reader";
Serial.println("❌ No RFID card detected");
String jsonString;
serializeJson(response, jsonString);
AsyncWebServerResponse *resp = request->beginResponse(408, "application/json", jsonString);
setCORSHeaders(resp);
request->send(resp);
}
});
// Handle CORS preflight for all routes
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->method() == HTTP_OPTIONS) {
AsyncWebServerResponse *response = request->beginResponse(200);
setCORSHeaders(response);
request->send(response);
} else {
AsyncWebServerResponse *response = request->beginResponse(404, "application/json", "{\"error\":\"Not Found\"}");
setCORSHeaders(response);
request->send(response);
}
});
// Start server
Serial.println("Connected to PostgreSQL database");
Serial.println("=====================================");
}
// RFID Karte lesen
String readRFIDCard() {
// Prüfen ob neue Karte vorhanden
if (!mfrc522.PICC_IsNewCardPresent()) {
return "";
}
// Karte auswählen
if (!mfrc522.PICC_ReadCardSerial()) {
return "";
}
// UID zusammensetzen
String uid = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
if (i > 0) uid += ":";
if (mfrc522.uid.uidByte[i] < 0x10) uid += "0";
uid += String(mfrc522.uid.uidByte[i], HEX);
}
uid.toUpperCase();
// Karte deaktivieren
mfrc522.PICC_HaltA();
return uid;
}

View File

@@ -11,10 +11,7 @@
AsyncWebServer server(80);
void setupRoutes(){
// Web Server Routes
// SPIFFS initialisieren
// Web Server Routes
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
@@ -24,6 +21,10 @@ void setupRoutes(){
request->send(SPIFFS, "/settings.html", "text/html");
});
server.on("/rfid", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/rfid.html", "text/html");
});
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/about.html", "text/html");
});

View File

@@ -17,6 +17,7 @@ const char* passwordSTA = "Delfine1!";
PrettyOTA OTAUpdates;
String getUniqueSSID();
void setupWifi() {