// SPDX-License-Identifier: MIT
// Arduino (ESP32) FreeRTOS flow – library-powered (no MQTT)
// Flow kontrol & struktur tugas tetap sama seperti versi sebelumnya.
#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h>
// ====== LIBRARIES (pastikan terpasang via Library Manager / dependencies Wokwi) ======
#include <DHT.h> // Adafruit DHT sensor library
#include <OneWire.h> // Paul Stoffregen
#include <DallasTemperature.h> // Miles Burton
#include <NewPing.h> // teckel12
// ---------- Pin Mapping ----------
#define PIN_DHT 23 // DHT22 data
#define PIN_DS18B20 4 // 1-Wire DQ
#define PIN_TRIG 5 // HC-SR04 TRIG
#define PIN_ECHO 18 // HC-SR04 ECHO
#define PIN_PH_ADC 32 // ADC1
#define PIN_SOIL_ADC 35 // ADC1 (input-only)
#define PIN_LDR_ADC 34 // ADC1 (input-only)
#define PIN_LDR_D0 33
#define PIN_RELAY_PUMP 25
#define PIN_RELAY_GROW 26
#define PIN_RELAY_FAN 27
#define RELAY_ACTIVE_LOW 1
#define PIN_SERVO 13
#define PIN_BUZZER 14
// ---------- Objects (libs) ----------
#define DHTTYPE DHT22
static DHT dht(PIN_DHT, DHTTYPE);
static OneWire oneWire(PIN_DS18B20);
static DallasTemperature ds(&oneWire);
static NewPing sonar(PIN_TRIG, PIN_ECHO, 400); // max 400 cm
// ---------- Config & thresholds ----------
typedef struct {
float Tmax_air; // °C
float Hmax; // %RH
float L_min_adc; // threshold ADC LDR (0..4095)
float pH_min, pH_max; // target pH
float level_min_cm; // minimal level (HC-SR04: jarak >= ini => rendah)
float soil_wet_min; // % start siram
float soil_wet_max; // % stop siram
int pump_on_sec; // mode interval: ON detik
int pump_off_min; // mode interval: OFF menit
bool pump_mode_soil; // true: soil, false: interval
} app_config_t;
static app_config_t CFG = {
.Tmax_air = 30.0f,
.Hmax = 80.0f,
.L_min_adc = 300.0f,
.pH_min = 5.8f, .pH_max = 6.2f,
.level_min_cm = 18.0f,
.soil_wet_min = 60.0f,
.soil_wet_max = 80.0f,
.pump_on_sec = 30,
.pump_off_min = 5,
.pump_mode_soil = false
};
// ---------- Shared State ----------
typedef struct {
// Sensors
float t_air = 0, rh = 0; // DHT22
float t_water = 0; // DS18B20
float ph = 7.0; // from ADC32
float soil_pct = 50.0; // from ADC35
int ldr_adc = 0; // ADC34
int ldr_d0 = 0; // digital
float level_cm = 0; // HC-SR04
// Health
bool dht_ok = false, ds_ok = false, hcsr_ok = false;
// Actuators
bool pump_on = false;
bool grow_on = false;
bool fan_on = false;
int servo_deg = 0;
bool alarm_low_level = false;
bool alarm_overheat = false;
} app_state_t;
static app_state_t ST;
static SemaphoreHandle_t st_mutex;
// ---------- Time helpers ----------
static inline uint32_t msec_now() { return millis(); }
static void st_lock() { xSemaphoreTake(st_mutex, portMAX_DELAY); }
static void st_unlock() { xSemaphoreGive(st_mutex); }
// ---------- Relay control (active-low & min on/off) ----------
typedef struct {
int pin;
bool active_low;
bool state; // logical ON/OFF
uint32_t last_change_ms;
uint32_t min_on_ms;
uint32_t min_off_ms;
} relay_t;
static relay_t R_PUMP = {PIN_RELAY_PUMP, RELAY_ACTIVE_LOW, false, 0, 10000, 10000};
static relay_t R_GROW = {PIN_RELAY_GROW, RELAY_ACTIVE_LOW, false, 0, 5000, 5000};
static relay_t R_FAN = {PIN_RELAY_FAN, RELAY_ACTIVE_LOW, false, 0, 5000, 5000};
static void relay_apply(relay_t *r, bool on) {
uint32_t now = msec_now();
if (on != r->state) {
uint32_t dwell = r->state ? r->min_on_ms : r->min_off_ms;
if (now - r->last_change_ms < dwell) return; // tahan min ON/OFF
r->state = on;
r->last_change_ms = now;
int level = r->active_low ? (on ? LOW : HIGH) : (on ? HIGH : LOW);
digitalWrite(r->pin, level);
}
}
static void relay_init(relay_t *r) {
pinMode(r->pin, OUTPUT);
r->state = false;
r->last_change_ms = msec_now();
digitalWrite(r->pin, r->active_low ? HIGH : LOW); // OFF
}
// ---------- Servo (LEDC 50Hz) ----------
static const int SERVO_CH = 0; // LEDC channel
static const int SERVO_RES = 16; // bits
static const int SERVO_FREQ = 50; // Hz
static void servo_init() {
ledcSetup(SERVO_CH, SERVO_FREQ, SERVO_RES);
ledcAttachPin(PIN_SERVO, SERVO_CH);
}
static void servo_write_deg(int deg) {
if (deg < 0) deg = 0; if (deg > 180) deg = 180;
float pulse_us = 1000.0f + (deg / 180.0f) * 1000.0f; // 1.0ms..2.0ms
float duty = pulse_us / 20000.0f; // 20ms period
uint32_t maxd = (1u << SERVO_RES) - 1u;
uint32_t duty_val = (uint32_t)(duty * maxd);
ledcWrite(SERVO_CH, duty_val);
}
// ---------- Buzzer (LEDC tone) ----------
static const int BUZZ_CH = 1;
static const int BUZZ_RES = 10;
static const int BUZZ_FREQ = 2000;
static void buzzer_init() {
ledcSetup(BUZZ_CH, BUZZ_FREQ, BUZZ_RES);
ledcAttachPin(PIN_BUZZER, BUZZ_CH);
ledcWrite(BUZZ_CH, 0);
}
static void buzzer_on() { ledcWrite(BUZZ_CH, 512); }
static void buzzer_off() { ledcWrite(BUZZ_CH, 0); }
// ---------- ADC ----------
static void adc_init_all() {
analogReadResolution(12); // 0..4095
analogSetPinAttenuation(PIN_PH_ADC, ADC_11db);
analogSetPinAttenuation(PIN_SOIL_ADC, ADC_11db);
analogSetPinAttenuation(PIN_LDR_ADC, ADC_11db);
}
static float adc_to_volt(int raw) {
return (3.3f * (float)raw) / 4095.0f; // aproksimasi
}
// ---------- IO init ----------
static void inputs_init() {
pinMode(PIN_LDR_D0, INPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_TRIG, OUTPUT);
digitalWrite(PIN_TRIG, LOW);
}
// ===================== TASKS =====================
// 250 ms: HC-SR04 (NewPing) + D0
void task_sensors_fast(void *arg) {
for (;;) {
unsigned int cm = sonar.ping_cm(); // 0 => timeout
bool ok = (cm > 0);
st_lock();
ST.hcsr_ok = ok;
if (ok) ST.level_cm = (float)cm; // tahan nilai lama jika timeout
ST.ldr_d0 = digitalRead(PIN_LDR_D0);
st_unlock();
vTaskDelay(pdMS_TO_TICKS(250));
}
}
// Slow sensors: DHT22 (2s), DS18B20 (1s), ADC pH/Soil/LDR (2s)
void task_sensors_slow(void *arg) {
uint32_t last_dht = 0, last_adc = 0, last_ds = 0;
for (;;) {
uint32_t now = msec_now();
// --- DHT22 setiap 2000 ms (Adafruit DHT) ---
if (now - last_dht >= 2000) {
float t = dht.readTemperature();
float h = dht.readHumidity();
bool ok = !isnan(t) && !isnan(h);
st_lock();
ST.dht_ok = ok;
if (ok) { ST.t_air = t; ST.rh = h; }
st_unlock();
last_dht = now;
}
// --- DS18B20 setiap 1000 ms (DallasTemperature) ---
if (now - last_ds >= 1000) {
ds.requestTemperatures(); // blocking pendek (<=750ms)
float tw = ds.getTempCByIndex(0); // satu device pada bus
bool ok = (tw > -55 && tw < 125); // range valid DS18B20
st_lock();
ST.ds_ok = ok;
if (ok) ST.t_water = tw;
st_unlock();
last_ds = now;
}
// --- ADC pH/Soil/LDR setiap 2000 ms ---
if (now - last_adc >= 2000) {
int raw_ph = analogRead(PIN_PH_ADC);
int raw_soil = analogRead(PIN_SOIL_ADC);
int raw_ldr = analogRead(PIN_LDR_ADC);
float v_ph = adc_to_volt(raw_ph);
float v_soil = adc_to_volt(raw_soil);
// pH mapping sederhana (sesuaikan dengan vMin/vMax chip custom)
const float vMin = 0.0f, vMax = 3.0f;
float nrm = (v_ph - vMin) / (vMax - vMin);
if (nrm < 0) nrm = 0; if (nrm > 1) nrm = 1;
float ph = nrm * 14.0f;
float soil = (v_soil / 3.3f) * 100.0f;
if (soil<0) soil=0; if (soil>100) soil=100;
st_lock();
ST.ph = ph;
ST.soil_pct = soil;
ST.ldr_adc = raw_ldr;
st_unlock();
last_adc = now;
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
// Control task: safety > auto, min on/off, histeresis
void task_control(void *arg) {
relay_init(&R_PUMP); relay_init(&R_GROW); relay_init(&R_FAN);
servo_init(); buzzer_init();
uint32_t pump_next_on_ms = msec_now();
uint32_t pump_on_until_ms = 0;
uint32_t last_beep = 0;
for (;;) {
st_lock();
float t_air = ST.t_air, rh = ST.rh, t_water = ST.t_water;
float ph = ST.ph, soil = ST.soil_pct;
int ldr_adc = ST.ldr_adc, ldr_d0 = ST.ldr_d0;
float level = ST.level_cm;
st_unlock();
// SAFETY
bool alarm_low_level = (level >= CFG.level_min_cm);
bool alarm_overheat = (t_air >= (CFG.Tmax_air + 5.0f));
if (alarm_low_level) relay_apply(&R_PUMP, false);
if (alarm_overheat) { relay_apply(&R_FAN, true); relay_apply(&R_GROW, false); }
// AUTO
// PUMP
if (!alarm_low_level) {
if (CFG.pump_mode_soil) {
if (soil < CFG.soil_wet_min) relay_apply(&R_PUMP, true);
else if (soil >= CFG.soil_wet_max) relay_apply(&R_PUMP, false);
} else {
uint32_t now = msec_now();
if (now >= pump_next_on_ms) {
relay_apply(&R_PUMP, true);
pump_on_until_ms = now + (uint32_t)CFG.pump_on_sec*1000UL;
pump_next_on_ms = now + (uint32_t)CFG.pump_off_min*60000UL;
}
if (R_PUMP.state && msec_now() >= pump_on_until_ms) {
relay_apply(&R_PUMP, false);
}
}
}
// GROW LIGHT
if (!alarm_overheat) {
bool enough_light = (ldr_d0 == 1) || (ldr_adc >= (int)CFG.L_min_adc);
if (!enough_light) relay_apply(&R_GROW, true);
else relay_apply(&R_GROW, false);
}
// FAN
if ( (t_air >= CFG.Tmax_air) || (rh >= CFG.Hmax) ) {
relay_apply(&R_FAN, true);
} else if ( (t_air <= CFG.Tmax_air-1) && (rh <= CFG.Hmax-5) ) {
relay_apply(&R_FAN, false);
}
// SERVO demo: flush jika pH tinggi
if (ph >= 6.8f) servo_write_deg(90);
else servo_write_deg(0);
// BUZZER patterns
uint32_t now = msec_now();
if (alarm_low_level) {
if (now - last_beep > 30000) last_beep = now;
uint32_t phase = now - last_beep;
bool on = (phase<150) || (phase>400 && phase<550) || (phase>800 && phase<950);
on ? buzzer_on() : buzzer_off();
} else if (alarm_overheat) {
if (now - last_beep > 10000) last_beep = now;
bool on = (now - last_beep) < 1000;
on ? buzzer_on() : buzzer_off();
} else {
buzzer_off();
}
// write back
st_lock();
ST.pump_on = R_PUMP.state;
ST.grow_on = R_GROW.state;
ST.fan_on = R_FAN.state;
ST.servo_deg = (ph >= 6.8f) ? 90 : 0;
ST.alarm_low_level = alarm_low_level;
ST.alarm_overheat = alarm_overheat;
st_unlock();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// Logger task (2s)
void task_logger(void *arg) {
for (;;) {
st_lock();
Serial.printf("[SNAPSHOT] Air=%.1fC %.0f%% Water=%.2fC pH=%.2f Soil=%.0f%% LDR=%d/D%d Level=%.1fcm | PUMP=%d GROW=%d FAN=%d SERVO=%d | ALM(level=%d,heat=%d) | OK(DHT=%d,DS=%d,HCSR=%d)\n",
ST.t_air, ST.rh, ST.t_water, ST.ph, ST.soil_pct, ST.ldr_adc, ST.ldr_d0, ST.level_cm,
ST.pump_on, ST.grow_on, ST.fan_on, ST.servo_deg,
ST.alarm_low_level, ST.alarm_overheat,
ST.dht_ok, ST.ds_ok, ST.hcsr_ok);
st_unlock();
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
// ---------- Arduino setup/loop ----------
void setup() {
Serial.begin(115200);
delay(200);
st_mutex = xSemaphoreCreateMutex();
// IO & ADC
inputs_init();
adc_init_all();
// Outputs
relay_init(&R_PUMP); relay_init(&R_GROW); relay_init(&R_FAN);
servo_init(); buzzer_init();
// Init libraries
dht.begin();
ds.begin();
ds.setResolution(12); // akurasi maksimum (konversi ~750ms)
// Start tasks (prioritas & core sama dengan sebelumnya)
xTaskCreatePinnedToCore(task_sensors_fast, "sens_fast", 4096, NULL, 7, NULL, 1);
xTaskCreatePinnedToCore(task_sensors_slow, "sens_slow", 6144, NULL, 6, NULL, 1);
xTaskCreatePinnedToCore(task_control, "control", 6144, NULL, 8, NULL, 0);
xTaskCreatePinnedToCore(task_logger, "logger", 4096, NULL, 3, NULL, 0);
}
void loop() {
// kosong – semua kerja di FreeRTOS tasks
}
Loading
ds18b20
ds18b20
pump
grow
fan