394 lines
12 KiB
C++
394 lines
12 KiB
C++
#include <Wire.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#include <ESP8266WiFi.h>
|
|
#include <Firebase_ESP_Client.h>
|
|
#include <ArduinoJson.h>
|
|
|
|
// --- Konfigurasi WiFi ---
|
|
#define WIFI_SSID "LMAO"
|
|
#define WIFI_PASSWORD "awokawok"
|
|
|
|
// --- Konfigurasi Firebase ---
|
|
#define FIREBASE_HOST "https://meja-iotv0-default-rtdb.firebaseio.com/"
|
|
#define FIREBASE_AUTH "AIzaSyC9YQJWMSajJQTlh1tGLRcIeui4rqGvl8E"
|
|
|
|
FirebaseData firebaseData;
|
|
FirebaseConfig config;
|
|
FirebaseAuth auth;
|
|
|
|
LiquidCrystal_I2C lcd(0x27, 16, 2);
|
|
|
|
// --- Definisi Pin ---
|
|
#define TRIG_PIN D5
|
|
int echoPins[4] = {D6, D7, D8, D4};
|
|
#define BUZZER_PIN D0
|
|
#define FORCE_BUTTON_PIN D3
|
|
|
|
float distances[4];
|
|
float lastValidDistances[4];
|
|
float lastSensorDistances[4];
|
|
|
|
// --- Sistem Reservasi ---
|
|
bool tableActivationSensorActive = false;
|
|
bool tableReserved = false;
|
|
bool tableOccupied = false;
|
|
bool refreshTriggered = false;
|
|
|
|
unsigned long reservationSessionStartOrRefreshTime = 0;
|
|
unsigned long lastPresenceTime = 0;
|
|
|
|
String tempLine1 = "";
|
|
String tempLine2 = "";
|
|
unsigned long messageStartTime = 0;
|
|
unsigned long messageDuration = 2000;
|
|
bool showTempMessage = false;
|
|
String lastLine1 = "";
|
|
String lastLine2 = "";
|
|
unsigned long lastLCDUpdate = 0;
|
|
const unsigned long LCD_UPDATE_INTERVAL = 1000;
|
|
|
|
const unsigned long RESERVATION_ACTIVE_DURATION = 30 * 60 * 1000;
|
|
const unsigned long IDLE_OCCUPIED_TIMEOUT = 10 * 60 * 1000;
|
|
const unsigned long WARNING_TIME_10_MIN = 10 * 60 * 1000;
|
|
const unsigned long WARNING_TIME_5_MIN = 5 * 60 * 1000;
|
|
|
|
bool warning10MinTriggered = false;
|
|
bool warning5MinTriggered = false;
|
|
|
|
const float PRESENCE_MIN = 50.0;
|
|
const float PRESENCE_MAX = 80.0;
|
|
const float MOTION_THRESHOLD = 5.0;
|
|
|
|
bool presenceDetected = false;
|
|
|
|
String customerName = "N/A";
|
|
|
|
bool forceShutdownRequested = false;
|
|
unsigned long lastButtonPress = 0;
|
|
const unsigned long BUTTON_DEBOUNCE = 200;
|
|
unsigned long lastFirebaseUpdate = 0;
|
|
const unsigned long FIREBASE_UPDATE_INTERVAL = 5000;
|
|
|
|
#define TABLE_ID "meja_001"
|
|
String deviceID = TABLE_ID;
|
|
|
|
void showTemporaryMessage(String line1, String line2, int duration = 2000) {
|
|
tempLine1 = line1;
|
|
tempLine2 = line2;
|
|
messageDuration = duration;
|
|
showTempMessage = true;
|
|
messageStartTime = millis();
|
|
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0); lcd.print(tempLine1);
|
|
lcd.setCursor(0, 1); lcd.print(tempLine2);
|
|
|
|
lastLCDUpdate = millis(); // supaya tidak update terlalu cepat
|
|
}
|
|
|
|
// ==== SETUP ====
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
WiFi.mode(WIFI_STA);
|
|
lcd.init(); lcd.backlight();
|
|
pinMode(TRIG_PIN, OUTPUT);
|
|
pinMode(BUZZER_PIN, OUTPUT);
|
|
pinMode(FORCE_BUTTON_PIN, INPUT_PULLUP);
|
|
for (int i = 0; i < 4; i++) pinMode(echoPins[i], INPUT);
|
|
|
|
connectWiFi();
|
|
if (WiFi.status() == WL_CONNECTED) initFirebase();
|
|
showTemporaryMessage("Sistem Siap!", deviceID);
|
|
}
|
|
|
|
// ==== LOOP ====
|
|
void loop() {
|
|
if (WiFi.status() != WL_CONNECTED) connectWiFi();
|
|
if (Firebase.ready() && millis() - lastFirebaseUpdate > FIREBASE_UPDATE_INTERVAL) {
|
|
updateFromFirebase();
|
|
updateToFirebase();
|
|
lastFirebaseUpdate = millis();
|
|
}
|
|
|
|
if (forceShutdownRequested) {
|
|
forceShutdown();
|
|
if (Firebase.ready()) {
|
|
Firebase.RTDB.setInt(&firebaseData, "/" + deviceID + "/force_shutdown", 0);
|
|
}
|
|
forceShutdownRequested = false;
|
|
}
|
|
|
|
if (digitalRead(FORCE_BUTTON_PIN) == LOW && millis() - lastButtonPress > BUTTON_DEBOUNCE) {
|
|
forceShutdown(); lastButtonPress = millis();
|
|
}
|
|
|
|
readSensors();
|
|
analyzeActivity();
|
|
if (tableActivationSensorActive) {
|
|
if (!tableReserved) startReservation();
|
|
if (tableReserved) {
|
|
checkSessionTimeout();
|
|
checkIdle();
|
|
checkWarnings();
|
|
}
|
|
} else if (tableReserved) {
|
|
endReservation();
|
|
}
|
|
displayLCD();
|
|
printSerial();
|
|
delay(500);
|
|
}
|
|
|
|
// ==== SENSOR & AKTIVITAS ====
|
|
float readDistance(int pin) {
|
|
float sum = 0; int valid = 0;
|
|
for (int i = 0; i < 3; i++) {
|
|
digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
|
|
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
|
|
digitalWrite(TRIG_PIN, LOW);
|
|
long d = pulseIn(pin, HIGH, 30000);
|
|
float dist = d * 0.034 / 2;
|
|
if (dist > 2 && dist < 400) { sum += dist; valid++; }
|
|
delay(10);
|
|
}
|
|
return (valid == 0) ? 0 : sum / valid;
|
|
}
|
|
|
|
void readSensors() {
|
|
for (int i = 0; i < 4; i++) {
|
|
float d = readDistance(echoPins[i]);
|
|
if (d > 0) lastValidDistances[i] = d;
|
|
distances[i] = lastValidDistances[i];
|
|
}
|
|
}
|
|
|
|
void analyzeActivity() {
|
|
unsigned long now = millis();
|
|
bool motion = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
float delta = abs(distances[i] - lastSensorDistances[i]);
|
|
if (lastSensorDistances[i] > 0 && delta >= MOTION_THRESHOLD) motion = true;
|
|
lastSensorDistances[i] = distances[i];
|
|
}
|
|
|
|
if (now - reservationSessionStartOrRefreshTime > 10000) refreshTriggered = false;
|
|
|
|
if (tableReserved && motion && !refreshTriggered) {
|
|
reservationSessionStartOrRefreshTime = now;
|
|
warning10MinTriggered = warning5MinTriggered = false;
|
|
refreshTriggered = true;
|
|
Serial.println("Sesi diperpanjang karena gerakan.");
|
|
}
|
|
|
|
presenceDetected = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (distances[i] >= PRESENCE_MIN && distances[i] <= PRESENCE_MAX) {
|
|
presenceDetected = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (presenceDetected) {
|
|
lastPresenceTime = now;
|
|
tableOccupied = true;
|
|
} else if (now - lastPresenceTime > IDLE_OCCUPIED_TIMEOUT) {
|
|
tableOccupied = false;
|
|
}
|
|
|
|
// Tambahkan warning jika terlalu dekat
|
|
static unsigned long lastWarningTime = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (distances[i] > 0 && distances[i] < 10.0 && millis() - lastWarningTime > 5000) {
|
|
showTooCloseWarning();
|
|
lastWarningTime = millis();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void showTooCloseWarning() {
|
|
showTemporaryMessage("!! TERLALU DEKAT !!", "Jarak < 10 cm");
|
|
}
|
|
|
|
// ==== WIFI & FIREBASE ====
|
|
void connectWiFi() {
|
|
lcd.clear(); lcd.setCursor(0, 0); lcd.print("Koneksi WiFi...");
|
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
|
for (int i = 0; i < 30 && WiFi.status() != WL_CONNECTED; i++) {
|
|
delay(500); Serial.print(".");
|
|
}
|
|
lcd.clear();
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
showTemporaryMessage("WiFi Terhubung!", WiFi.localIP().toString());
|
|
Serial.println("WiFi IP: " + WiFi.localIP().toString());
|
|
} else {
|
|
showTemporaryMessage("Gagal WiFi!", "Coba lagi...");
|
|
}
|
|
}
|
|
|
|
void initFirebase() {
|
|
config.host = FIREBASE_HOST;
|
|
config.signer.tokens.legacy_token = FIREBASE_AUTH;
|
|
Firebase.begin(&config, &auth);
|
|
Firebase.reconnectWiFi(true);
|
|
Serial.println(Firebase.ready() ? "Firebase Terhubung!" : "Gagal Firebase!");
|
|
}
|
|
|
|
void updateFromFirebase() {
|
|
String base = "/" + deviceID;
|
|
if (Firebase.RTDB.getInt(&firebaseData, base + "/sensors/table_activation_sensor_active"))
|
|
tableActivationSensorActive = firebaseData.intData() == 1;
|
|
else
|
|
tableActivationSensorActive = false;
|
|
|
|
if (Firebase.RTDB.getString(&firebaseData, base + "/reserved_by")) {
|
|
customerName = firebaseData.stringData();
|
|
if (customerName.length() > 8) customerName = customerName.substring(0, 8);
|
|
} else customerName = "N/A";
|
|
|
|
if (Firebase.RTDB.getInt(&firebaseData, base + "/force_shutdown")) {
|
|
forceShutdownRequested = firebaseData.intData() == 1;
|
|
} else {
|
|
forceShutdownRequested = false;
|
|
}
|
|
}
|
|
|
|
void updateToFirebase() {
|
|
if (!Firebase.ready()) return;
|
|
String path = "/" + deviceID;
|
|
FirebaseJson json;
|
|
json.set("device_id", deviceID);
|
|
json.set("mac_address", WiFi.macAddress());
|
|
json.set("last_update", millis() / 1000);
|
|
json.set("table_occupied", presenceDetected ? 1 : 0);
|
|
json.set("time_remaining_minutes", (int)(getRemainingSessionTime() / 60000));
|
|
for (int i = 0; i < 4; i++) json.set("sensors/s" + String(i + 1), (int)distances[i]);
|
|
json.set("sensors/table_activation_sensor_active", tableActivationSensorActive ? 1 : 0);
|
|
Firebase.RTDB.updateNode(&firebaseData, path, &json);
|
|
}
|
|
|
|
// ==== RESERVASI ====
|
|
void resetFlags() {
|
|
for (int i = 0; i < 4; i++) lastSensorDistances[i] = 0;
|
|
warning10MinTriggered = warning5MinTriggered = refreshTriggered = false;
|
|
}
|
|
|
|
void startReservation() {
|
|
tableReserved = true;
|
|
reservationSessionStartOrRefreshTime = millis();
|
|
lastPresenceTime = millis();
|
|
resetFlags();
|
|
buzzerAlert(2, 500, 150);
|
|
Serial.println("Reservasi dimulai oleh " + customerName);
|
|
showTemporaryMessage("MEJA " + deviceID.substring(5), "RVSP: " + customerName);
|
|
}
|
|
|
|
void endReservation() {
|
|
tableReserved = false;
|
|
tableOccupied = false;
|
|
resetFlags();
|
|
showTemporaryMessage("RESERVASI", "BERAKHIR");
|
|
}
|
|
|
|
void forceShutdown() {
|
|
tableActivationSensorActive = false;
|
|
endReservation();
|
|
Firebase.RTDB.setInt(&firebaseData, "/" + deviceID + "/sensors/table_activation_sensor_active", 0);
|
|
Firebase.RTDB.setString(&firebaseData, "/" + deviceID + "/reserved_by", "N/A");
|
|
customerName = "N/A";
|
|
buzzerAlert(5, 100, 50);
|
|
lcd.clear(); lcd.setCursor(0, 0); lcd.print("TERIMA KASIH");
|
|
lcd.setCursor(0, 1); lcd.print("SUDAH DATANG!"); delay(3000);
|
|
}
|
|
|
|
unsigned long getRemainingSessionTime() {
|
|
if (!tableReserved) return 0;
|
|
unsigned long elapsed = millis() - reservationSessionStartOrRefreshTime;
|
|
return (elapsed >= RESERVATION_ACTIVE_DURATION) ? 0 : RESERVATION_ACTIVE_DURATION - elapsed;
|
|
}
|
|
|
|
void checkSessionTimeout() {
|
|
if (getRemainingSessionTime() == 0) {
|
|
tableActivationSensorActive = false;
|
|
endReservation();
|
|
Firebase.RTDB.setInt(&firebaseData, "/" + deviceID + "/sensors/table_activation_sensor_active", 0);
|
|
Firebase.RTDB.setString(&firebaseData, "/" + deviceID + "/reserved_by", "N/A");
|
|
}
|
|
}
|
|
|
|
void checkIdle() {
|
|
if (millis() - lastPresenceTime >= IDLE_OCCUPIED_TIMEOUT) tableOccupied = false;
|
|
else tableOccupied = true;
|
|
}
|
|
|
|
void checkWarnings() {
|
|
unsigned long remain = getRemainingSessionTime();
|
|
if (remain <= WARNING_TIME_5_MIN && !warning5MinTriggered) {
|
|
warning5MinTriggered = true;
|
|
buzzerAlert(5, 200, 80);
|
|
showWarningLCD("< 5 menit!");
|
|
} else if (remain <= WARNING_TIME_10_MIN && !warning10MinTriggered) {
|
|
warning10MinTriggered = true;
|
|
buzzerAlert(3, 300, 100);
|
|
showWarningLCD("< 10 menit!");
|
|
}
|
|
}
|
|
|
|
void showWarningLCD(String text) {
|
|
showTemporaryMessage("** PERINGATAN **", text);
|
|
}
|
|
|
|
// ==== TAMPILAN ====
|
|
void displayLCD() {
|
|
if (millis() - lastLCDUpdate < LCD_UPDATE_INTERVAL) return;
|
|
|
|
if (showTempMessage) {
|
|
if (millis() - messageStartTime >= messageDuration) {
|
|
showTempMessage = false;
|
|
lastLine1 = ""; // paksa refresh ke tampilan normal
|
|
lastLine2 = "";
|
|
}
|
|
return;
|
|
}
|
|
|
|
String line1 = "MEJA " + deviceID.substring(5);
|
|
String line2;
|
|
|
|
if (WiFi.status() != WL_CONNECTED) line2 = "WiFi terputus";
|
|
else if (!tableReserved) line2 = "TERSEDIA";
|
|
else line2 = "RVSP: " + customerName;
|
|
|
|
if (line1 != lastLine1 || line2 != lastLine2) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0); lcd.print(line1);
|
|
lcd.setCursor(0, 1); lcd.print(line2);
|
|
lastLine1 = line1;
|
|
lastLine2 = line2;
|
|
lastLCDUpdate = millis();
|
|
}
|
|
}
|
|
|
|
void printSerial() {
|
|
Serial.println("\n=== STATUS " + deviceID + " ===");
|
|
Serial.println("WiFi: " + String(WiFi.status() == WL_CONNECTED ? "YA" : "TIDAK"));
|
|
Serial.println("Firebase: " + String(Firebase.ready() ? "YA" : "TIDAK"));
|
|
Serial.print("Sensor: ");
|
|
for (int i = 0; i < 4; i++) Serial.print("S" + String(i+1) + ":" + String(distances[i]) + " ");
|
|
Serial.println();
|
|
Serial.println("Aktif: " + String(tableActivationSensorActive ? "YA" : "TIDAK"));
|
|
Serial.println("Reserved: " + String(tableReserved ? "YA" : "TIDAK"));
|
|
Serial.println("Occupied: " + String(tableOccupied ? "YA" : "TIDAK"));
|
|
Serial.println("Sisa Waktu: " + String(getRemainingSessionTime() / 60000) + " menit");
|
|
Serial.println("Pemesan: " + customerName);
|
|
Serial.println("Warning10: " + String(warning10MinTriggered));
|
|
Serial.println("Warning5: " + String(warning5MinTriggered));
|
|
}
|
|
|
|
// ==== BUZZER ====
|
|
void buzzerAlert(int count, int onTime, int offTime) {
|
|
for (int i = 0; i < count; i++) {
|
|
digitalWrite(BUZZER_PIN, HIGH); delay(onTime);
|
|
digitalWrite(BUZZER_PIN, LOW); delay(offTime);
|
|
}
|
|
}
|