Initial commit
This commit is contained in:
416
borrow-system-locker-esp32.ino
Normal file
416
borrow-system-locker-esp32.ino
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <LiquidCrystal_I2C.h>
|
||||||
|
#include <SparkFun_Qwiic_Keypad_Arduino_Library.h>
|
||||||
|
#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<JsonArray>();
|
||||||
|
int doorCount = 0;
|
||||||
|
|
||||||
|
for (JsonVariant locker : lockerArray) {
|
||||||
|
int doorNumber = locker.as<int>();
|
||||||
|
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<String>();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
config.h.example
Normal file
15
config.h.example
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// config.h.example - Template configuration file
|
||||||
|
// Copy this file to config.h and fill in your actual values
|
||||||
|
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
// WiFi credentials
|
||||||
|
const char* WIFI_SSID = "YOUR_WIFI_SSID";
|
||||||
|
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
|
||||||
|
|
||||||
|
// Backend API configuration
|
||||||
|
const char* BACKEND_URL = "https://insta.the1s.de/backend/api";
|
||||||
|
const char* API_KEY = "12345678"; // Replace with your 8-digit API key
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
||||||
Reference in New Issue
Block a user