#include #include #include "DFRobotDFPlayerMini.h" #include #include #include #include #include /* ================= CONFIGURATION ================= */ const char* ssid = "OPPO A57"; const char* password = "11223344"; const char* mqtt_server = "192.168.254.121"; const int mqtt_port = 1883; /* ================= OBJECTS & PINS ================= */ #define SS_PIN 5 #define RST_PIN 4 #define BUZZER_PIN 13 MFRC522 rfid(SS_PIN, RST_PIN); HardwareSerial mySerial(2); DFRobotDFPlayerMini dfplayer; LiquidCrystal_I2C lcd(0x27, 16, 2); WiFiClient espClient; PubSubClient client(espClient); int merah = 23, hijau = 22, biru = 21; /* ================= STATE & TIMERS ================= */ String mode = "standby"; unsigned long lastWifiCheck = 0; unsigned long lastHeartbeat = 0; bool isAudioPlaying = false; unsigned long audioStartTime = 0; const unsigned long AUDIO_DURATION = 3500; // jeda 3 detik String lastUID = ""; unsigned long lastScanTime = 0; const unsigned long SCAN_DELAY = 3000; // jeda 3 detik String scrollMessage = " NAWASENA SMART LIBRARY SYSTEM - BAKORWIL III MALANG "; unsigned long lastScroll = 0; int scrollCursor = 0; /* ================= HELPERS ================= */ void setRGB(bool r, bool g, bool b) { digitalWrite(merah, r); digitalWrite(hijau, g); digitalWrite(biru, b); } void beep(int duration) { digitalWrite(BUZZER_PIN, HIGH); delay(duration); digitalWrite(BUZZER_PIN, LOW); } void playTrack(int track) { if (isAudioPlaying) return; Serial.print("🔊 Memutar Track: "); Serial.println(track); dfplayer.play(track); isAudioPlaying = true; audioStartTime = millis(); } void applyModeVisual() { if (mode == "admin") setRGB(1, 1, 0); else if (mode == "scan_member") setRGB(0, 1, 0); else if (mode == "wait") setRGB(1, 0, 0); else if (mode == "identified") setRGB(0, 1, 1); else setRGB(0, 0, 1); } /* ================= MQTT CALLBACK ================= */ void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) message += (char)payload[i]; Serial.println("📥 MQTT Diterima [" + String(topic) + "]: " + message); if (String(topic) == "rfid/mode/set") { mode = message; lcd.clear(); if (mode == "scan_member") playTrack(6); else if (mode == "admin") playTrack(5); applyModeVisual(); } if (String(topic) == "rfid/status") { lcd.clear(); if (message == "valid") { mode = "identified"; lcd.setCursor(0, 0); lcd.print(" KARTU TERBACA "); lcd.setCursor(0, 1); lcd.print(" SILAHKAN... "); playTrack(4); } else if (message == "success" || message == "finish") { setRGB(0, 1, 0); lcd.setCursor(0, 0); lcd.print(" TRANSAKSI OK "); lcd.setCursor(0, 1); lcd.print(" TERIMA KASIH "); playTrack(8); delay(2500); mode = "standby"; } else if (message == "registered") { setRGB(0, 1, 0); lcd.setCursor(0, 0); lcd.print(" REGISTRASI OK "); lcd.setCursor(0, 1); lcd.print(" KARTU TERTAUT "); playTrack(7); delay(2500); mode = "standby"; } else if (message == "error" || message == "failed") { setRGB(1, 0, 0); lcd.setCursor(0, 0); lcd.print(" AKSES DITOLAK "); lcd.setCursor(0, 1); lcd.print(" COBA LAGI... "); playTrack(3); delay(2500); } lcd.clear(); applyModeVisual(); } } void reconnectMQTT() { while (!client.connected()) { Serial.print("🔄 Menghubungkan MQTT..."); String clientId = "ESP32_Nawasena_" + String(random(0xffff), HEX); if (client.connect(clientId.c_str(), NULL, NULL, "rfid/device/status", 1, true, "offline")) { Serial.println("TERHUBUNG!"); client.subscribe("rfid/mode/set"); client.subscribe("rfid/status"); client.subscribe("rfid/scan/admin"); client.publish("rfid/device/status", "online", true); } else { Serial.print("Gagal, rc="); Serial.print(client.state()); delay(5000); } } } /* ================= CORE FUNCTIONS ================= */ void handleRFID() { String uid = ""; for (byte i = 0; i < rfid.uid.size; i++) { uid += (rfid.uid.uidByte[i] < 0x10 ? "0" : ""); uid += String(rfid.uid.uidByte[i], HEX); } uid.toUpperCase(); if (uid == lastUID && (millis() - lastScanTime < SCAN_DELAY)) { // Sesi dihentikan dengan aman jika kartu yang sama di-tap berulang kali rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); return; } // Perbarui waktu scan terakhir yang sukses lastUID = uid; lastScanTime = millis(); Serial.println("🆔 UID Terdeteksi: " + uid); beep(150); setRGB(1, 0, 1); lcd.clear(); lcd.setCursor(0, 0); lcd.print(" MEMPROSES "); lcd.setCursor(0, 1); lcd.print(" MOHON TUNGGU.. "); // Publikasikan data murni berdasarkan mode via MQTT saja (API Dihapus) if (mode == "admin") { client.publish("rfid/scan/admin", uid.c_str()); } else if (mode == "scan_member") { client.publish("rfid/scan/member", uid.c_str()); } else { // Default fallback jika standby atau mode lainnya ke topic umum client.publish("rfid/scan/general", uid.c_str()); } delay(500); applyModeVisual(); rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); } void updateScrollText() { if (millis() - lastScroll > 350) { lastScroll = millis(); lcd.setCursor(0, 1); String displayMsg = scrollMessage.substring(scrollCursor, scrollCursor + 16); if (displayMsg.length() < 16) { displayMsg += scrollMessage.substring(0, 16 - displayMsg.length()); } lcd.print(displayMsg); scrollCursor++; if (scrollCursor >= scrollMessage.length()) scrollCursor = 0; } } /* ================= MAIN PROGRAM ================= */ void setup() { Serial.begin(115200); pinMode(merah, OUTPUT); pinMode(hijau, OUTPUT); pinMode(biru, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); setRGB(1, 1, 1); Wire.begin(32, 33); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(" NAWASENA SMART "); lcd.setCursor(0, 1); lcd.print(" BOOTING... "); mySerial.begin(9600, SERIAL_8N1, 25, 26); if (dfplayer.begin(mySerial)) { dfplayer.volume(30); } WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); SPI.begin(18, 15, 19, 5); rfid.PCD_Init(); delay(1000); playTrack(1); lcd.clear(); applyModeVisual(); } void loop() { if (!client.connected()) { reconnectMQTT(); } client.loop(); if (millis() - lastHeartbeat > 30000) { lastHeartbeat = millis(); if(client.connected()) { client.publish("rfid/device/status", "online", true); } } if (isAudioPlaying && (millis() - audioStartTime >= AUDIO_DURATION)) { isAudioPlaying = false; } // Logika Tampilan LCD if (mode != "identified" && mode != "wait") { lcd.setCursor(0, 0); if (mode == "scan_member") { lcd.print(" TAP KARTU ANDA "); lcd.setCursor(0, 1); lcd.print(" ( ANGGOTA ) "); } else if (mode == "admin") { lcd.print(" MODE REGISTRASI"); lcd.setCursor(0, 1); lcd.print(" SCAN KARTU... "); } else { lcd.print(" NAWASENA READY "); updateScrollText(); } } if (!isAudioPlaying) { if (rfid.PICC_IsNewCardPresent()) { if (rfid.PICC_ReadCardSerial()) { handleRFID(); } } } }