#include #include #include #include #include #include #include "config.h" // Include private configuration // Hardware configuration const int RELAY_PINS[6] = {13, 12, 14, 27, 26, 25}; // Relay pins for doors 1-6 const int DOOR_OPEN_TIME = 5000; // 5 seconds in milliseconds // I2C devices LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD with I2C address 0x27 KEYPAD keypad; // Qwiic Keypad // State variables String currentCode = ""; bool processingLoan = false; int failedAttempts = 0; unsigned long lockoutEndTime = 0; const int MAX_FAILED_ATTEMPTS = 3; const unsigned long LOCKOUT_DURATION = 30000; // 30 seconds in milliseconds void setup() { Serial.begin(115200); // Initialize I2C Wire.begin(); // Initialize LCD lcd.init(); lcd.backlight(); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Starte..."); // Initialize keypad if (!keypad.begin()) { Serial.println("Keypad not connected!"); lcd.clear(); lcd.print("Keypad Fehler!"); while (1); } // Initialize relay pins (active LOW for most relay modules) for (int i = 0; i < 6; i++) { pinMode(RELAY_PINS[i], OUTPUT); digitalWrite(RELAY_PINS[i], HIGH); // Keep doors locked initially } // Connect to WiFi connectToWiFi(); // Ready message lcd.clear(); lcd.setCursor(0, 0); lcd.print("Code eingeben:"); lcd.setCursor(0, 1); lcd.print(""); // Code will appear here Serial.println("System ready!"); } void loop() { // Check for lockout timeout if (lockoutEndTime > 0) { if (millis() < lockoutEndTime) { // Still in lockout period unsigned long remainingSeconds = (lockoutEndTime - millis()) / 1000; lcd.setCursor(0, 1); lcd.print("Warte "); lcd.print(remainingSeconds); lcd.print("s "); delay(1000); return; } else { // Lockout period ended lockoutEndTime = 0; failedAttempts = 0; resetState(); } } // Check if keypad button was pressed keypad.updateFIFO(); char button = keypad.getButton(); if (button != 0 && !processingLoan) { handleKeypadInput(button); } // Check WiFi connection if (WiFi.status() != WL_CONNECTED) { connectToWiFi(); } } void handleKeypadInput(char button) { if (button == '#') { // Submit code if (currentCode.length() == 6) { processLoanCode(currentCode); } else { showError("Code muss 6-stellig sein!"); currentCode = ""; updateCodeDisplay(); } } else if (button == '*') { // Clear current input currentCode = ""; updateCodeDisplay(); lcd.setCursor(0, 0); lcd.print("Code eingeben: "); } else { // Add digit to code if (currentCode.length() < 6) { currentCode += button; updateCodeDisplay(); } } } void updateCodeDisplay() { lcd.setCursor(0, 1); // Display asterisks for entered digits String display = ""; for (int i = 0; i < currentCode.length(); i++) { display += "*"; } // Pad with spaces while (display.length() < 16) { display += " "; } lcd.print(display); } void processLoanCode(String code) { processingLoan = true; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Verarbeite..."); Serial.println("Processing loan code: " + code); // Get loan information from backend String lockers = ""; bool isReturn = false; if (!getLoanInfo(code, lockers, isReturn)) { failedAttempts++; if (failedAttempts >= MAX_FAILED_ATTEMPTS) { // Start lockout period lockoutEndTime = millis() + LOCKOUT_DURATION; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Zu viele Fehler!"); lcd.setCursor(0, 1); lcd.print("Gesperrt 30s"); delay(2000); return; } else { showError("Ungültiger Code!"); lcd.setCursor(0, 1); lcd.print("Versuch "); lcd.print(failedAttempts); lcd.print("/"); lcd.print(MAX_FAILED_ATTEMPTS); delay(2000); } resetState(); return; } // Code was correct - reset failed attempts failedAttempts = 0; // Parse locker numbers DynamicJsonDocument doc(512); DeserializationError error = deserializeJson(doc, lockers); if (error) { Serial.println("JSON parsing failed!"); showError("System Fehler!"); resetState(); return; } // Display action type lcd.clear(); lcd.setCursor(0, 0); if (isReturn) { lcd.print("Rückgabe"); } else { lcd.print("Ausleihe"); } // Open doors one by one JsonArray lockerArray = doc.as(); int doorCount = 0; for (JsonVariant locker : lockerArray) { int doorNumber = locker.as(); if (doorNumber >= 1 && doorNumber <= 6) { doorCount++; openDoor(doorNumber); } } if (doorCount == 0) { showError("Keine Fächer!"); resetState(); return; } // Update loan status in backend if (isReturn) { setReturnDate(code); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Rückgabe OK!"); } else { setTakeDate(code); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Ausleihe OK!"); } lcd.setCursor(0, 1); lcd.print("Vielen Dank!"); delay(3000); resetState(); } bool getLoanInfo(String loanCode, String &lockers, bool &isReturn) { if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi not connected!"); return false; } HTTPClient http; String url = String(BACKEND_URL) + "/get-loan-by-code/" + API_KEY + "/" + loanCode; http.begin(url); int httpCode = http.GET(); if (httpCode != 200) { Serial.println("HTTP error: " + String(httpCode)); http.end(); return false; } String payload = http.getString(); http.end(); // Parse response DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, payload); if (error) { Serial.println("JSON parsing failed!"); return false; } // Check if loan was found if (!doc.containsKey("data")) { Serial.println("No data in response"); return false; } JsonObject data = doc["data"]; // Get lockers array as string lockers = data["lockers"].as(); // Check if this is a return (returned_date is null) or pickup (returned_date is set) // If returned_date is null, this is a return action // If take_date is null, this is a pickup action isReturn = !data["returned_date"].isNull(); // Actually, based on API: if returned_date is null, user is returning items // If take_date is null, user is taking items if (data["take_date"].isNull()) { isReturn = false; // User is taking items } else if (data["returned_date"].isNull()) { isReturn = true; // User is returning items } else { // Both dates set - loan already completed Serial.println("Loan already completed"); return false; } Serial.println("Loan info retrieved. Lockers: " + lockers + ", isReturn: " + String(isReturn)); return true; } void openDoor(int doorNumber) { if (doorNumber < 1 || doorNumber > 6) { return; } int pinIndex = doorNumber - 1; lcd.setCursor(0, 1); lcd.print("Öffne Fach "); lcd.print(doorNumber); lcd.print(" "); Serial.println("Opening door " + String(doorNumber)); // Activate relay (LOW = on for most relay modules) digitalWrite(RELAY_PINS[pinIndex], LOW); // Keep door open for specified time delay(DOOR_OPEN_TIME); // Deactivate relay (close door) digitalWrite(RELAY_PINS[pinIndex], HIGH); Serial.println("Door " + String(doorNumber) + " closed"); } bool setReturnDate(String loanCode) { if (WiFi.status() != WL_CONNECTED) { return false; } HTTPClient http; String url = String(BACKEND_URL) + "/set-return-date/" + API_KEY + "/" + loanCode; http.begin(url); int httpCode = http.POST(""); bool success = (httpCode == 200); http.end(); Serial.println("Set return date: " + String(success ? "success" : "failed")); return success; } bool setTakeDate(String loanCode) { if (WiFi.status() != WL_CONNECTED) { return false; } HTTPClient http; String url = String(BACKEND_URL) + "/set-take-date/" + API_KEY + "/" + loanCode; http.begin(url); int httpCode = http.POST(""); bool success = (httpCode == 200); http.end(); Serial.println("Set take date: " + String(success ? "success" : "failed")); return success; } void showError(String message) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("FEHLER:"); lcd.setCursor(0, 1); lcd.print(message); Serial.println("Error: " + message); delay(2000); } void resetState() { currentCode = ""; processingLoan = false; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Code eingeben:"); lcd.setCursor(0, 1); lcd.print(""); } void connectToWiFi() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Verbinde WiFi..."); Serial.print("Connecting to WiFi"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 20) { delay(500); Serial.print("."); lcd.setCursor(0, 1); for (int i = 0; i < (attempts % 16); i++) { lcd.print("."); } attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println("\nConnected to WiFi!"); Serial.println("IP: " + WiFi.localIP().toString()); lcd.clear(); lcd.setCursor(0, 0); lcd.print("WiFi verbunden!"); delay(1500); } else { Serial.println("\nFailed to connect to WiFi!"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("WiFi Fehler!"); delay(2000); } }