#define BLYNK_TEMPLATE_ID "TMPL6kPD-zytL"
#define BLYNK_TEMPLATE_NAME "Tưới nước tự động"
#define BLYNK_AUTH_TOKEN "o7jrqlw2NuAYRsv6Gl-KGdiBtleFTdYZ"
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
#include <DHT.h>
#include <RTClib.h>
#include <Adafruit_GFX.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_SSD1306.h>
// Đánh dấu các chân cắm
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCL 22
#define SDA 21
#define aLED_PIN 19 // Led thông báo hệ thống đã khởi động
#define wLED_PIN 18 // Led thông báo cần tưới nước
#define RELAY_PIN 17 // Relay cấp điện máy bơm
#define BUZZER_PIN 23 // Loa
#define LDR_PIN 32 // Cảm biến cường độ ánh sáng
#define SOIL_PIN 34 // Cảm biến độ ẩm đất
#define PH_PIN 35 // Cảm biến đo độ pH đất
#define DHT_PIN 25 // Cảm biến độ ẩm và nhiệt độ không khí
#define DHTTYPE DHT22
#define NGUONG_NANG_GAT 3000 // Ngưỡng ánh sáng để không tưới cây
// Cấu hình WiFi cho Wokwi
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// Khởi tạo các đối tượng
BlynkTimer timer;
DHT dht(DHT_PIN, DHTTYPE);
RTC_DS1307 rtc;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// === CÁC BIẾN NÂNG CẤP ===
int WateringMode = 1; // 1 = Tự động, 0 = Tắt
int nguongKho = 75;
String thoiGianTuoiCuoi = "Chua tuoi";
int tongLuongNuocTuoi = 0;
int luongNuocThuCong = 500;
// Biến cho tưới thủ công không gián đoạn
bool dangTuoiThuCong = false;
unsigned long thoiDiemBatDauTuoiThuCong = 0;
unsigned long thoiGianCanTuoiThuCong = 0;
// === HÀM ĐÃ SỬA LẠI CHO LOGIC ĐÚNG ===
// Hàm chuyển đổi trạng thái đất dựa trên % độ khô
String Soil_Status(int phanTramDoKho){
if (phanTramDoKho > 70) return "KHO"; // Nếu độ khô trên 70% -> Đất khô
if (phanTramDoKho < 30) return "UOT"; // Nếu độ khô dưới 30% -> Đất ướt
return "TOT"; // Còn lại là tốt
}
// === TÍNH NĂNG MỚI: HÀM XỬ LÝ pH ===
String pH_Status(float ph) {
if (ph < 6.0) return "Dat Chua";
if (ph > 7.5) return "Dat Kiem";
return "Trung Tinh";
}
// === CÁC HÀM BLYNK ===
BLYNK_WRITE(V10) {
WateringMode = param.asInt();
if (WateringMode == 1) Serial.println("Che do tuoi tu dong: BAT");
else Serial.println("Che do tuoi tu dong: TAT");
}
// ĐÃ SỬA: Nút Tưới Thủ Công không còn dùng delay()
BLYNK_WRITE(V11) {
if (param.asInt() == 1 && !dangTuoiThuCong) {
dangTuoiThuCong = true;
thoiGianCanTuoiThuCong = (long(luongNuocThuCong) * 1000) / 50;
thoiDiemBatDauTuoiThuCong = millis();
Serial.print("Kich hoat tuoi thu cong "); Serial.print(luongNuocThuCong); Serial.println(" ml...");
digitalWrite(wLED_PIN, HIGH);
tone(BUZZER_PIN, 880, 200);
digitalWrite(RELAY_PIN, HIGH);
}
}
BLYNK_WRITE(V12) {
nguongKho = param.asInt();
Serial.print("Da dat nguong do kho moi: "); Serial.println(nguongKho);
}
BLYNK_WRITE(V13) {
luongNuocThuCong = param.asInt();
Serial.print("Da dat luong nuoc tuoi thu cong: "); Serial.print(luongNuocThuCong); Serial.println(" ml");
}
// Hàm gửi dữ liệu cảm biến lên Blynk theo chu kỳ
void sendSensorData() {
float h = dht.readHumidity();
float t = dht.readTemperature();
int ldrValue = analogRead(LDR_PIN);
int soilValue = analogRead(SOIL_PIN);
int phRawValue = analogRead(PH_PIN);
float phValue = phRawValue * (14.0 / 4095.0);
if (isnan(h) || isnan(t)) {
Serial.println("Loi doc cam bien DHT!");
return;
}
// Gửi trạng thái đất dựa trên % độ khô
int doKhoDat = map(soilValue, 0, 4095, 0, 100);
Blynk.virtualWrite(V6, Soil_Status(doKhoDat));
int percentMoisture = map(soilValue, 4095, 0, 0, 100);
// Gửi dữ liệu lên các Datastream của Blynk
Blynk.virtualWrite(V0, t);
Blynk.virtualWrite(V1, h);
Blynk.virtualWrite(V2, ldrValue);
Blynk.virtualWrite(V3, percentMoisture);
Blynk.virtualWrite(V4, phValue); // Gửi giá trị pH đã chuyển đổi
Blynk.virtualWrite(V15, pH_Status(phValue)); // Gửi trạng thái pH bằng chữ
}
void setup(){
Serial.begin(115200);
dht.begin();
Wire.begin(SDA, SCL);
rtc.begin();
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
pinMode(LDR_PIN, INPUT);
pinMode(PH_PIN, INPUT);
pinMode(SOIL_PIN, INPUT);
pinMode(aLED_PIN, OUTPUT);
pinMode(wLED_PIN, OUTPUT);
pinMode(RELAY_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
digitalWrite(aLED_PIN, LOW);
digitalWrite(wLED_PIN, LOW);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, password);
timer.setInterval(5000L, sendSensorData);
}
void loop() {
Blynk.run();
timer.run();
static bool daGuiCanhBaoDatKho = false;
static bool daGuiCanhBaoPH = false;
float t = dht.readTemperature();
float h = dht.readHumidity();
int ldrValue = analogRead(LDR_PIN);
int soilValue = analogRead(SOIL_PIN);
int phRawValue = analogRead(PH_PIN);
float phValue = phRawValue * (14.0 / 4095.0);
String trangThaiPH = pH_Status(phValue);
DateTime now = rtc.now();
// === DÒNG SỬA QUAN TRỌNG ===
// Chuyển đổi giá trị thô sang % độ khô một lần duy nhất
int doKhoDat = map(soilValue, 0, 4095, 0, 100);
// Hiển thị dữ liệu lên OLED
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0); display.print("N.Do: "); display.print(t, 1); display.print("C");
display.setCursor(0, 10); display.print("D.Am: "); display.print(h, 1); display.print("%");
display.setCursor(0, 20); display.print("Dat: "); display.print(Soil_Status(doKhoDat)); // Gọi hàm với giá trị %
display.setCursor(70, 20); display.print("pH: "); display.print(phValue, 1);
display.setCursor(0, 30); display.print("pH ST: "); display.print(trangThaiPH); // Hiển thị trạng thái pH
display.setCursor(0, 40); display.print("A.Sang: "); display.print(ldrValue);
display.setCursor(0, 54); display.print("L.Tuoi: "); display.print(thoiGianTuoiCuoi);
display.display();
// === TÍNH NĂNG MỚI: Xử lý tưới thủ công không gián đoạn ===
if (dangTuoiThuCong) {
if (millis() - thoiDiemBatDauTuoiThuCong >= thoiGianCanTuoiThuCong) {
digitalWrite(RELAY_PIN, LOW);
digitalWrite(wLED_PIN, LOW);
tone(BUZZER_PIN, 440, 300);
int luongNuocDotNay = luongNuocThuCong;
tongLuongNuocTuoi += luongNuocDotNay;
Blynk.virtualWrite(V5, luongNuocDotNay);
Blynk.virtualWrite(V8, tongLuongNuocTuoi);
char buffer[6];
sprintf(buffer, "%02d:%02d", now.hour(), now.minute());
thoiGianTuoiCuoi = String(buffer);
Blynk.virtualWrite(V7, thoiGianTuoiCuoi);
Serial.println("Tuoi thu cong hoan tat.");
dangTuoiThuCong = false; // Reset cờ
}
}
// Logic tưới tự động
if (WateringMode == 1 && doKhoDat > nguongKho && !dangTuoiThuCong) {
if (ldrValue >= NGUONG_NANG_GAT) {
Serial.println("Dat kho, nhung troi qua nang. Hoan tuoi.");
} else {
if (!daGuiCanhBaoDatKho) {
Blynk.logEvent("dat_kho_canh_bao");
Serial.println("Dat kho! Da gui canh bao len Blynk.");
daGuiCanhBaoDatKho = true;
}
digitalWrite(wLED_PIN, HIGH);
tone(BUZZER_PIN, 1000, 200);
long startTime = millis();
digitalWrite(RELAY_PIN, HIGH);
while (map(analogRead(SOIL_PIN), 0, 4095, 0, 100) > (nguongKho - 15)) {
Blynk.run();
delay(200);
}
digitalWrite(RELAY_PIN, LOW);
long waterTime = (millis() - startTime) / 1000;
int waterAmount = waterTime * 50;
Serial.print("Da tuoi xong. Luong nuoc dot nay: "); Serial.print(waterAmount); Serial.println(" ml");
tongLuongNuocTuoi += waterAmount;
Blynk.virtualWrite(V5, waterAmount);
Blynk.virtualWrite(V8, tongLuongNuocTuoi);
DateTime now_end = rtc.now();
char buffer[6];
sprintf(buffer, "%02d:%02d", now_end.hour(), now_end.minute());
thoiGianTuoiCuoi = String(buffer);
Blynk.virtualWrite(V7, thoiGianTuoiCuoi);
Blynk.logEvent("thong_bao_tuoi_cay");
digitalWrite(wLED_PIN, LOW);
tone(BUZZER_PIN, 500, 400);
delay(3000);
}
} else {
daGuiCanhBaoDatKho = false;
}
// === TÍNH NĂNG MỚI: Logic cảnh báo pH ===
if (phValue < 6.0 || phValue > 7.5) {
if (!daGuiCanhBaoPH) {
Blynk.logEvent("canh_bao_ph"); // Gửi cảnh báo
Serial.println("pH dat bat thuong! Da gui canh bao len Blynk.");
daGuiCanhBaoPH = true; // Đánh dấu đã gửi để không lặp lại
}
} else {
daGuiCanhBaoPH = false; // Reset cờ nếu pH đã về mức bình thường
}
}