513 lines
12 KiB
C++
513 lines
12 KiB
C++
|
|
#include <WiFi.h>
|
|
#include <Firebase_ESP_Client.h>
|
|
#include <time.h>
|
|
#include <ESP32Servo.h>
|
|
#include <Wire.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#include <SPI.h>
|
|
#include <MFRC522.h>
|
|
#include <HTTPClient.h>
|
|
#include <Preferences.h>
|
|
|
|
|
|
|
|
// ✅ Firebase
|
|
#include "addons/TokenHelper.h"
|
|
#include "addons/RTDBHelper.h"
|
|
// Firebase
|
|
#define API_KEY "AIzaSyCaLvOB31bMXPwFfIz5Gy5ecGZhDs6kW34"
|
|
#define DATABASE_URL "https://sistemparkir-cc7d8-default-rtdb.firebaseio.com/"
|
|
#define LEGACY_TOKEN "1uQ1lRWJqVoxeuGaSbJvzjEGX9MpRdaqXCFAxrP0"
|
|
|
|
// RFID Pins
|
|
#define SS_MASUK 19
|
|
#define RST_MASUK 23
|
|
#define SS_KELUAR 2
|
|
#define RST_KELUAR 15
|
|
|
|
// SPI Shared Bus
|
|
#define SCK 17
|
|
#define MISO 22
|
|
#define MOSI 21
|
|
|
|
// Servo
|
|
#define SERVO_MASUK_PIN 33
|
|
#define SERVO_KELUAR_PIN 25
|
|
|
|
// Buzzer
|
|
#define BUZZER_PIN 13
|
|
|
|
// IR Sensor
|
|
#define IR_MASUK_PIN 34
|
|
#define IR_KELUAR_PIN 35
|
|
|
|
// LED Status
|
|
#define LED_PIN 12
|
|
|
|
// Tombol Reset
|
|
#define RESET_BUTTON_PIN 14
|
|
|
|
|
|
const char* ssid = "Fajar";
|
|
const char* password = "fajar123";
|
|
|
|
// Firebase objects
|
|
FirebaseData fbdo;
|
|
FirebaseAuth auth;
|
|
FirebaseConfig config;
|
|
String apiKey;
|
|
String databaseUrl;
|
|
const char* ntpServer = "time.nist.gov";
|
|
const long gmtOffset_sec = 7 * 3600; // GMT+7
|
|
const int daylightOffset_sec = 0;
|
|
|
|
|
|
// RFID & Servo
|
|
MFRC522 rfidMasuk(SS_MASUK, RST_MASUK);
|
|
MFRC522 rfidKeluar(SS_KELUAR, RST_KELUAR);
|
|
Servo servoMasuk;
|
|
Servo servoKeluar;
|
|
|
|
// LCD
|
|
LiquidCrystal_I2C lcd(0x27, 16, 2);
|
|
|
|
// Variabel
|
|
int totalSlot = 10;
|
|
int jumlahMasuk = 0;
|
|
int jumlahKeluar = 0;
|
|
int slotTerpakai = 0;
|
|
unsigned long previousMillis = 0;
|
|
bool ledState = false;
|
|
|
|
unsigned long buttonPressStart = 0;
|
|
bool buttonPressed = false;
|
|
const unsigned long resetHoldTime = 10000; // 10 detik
|
|
bool emergencyActive = false;
|
|
|
|
|
|
void tampilkanDefaultLCD() {
|
|
int slotKosong = 0;
|
|
|
|
// Mengambil data slot kosong langsung dari Firebase
|
|
if (Firebase.RTDB.getInt(&fbdo, "info_parkir/slot")) {
|
|
slotKosong = fbdo.intData(); // Mengambil nilai slot kosong dari Firebase
|
|
}
|
|
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(" SIPARKIR SLOT:");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(" " + String(slotKosong)); // Menampilkan jumlah slot kosong dari Firebase
|
|
}
|
|
|
|
|
|
void tampilkanPesanLCD(String pesan) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(pesan);
|
|
delay(3000);
|
|
tampilkanDefaultLCD();
|
|
}
|
|
|
|
void tampilkanPesanLCDDuaBaris(String baris1, String baris2) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(baris1);
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(baris2);
|
|
delay(3000);
|
|
tampilkanDefaultLCD();
|
|
}
|
|
|
|
bool bukaPintu(Servo& servo, int irPin, bool isMasuk) {
|
|
// Buka pintu
|
|
if (isMasuk) {
|
|
servo.write(90); // buka arah masuk
|
|
} else {
|
|
servo.write(90); // buka arah keluar (ubah dari 0 ke 90)
|
|
}
|
|
|
|
Serial.println("🚗 Pintu terbuka, menunggu kendaraan melewati sensor IR...");
|
|
|
|
unsigned long waktuMulai = millis();
|
|
const unsigned long batasWaktu = 10000; // timeout jika kendaraan tidak muncul
|
|
bool terdeteksi = false;
|
|
|
|
// Tunggu kendaraan muncul
|
|
while (!terdeteksi && (millis() - waktuMulai < batasWaktu)) {
|
|
if (digitalRead(irPin) == LOW) {
|
|
terdeteksi = true;
|
|
Serial.println("✅ Kendaraan terdeteksi IR...");
|
|
}
|
|
delay(100);
|
|
}
|
|
|
|
if (!terdeteksi) {
|
|
Serial.println("⚠️ Tidak ada kendaraan yang muncul, timeout.");
|
|
|
|
// Tutup pintu
|
|
if (isMasuk) {
|
|
servo.write(0);
|
|
} else {
|
|
servo.write(0); // tutup keluar (ubah dari 90 ke 0)
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Tunggu kendaraan lewat
|
|
while (digitalRead(irPin) == LOW) {
|
|
delay(100);
|
|
}
|
|
|
|
Serial.println("✅ Kendaraan sudah melewati sensor IR.");
|
|
|
|
// Tutup pintu
|
|
if (isMasuk) {
|
|
servo.write(0);
|
|
} else {
|
|
servo.write(0); // ubah dari 90 ke 0
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void updateFirebase() {
|
|
int slotKosong = totalSlot - slotTerpakai;
|
|
Firebase.RTDB.setInt(&fbdo, "info_parkir/masuk", jumlahMasuk);
|
|
Firebase.RTDB.setInt(&fbdo, "info_parkir/keluar", jumlahKeluar);
|
|
Firebase.RTDB.setInt(&fbdo, "info_parkir/slot", slotKosong);
|
|
}
|
|
|
|
void kirimDataKeServer(String uid, String activity) {
|
|
HTTPClient http;
|
|
http.begin("https://esp32-firebase-service.vercel.app/api/kirim-data");
|
|
|
|
time_t now;
|
|
struct tm timeinfo;
|
|
if (!getLocalTime(&timeinfo)) {
|
|
Serial.println("❌ Gagal mendapatkan waktu lokal");
|
|
return;
|
|
}
|
|
|
|
char timeString[30];
|
|
strftime(timeString, sizeof(timeString), "%Y-%m-%dT%H:%M:%S.000Z", &timeinfo);
|
|
|
|
String payload = "{\"uid\":\"" + uid + "\",\"activity\":\"" + activity + "\",\"time\":\"" + String(timeString) + "\"}";
|
|
Serial.println("📤 Kirim Payload: " + payload);
|
|
|
|
http.begin("https://esp32-firebase-service.vercel.app/api/kirim-data"); // ✅ langsung tulis URL-nya
|
|
http.addHeader("Content-Type", "application/json");
|
|
|
|
int httpResponseCode = http.POST(payload);
|
|
|
|
if (httpResponseCode > 0) {
|
|
Serial.printf("✅ Kirim berhasil: %d\n", httpResponseCode);
|
|
String response = http.getString();
|
|
Serial.println("Response: " + response);
|
|
} else {
|
|
Serial.printf("❌ Kirim gagal: %s\n", http.errorToString(httpResponseCode).c_str());
|
|
}
|
|
|
|
http.end();
|
|
}
|
|
|
|
|
|
void loadDataFromFirebase() {
|
|
if (Firebase.RTDB.getInt(&fbdo, "info_parkir/masuk")) {
|
|
jumlahMasuk = fbdo.intData();
|
|
}
|
|
|
|
if (Firebase.RTDB.getInt(&fbdo, "info_parkir/keluar")) {
|
|
jumlahKeluar = fbdo.intData();
|
|
}
|
|
|
|
int slotKosong = 10;
|
|
if (Firebase.RTDB.getInt(&fbdo, "info_parkir/slot")) {
|
|
slotKosong = fbdo.intData();
|
|
}
|
|
|
|
slotTerpakai = totalSlot - slotKosong;
|
|
|
|
Serial.println("✅ Data berhasil dimuat dari Firebase");
|
|
}
|
|
|
|
|
|
|
|
String getUID(MFRC522& rfid) {
|
|
String uid = "";
|
|
for (byte i = 0; i < rfid.uid.size; i++) {
|
|
uid += String(rfid.uid.uidByte[i], HEX);
|
|
}
|
|
uid.toUpperCase();
|
|
return uid;
|
|
}
|
|
|
|
void bunyiBuzzerTerdaftar() {
|
|
digitalWrite(BUZZER_PIN, HIGH);
|
|
delay(100);
|
|
digitalWrite(BUZZER_PIN, LOW);
|
|
}
|
|
|
|
void bunyiBuzzerTidakTerdaftar() {
|
|
for (int i = 0; i < 5; i++) {
|
|
digitalWrite(BUZZER_PIN, HIGH);
|
|
delay(100);
|
|
digitalWrite(BUZZER_PIN, LOW);
|
|
delay(400);
|
|
}
|
|
}
|
|
void prosesKartu(MFRC522& rfid, bool isMasuk) {
|
|
String rfidUID = getUID(rfid);
|
|
Serial.println((isMasuk ? "Masuk" : "Keluar") + String(" UID: ") + rfidUID);
|
|
String path = "daftar_kartu/" + rfidUID;
|
|
|
|
if (Firebase.RTDB.getBool(&fbdo, path + "/value") && fbdo.boolData()) {
|
|
if (Firebase.RTDB.getBool(&fbdo, path + "/status")) {
|
|
bool status = fbdo.boolData();
|
|
|
|
if (isMasuk) {
|
|
if (!status) { // Belum masuk
|
|
if (slotTerpakai < totalSlot) {
|
|
tampilkanPesanLCD("Silahkan Masuk");
|
|
|
|
bool kendaraanLewat = bukaPintu(servoMasuk, IR_MASUK_PIN, true);
|
|
if (kendaraanLewat) {
|
|
jumlahMasuk++; // ✅ hanya tambah kalau IR mendeteksi kendaraan
|
|
slotTerpakai++; // ✅
|
|
Firebase.RTDB.setBool(&fbdo, path + "/status", true);
|
|
updateFirebase();
|
|
bunyiBuzzerTerdaftar();
|
|
tampilkanDefaultLCD();
|
|
kirimDataKeServer(rfidUID, "masuk");
|
|
} else {
|
|
tampilkanPesanLCD("Gagal Masuk!");
|
|
bunyiBuzzerTidakTerdaftar();
|
|
}
|
|
} else {
|
|
tampilkanPesanLCD("Parkiran Penuh!");
|
|
}
|
|
} else {
|
|
tampilkanPesanLCD("Sudah Masuk!");
|
|
}
|
|
|
|
} else { // Keluar
|
|
if (status) { // Sudah masuk
|
|
tampilkanPesanLCD("Terima Kasih");
|
|
|
|
bool kendaraanLewat = bukaPintu(servoKeluar, IR_KELUAR_PIN, false);
|
|
if (kendaraanLewat) {
|
|
jumlahKeluar++; // ✅ hanya kurang kalau IR mendeteksi kendaraan
|
|
slotTerpakai--; // ✅
|
|
Firebase.RTDB.setBool(&fbdo, path + "/status", false);
|
|
updateFirebase();
|
|
bunyiBuzzerTerdaftar();
|
|
tampilkanDefaultLCD();
|
|
kirimDataKeServer(rfidUID, "keluar");
|
|
} else {
|
|
tampilkanPesanLCD("Gagal Keluar!");
|
|
bunyiBuzzerTidakTerdaftar();
|
|
}
|
|
} else {
|
|
tampilkanPesanLCD("Belum Masuk!");
|
|
}
|
|
}
|
|
|
|
} else {
|
|
tampilkanPesanLCD("Status Tidak Ada");
|
|
bunyiBuzzerTidakTerdaftar();
|
|
}
|
|
|
|
} else {
|
|
tampilkanPesanLCDDuaBaris("Kartu Tidak", "Terdaftar");
|
|
bunyiBuzzerTidakTerdaftar();
|
|
}
|
|
|
|
rfid.PICC_HaltA();
|
|
rfid.PCD_StopCrypto1();
|
|
}
|
|
|
|
|
|
|
|
void resetDataFirebase() {
|
|
Serial.println("⚠️ Reset data Firebase...");
|
|
|
|
Firebase.RTDB.setInt(&fbdo, "info_parkir/masuk", 0);
|
|
Firebase.RTDB.setInt(&fbdo, "info_parkir/keluar", 0);
|
|
Firebase.RTDB.setInt(&fbdo, "info_parkir/slot", totalSlot);
|
|
|
|
jumlahMasuk = 0;
|
|
jumlahKeluar = 0;
|
|
slotTerpakai = 0;
|
|
|
|
// Ambil isi JSON
|
|
if (Firebase.RTDB.getJSON(&fbdo, "daftar_kartu")) {
|
|
FirebaseJson json = fbdo.to<FirebaseJson>(); // Ambil JSON penuh
|
|
|
|
String raw;
|
|
json.toString(raw, true);
|
|
Serial.println("📦 Data JSON:");
|
|
Serial.println(raw);
|
|
|
|
size_t len = json.iteratorBegin();
|
|
Serial.printf("🔍 Total Key: %d\n", len);
|
|
|
|
String uidKey, dummyVal;
|
|
int type;
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
json.iteratorGet(i, type, uidKey, dummyVal);
|
|
|
|
if (uidKey.length() > 0) {
|
|
String statusPath = "daftar_kartu/" + uidKey + "/status";
|
|
bool success = Firebase.RTDB.setBool(&fbdo, statusPath, false);
|
|
Serial.print("Set status false untuk UID ");
|
|
Serial.print(uidKey);
|
|
Serial.println(success ? " ✅ BERHASIL" : " ❌ GAGAL");
|
|
} else {
|
|
Serial.println("❌ UID kosong, dilewati");
|
|
}
|
|
|
|
delay(200); // Hindari limit koneksi
|
|
}
|
|
|
|
json.iteratorEnd();
|
|
} else {
|
|
Serial.println("❌ Gagal ambil data daftar_kartu");
|
|
}
|
|
|
|
tampilkanPesanLCD("Data Direset");
|
|
}
|
|
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
Wire.begin(27, 26);
|
|
lcd.init();
|
|
lcd.backlight();
|
|
tampilkanDefaultLCD();
|
|
|
|
|
|
servoMasuk.attach(SERVO_MASUK_PIN);
|
|
servoKeluar.attach(SERVO_KELUAR_PIN);
|
|
|
|
pinMode(BUZZER_PIN, OUTPUT);
|
|
pinMode(IR_MASUK_PIN, INPUT_PULLUP);
|
|
pinMode(IR_KELUAR_PIN, INPUT_PULLUP);
|
|
pinMode(LED_PIN, OUTPUT);
|
|
pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
|
|
|
|
SPI.begin(SCK, MISO, MOSI, SS_MASUK);
|
|
rfidMasuk.PCD_Init();
|
|
rfidKeluar.PCD_Init();
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
|
|
// Inisialisasi WiFi
|
|
WiFi.begin(ssid, password);
|
|
Serial.print("Menghubungkan ke WiFi...");
|
|
while (WiFi.status() != WL_CONNECTED) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
}
|
|
Serial.println("\n✅ Terhubung ke WiFi");
|
|
Serial.print("IP Address: ");
|
|
Serial.println(WiFi.localIP());
|
|
|
|
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
|
|
|
|
Serial.println("Menunggu waktu NTP...");
|
|
|
|
struct tm timeinfo;
|
|
while (!getLocalTime(&timeinfo)) {
|
|
Serial.print(".");
|
|
delay(500);
|
|
}
|
|
Serial.println("\nWaktu NTP berhasil didapatkan:");
|
|
Serial.println(&timeinfo, "%Y-%m-%d %H:%M:%S");
|
|
|
|
|
|
// === Firebase config ===
|
|
config.api_key = API_KEY;
|
|
config.database_url = DATABASE_URL;
|
|
config.signer.tokens.legacy_token = LEGACY_TOKEN;
|
|
|
|
Firebase.begin(&config, &auth);
|
|
Firebase.reconnectWiFi(true);
|
|
|
|
|
|
// Load data awal dari Firebase
|
|
loadDataFromFirebase();
|
|
|
|
tampilkanDefaultLCD();
|
|
|
|
Serial.println("🚀 Setup selesai.");
|
|
}
|
|
|
|
|
|
void loop() {
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
unsigned long currentMillis = millis();
|
|
if (currentMillis - previousMillis >= 500) {
|
|
previousMillis = currentMillis;
|
|
ledState = !ledState;
|
|
digitalWrite(LED_PIN, ledState);
|
|
}
|
|
return;
|
|
} else {
|
|
digitalWrite(LED_PIN, HIGH);
|
|
}
|
|
|
|
int buttonState = digitalRead(RESET_BUTTON_PIN);
|
|
|
|
if (buttonState == LOW && !buttonPressed) {
|
|
buttonPressed = true;
|
|
buttonPressStart = millis();
|
|
emergencyActive = true;
|
|
|
|
// Bunyikan buzzer darurat
|
|
digitalWrite(BUZZER_PIN, HIGH);
|
|
Serial.println("🚨 Tombol emergency ditekan - buzzer aktif");
|
|
}
|
|
|
|
if (buttonState == LOW && buttonPressed) {
|
|
if (millis() - buttonPressStart >= resetHoldTime) {
|
|
Serial.println("♻️ Tombol ditekan lama - reset sistem");
|
|
|
|
// Matikan buzzer
|
|
digitalWrite(BUZZER_PIN, LOW);
|
|
resetDataFirebase();
|
|
// Lakukan reset (misal: reset data parkir, atau restart ESP
|
|
|
|
tampilkanPesanLCDDuaBaris("Sistem di-reset", "oleh petugas");
|
|
|
|
buttonPressed = false;
|
|
emergencyActive = false;
|
|
}
|
|
}
|
|
|
|
if (buttonState == HIGH && buttonPressed) {
|
|
// Tombol dilepas sebelum 10 detik
|
|
digitalWrite(BUZZER_PIN, LOW);
|
|
buttonPressed = false;
|
|
emergencyActive = false;
|
|
Serial.println("🔕 Tombol emergency dilepas");
|
|
}
|
|
|
|
|
|
if (rfidMasuk.PICC_IsNewCardPresent() && rfidMasuk.PICC_ReadCardSerial()) {
|
|
prosesKartu(rfidMasuk, true);
|
|
}
|
|
|
|
if (rfidKeluar.PICC_IsNewCardPresent() && rfidKeluar.PICC_ReadCardSerial()) {
|
|
prosesKartu(rfidKeluar, false);
|
|
}
|
|
}
|