283 lines
7.6 KiB
C++
283 lines
7.6 KiB
C++
#include <SPI.h>
|
|
#include <MFRC522.h>
|
|
#include "DFRobotDFPlayerMini.h"
|
|
#include <WiFi.h>
|
|
#include <ArduinoJson.h>
|
|
#include <Wire.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#include <PubSubClient.h>
|
|
|
|
/* ================= 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();
|
|
}
|
|
}
|
|
}
|
|
}
|