#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "FC28.h"
// === PIN & LIBRARY ===
#define PIN_SOIL 34
#define PIN_SERVO 18
LiquidCrystal_I2C lcd(0x27, 20, 4);
Servo servo;
FC28Sensor mySensor;
// === WIFI CONFIG ===
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// === LARAVEL LOCALTONET CONFIG ===
const char* laravel_sensor_url = "http://hrlbi5kne.localto.net/api/perbarui-sensor";
const char* laravel_status_url = "http://hrlbi5kne.localto.net/api/status-pompa";
const char* laravel_siram_url = "http://hrlbi5kne.localto.net/api/siram-manual";
// === QUEUE & TASK ===
typedef struct {
float kelembapan;
int statusPompa;
String statusTanah;
} SensorData;
QueueHandle_t dataQueue;
TaskHandle_t TaskHTTPWorker;
TaskHandle_t TaskPompaWorker;
SemaphoreHandle_t wifiSemaphore;
// === GLOBAL HTTP CLIENT ===
HTTPClient http;
WiFiClient client;
void setup() {
Serial.begin(115200);
mySensor.initFC28Sensor(115200, PIN_SOIL);
servo.attach(PIN_SERVO);
lcd.init();
lcd.backlight();
lcd.setCursor(2, 0); lcd.print("Siraman Otomatis");
lcd.setCursor(2, 1); lcd.print("Monitoring System");
delay(2000);
lcd.clear();
Serial.println("Menyambungkan WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("\nā
WiFi Terhubung!");
wifiSemaphore = xSemaphoreCreateMutex();
dataQueue = xQueueCreate(10, sizeof(SensorData));
xTaskCreatePinnedToCore(
taskHTTPWorker,
"Task HTTP Worker",
32768,
NULL,
1,
&TaskHTTPWorker,
1
);
xTaskCreatePinnedToCore(
taskPompaWorker,
"Task Pompa Worker",
32768,
NULL,
1,
&TaskPompaWorker,
1
);
}
void loop() {
float nilaiKelembapan = mySensor.getSoilMoisture(); // Gunakan sensor FC28
int statusPompa = 0; // Untuk laporan, tidak kontrol servo
String statusTanah;
// === STATUS TANAH ===
if (nilaiKelembapan <= 30.0) {
statusTanah = "Sangat Kering";
} else if (nilaiKelembapan <= 50.0) {
statusTanah = "Kering";
} else if (nilaiKelembapan <= 70.0) {
statusTanah = "Lembab";
} else if (nilaiKelembapan <= 85.0) {
statusTanah = "Basah";
} else {
statusTanah = "Terlalu Basah";
}
// === LCD DISPLAY ===
lcd.setCursor(0, 0); lcd.print("Status: ");
lcd.setCursor(8, 0); lcd.print(statusTanah + " ");
lcd.setCursor(0, 1); lcd.print("Soil : ");
lcd.setCursor(8, 1); lcd.print(nilaiKelembapan, 1); lcd.print("% ");
lcd.setCursor(0, 2); lcd.print("Pompa : ");
lcd.setCursor(8, 2); lcd.print(" "); // Kosongkan, akan diupdate oleh taskPompaWorker
// === SERIAL MONITOR ===
Serial.println("=== DATA SENSOR ===");
Serial.printf("Kelembapan: %.1f%% | Status: %s\n", nilaiKelembapan, statusTanah.c_str());
Serial.printf("Free heap: %d | Stack high water mark: %d\n", ESP.getFreeHeap(), uxTaskGetStackHighWaterMark(NULL));
// === KIRIM KE QUEUE ===
SensorData data = { nilaiKelembapan, statusPompa, statusTanah };
xQueueSend(dataQueue, &data, portMAX_DELAY);
delay(5000);
}
void taskHTTPWorker(void *parameter) {
SensorData data;
unsigned long lastLaravel = 0;
for (;;) {
if (xQueueReceive(dataQueue, &data, portMAX_DELAY) == pdTRUE) {
unsigned long now = millis();
Serial.printf("Task HTTPWorker - Stack high water mark: %d\n", uxTaskGetStackHighWaterMark(NULL));
if (now - lastLaravel >= 5000) {
if (xSemaphoreTake(wifiSemaphore, portMAX_DELAY) == pdTRUE) {
kirimLaravel(data.kelembapan, data.statusPompa, data.statusTanah);
xSemaphoreGive(wifiSemaphore);
}
lastLaravel = now;
}
}
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
void taskPompaWorker(void *parameter) {
unsigned long lastCheck = 0;
bool pompaAktif = false;
unsigned long pompaStartTime = 0;
for (;;) {
unsigned long now = millis();
float kelembapan = mySensor.getSoilMoisture(); // Gunakan sensor FC28
Serial.printf("Task PompaWorker - Stack high water mark: %d\n", uxTaskGetStackHighWaterMark(NULL));
// Logika otomatis berdasarkan kelembapan
if (!pompaAktif && kelembapan < 40.0) {
servo.write(180); // Pompa NYALA
lcd.setCursor(8, 2); lcd.print("NYALA ");
Serial.println("š§ Pompa dihidupkan (otomatis - kelembapan rendah)");
pompaAktif = true;
pompaStartTime = now;
} else if (pompaAktif && kelembapan >= 40.0 && (now - pompaStartTime >= 5000)) {
servo.write(0); // Pompa MATI
lcd.setCursor(8, 2); lcd.print("MATI ");
Serial.println("š§ Pompa dimatikan (otomatis - kelembapan cukup)");
pompaAktif = false;
}
// Cek perintah manual
if (now - lastCheck >= 5000 && !pompaAktif) {
String statusPompa = "";
if (xSemaphoreTake(wifiSemaphore, portMAX_DELAY) == pdTRUE) {
statusPompa = cekStatusPompa();
xSemaphoreGive(wifiSemaphore);
}
if (statusPompa == "ON") {
servo.write(180); // Pompa NYALA
lcd.setCursor(8, 2); lcd.print("NYALA ");
Serial.println("š§ Pompa dihidupkan (manual)");
pompaAktif = true;
pompaStartTime = now;
}
lastCheck = now;
}
// Matikan pompa setelah 5 detik untuk perintah manual
if (pompaAktif && (now - pompaStartTime >= 5000)) {
servo.write(0); // Pompa MATI
lcd.setCursor(8, 2); lcd.print("MATI ");
Serial.println("š§ Pompa dimatikan otomatis setelah 5 detik (manual)");
if (xSemaphoreTake(wifiSemaphore, portMAX_DELAY) == pdTRUE) {
kirimSiramManual("OFF", "Pompa mati otomatis setelah 5 detik");
xSemaphoreGive(wifiSemaphore);
}
pompaAktif = false;
}
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
String cekStatusPompa() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("ā [Status Pompa] Tidak ada WiFi");
return "OFF";
}
http.begin(client, laravel_status_url);
http.addHeader("localtonet-skip-warning", "true");
http.setTimeout(5000);
String statusPompa = "OFF";
Serial.printf("Free heap before JSON: %d\n", ESP.getFreeHeap());
int httpCode = http.GET();
if (httpCode == 200) {
String response = http.getString();
Serial.printf("ā
[Status Pompa] %d : %s\n", httpCode, response.c_str());
// Parse JSON response
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, response);
if (!error) {
statusPompa = doc["status_pompa"].as<String>();
} else {
Serial.printf("ā [Status Pompa] Gagal parse JSON: %s\n", error.c_str());
}
} else {
Serial.printf("ā [Status Pompa] %d : %s\n", httpCode, http.errorToString(httpCode).c_str());
}
http.end();
Serial.printf("Free heap after HTTP: %d\n", ESP.getFreeHeap());
vTaskDelay(100 / portTICK_PERIOD_MS);
return statusPompa;
}
void kirimSiramManual(String statusPompa, String catatan) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("ā [Siram Manual] Tidak ada WiFi");
return;
}
String payload;
payload.reserve(256);
payload = "{";
payload += "\"status_pompa\": \"" + statusPompa + "\",";
payload += "\"catatan\": \"" + catatan + "\"";
payload += "}";
Serial.println("š [Siram Manual] Payload: " + payload);
Serial.printf("Free heap before POST: %d\n", ESP.getFreeHeap());
http.begin(client, laravel_siram_url);
http.addHeader("Content-Type", "application/json");
http.addHeader("localtonet-skip-warning", "true");
http.setTimeout(5000);
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("ā
[Siram Manual] %d : %s\n", httpCode, http.getString().c_str());
} else {
Serial.printf("ā [Siram Manual] %d : %s\n", httpCode, http.errorToString(httpCode).c_str());
}
http.end();
Serial.printf("Free heap after POST: %d\n", ESP.getFreeHeap());
vTaskDelay(100 / portTICK_PERIOD_MS);
}
void kirimLaravel(float kelembapan, int statusPompa, String statusTanah) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("ā [Laravel] Tidak ada WiFi");
return;
}
String payload;
payload.reserve(256);
payload = "{";
payload += "\"kelembapan_tanah\": " + String(kelembapan, 1) + ",";
payload += "\"status_pompa\": \"" + String(statusPompa ? "ON" : "OFF") + "\",";
payload += "\"status_tanah\": \"" + statusTanah + "\",";
payload += "\"status_tombol\": \"AUTO\",";
payload += "\"mode_siram\": \"otomatis\"}";
Serial.println("š [Laravel] Payload: " + payload);
Serial.printf("Free heap before POST: %d\n", ESP.getFreeHeap());
http.begin(client, laravel_sensor_url);
http.addHeader("Content-Type", "application/json");
http.addHeader("localtonet-skip-warning", "true");
http.setTimeout(5000);
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("ā
[Laravel] %d : %s\n", httpCode, http.getString().c_str());
} else {
Serial.printf("ā [Laravel] %d : %s\n", httpCode, http.errorToString(httpCode).c_str());
}
http.end();
Serial.printf("Free heap after POST: %d\n", ESP.getFreeHeap());
vTaskDelay(100 / portTICK_PERIOD_MS);
}