#include #include #include #include #include #include #include #include // 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 // 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 // Server Configuration MFRC522 rfid(SS_PIN, RST_PIN); // HTTP clients HTTPClient httpClient; WiFiClientSecure secureClient; // Database table structure // 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!"); } else { Serial.print("RFID reader found, version: 0x"); Serial.println(version, HEX); } Serial.println("RFID Reader initialized"); } // Initialize WiFi void initWiFi() { WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(1000); Serial.print("."); attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println(); Serial.print("WiFi connected! IP address: "); Serial.println(WiFi.localIP()); } else { Serial.println(); Serial.println("WiFi connection failed!"); } } // 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() && result.containsKey("data")) { JsonArray data = result["data"].as(); 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()) { response["success"] = true; // Handle different response formats if (dbResponse.containsKey("data")) { response["data"] = dbResponse["data"]; response["count"] = dbResponse["data"].as().size(); } else { // Direct array response response["data"] = dbResponse.as(); response["count"] = dbResponse.as().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 vorname = requestDoc["vorname"].as(); String nachname = requestDoc["nachname"].as(); int alter = requestDoc["alter"].as(); 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()) { 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 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()) { 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 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("====================================="); }