#include #include #include #include #include #include #include #include // ====== WiFi ====== #define WIFI_SSID "POCO F4" #define WIFI_PASSWORD "87654321" // ====== Firebase RTDB ====== #define API_KEY "AIzaSyDgtq_bU0aNjKwErdns50EldaRRSdd5Sg0" #define DATABASE_URL "https://hamaguard-e911d-default-rtdb.firebaseio.com" #define USER_EMAIL "test@gmail.com" #define USER_PASSWORD "123456" // ====== REST API Endpoint ====== const char* apiEndpoint = "https://service-hama-guard.vercel.app/api/notifikasi"; // ====== Pin ====== #define PIR_PIN 33 #define TRIG_PIN 25 #define ECHO_PIN 26 #define BUZZER_PIN 13 #define SERVO_PIN 32 // ====== Firebase Objects ====== FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // ====== Sensor & Servo ====== Adafruit_AMG88xx amg; float pixels[AMG88xx_PIXEL_ARRAY_SIZE]; Servo servo; // ====== Kalibrasi AMG8833 ====== const float amgOffset = 2,94 ; // ====== Control Flags ====== bool isAutoMode = true; bool isRepellerOn = false; String prevMode = ""; // ====== Timing Control ====== unsigned long lastSensorSend = 0; unsigned long lastNotifSend = 0; unsigned long humanBuzzEndTime = 0; const unsigned long SENSOR_INTERVAL = 1000; const unsigned long NOTIF_INTERVAL = 5000; // ====== Repeller Task Control ====== TaskHandle_t repellerTaskHandle = NULL; bool repellerActive = false; unsigned long repellerEndTime = 0; // ====== FreeRTOS Servo Task ====== void repellerTask(void* parameter) { static int pos = 0; static bool forward = true; const int step = 6; const int delayMs = 10; while (true) { if (repellerActive) { digitalWrite(BUZZER_PIN, HIGH); servo.write(pos); if (forward) { pos += step; if (pos >= 180) { pos = 180; forward = false; } } else { pos -= step; if (pos <= 0) { pos = 0; forward = true; } } vTaskDelay(delayMs / portTICK_PERIOD_MS); } else { digitalWrite(BUZZER_PIN, LOW); servo.write(90); vTaskDelay(10 / portTICK_PERIOD_MS); } } } void setupTime() { configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); Serial.print("Sinkronisasi waktu NTP"); while (time(nullptr) < 8 * 3600 * 2) { delay(500); Serial.print("."); } Serial.println(" ✓"); } String getISO8601Timestamp() { time_t now = time(nullptr); struct tm timeinfo; localtime_r(&now, &timeinfo); char buf[30]; strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &timeinfo); return String(buf); } void setup() { Serial.begin(115200); delay(500); Serial.print("Menghubungkan ke WiFi"); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); int retry = 0; while (WiFi.status() != WL_CONNECTED && retry < 20) { delay(500); Serial.print("."); retry++; } if (WiFi.status() == WL_CONNECTED) { Serial.println(" ✓"); Serial.println("WiFi terhubung ke: " + String(WiFi.SSID())); Serial.println("IP Address: " + WiFi.localIP().toString()); } else { Serial.println(" ✗"); Serial.println("Gagal terhubung ke WiFi. Periksa SSID/Password."); } setupTime(); config.api_key = API_KEY; config.database_url = DATABASE_URL; auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); delay(1000); if (Firebase.ready()) { Serial.println("Firebase terhubung ✓"); } else { Serial.println("Firebase tidak terhubung ✗"); } pinMode(PIR_PIN, INPUT); pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); servo.attach(SERVO_PIN); servo.write(90); if (!amg.begin()) { Serial.println("Sensor AMG8833 tidak ditemukan!"); while (1) delay(10); } else { Serial.println("Sensor AMG8833 berhasil diinisialisasi."); } xTaskCreatePinnedToCore(repellerTask, "RepellerTask", 2048, NULL, 1, &repellerTaskHandle, 1); Serial.println("Setup selesai. Sistem siap berjalan."); } String classifyObject(float distance, float maxTemp, int hotGridCount) { if ((distance <= 5 && maxTemp >= 41 && hotGridCount >= 9 && hotGridCount <= 14) || (distance <= 10 && maxTemp >= 37-39 && hotGridCount >= 3 && hotGridCount <= 4)) { return "burung"; } if ((distance <= 5 && maxTemp >= 36 && maxTemp <= 37 && hotGridCount >= 64) || ((distance >= 10 && distance <= 35) && maxTemp >= 36 && maxTemp <= 28 && hotGridCount >= 12 && hotGridCount <= 30) || (distance <= 30 && maxTemp == 35 && hotGridCount >= 15 && hotGridCount <= 20)) { return "manusia"; } if (maxTemp >= 36.0) { return "hama_unknown"; } return "tidak_terdeteksi"; } void loop() { if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi terputus. Mencoba reconnect..."); WiFi.disconnect(); WiFi.reconnect(); unsigned long startAttemptTime = millis(); while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) { delay(500); Serial.print("."); } if (WiFi.status() == WL_CONNECTED) { Serial.println("\nWiFi berhasil terhubung kembali!"); } else { Serial.println("\nGagal reconnect ke WiFi."); } } unsigned long now = millis(); if (Firebase.RTDB.getString(&fbdo, "/control/mode")) { String mode = fbdo.stringData(); isAutoMode = (mode == "auto"); if (mode != prevMode) { sendModeChangeNotification(isAutoMode ? "Otomatis" : "Manual"); prevMode = mode; } } if (Firebase.RTDB.getBool(&fbdo, "/control/repeller")) { isRepellerOn = fbdo.boolData(); } if (!isAutoMode) { repellerActive = isRepellerOn; return; } amg.readPixels(pixels); float maxTemp = -100; int hotGridCount = 0; for (int i = 0; i < AMG88xx_PIXEL_ARRAY_SIZE; i++) { pixels[i] += amgOffset; if (pixels[i] > maxTemp) maxTemp = pixels[i]; if (pixels[i] > 23.0) hotGridCount++; } bool pirDetected = digitalRead(PIR_PIN) == HIGH; digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); long duration = pulseIn(ECHO_PIN, HIGH); float distance_cm = duration * 0.034 / 2.0; String jenis = classifyObject(distance_cm, maxTemp, hotGridCount); bool classified = (jenis != "tidak_terdeteksi"); // === Logika pengusir berdasarkan klasifikasi === if (pirDetected || jenis == "burung" || jenis == "ayam" || jenis == "hama_unknown") { repellerActive = true; repellerEndTime = millis() + 10000; } else if (jenis == "manusia") { repellerActive = false; digitalWrite(BUZZER_PIN, HIGH); humanBuzzEndTime = millis() + 10000; } if (repellerActive && millis() > repellerEndTime) { repellerActive = false; } if (jenis == "manusia" && millis() > humanBuzzEndTime) { digitalWrite(BUZZER_PIN, LOW); } if (now - lastSensorSend >= SENSOR_INTERVAL) { sendSensorData(pirDetected, distance_cm, jenis, maxTemp); lastSensorSend = now; } if ((now - lastNotifSend >= NOTIF_INTERVAL) && classified) { sendNotification(jenis, distance_cm, maxTemp); lastNotifSend = now; } } void sendSensorData(bool pir, float dist, String jenis, float maxTemp) { if (!Firebase.ready()) return; FirebaseJson sensorJson; sensorJson.set("pir", pir); sensorJson.set("ultrasonik", dist); sensorJson.set("jenis_deteksi", jenis); sensorJson.set("pengusir", (jenis != "tidak_terdeteksi")); sensorJson.set("timestamp", millis()); FirebaseJson thermalJson; FirebaseJsonArray arr; for (int i = 0; i < AMG88xx_PIXEL_ARRAY_SIZE; i++) arr.add(pixels[i]); thermalJson.set("temperatures", arr); thermalJson.set("timestamp", millis()); Firebase.RTDB.setJSON(&fbdo, "/sensor", &sensorJson); Firebase.RTDB.setJSON(&fbdo, "/thermal_data", &thermalJson); } void sendNotification(String jenis, float distance, float suhu) { HTTPClient http; http.begin(apiEndpoint); http.addHeader("Content-Type", "application/json"); DynamicJsonDocument json(512); json["type"] = "warning"; json["title"] = "Deteksi " + jenis; json["message"] = "Terdeteksi " + jenis + " pada jarak " + String(distance, 1) + " cm dan suhu maksimum " + String(suhu, 1) + "°C"; json["timestamp"] = getISO8601Timestamp(); String requestBody; serializeJson(json, requestBody); http.POST(requestBody); http.end(); } void sendModeChangeNotification(String mode) { HTTPClient http; http.begin(apiEndpoint); http.addHeader("Content-Type", "application/json"); DynamicJsonDocument json(512); json["type"] = "success"; json["title"] = "Mode Diganti"; json["message"] = "Mode sistem diganti menjadi " + mode; json["timestamp"] = getISO8601Timestamp(); String body; serializeJson(json, body); http.POST(body); http.end(); }