325 lines
8.8 KiB
C++
325 lines
8.8 KiB
C++
#include <WiFi.h>
|
|
#include <Wire.h>
|
|
#include <Adafruit_AMG88xx.h>
|
|
#include <Firebase_ESP_Client.h>
|
|
#include <ESP32Servo.h>
|
|
#include <HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
#include <time.h>
|
|
|
|
// ====== 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();
|
|
} |