#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>
// ═══════════════════════════════════════════════════
// CẤU HÌNH
// ═══════════════════════════════════════════════════
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASS = "";
const char* MQTT_HOST = "6d90f0ad6b234605a84019febee90168.s1.eu.hivemq.cloud";
const int MQTT_PORT = 8883;
const char* MQTT_USER = "phuong123";
const char* MQTT_PASS = "Phuong123";
// ═══════════════════════════════════════════════════
// CHÂN GPIO
// ═══════════════════════════════════════════════════
#define PIN_DHT 4
#define PIN_LDR 35
#define PIN_PIR 13
#define PIN_SOIL 34
#define PIN_GAS 32
#define PIN_RELAY_PUMP 26
#define PIN_RELAY_LED 27
#define PIN_SERVO_FAN 25
#define PIN_BUZZER 33
#define I2C_SDA 21
#define I2C_SCL 22
// ═══════════════════════════════════════════════════
// ĐỐI TƯỢNG
// ═══════════════════════════════════════════════════
WiFiClientSecure tlsClient;
PubSubClient mqtt(tlsClient);
DHT dht(PIN_DHT, DHT22);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo fanServo;
// ─── Cảm biến ───────────────────────────────────────
float sens_temp = 0.0f;
float sens_hum = 0.0f;
int sens_soil = 0;
int sens_light = 0;
int sens_gas = 0;
bool sens_motion = false;
// ─── Thiết bị ───────────────────────────────────────
bool dev_pump = false;
bool dev_led = false;
bool dev_autoMode = true;
int dev_fanDeg = 0;
// ─── Timing ─────────────────────────────────────────
unsigned long t_sensor = 0;
unsigned long t_lcd = 0;
unsigned long t_wifi = 0;
unsigned long t_mqtt = 0;
uint8_t lcdPage = 0;
// ─── Trạng thái kết nối ─────────────────────────────
bool wifiOK = false;
bool mqttOK = false;
// ═══════════════════════════════════════════════════
// HELPER: Ghi 1 dòng LCD đúng 16 ký tự
// ═══════════════════════════════════════════════════
void lp(uint8_t row, const char* text) {
char b[17];
memset(b, ' ', 16);
b[16] = '\0';
int len = strlen(text);
if (len > 16) len = 16;
memcpy(b, text, len);
lcd.setCursor(0, row);
lcd.print(b);
}
// ═══════════════════════════════════════════════════
// CALLBACK MQTT
// ═══════════════════════════════════════════════════
void onMessage(char* topic, byte* payload, unsigned int len) {
char buf[64] = {};
memcpy(buf, payload, min(len, (unsigned)63));
String msg(buf);
msg.trim();
String t(topic);
Serial.printf("[MQTT IN] %s = %s\n", topic, buf);
if (t == "farm/control/pump") {
dev_pump = (msg == "ON");
digitalWrite(PIN_RELAY_PUMP, dev_pump ? HIGH : LOW);
Serial.printf(" >> Bom: %s\n", dev_pump ? "ON" : "OFF");
} else if (t == "farm/control/light") {
dev_led = (msg == "ON");
digitalWrite(PIN_RELAY_LED, dev_led ? HIGH : LOW);
Serial.printf(" >> Den: %s\n", dev_led ? "ON" : "OFF");
} else if (t == "farm/control/fan") {
dev_fanDeg = constrain(msg.toInt(), 0, 180);
fanServo.write(dev_fanDeg);
Serial.printf(" >> Fan: %d deg\n", dev_fanDeg);
} else if (t == "farm/control/buzzer") {
if (msg == "ON") tone(PIN_BUZZER, 1500, 800);
else { noTone(PIN_BUZZER); digitalWrite(PIN_BUZZER, LOW); }
} else if (t == "farm/control/mode") {
dev_autoMode = (msg == "AUTO");
Serial.printf(" >> Mode: %s\n", dev_autoMode ? "AUTO" : "MANUAL");
}
}
// ═══════════════════════════════════════════════════
// ĐỌC CẢM BIẾN
// ═══════════════════════════════════════════════════
void readSensors() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) sens_temp = t;
if (!isnan(h)) sens_hum = h;
sens_light = constrain(map(analogRead(PIN_LDR), 4095, 0, 0, 100), 0, 100);
sens_soil = constrain(map(analogRead(PIN_SOIL), 0, 4095, 0, 100), 0, 100);
sens_gas = constrain(map(analogRead(PIN_GAS), 0, 4095, 0, 100), 0, 100);
sens_motion = (bool)digitalRead(PIN_PIR);
}
// ═══════════════════════════════════════════════════
// AUTO CONTROL
// ═══════════════════════════════════════════════════
void autoControl() {
if (!dev_autoMode) return;
bool newPump = (sens_soil < 30);
if (newPump != dev_pump) {
dev_pump = newPump;
digitalWrite(PIN_RELAY_PUMP, dev_pump ? HIGH : LOW);
if (mqttOK) mqtt.publish("farm/control/pump", dev_pump ? "ON" : "OFF");
}
bool newLed = (sens_light < 40);
if (newLed != dev_led) {
dev_led = newLed;
digitalWrite(PIN_RELAY_LED, dev_led ? HIGH : LOW);
if (mqttOK) mqtt.publish("farm/control/light", dev_led ? "ON" : "OFF");
}
int newFan = (sens_temp > 35.0f) ? 180 : (sens_temp > 30.0f) ? 90 : 0;
if (newFan != dev_fanDeg) {
dev_fanDeg = newFan;
fanServo.write(dev_fanDeg);
}
if (sens_gas > 50) tone(PIN_BUZZER, 1000);
else { noTone(PIN_BUZZER); digitalWrite(PIN_BUZZER, LOW); }
}
// ═══════════════════════════════════════════════════
// PUBLISH
// ═══════════════════════════════════════════════════
void publishAll() {
if (!mqttOK || !mqtt.connected()) return;
char b[16];
dtostrf(sens_temp, 5, 1, b); mqtt.publish("farm/sensors/temperature", b);
dtostrf(sens_hum, 5, 1, b); mqtt.publish("farm/sensors/humidity", b);
itoa(sens_soil, b, 10); mqtt.publish("farm/sensors/soil", b);
itoa(sens_light, b, 10); mqtt.publish("farm/sensors/light", b);
itoa(sens_gas, b, 10); mqtt.publish("farm/sensors/gas", b);
mqtt.publish("farm/sensors/motion", sens_motion ? "1" : "0");
StaticJsonDocument<256> doc;
doc["temp"] = sens_temp;
doc["hum"] = sens_hum;
doc["soil"] = sens_soil;
doc["light"] = sens_light;
doc["gas"] = sens_gas;
doc["motion"] = sens_motion;
doc["pump"] = dev_pump;
doc["led"] = dev_led;
doc["fan_deg"] = dev_fanDeg;
doc["auto_mode"] = dev_autoMode;
doc["uptime_s"] = (uint32_t)(millis() / 1000);
char json[256];
serializeJson(doc, json);
mqtt.publish("farm/status", json);
// Serial
Serial.println(F("\n+-------- REALTIME --------+"));
Serial.printf( "| Nhiet do : %5.1f C |\n", sens_temp);
Serial.printf( "| Do am KK : %5.1f %% |\n", sens_hum);
Serial.printf( "| Do am dat: %3d %% |\n", sens_soil);
Serial.printf( "| Anh sang : %3d %% |\n", sens_light);
Serial.printf( "| Gas MQ2 : %3d %% |\n", sens_gas);
Serial.printf( "| PIR : %-14s|\n", sens_motion ? "PHAT HIEN!" : "Binh thuong");
Serial.println(F("+---------------------------+"));
Serial.printf( "| Bom:%-3s Den:%-3s Fan:%-3d |\n",
dev_pump ? "ON" : "OFF", dev_led ? "ON" : "OFF", dev_fanDeg);
Serial.printf( "| Mode : %-18s|\n", dev_autoMode ? "TU DONG (AUTO)" : "THU CONG");
Serial.printf( "| WiFi : %-18s|\n", wifiOK ? "CONNECTED" : "CONNECTING...");
Serial.printf( "| MQTT : %-18s|\n", mqttOK ? "CONNECTED (Cloud)" : "CONNECTING...");
Serial.println(F("+---------------------------+"));
}
// ═══════════════════════════════════════════════════
// LCD — 4 trang xoay vòng mỗi 2.5s
// ═══════════════════════════════════════════════════
void refreshLCD() {
if (millis() - t_lcd < 2500) return;
t_lcd = millis();
char r0[17], r1[17];
switch (lcdPage % 4) {
case 0:
snprintf(r0, 17, "T:%.1fC H:%.0f%%", sens_temp, sens_hum);
snprintf(r1, 17, "Dat:%d%% Sang:%d%%", sens_soil, sens_light);
break;
case 1:
snprintf(r0, 17, "Gas:%d%% Fan:%ddeg", sens_gas, dev_fanDeg);
snprintf(r1, 17, "PIR:%s", sens_motion ? "PHAT HIEN!" : "Binh thuong");
break;
case 2:
snprintf(r0, 17, "Bom:%s Den:%s",
dev_pump ? "ON " : "OFF", dev_led ? "ON " : "OFF");
snprintf(r1, 17, "%s %us",
dev_autoMode ? "AUTO" : "MAN.", (unsigned)(millis()/1000));
break;
case 3:
// Trang kết nối — quan trọng nhất
if (mqttOK && mqtt.connected()) {
snprintf(r0, 17, "MQTT:CONNECTED!");
snprintf(r1, 17, "HiveMQ Cloud OK");
} else if (wifiOK) {
snprintf(r0, 17, "WiFi: OK ");
snprintf(r1, 17, "MQTT:Dang ket...");
} else {
snprintf(r0, 17, "WiFi: Dang ket..");
snprintf(r1, 17, "Xin cho... ");
}
break;
default:
snprintf(r0, 17, "Smart Farm v3.2");
snprintf(r1, 17, "Running OK ");
}
lp(0, r0);
lp(1, r1);
lcdPage++;
}
// ═══════════════════════════════════════════════════
// SETUP
// ═══════════════════════════════════════════════════
void setup() {
Serial.begin(115200);
delay(200);
// ─── GPIO ────────────────────────────────────────
pinMode(PIN_PIR, INPUT);
pinMode(PIN_RELAY_PUMP, OUTPUT); digitalWrite(PIN_RELAY_PUMP, LOW);
pinMode(PIN_RELAY_LED, OUTPUT); digitalWrite(PIN_RELAY_LED, LOW);
pinMode(PIN_BUZZER, OUTPUT); digitalWrite(PIN_BUZZER, LOW);
analogReadResolution(12);
// ─── Servo ───────────────────────────────────────
fanServo.attach(PIN_SERVO_FAN, 500, 2400);
fanServo.write(0);
// ─── DHT22 ───────────────────────────────────────
dht.begin();
// ─── LCD — hiển thị NGAY ─────────────────────────
Wire.begin(I2C_SDA, I2C_SCL);
lcd.init();
lcd.backlight();
// Hiển thị banner khởi động ngay lập tức
lp(0, " SMART FARM ");
lp(1, " Dang khoi dong");
// ─── Serial banner ───────────────────────────────
Serial.println(F("\n=========================================="));
Serial.println(F(" SMART FARM IoT - Wokwi + HiveMQ Cloud"));
Serial.println(F("=========================================="));
Serial.printf("Broker: %s\n", MQTT_HOST);
Serial.printf("Port : %d (TLS)\n", MQTT_PORT);
Serial.printf("User : %s\n", MQTT_USER);
Serial.println(F("=========================================="));
// ─── TLS ─────────────────────────────────────────
tlsClient.setInsecure();
// ─── MQTT config ─────────────────────────────────
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512);
mqtt.setKeepAlive(60);
mqtt.setSocketTimeout(15);
// ─── Bắt đầu WiFi NON-BLOCKING ───────────────────
Serial.printf("[WiFi] Bat dau ket noi: %s\n", WIFI_SSID);
lp(0, "WiFi: Dang ket..");
lp(1, WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
// Không while() ở đây — xử lý trong loop()
}
// ═══════════════════════════════════════════════════
// LOOP — hoàn toàn non-blocking
// ═══════════════════════════════════════════════════
void loop() {
unsigned long now = millis();
// ─── 1. Kiểm tra WiFi (mỗi 500ms) ───────────────
if (now - t_wifi >= 500) {
t_wifi = now;
if (WiFi.status() == WL_CONNECTED) {
if (!wifiOK) {
// Vừa kết nối xong
wifiOK = true;
String ip = WiFi.localIP().toString();
Serial.println(F("[WiFi] *** CONNECTED! ***"));
Serial.printf("[WiFi] IP : %s\n", ip.c_str());
Serial.printf("[WiFi] RSSI: %d dBm\n", WiFi.RSSI());
// LCD hiển thị ngay
lp(0, "WiFi: CONNECTED!");
char ipbuf[17];
snprintf(ipbuf, 17, "IP:%s", ip.c_str());
lp(1, ipbuf);
}
} else {
wifiOK = false;
mqttOK = false;
}
}
// ─── 2. Kiểm tra & kết nối MQTT (mỗi 3s) ────────
if (wifiOK && (now - t_mqtt >= 3000)) {
t_mqtt = now;
if (!mqtt.connected()) {
mqttOK = false;
char cid[36];
snprintf(cid, sizeof(cid), "ESP32Farm_%08lX",
(unsigned long)(ESP.getEfuseMac() & 0xFFFFFFFF));
Serial.printf("[MQTT] Dang ket noi... (CID: %s)\n", cid);
lp(0, "MQTT: Dang ket..");
lp(1, "HiveMQ Cloud... ");
bool ok = mqtt.connect(cid, MQTT_USER, MQTT_PASS,
"farm/status/online", 0, true, "offline");
if (ok) {
mqttOK = true;
mqtt.publish("farm/status/online", "online", true);
mqtt.subscribe("farm/control/#");
// Serial báo thành công
Serial.println(F("[MQTT] *** KET NOI THANH CONG! ***"));
Serial.printf("[MQTT] Broker: %s:%d\n", MQTT_HOST, MQTT_PORT);
Serial.println(F("[MQTT] Subscribe: farm/control/#"));
// LCD báo thành công RÕ RÀNG
lp(0, "MQTT:CONNECTED! ");
lp(1, "HiveMQ Cloud OK!");
// Giữ 2 giây để người xem thấy
delay(2000);
} else {
int rc = mqtt.state();
Serial.printf("[MQTT] Ket noi that bai rc=%d\n", rc);
char errLine[17];
snprintf(errLine, 17, "MQTT fail rc=%d", rc);
lp(0, "MQTT: FAIL! ");
lp(1, errLine);
// Nếu sai credentials — không retry
if (rc == 4) {
Serial.println(F("[MQTT] SAI USER/PASS! Dung chuong trinh."));
lp(0, "SAI USER/PASS! ");
lp(1, "Kiem tra lai! ");
while (true) delay(500);
}
// Các lỗi khác: đợi 3s rồi thử lại tự động
t_mqtt = now - 2000; // thử lại sau 1s
}
} else {
mqttOK = true;
}
}
// ─── 3. MQTT loop (xử lý message đến) ───────────
if (mqttOK) mqtt.loop();
// ─── 4. Đọc cảm biến + auto control + publish ───
if (now - t_sensor >= 2000) {
t_sensor = now;
readSensors();
autoControl();
publishAll();
// In trạng thái kết nối vào Serial mỗi lần đọc
if (!wifiOK) {
Serial.println(F("[STATUS] WiFi: Dang ket noi..."));
lp(0, "WiFi: Dang ket..");
lp(1, "Xin cho... ");
} else if (!mqttOK) {
Serial.println(F("[STATUS] WiFi: OK | MQTT: Dang ket noi..."));
}
}
// ─── 5. Xoay trang LCD ───────────────────────────
refreshLCD();
}