468 lines
8.9 KiB
C++
468 lines
8.9 KiB
C++
#include <WiFi.h>
|
||
#include <Firebase_ESP_Client.h>
|
||
#include <esp_task_wdt.h>
|
||
|
||
// ================= WIFI =================
|
||
#define WIFI_SSID "Angga"
|
||
#define WIFI_PASSWORD "upinipinupin"
|
||
|
||
// ================= FIREBASE =================
|
||
#define DATABASE_URL "https://tomat2-default-rtdb.asia-southeast1.firebasedatabase.app"
|
||
#define DATABASE_SECRET "EsSGEME2IMqmVvWx2VZLkLSKAnJ4Meg6AblUOyxO"
|
||
|
||
FirebaseData fbdo;
|
||
FirebaseAuth auth;
|
||
FirebaseConfig config;
|
||
|
||
// ================= PIN =================
|
||
#define SOIL_PIN 34
|
||
#define PIR_PIN 27
|
||
#define RELAY_PIN 26
|
||
#define BUZZER_PIN 25
|
||
|
||
// ================= KALIBRASI =================
|
||
#define SOIL_DRY 2400
|
||
#define SOIL_WET 1100
|
||
|
||
// ================= GLOBAL =================
|
||
volatile bool pirFlag = false;
|
||
volatile unsigned long lastInterrupt = 0;
|
||
|
||
int pirState = 0;
|
||
unsigned long pirHoldTime = 0;
|
||
const int pirActiveDuration = 3000;
|
||
|
||
int lastMoist = -1;
|
||
int lastPir = -1;
|
||
int lastPump = -1;
|
||
|
||
int lastPumpState = 0;
|
||
|
||
String mode = "auto";
|
||
int pumpManual = 0;
|
||
|
||
int minMoist = 60;
|
||
int maxMoist = 80;
|
||
|
||
unsigned long lastSend = 0;
|
||
unsigned long lastRead = 0;
|
||
|
||
const int sendInterval = 10000;
|
||
const int readInterval = 7000;
|
||
|
||
// ================= OFFLINE =================
|
||
bool offlineMode = false;
|
||
|
||
unsigned long lastOfflineCheck = 0;
|
||
const int offlineCheckInterval = 10000;
|
||
|
||
// ================= BUZZER =================
|
||
unsigned long lastBuzz = 0;
|
||
unsigned long buzzerStart = 0;
|
||
|
||
bool buzzerActive = false;
|
||
|
||
const int buzzCooldown = 5000;
|
||
const int buzzerDuration = 2000;
|
||
|
||
bool sendPirNow = false;
|
||
|
||
// ================= ISR =================
|
||
void IRAM_ATTR pirISR() {
|
||
|
||
if (millis() - lastInterrupt > 200) {
|
||
|
||
pirFlag = true;
|
||
lastInterrupt = millis();
|
||
}
|
||
}
|
||
|
||
// ================= WIFI =================
|
||
void connectWiFi() {
|
||
|
||
static unsigned long lastAttempt = 0;
|
||
|
||
// sudah connect
|
||
if (WiFi.status() == WL_CONNECTED) {
|
||
return;
|
||
}
|
||
|
||
// retry tiap 10 detik
|
||
if (millis() - lastAttempt < 10000) {
|
||
return;
|
||
}
|
||
|
||
lastAttempt = millis();
|
||
|
||
Serial.println("Connecting WiFi...");
|
||
|
||
WiFi.disconnect();
|
||
|
||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||
}
|
||
|
||
// ================= OFFLINE CHECK =================
|
||
void checkOfflineMode() {
|
||
|
||
// cek tiap 10 detik
|
||
if (millis() - lastOfflineCheck < offlineCheckInterval) {
|
||
return;
|
||
}
|
||
|
||
lastOfflineCheck = millis();
|
||
|
||
// ===== WIFI PUTUS =====
|
||
if (WiFi.status() != WL_CONNECTED) {
|
||
|
||
if (!offlineMode) {
|
||
Serial.println("⚠️ OFFLINE MODE");
|
||
}
|
||
|
||
offlineMode = true;
|
||
return;
|
||
}
|
||
|
||
// ===== WIFI NORMAL =====
|
||
if (offlineMode) {
|
||
Serial.println("✅ BACK ONLINE");
|
||
}
|
||
|
||
offlineMode = false;
|
||
}
|
||
|
||
// ================= SAFE SEND =================
|
||
bool sendData(FirebaseJson &json) {
|
||
|
||
// jangan kirim saat offline
|
||
if (
|
||
offlineMode ||
|
||
WiFi.status() != WL_CONNECTED
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
// firebase belum ready
|
||
if (!Firebase.ready()) {
|
||
return false;
|
||
}
|
||
|
||
// kirim 1x saja
|
||
return Firebase.RTDB.setJSON(
|
||
&fbdo,
|
||
"/sensor",
|
||
&json
|
||
);
|
||
}
|
||
|
||
// ================= SETUP =================
|
||
void setup() {
|
||
|
||
Serial.begin(115200);
|
||
|
||
pinMode(PIR_PIN, INPUT);
|
||
|
||
pinMode(RELAY_PIN, OUTPUT);
|
||
pinMode(BUZZER_PIN, OUTPUT);
|
||
|
||
// relay OFF
|
||
digitalWrite(RELAY_PIN, HIGH);
|
||
|
||
digitalWrite(BUZZER_PIN, LOW);
|
||
|
||
analogSetAttenuation(ADC_11db);
|
||
|
||
attachInterrupt(
|
||
digitalPinToInterrupt(PIR_PIN),
|
||
pirISR,
|
||
RISING
|
||
);
|
||
|
||
// ===== WIFI =====
|
||
WiFi.mode(WIFI_STA);
|
||
WiFi.setSleep(false);
|
||
|
||
connectWiFi();
|
||
|
||
WiFi.setAutoReconnect(true);
|
||
WiFi.persistent(true);
|
||
|
||
// ===== FIREBASE =====
|
||
config.database_url = DATABASE_URL;
|
||
|
||
config.signer.tokens.legacy_token =
|
||
DATABASE_SECRET;
|
||
|
||
Firebase.begin(&config, &auth);
|
||
|
||
Firebase.reconnectWiFi(true);
|
||
|
||
fbdo.setBSSLBufferSize(2048, 512);
|
||
|
||
config.timeout.serverResponse = 3000;
|
||
|
||
// ===== WATCHDOG =====
|
||
esp_task_wdt_config_t wdt_config = {
|
||
.timeout_ms = 10000,
|
||
.idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
|
||
.trigger_panic = true
|
||
};
|
||
|
||
esp_task_wdt_init(&wdt_config);
|
||
|
||
esp_task_wdt_add(NULL);
|
||
|
||
Serial.println("System Ready");
|
||
}
|
||
|
||
// ================= LOOP =================
|
||
void loop() {
|
||
|
||
esp_task_wdt_reset();
|
||
|
||
// ===== CHECK ONLINE/OFFLINE =====
|
||
checkOfflineMode();
|
||
|
||
// ===== RECONNECT WIFI =====
|
||
connectWiFi();
|
||
|
||
// ================= PIR =================
|
||
if (pirFlag) {
|
||
|
||
pirFlag = false;
|
||
|
||
Serial.println("⚠️ GERAKAN!");
|
||
|
||
pirState = 1;
|
||
|
||
pirHoldTime = millis();
|
||
|
||
sendPirNow = true;
|
||
|
||
// ===== BUZZER =====
|
||
if (millis() - lastBuzz > buzzCooldown) {
|
||
|
||
lastBuzz = millis();
|
||
|
||
buzzerStart = millis();
|
||
|
||
buzzerActive = true;
|
||
}
|
||
}
|
||
|
||
// ================= BUZZER =================
|
||
if (buzzerActive) {
|
||
|
||
static unsigned long lastToggle = 0;
|
||
static bool state = false;
|
||
|
||
if (millis() - lastToggle > 100) {
|
||
|
||
lastToggle = millis();
|
||
|
||
state = !state;
|
||
|
||
digitalWrite(BUZZER_PIN, state);
|
||
}
|
||
|
||
// ===== STOP BUZZER =====
|
||
if (millis() - buzzerStart > buzzerDuration) {
|
||
|
||
buzzerActive = false;
|
||
|
||
digitalWrite(BUZZER_PIN, LOW);
|
||
}
|
||
}
|
||
|
||
// ================= RESET PIR =================
|
||
if (
|
||
pirState == 1 &&
|
||
millis() - pirHoldTime > pirActiveDuration
|
||
) {
|
||
|
||
pirState = 0;
|
||
}
|
||
|
||
// ================= SENSOR =================
|
||
int soilRaw = analogRead(SOIL_PIN);
|
||
|
||
int moisture = map(
|
||
soilRaw,
|
||
SOIL_DRY,
|
||
SOIL_WET,
|
||
0,
|
||
100
|
||
);
|
||
|
||
moisture = constrain(moisture, 0, 100);
|
||
|
||
// ==================================================
|
||
// ================= LOGIKA POMPA ===================
|
||
// ==================================================
|
||
|
||
int pumpState = lastPumpState;
|
||
|
||
// ===== OFFLINE =====
|
||
if (offlineMode) {
|
||
|
||
// paksa AUTO lokal
|
||
if (moisture < minMoist) {
|
||
|
||
pumpState = 1;
|
||
|
||
} else if (moisture > maxMoist) {
|
||
|
||
pumpState = 0;
|
||
}
|
||
|
||
} else {
|
||
|
||
// ===== MODE NORMAL =====
|
||
if (mode == "manual") {
|
||
|
||
pumpState = pumpManual;
|
||
|
||
} else {
|
||
|
||
if (moisture < minMoist) {
|
||
|
||
pumpState = 1;
|
||
|
||
} else if (moisture > maxMoist) {
|
||
|
||
pumpState = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
lastPumpState = pumpState;
|
||
|
||
// relay active LOW
|
||
digitalWrite(
|
||
RELAY_PIN,
|
||
pumpState ? LOW : HIGH
|
||
);
|
||
|
||
// ================= DEBUG =================
|
||
Serial.printf(
|
||
"OFF:%d Moist:%d Pump:%d PIR:%d WiFi:%d\n",
|
||
offlineMode,
|
||
moisture,
|
||
pumpState,
|
||
pirState,
|
||
WiFi.status()
|
||
);
|
||
|
||
// ==================================================
|
||
// ============== FIREBASE DI BAWAH =================
|
||
// ==================================================
|
||
|
||
// ================= READ FIREBASE =================
|
||
if (
|
||
!offlineMode &&
|
||
millis() - lastRead > readInterval
|
||
) {
|
||
|
||
if (
|
||
WiFi.status() == WL_CONNECTED &&
|
||
Firebase.ready()
|
||
) {
|
||
|
||
Firebase.RTDB.getString(
|
||
&fbdo,
|
||
"/control/mode"
|
||
);
|
||
|
||
if (fbdo.dataType() == "string") {
|
||
mode = fbdo.stringData();
|
||
}
|
||
|
||
Firebase.RTDB.getInt(
|
||
&fbdo,
|
||
"/control/pump"
|
||
);
|
||
|
||
if (fbdo.dataType() == "int") {
|
||
pumpManual = fbdo.intData();
|
||
}
|
||
|
||
Firebase.RTDB.getInt(
|
||
&fbdo,
|
||
"/config/min_moisture"
|
||
);
|
||
|
||
if (fbdo.dataType() == "int") {
|
||
minMoist = fbdo.intData();
|
||
}
|
||
|
||
Firebase.RTDB.getInt(
|
||
&fbdo,
|
||
"/config/max_moisture"
|
||
);
|
||
|
||
if (fbdo.dataType() == "int") {
|
||
maxMoist = fbdo.intData();
|
||
}
|
||
}
|
||
|
||
lastRead = millis();
|
||
}
|
||
|
||
// ================= PIR REALTIME =================
|
||
if (sendPirNow) {
|
||
|
||
// reset flag dulu
|
||
sendPirNow = false;
|
||
|
||
// hanya kirim kalau online
|
||
if (!offlineMode) {
|
||
|
||
FirebaseJson json;
|
||
|
||
json.set("moisture", moisture);
|
||
json.set("pir", 1);
|
||
json.set("pump", pumpState);
|
||
|
||
if (sendData(json)) {
|
||
Serial.println("🚀 PIR SENT");
|
||
}
|
||
}
|
||
}
|
||
|
||
// ================= PERIODIC SEND =================
|
||
if (
|
||
!offlineMode &&
|
||
millis() - lastSend > sendInterval
|
||
) {
|
||
|
||
if (
|
||
abs(moisture - lastMoist) > 3 ||
|
||
pirState != lastPir ||
|
||
pumpState != lastPump
|
||
) {
|
||
|
||
FirebaseJson json;
|
||
|
||
json.set("moisture", moisture);
|
||
json.set("pir", pirState);
|
||
json.set("pump", pumpState);
|
||
|
||
if (sendData(json)) {
|
||
|
||
Serial.printf(
|
||
"SEND → M:%d PIR:%d P:%d\n",
|
||
moisture,
|
||
pirState,
|
||
pumpState
|
||
);
|
||
}
|
||
|
||
lastMoist = moisture;
|
||
lastPir = pirState;
|
||
lastPump = pumpState;
|
||
}
|
||
|
||
lastSend = millis();
|
||
}
|
||
|
||
delay(20);
|
||
} |