From 6f11946b61fb3e4bc2cac6b95099ac43033af0e6 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Fri, 6 Jun 2025 16:12:23 +0200 Subject: [PATCH] Add rfid.h (viel arbeit) --- src/rfid.h | 548 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 src/rfid.h diff --git a/src/rfid.h b/src/rfid.h new file mode 100644 index 0000000..ee09ae0 --- /dev/null +++ b/src/rfid.h @@ -0,0 +1,548 @@ +#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("====================================="); +}