#include <WiFi.h>
#include "PubSubClient.h"
#include "DHTesp.h"
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>

//Kênh gửi
const char * MQTTServer = "broker.emqx.io";
const char* data_ldr_Topic = "IoT/Blinds"; //LDR
const char* data_gas_Topic = "IoT/Gasensor"; //GAS
const char* data_pir_Topic = "IoT/PIRSensor"; //PIR
const char* data_ultra_Topic = "IoT/Ultrasonic"; //Ultrasonic
const char* data_rain_Topic = "IoT/RAINSensor"; //Rain
const char* data_dht_Topic = "IoT/DHT22"; //TEMP & HUMI
const char* dhtCharTopic = "IoT/CharData";
const char* alertTopic = "IoT/Alert"; // nhiệt độ high/low/normal

// Kênh nhận
const char* doorControlTopic = "IoT/DoorControl";
const char* doorControlTopicResponse = "IoT/DoorControl/Response";
const char* AirControlTopic = "IoT/AirControl";
const char* AirControlTopicResponse = "IoT/AirControlTopic/Response";
const char* homeControlTopic = "IoT/HomeControl";
const char* homeControlTopicResponse = "IoT/HomeControl/Response";

// Tạo ID ngẫu nhiên tại: https://www.guidgen.com/
const char * MQTT_ID = "c5afa5ae-d3a0-48b7-b850-92015a281909";
int Port = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

//............................................................................................................
LiquidCrystal_I2C lcd(0x27, 20, 4);

//Các đèn led báo hiệu
const int ledAir = 16; //led xanh lam : máy điều hòa
const int ledHome = 17; //led trắng : thể hiện đèn trong nhà, cửa ngoài, sân
const int ledWar = 4; //led đỏ : thể hiện cảnh báo nhấp nháy hoặc sáng liên tục
const int ledPir = 32; //led cam :  đèn ở cửa trong nhà, nhà vệ sinh

//DHT22
const int DHT_PIN = 23;
DHTesp dhtSensor;

//Buzzer
const int buzzer = 14;

//Gas Sensor
const int gasPin = 12;

//PIR
const int pir = 25;

//LDR
const int LDR_PIN = 34;

//Rain Sensor
const int RAIN_ANALOG = 18;
const int RAIN_DIGITAL = 19;

//Ultrasonic
const int TRIG_PIN = 27;
const int ECHO_PIN = 26;
float duration_us, distance_cm;

//Servo
const int servoBlindsPin = 15;
Servo servoBlinds; //Rèm cửa

const int servoWindowPin = 13;
Servo servoWindow; //Cửa sổ

const int servoDoorPin = 2;
Servo servoDoor; //Cửa nhà

const int servoDryingRackPin = 5;
Servo servoDryingRack; //Giàn phơi đồ

void WIFIConnect() {
  Serial.println("Connecting to SSID: Wokwi-GUEST");
  WiFi.begin("Wokwi-GUEST", "");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi connected");
  Serial.print(", IP address: ");
  Serial.println(WiFi.localIP());
}

