// --- KHAI BÁO THƯ VIỆN ---
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
// --- THƯ VIỆN MỚI CHO IoT ---
#include <WiFi.h> // Thư viện cho Wi-Fi
#include <FirebaseESP32.h> // Thư viện cho Firebase (cần cài đặt)
// --- THÔNG TIN CẤU HÌNH (BẠN PHẢI THAY ĐỔI) ---
#define WIFI_SSID "ducdatdau"
#define WIFI_PASSWORD "040220025"
// API Key và Database URL lấy từ dự án Firebase của bạn
#define FIREBASE_API_KEY "AIzaSyCkXaZ-E6GuaQtWVSU1ewXrI1_EhO61qJE"
#define FIREBASE_DATABASE_URL "baidoxethongminh-119a6-default-rtdb.firebaseio.com"
// --- CẤU HÌNH HỆ THỐNG ---
#define TIMEWAIT 1000
#define servo1Pin 18
#define servo2Pin 19
#define posOpen 90
#define posClose 0
#define inPos 32
#define outPos 13
// Sửa chân buzzer nếu bạn dùng chân 5
#define buzzer 5
// #define buzzer 5 // (Nếu bạn dùng chân 5)
// --- KHỞI TẠO ĐỐI TƯỢNG VÀ BIẾN TOÀN CỤC ---
LiquidCrystal_I2C lcd(0x27, 16, 2);
unsigned long timeWait = millis();
Servo servo1;
Servo servo2;
bool inPosState = HIGH;
bool outPosState = HIGH;
const int slots[5] = {14, 27, 26, 25, 33};
int carNum = 0; // Chỉ dùng để đếm số ô 'F' trên LCD
int pos1 = 0;
int pos2 = 0;
// [SỬA LỖI] Biến mới để đếm tổng số xe thực tế trong bãi
int totalCarsInLot = 0;
// --- KHỞI TẠO ĐỐI TƯỢNG FIREBASE ---
FirebaseData fbdo; // Đối tượng xử lý dữ liệu Firebase
FirebaseAuth auth; // Đối tượng xác thực
FirebaseConfig config; // Đối tượng cấu hình
// --- HÀM THIẾT LẬP (setup) ---
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.setCursor(4, 0);
lcd.print("Welcome!");
delay(1000);
lcd.clear();
// --- Cấu hình Pin (giữ nguyên) ---
pinMode(buzzer, OUTPUT);
pinMode(inPos, INPUT_PULLUP);
pinMode(outPos, INPUT_PULLUP);
for (int i = 0; i < 5; i++) {
pinMode(slots[i], INPUT_PULLUP);
}
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
servo1.setPeriodHertz(50);
servo2.setPeriodHertz(50);
servo1.attach(servo1Pin, 500, 2400);
servo2.attach(servo2Pin, 500, 2400);
// --- KHỐI LỆNH MỚI: KẾT NỐI WIFI ---
lcd.setCursor(0, 0);
lcd.print("Connecting WiFi...");
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.print(".");
}
Serial.println("\nWiFi connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
lcd.clear();
lcd.print("WiFi Connected!");
delay(1000);
// --- KHỐI LỆNH MỚI: KẾT NỐI FIREBASE ---
lcd.clear();
lcd.print("Connecting DB...");
Serial.printf("Firebase Client v%s\n", FIREBASE_CLIENT_VERSION);
// Gán API Key và Database URL vào cấu hình
config.api_key = FIREBASE_API_KEY;
config.database_url = FIREBASE_DATABASE_URL;
// Đăng nhập ẩn danh vào Firebase
config.token_status_callback = tokenStatusCallback; // (Cần cho thư viện mới)
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
// Chờ Firebase sẵn sàng
// (Trong dự án thực tế, bạn nên xử lý nếu không kết nối được)
// Serial.print("Waiting for Firebase... ");
// while (!Firebase.ready()) {
// Serial.print(".");
// delay(1000);
// }
// Serial.println(" Firebase ready.");
showLCD(); // Hiển thị trạng thái ban đầu
}
// --- HÀM VÒNG LẶP (loop) ---
void loop() {
// Cập nhật LCD (giữ nguyên logic)
if (pos1 != posOpen && digitalRead(inPos) != LOW && digitalRead(outPos) != LOW) {
showLCD();
}
// --- LOGIC XỬ LÝ XE VÀO (ĐÃ CẬP NHẬT) ---
if (digitalRead(inPos) == LOW) {
if (inPosState == HIGH) {
timeWait = millis();
// [SỬA LỖI] Dùng totalCarsInLot để kiểm tra
if (totalCarsInLot >= 5) {
lcd.clear();
lcd.print("Car Full");
lcd.setCursor(0, 1);
lcd.print("Slot Unavailable");
beep(800, 2);
}
inPosState = LOW;
}
// [SỬA LỖI] Dùng totalCarsInLot để kiểm tra
if (totalCarsInLot < 5) {
if (millis() - timeWait > TIMEWAIT) {
if (pos1 == posClose) {
for (int i = posClose; i < posOpen; i += 5) {
servo2.write(i);
delay(15);
}
servo2.write(posOpen);
pos1 = posOpen;
// [SỬA LỖI] Tăng biến đếm tổng
totalCarsInLot++;
lcd.clear();
lcd.print("Car Entered");
beep(500, 1);
// --- CHỨC NĂNG MỚI: GỌI HÀM LƯU LÊN SERVER ---
logFirebaseEvent("Car_Entered", totalCarsInLot);
}
}
}
} else {
if (inPosState == LOW) {
timeWait = millis();
lcd.clear();
showLCD();
inPosState = HIGH;
}
if (millis() - timeWait > TIMEWAIT) {
if (pos1 == posOpen) {
for (int i = posOpen; i > posClose; i -= 5) {
servo2.write(i);
delay(15);
}
servo2.write(posClose);
pos1 = posClose;
}
}
}
// --- LOGIC XỬ LÝ XE RA (ĐÃ CẬP NHẬT) ---
if (digitalRead(outPos) == LOW) {
if (outPosState == HIGH) {
timeWait = millis();
outPosState = LOW;
}
if (millis() - timeWait > TIMEWAIT) {
if (pos2 == posClose) {
for (int i = posClose; i < posOpen; i += 5) {
servo1.write(i);
delay(15);
}
servo1.write(posOpen);
pos2 = posOpen;
// [SỬA LỖI] Giảm biến đếm tổng
// Đảm bảo không bị âm
if (totalCarsInLot > 0) {
totalCarsInLot--;
}
lcd.clear();
lcd.print("Car is out");
beep(500, 1);
// --- CHỨC NĂNG MỚI: GỌI HÀM LƯU LÊN SERVER ---
logFirebaseEvent("Car_Exited", totalCarsInLot);
}
}
} else {
if (outPosState == LOW) {
timeWait = millis();
lcd.clear();
showLCD();
outPosState = HIGH;
}
if (millis() - timeWait > TIMEWAIT) {
if (pos2 == posOpen) {
for (int i = posOpen; i > posClose; i -= 5) {
servo1.write(i);
delay(15);
}
servo1.write(posClose);
pos2 = posClose;
}
}
}
} // --- KẾT THÚC VÒNG LẶP void loop() ---
// --- CÁC HÀM CHỨC NĂNG PHỤ ---
// Hàm beep (giữ nguyên)
void beep(int d, int num) {
for (int i = 0; i < num; i++) {
digitalWrite(buzzer, HIGH);
delay(d);
digitalWrite(buzzer, LOW);
delay(d);
}
}
// Hàm showLCD (giữ nguyên)
// carNum ở đây chỉ là biến cục bộ để đếm 5 ô E/F
void showLCD() {
carNum = 0;
for (int n = 0; n < 5; n++) {
if (n == 0) {
lcd.setCursor(0, 0);
lcd.print("S1:");
}
if (n == 1) {
lcd.setCursor(5, 0);
lcd.print("S2:");
}
if (n == 2) {
lcd.setCursor(10, 0);
lcd.print("S3:");
}
if (n == 3) {
lcd.setCursor(0, 1);
lcd.print("S4:");
}
if (n == 4) {
lcd.setCursor(5, 1);
lcd.print("S5:");
}
if (digitalRead(slots[n]) == LOW) {
lcd.print("F");
carNum++;
} else {
lcd.print("E");
}
}
lcd.setCursor(10, 1);
lcd.print("XE:"); // In "C: " (viết tắt của Cars)
lcd.print(carNum); // In số xe đang đỗ (biến đếm từ 5 ô)
lcd.print("/5"); // In tổng số 5 chỗ
}
// --- HÀM MỚI: GỬI DỮ LIỆU LÊN FIREBASE ---
/**
* @brief Gửi một bản ghi (log) sự kiện lên Firebase
* @param event Loại sự kiện (ví dụ: "Car_Entered")
* @param count Số xe tổng cộng trong bãi (totalCarsInLot)
*/
void logFirebaseEvent(String event, int count) {
// Chỉ gửi nếu Firebase đã sẵn sàng
if (Firebase.ready()) {
// Tạo một chuỗi JSON (dạng text) để gửi đi
// Ví dụ: {"event":"Car_Entered", "totalCount":1, "timestamp": 1234567}
String jsonData = "";
jsonData += "{\"event\":\"" + event + "\",";
jsonData += "\"totalCount\":" + String(count) + ",";
// Thêm một mốc thời gian (dùng hàm .sv "ServerValue.TIMESTAMP" của Firebase)
jsonData += "\"timestamp\":{\".sv\":\"timestamp\"}";
jsonData += "}";
// Dùng `pushJSON` để tạo một ID ngẫu nhiên (giống như một file log)
// Dữ liệu sẽ được lưu tại /parking_logs/<ID_ngau_nhien>/<jsonData>
Serial.print("Sending log to Firebase: ");
Serial.println(jsonData);
if (Firebase.pushJSON(fbdo, "/parking_logs", jsonData)) {
Serial.println("Log sent successfully!");
} else {
Serial.println("Failed to send log.");
Serial.println("REASON: " + fbdo.errorReason());
}
// LƯU Ý QUAN TRỌNG:
// Lệnh `pushJSON` là một lệnh "blocking" (khóa).
// Nó có thể mất 1-2 giây để gửi. Trong lúc đó, code sẽ bị dừng.
// Trong dự án thực tế, bạn nên dùng hàm `pushJSONAsync` (bất đồng bộ)
// nhưng nó phức tạp hơn. Với mô hình này, `pushJSON` là đủ dùng.
}
}