1) #include <WiFi.h>
2) #include <HTTPClient.h>
3) #include <SPI.h>
4) #include <Wire.h>
5) #include <MFRC522.h>
6) #include <LiquidCrystal_I2C.h>
7) #include <ArduinoJson.h>
8) 
9) #define DEBUG_MODE true
10) 
11) const char* ssid = "KOPI";
12) const char* password = "digoreng123";
13) const String API_BASE_URL = "https://smartlocker.punyapadias.my.id/api/";
14) 
15) #define SS_PIN 21
16) #define RST_PIN 5
17) MFRC522 mfrc522(SS_PIN, RST_PIN);
18) 
19) LiquidCrystal_I2C lcd(0x27, 16, 2);
20) 
21) const int trigPins[3] = {17, 16, 33};
22) const int echoPins[3] = {35, 34, 32};
23) 
24) #define RELAY1_PIN 14
25) #define RELAY2_PIN 27
26) #define RELAY3_PIN 26
27) 
28) #define BUZZER_PIN 13
29) 
30) enum SystemState {
31)   STATE_IDLE,
32)   STATE_RFID_DETECTED,
33)   STATE_OPENING_LOCKER,
34)   STATE_REGISTERING_RFID,
35)   STATE_ERROR
36) };
37) SystemState currentState = STATE_IDLE;
38) 
39) unsigned long lastApiCallTime = 0;
40) const long apiCallInterval = 5000;
41) unsigned long rfidActionStartTime = 0;
42) const long rfidActionDuration = 10000;
43) bool lcdIdleShown = false;
44) 
45) 
46) struct RFIDMapping {
47)   String uid;
48)   int lokerId;
49)   int relayPin;
50) };
51) 
52) RFIDMapping rfidMappings[] = {
53)   {"36CB3503", 1, RELAY1_PIN},
54)   {"5C312903", 2, RELAY2_PIN},
55)   {"611B2803", 3, RELAY3_PIN},
56) };
57) const int jumlahRFID = sizeof(rfidMappings) / sizeof(rfidMappings[0]);
58) 
59) void setRFIDtoLoker(int lokerId, String rfid);
60) void updateStatusLoker(int lokerId, String status);
61) int getFirstEmptyRFIDLokerId();
62) String getStatusLoker(int lokerId);
63) void clearRFID();
64) void playBuzzer(int count, int durationMs, int pauseMs);
65) void displayLcdMessage(String line1, String line2, int duration = 0);
66) void handleRFIDAction(String uidStr, int mappedLokerId, int mappedRelayPin);
67) void handleNewRFIDRegistration(String uidStr);
68) 
69) void displayDefaultScreen() {
70)   lcd.clear();
71)   lcd.setCursor(0, 0);
72)   lcd.print("Loker Pintar");
73)   lcd.setCursor(0, 1);
74)   lcd.print("Scan Kartu");
75) }
76) 
77) void setup() {
78)   Serial.begin(115200);
79)   Wire.begin(4, 22);
80)   lcd.init();
81)   lcd.backlight();
82)   lcd.setCursor(0, 0);
83)   lcd.print("Inisialisasi...");
84) 
85)   // Menyambung ke WiFi
86)   WiFi.begin(ssid, password);
87)   lcd.setCursor(0, 1);
88)   lcd.print("WiFi connecting...");
89)   while (WiFi.status() != WL_CONNECTED) {
90)     delay(500);
91)     Serial.print(".");
92)   }
93) 
94)   lcd.setCursor(0, 1);
95)   lcd.print("WiFi connected   ");
96)   Serial.println("\nWiFi Connected. IP: " + WiFi.localIP().toString());
97) 
98)   // Inisialisasi RFID
99)   SPI.begin();
100)   mfrc522.PCD_Init();
101) 
102)   // Inisialisasi sensor ultrasonik
103)   for (int i = 0; i < 3; i++) {
104)     pinMode(trigPins[i], OUTPUT);
105)     pinMode(echoPins[i], INPUT);
106)   }
107) 
108)   // Inisialisasi relay dan buzzer
109)   pinMode(RELAY1_PIN, OUTPUT); digitalWrite(RELAY1_PIN, HIGH);
110)   pinMode(RELAY2_PIN, OUTPUT); digitalWrite(RELAY2_PIN, HIGH);
111)   pinMode(RELAY3_PIN, OUTPUT); digitalWrite(RELAY3_PIN, HIGH);
112) 
113)   pinMode(BUZZER_PIN, OUTPUT);
114)   digitalWrite(BUZZER_PIN, LOW);
115) 
116)   // Tampilkan layar utama
117)   lcd.clear();
118)   displayDefaultScreen();
119)   
120)   lastApiCallTime = millis();
121) }
122) 
123) void loop() {
124)   // Debug: tampilkan state saat ini setiap 1 detik
125)   static unsigned long lastDebugTime = 0;
126)   if (millis() - lastDebugTime > 1000) {
127)     Serial.println("[DEBUG] Current state: " + String(currentState));
128)     lastDebugTime = millis();
129)   }
130) 
131)   // Recovery: jika RFID tidak terbaca dalam waktu lama, inisialisasi ulang
132)   static unsigned long lastRFIDScanTime = 0;
133)   if (millis() - lastRFIDScanTime > 10000) {
134)     Serial.println("[RFID] Timeout, re-initialize RFID.");
135)     mfrc522.PCD_Init();
136)     lastRFIDScanTime = millis();
137)   }
138) 
139)   switch (currentState) {
140)     case STATE_IDLE:
141)       // Tampilkan LCD hanya sekali saat idle
142)       if (!lcdIdleShown) {
143)         displayDefaultScreen();
144)         lcdIdleShown = true;
145)       }
146) 
147)       // Cek kartu RFID
148)       if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
149)         lcdIdleShown = false; // agar layar bisa refresh nanti
150)         playBuzzer(1, 50, 0);
151)         lastRFIDScanTime = millis(); // reset timeout counter
152) 
153)         // Ambil UID
154)         String uidStr = "";
155)         for (byte i = 0; i < mfrc522.uid.size; i++) {
156)           if (mfrc522.uid.uidByte[i] < 0x10) uidStr += "0";
157)           uidStr += String(mfrc522.uid.uidByte[i], HEX);
158)         }
159)         uidStr.toUpperCase();
160) 
161)         Serial.println("[RFID] UID: " + uidStr);
162)         displayLcdMessage("RFID Terbaca:", uidStr);
163) 
164)         // Cek UID pada daftar
165)         bool found = false;
166)         int lokerId = -1;
167)         int relayPin = -1;
168)         for (int i = 0; i < jumlahRFID; i++) {
169)           if (uidStr == rfidMappings[i].uid) {
170)             found = true;
171)             lokerId = rfidMappings[i].lokerId;
172)             relayPin = rfidMappings[i].relayPin;
173)             break;
174)           }
175)         }
176) 
177)         if (found) {
178)           currentState = STATE_OPENING_LOCKER;
179)           handleRFIDAction(uidStr, lokerId, relayPin);
180)         } else {
181)           // Jika tidak ditemukan, aktifkan buzzer "tit-tit-tit" dan tampilkan pesan
182)           playBuzzer(3, 100, 100);  // Bunyi 3 kali dengan interval 100ms
183)           displayLcdMessage("Kartu Tidak", "Terdaftar!", 2000);
184)           currentState = STATE_REGISTERING_RFID;
185)           handleNewRFIDRegistration(uidStr);
186)         }
187) 
188)         clearRFID();
189)         delay(200); // beri waktu recovery RFID
190)       }
191)       break;
192) 
193)     case STATE_REGISTERING_RFID:
194)     case STATE_OPENING_LOCKER:
195)       lcdIdleShown = false;
196)       currentState = STATE_IDLE;
197)       break;
198) 
199)     case STATE_ERROR:
200)       if (WiFi.status() != WL_CONNECTED) {
201)         displayLcdMessage("WiFi Disconnect!", "Reconnecting...");
202)         WiFi.reconnect();
203)         delay(1000);
204)       } else {
205)         lcdIdleShown = false;
206)         currentState = STATE_IDLE;
207)       }
208)       break;
209)   }
210) }
211) 
212) 
213) 
214) 
215) 
216) void handleRFIDAction(String uidStr, int mappedLokerId, int mappedRelayPin) {
217)   if (DEBUG_MODE) Serial.println("[RFID Action] Membuka Loker ID: " + String(mappedLokerId) + " dengan UID: " + uidStr);
218) 
219)   // Aktifkan relay untuk membuka loker
220)   digitalWrite(mappedRelayPin, LOW);
221)   playBuzzer(2, 70, 50);
222)   if (DEBUG_MODE) Serial.println("[RFID Action] Relay " + String(mappedRelayPin) + " AKTIF (Membuka).");
223) 
224)   // Menunggu beberapa detik sambil menampilkan jarak & status loker
225)   unsigned long showStart = millis();
226)   float jarak = 0.0;
227)   String status = "";
228) 
229)   // Baca sensor ultrasonik hanya sekali
230)   int trigPin = trigPins[mappedLokerId - 1];
231)   int echoPin = echoPins[mappedLokerId - 1];
232)   
233)   // Tunggu 10 detik dan update status loker
234)   while (millis() - showStart < 10000) {
235)     // Menghitung jarak dengan sensor ultrasonik
236)     digitalWrite(trigPin, LOW);
237)     delayMicroseconds(2);
238)     digitalWrite(trigPin, HIGH);
239)     delayMicroseconds(10);
240)     digitalWrite(trigPin, LOW);
241) 
242)     long durasi = pulseIn(echoPin, HIGH);
243)     jarak = durasi * 0.034 / 2;  // Menghitung jarak dalam cm
244)     status = (jarak < 10) ? "digunakan" : "kosong";  // Status jika jarak < 10 cm dianggap 'digunakan'
245)     
246)     // Tampilkan informasi di LCD
247)     displayLcdMessage("Loker " + String(mappedLokerId),
248)                     status + " | " + String(jarak, 0) + "cm");
249)     delay(500);  // Mengurangi delay untuk responsifitas yang lebih cepat
250)   }
251) 
252)   // Matikan relay setelah 10 detik
253)   digitalWrite(mappedRelayPin, HIGH);
254)   if (DEBUG_MODE) Serial.println("[RFID Action] Relay " + String(mappedRelayPin) + " NONAKTIF (Terkunci).");
255) 
256)   // Perbarui status loker ke API
257)   updateStatusLoker(mappedLokerId, status);
258)   
259)   // Kembali ke state idle setelah selesai
260)   currentState = STATE_IDLE;
261) }
262) 
263) 
264) 
265) void handleNewRFIDRegistration(String uidStr) {
266)   displayLcdMessage("Mencari Loker", "Kosong...");
267)   int kosongLokerIdAPI = getFirstEmptyRFIDLokerId();
268) 
269)   if (kosongLokerIdAPI != -1) {
270)     setRFIDtoLoker(kosongLokerIdAPI, uidStr);
271)     displayLcdMessage("RFID Diset", "Loker ID: " + String(kosongLokerIdAPI), 2000);
272)     playBuzzer(1, 150, 0);
273)   } else {
274)     displayLcdMessage("Gagal Set RFID", "Loker Penuh", 2000);
275)     playBuzzer(3, 100, 50);
276)   }
277) }
278) 
279) void updateStatusLoker(int lokerId, String status) {
280)   if (WiFi.status() == WL_CONNECTED) {
281)     HTTPClient http;
282)     String url = API_BASE_URL + "lokers/" + String(lokerId) + "/status";
283) 
284)     http.begin(url);
285)     http.setTimeout(3000); 
286)     http.addHeader("Content-Type", "application/json");
287) 
288)     String jsonPayload = "{\"status\": \"" + status + "\"}";
289)     int httpResponseCode = http.POST(jsonPayload);
290) 
291)     if (httpResponseCode > 0) {
292)       String response = http.getString();
293)       if (DEBUG_MODE) Serial.println("[API] Status update response: " + response);
294)     } else {
295)       currentState = STATE_ERROR;
296)       displayLcdMessage("API Gagal", "Update Status", 2000);
297)       playBuzzer(2, 100, 50);
298)     }
299)     http.end();
300)   } else {
301)     if (DEBUG_MODE) Serial.println("[API] WiFi belum terhubung, tidak bisa update status.");
302)     currentState = STATE_ERROR;
303)     displayLcdMessage("WiFi Disconnect!", "API Update Gagal", 2000);
304)     playBuzzer(2, 100, 50);
305)   }
306) }
307) 
308) int getFirstEmptyRFIDLokerId() {
309)   if (WiFi.status() == WL_CONNECTED) {
310)     HTTPClient http;
311)     String url = API_BASE_URL + "lokers/pertama-rfid-null";
312) 
313)     http.begin(url);
314)     http.setTimeout(3000);
315)     int httpCode = http.GET();
316) 
317)     if (httpCode == 200) {
318)       String response = http.getString();
319)       StaticJsonDocument<256> doc;
320)       DeserializationError error = deserializeJson(doc, response);
321) 
322)       if (error) return -1;
323) 
324)       if (doc.containsKey("data") && doc["data"].containsKey("id")) {
325)         int parsedId = doc["data"]["id"].as<int>();
326)         if (DEBUG_MODE) Serial.println("[API] ID loker kosong ditemukan: " + String(parsedId));
327)         http.end();
328)         return parsedId;
329)       } else {
330)         if (DEBUG_MODE) Serial.println("[API] Respons JSON tidak mengandung 'data.id'.");
331)         http.end();
332)         return -1;
333)       }
334)     } else if (httpCode == 404) {
335)       http.end();
336)       return -1;
337)     } else {
338)       currentState = STATE_ERROR;
339)       http.end();
340)     }
341)   } else {
342)     currentState = STATE_ERROR;
343)     displayLcdMessage("WiFi Disconnect!", "API Get Gagal", 2000);
344)     playBuzzer(2, 100, 50);
345)   }
346)   return -1;
347) }
348) 
349) void setRFIDtoLoker(int lokerId, String rfid) {
350)   if (WiFi.status() == WL_CONNECTED) {
351)     HTTPClient http;
352)     String url = API_BASE_URL + "lokers/" + String(lokerId) + "/setrfid";
353) 
354)     http.begin(url);
355)     http.setTimeout(3000); 
356)     http.addHeader("Content-Type", "application/json");
357) 
358)     String jsonPayload = "{\"rfid\": \"" + rfid + "\"}";
359)     int httpResponseCode = http.POST(jsonPayload);
360) 
361)     if (httpResponseCode > 0) {
362)       String response = http.getString();
363)       if (DEBUG_MODE) Serial.println("[API] SET RFID Response: " + response);
364)       StaticJsonDocument<256> doc;
365)       DeserializationError error = deserializeJson(doc, response);
366) 
367)       if (!error && doc.containsKey("message")) {
368)         String message = doc["message"].as<String>();
369)         if (message.indexOf("RFID sudah digunakan") != -1) {
370)           if (DEBUG_MODE) Serial.println("[API] Server Report: RFID sudah digunakan.");
371)           displayLcdMessage("RFID Sudah", "Terdaftar", 2000);
372)           playBuzzer(2, 100, 50);
373)         }
374)       }
375)     } else {
376)       if (DEBUG_MODE) Serial.println("[API] Gagal kirim set RFID: " + http.errorToString(httpResponseCode));
377)       currentState = STATE_ERROR;
378)       displayLcdMessage("API Gagal", "Set RFID", 2000);
379)       playBuzzer(2, 100, 50);
380)     }
381)     http.end();
382)   } else {
383)     if (DEBUG_MODE) Serial.println("[API] WiFi belum terhubung, tidak bisa set RFID.");
384)     currentState = STATE_ERROR;
385)     displayLcdMessage("WiFi Disconnect!", "API Set RFID Gagal", 2000);
386)     playBuzzer(2, 100, 50);
387)   }
388) }
389) 
390) String getStatusLoker(int lokerId) {
391)   if (DEBUG_MODE) Serial.println("[API] getStatusLoker dipanggil untuk Loker " + String(lokerId));
392)   if (WiFi.status() == WL_CONNECTED) {
393)     HTTPClient http;
394)     String url = API_BASE_URL + "single-loker/" + String(lokerId);
395) 
396)     http.begin(url);
397)     http.setTimeout(3000);
398)     int httpCode = http.GET();
399)     if (DEBUG_MODE) Serial.println("[API] HTTP Code (getStatusLoker): " + String(httpCode));
400) 
401)     if (httpCode == 200) {
402)       String payload = http.getString();
403)       if (DEBUG_MODE) Serial.println("[API] GET LOKER Status Response: " + payload);
404) 
405)       StaticJsonDocument<256> doc;
406)       DeserializationError error = deserializeJson(doc, payload);
407) 
408)       if (!error && doc.containsKey("data") && doc["data"].containsKey("status")) {
409)         String status = doc["data"]["status"].as<String>();
410)         if (DEBUG_MODE) Serial.println("[API] Status dari API: " + status);
411)         http.end();
412)         return status;
413)       } else {
414)         if (DEBUG_MODE) Serial.println("[API] Parsing JSON error atau format tidak sesuai.");
415)         if (DEBUG_MODE) Serial.println(error.c_str());
416)         http.end();
417)       }
418)     } else if (httpCode == 404) {
419)       if (DEBUG_MODE) Serial.println("[API] Loker " + String(lokerId) + " tidak ditemukan.");
420)       http.end();
421)       return "NotFound";
422)     } else {
423)       if (DEBUG_MODE) Serial.println("[API] Gagal ambil status loker: " + http.errorToString(httpCode));
424)       currentState = STATE_ERROR;
425)       http.end();
426)     }
427)   } else {
428)     if (DEBUG_MODE) Serial.println("[API] WiFi belum terhubung, tidak bisa get status loker.");
429)     currentState = STATE_ERROR;
430)   }
431)   return "Unknown";
432) }
433) 
434) void clearRFID() {
435)   mfrc522.PICC_HaltA();
436)   mfrc522.PCD_StopCrypto1();
437)   if (DEBUG_MODE) Serial.println("[RFID] RFID Reader Direset.");
438) }
439) 
440) void playBuzzer(int count, int durationMs, int pauseMs) {
441)   for (int i = 0; i < count; i++) {
442)     digitalWrite(BUZZER_PIN, HIGH);
443)     delay(durationMs);
444)     digitalWrite(BUZZER_PIN, LOW);
445)     if (i < count - 1) delay(pauseMs);
446)   }
447) }
448) 
449) void displayLcdMessage(String line1, String line2, int duration) {
450)   lcd.clear();
451)   lcd.setCursor(0, 0);
452)   lcd.print(line1);
453)   lcd.setCursor(0, 1);
454)   lcd.print(line2);
455)   if (duration > 0) delay(duration);
456) }