void MQTT_Reconnect() {
  while (!client.connected()) {
    if (client.connect(MQTT_ID)) {
      Serial.print("MQTT Topic: ");
      Serial.print(data_ldr_Topic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(data_gas_Topic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(data_pir_Topic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(data_ultra_Topic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(data_rain_Topic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(data_dht_Topic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(alertTopic);
      Serial.println(" connected");
      Serial.println("");

      Serial.print("MQTT Topic: ");
      Serial.print(dhtCharTopic);
      Serial.println(" connected");
      Serial.println("");
    } else {
      Serial.print("failed, rc = ");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}


void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.println(topic);
  Serial.print("Message: ");
  String receivedMessage;
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    receivedMessage += (char)message[i];
  }
  //Mở cửa nhà
  Serial.println();
  if (String(topic) == doorControlTopic) {
    if (receivedMessage == "Open The Door") {
      servoDoor.write(180); // Mở cửa nhà
      client.publish(doorControlTopicResponse, "Cửa đã mở");
    } else if (receivedMessage == "Closed The Door") {
      servoDoor.write(0); // Đóng cửa nhà
      client.publish(doorControlTopicResponse, "Cửa đã đóng");
    }
  }

  if (String(topic) == AirControlTopic) {
    if (receivedMessage == "Turn on Air") {
      digitalWrite(ledAir, HIGH); // Bật lò sưởi
      client.publish(AirControlTopicResponse, "Điều hòa đã bật");
    } else if (receivedMessage == "Turn off Air") {
      digitalWrite(ledAir, LOW); // Tắt lò sưởi
      client.publish(AirControlTopicResponse, "Điều hòa đã tắt");
    }
  }

  if (String(topic) == homeControlTopic) {
    if (receivedMessage == "Turn on Led Home") {
      digitalWrite(ledHome, HIGH); // Bật ledHome
      client.publish(homeControlTopicResponse, "Đèn đã bật");
    } else if (receivedMessage == "Turn on Led Home") {
      digitalWrite(ledHome, LOW); // Tắt ledHome
      client.publish(homeControlTopicResponse, "Đèn đã tắt");
    }
  }
}


void setup() {
  Serial.begin(115200);
  //LCD
  lcd.init();
  lcd.backlight();
  noTone(buzzer);

  // Print something
  lcd.setCursor(2, 0);
  lcd.print("Welcome IOT!");

  //Rain Sensor
  pinMode(RAIN_ANALOG, INPUT);
  pinMode(RAIN_DIGITAL, INPUT);
  analogReadResolution(10);

  //Ultrasonic
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  //DHT22
  dhtSensor.setup(DHT_PIN, DHTesp::DHT22);

  //Led và buzzer
  pinMode(ledAir, OUTPUT); //led xanh lam: máy điều hòa
  pinMode(ledHome, OUTPUT); // led trắng: đèn ở khu vực luôn sáng khi trời tối như cửa ngoài, cổng...
  pinMode(ledWar, OUTPUT); //led đỏ: cảnh báo có khí gas
  pinMode(ledPir, OUTPUT); //led cam: đèn cửa trong nhà, nhà vệ sinh, khu ít người qua lại

  digitalWrite(ledAir, LOW);
  digitalWrite(ledHome, LOW);
  digitalWrite(ledWar, LOW);
  digitalWrite(ledPir, LOW);

  //Servo
  servoBlinds.attach(servoBlindsPin, 500, 2400); 
  servoBlinds.write(0);

  servoWindow.attach(servoWindowPin, 500, 2400); 
  servoWindow.write(0);

  servoDryingRack.attach(servoDryingRackPin, 500, 2400); 
  servoDryingRack.write(0);

  servoDoor.attach(servoDoorPin, 500, 2400); 
  servoDoor.write(0);

  //Buzzer
  pinMode(buzzer, OUTPUT);

  //Gas Sensor
  pinMode(gasPin, INPUT);

  //LDR
  pinMode(LDR_PIN, INPUT);

  //PIR
  pinMode(pir, INPUT);

  WIFIConnect();
  client.setServer(MQTTServer, Port);
  client.setCallback(callback);

  //Đăng ký kênh nhận
  client.subscribe(doorControlTopic);
  client.subscribe(AirControlTopic);
  client.subscribe(homeControlTopic);
}


//LDR: điều khiển mở/đóng rèm cửa theo cường độ ánh sáng, map giá trị từ 1 đến 100 lux................................................................
void controlBlinds() {
  int rawLight = analogRead(LDR_PIN);
  int lightValue = map(rawLight, 0, 1024, 100, 0);

  Serial.print("Lux: ");
  Serial.println(lightValue);

  if (lightValue > 50) {
    Serial.println("Mở rèm cửa");
    servoBlinds.write(180); // mở rèm
    delay(500);

    //Gửi
    client.publish(data_ldr_Topic, "Open the blinds");
  } else {
    Serial.println("Đóng rèm cửa");
    servoBlinds.write(0);
    delay(500);

    //Gửi
    client.publish(data_ldr_Topic, "Close the blinds");
  }
}

//GAS: khi có khí gas thì đồng thời bật còi báo động, led cảnh báo và mở cửa sổ...................................................................
unsigned long previousMillis = 0;
const long interval = 250;

void checkGasSensor() {
  unsigned long currentMillis = millis();

  if (digitalRead(gasPin) == HIGH) {

    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;

      Serial.println("Cảnh báo: Có khí GAS rò gỉ");

      digitalWrite(ledWar, HIGH); // Nhấp nháy đèn LED
      tone(buzzer, 2000); //Bật còi báo động
      servoWindow.write(180); //Mở cửa sổ
      delay(500);

      lcd.setCursor(0, 2);  // Đặt con trỏ tại dòng thứ ba để hiển thị lên lcd
      lcd.print("CANH BAO: Co khi GAS");

      // Gửi tin nhắn đến MQTT
      client.publish(data_gas_Topic, "Gas leak");
    }
  } else {

    Serial.println("Không có khí GAS");

    digitalWrite(ledWar, LOW); // Đảm bảo đèn LED tắt
    noTone(buzzer); //Tắt còi báo động
    servoWindow.write(0); //Đóng cửa sổ
    delay(500);

    lcd.setCursor(0, 2);  // Đặt con trỏ tại dòng thứ ba
    lcd.print("                    "); // Xóa dòng thứ ba

    // Gửi tin nhắn đến MQTT
    client.publish(data_gas_Topic, "No gas detected");
  }
}

//PIR: khi phát hiện chuyển động thì bật đèn ở cửa, nhà vệ sinh..................................................................................
bool pirState = LOW;
void checkPIRSensor() {
  bool currentPIRState = digitalRead(pir);
  if (currentPIRState != pirState) {
    pirState = currentPIRState;
    if (pirState == HIGH) {

      Serial.println("Phát hiện chuyển động");
      digitalWrite(ledPir, HIGH);// Bật đèn

      lcd.setCursor(0, 3);
      lcd.print("Motion Detected");

      // Gửi thông điệp MQTT
      client.publish(data_pir_Topic, "Motion Detected");
    } else {
      Serial.println("Không phát hiện chuyển động");
      digitalWrite(ledPir, LOW);// tắt đèn
      lcd.setCursor(0, 3);
      lcd.print("               ");

      // Gửi thông điệp MQTT
      client.publish(data_pir_Topic, "No Motion Detected");
    }
  }
}

//ULTRA: khi mực nước hơn 5 cm thì bật còi và đèn...................................................................................................
const float height = 350; //độ cao nơi gắn cảm biến 350 cm
const float height_War = 50; // độ cao mực nước cảnh báo
void checkUltrasonic() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  duration_us = pulseIn(ECHO_PIN, HIGH);
  distance_cm = height - duration_us * 0.034 / 2;

  Serial.print("Độ cao mực nước: ");
  Serial.println(distance_cm);

  if (distance_cm > height_War) {
    digitalWrite(ledWar, HIGH); // Bật đèn cảnh báo
    tone(buzzer, 1000); // Bật còi báo động

    //Gửi
    client.publish(data_ultra_Topic, "Flooding");
  } else {
    digitalWrite(ledWar, LOW); // Tắt đèn cảnh báo
    noTone(buzzer); // Tắt còi báo động
  }

  //Gửi
  client.publish(data_ultra_Topic, "No Flooding");
}


//RAIN: khi trời mưa thì kéo dàn phơi đồ và ngược lại..........................................................................................
void checkRainSensor() {
  int rainAnalogValue = analogRead(RAIN_ANALOG);
  bool isRaining = digitalRead(RAIN_DIGITAL) == HIGH;

  // Serial.print("Rain amount: ");
  // Serial.println(rainAnalogValue);
  if (isRaining) {

    Serial.println("It's raining! Kéo giàn phơi vào.");
    servoDryingRack.write(0); // Kéo giàn phơi vào
    delay(500);

    //Gửi
    client.publish(data_rain_Topic, "Rain Detected");
  } else {
    Serial.println("No rain detected. Kéo giàn phơi ra.");
    servoDryingRack.write(180); // Kéo giàn phơi ra
    delay(500);

    //Gửi
    client.publish(data_rain_Topic, "No Rain Detected");
  }
}


//DHT: Gửi nhiệt độ và độ ẩm, bật điều hòa khi nhiệt độ > 40 && < 20.......................................................................................................................................
const int NUM_READINGS = 5;
float tempReadings[NUM_READINGS] = {0.0};
float humReadings[NUM_READINGS] = {0.0};
int readIndex = 0;
float totalTemp = 0.0;
float totalHum = 0.0;
int numActualReadings = 0; // Biến theo dõi số lượng đọc giá trị thực tế

void readAndDisplayDHT() {
  // Đọc giá trị nhiệt độ và độ ẩm
  float temperature = dhtSensor.getTemperature();
  float humidity = dhtSensor.getHumidity();

  // Kiểm tra xem giá trị đọc được có hợp lệ
  if (isnan(temperature) || isnan(humidity)) {
    lcd.clear();
    lcd.print("Error reading DHT");
    return;
  }
  // Định dạng dữ liệu để gửi
  String payload = String(temperature) + "," + String(humidity);

  // Gửi dữ liệu đến topic MQTT
  client.publish(dhtCharTopic, payload.c_str());

  // Cập nhật mảng trượt của giá trị nhiệt độ và độ ẩm
  totalTemp -= tempReadings[readIndex];
  totalHum -= humReadings[readIndex];
  tempReadings[readIndex] = temperature;
  humReadings[readIndex] = humidity;
  totalTemp += temperature;
  totalHum += humidity;

  if (numActualReadings < NUM_READINGS) {
    numActualReadings++;
  }

  readIndex = (readIndex + 1) % NUM_READINGS;

  // Tính trung bình
  float avgTemp = (numActualReadings == NUM_READINGS) ? (totalTemp / NUM_READINGS) : (totalTemp / numActualReadings);
  float avgHum = (numActualReadings == NUM_READINGS) ? (totalHum / NUM_READINGS) : (totalHum / numActualReadings);

  // Gửi dữ liệu nhiệt độ và độ ẩm
  char tempHumString[20];
  sprintf(tempHumString, "%0.2f,%0.2f", avgTemp, avgHum);
  client.publish(data_dht_Topic, tempHumString);

  // Kiểm tra ngưỡng và hiển thị giá trị hoặc cảnh báo
  if (avgTemp > 40) {

    Serial.println("Nhiệt độ trên 40°C");

    lcd.setCursor(0, 0);
    // Đặt con trỏ vào vị trí đầu của dòng 1
    lcd.print("                    "); // Ghi đè dòng 1 bằng khoảng trắng
    lcd.setCursor(0, 1); // Đặt con trỏ vào vị trí đầu của dòng 2
    lcd.print("                    ");
    lcd.setCursor(0, 0);
    lcd.print("WARNING: Temp HIGH");
    digitalWrite(ledAir, HIGH);// bật điều hòa

    //Gửi
    client.publish(alertTopic, "WARNING: HIGH");

  } else if (avgTemp < 20) {
    lcd.setCursor(0, 0); // Đặt con trỏ vào vị trí đầu của dòng 1
    lcd.print("                    "); // Ghi đè dòng 1 bằng khoảng trắng
    lcd.setCursor(0, 1); // Đặt con trỏ vào vị trí đầu của dòng 2
    lcd.print("                    ");
    lcd.setCursor(0, 0);
    lcd.print("WARNING: Temp LOW");
    digitalWrite(ledAir, HIGH); //bật điều hòa

    //Gửi
    client.publish(alertTopic, "WARNING: LOW");
  } else {
    // Hiển thị thông thường nếu không vượt ngưỡng
    lcd.setCursor(0, 0); // Đặt con trỏ vào vị trí đầu của dòng 1
    lcd.print("                    "); // Ghi đè dòng 1 bằng khoảng trắng
    lcd.setCursor(0, 1); // Đặt con trỏ vào vị trí đầu của dòng 2
    lcd.print("                    ");

    lcd.setCursor(0, 0);
    lcd.print("NhietDo: ");
    lcd.print(temperature);
    lcd.print(" C");

    lcd.setCursor(0, 1);
    lcd.print("DoAm: ");
    lcd.print(humidity);
    lcd.print("%");

    digitalWrite(ledAir, LOW);// tắt điều hòa

    //Gửi
    client.publish(alertTopic, "WARNING: NORMAL");
  }
}

//--------------------------------------------------------
void process() {
  checkPIRSensor();
  controlBlinds();
  checkGasSensor();
  readAndDisplayDHT();
  checkUltrasonic();
  checkRainSensor();
  Serial.println("......................");
}

void loop() {
  if (!client.connected()) {
    MQTT_Reconnect();
    client.subscribe(doorControlTopic);
    client.subscribe(doorControlTopicResponse);
    client.subscribe(AirControlTopic);
    client.subscribe(AirControlTopicResponse);
    client.subscribe(homeControlTopic);
    client.subscribe(homeControlTopicResponse);
  }
  client.loop();
  process();
  delay(1000);
}
rain-sensorBreakout