// Khai báo thư viện
#include "DHTesp.h"            // Điều khiển DHT22
#include <WiFi.h>              // Kết nối wifi
#include <ThingSpeak.h>        // Lưu dữ liệu lên cloud ThingSpeak
#include <PubSubClient.h>      // Kết nối MQTTServer
#include <ArduinoJson.h>       // Đóng gói dữ liệu thành Json
#include <Wire.h>              // Giao tiếp với các thiết bị I2C
#include <LiquidCrystal_I2C.h> // Điều khiển LCD 16x2 
#include <TM1637.h>            // Điều khiển module hiển thị 7 đoạn (4 chữ số)
#include "RTClib.h"            // Điều khiển RTC DS1307

//===============================================================================
// Khai báo chân thiết bị
const int pinRedButton = 25;
const int pinGreenButton = 16;

const int pinBuzzer = 4;
const int pinGreenLed = 2;

const int pinDHT22 = 26;
const int pinPhotoSensor = 33;
const int pinSoundSensor = 34;

const int pinPIR = 27; 
const int pinTRIG = 32;
const int pinECHO = 35;

const int pinRelayForLed = 5;
const int pinRelayForFan = 23;

const int CLK = 18;
const int DIO = 19;

//===============================================================================
// Khai báo biến toàn cục
const char* ssid = "Wokwi-GUEST";
const char* password = "";

const char* MQTTServer = "broker.hivemq.com"; 
const int port = 1883;

const unsigned long myChannelId = 2788701;
const char* myWriteAPIKey = "UX62EIZAAZ2GTPDC";

WiFiClient ThingSpeakClient;
WiFiClient MQTTClient; // Được dùng để kết nối tới MQTT  
PubSubClient client(MQTTClient); /*
Vẫn sử dụng wifiClient để kết nối đến MQTTServer nhưng đã được thư viện hỗ trợ */

bool isWorking = false;
bool isPresent = false;

bool stop = false;
bool colon = false;

bool isLedAuto = true;
bool isLedSend = false;
bool isFanAuto = true;
bool isFanSend = false;

unsigned long lastBlinkTime = 0;
unsigned long lastPubEnvDataTime = 0;
unsigned long lastStoreEnvDataTime = 0;
unsigned long lastCountdownTime = 0;

unsigned long lastToneTime = 0;
unsigned long lastMillis = 0;
unsigned long lastDistance = 0;
unsigned long lastPresent = 0;

int lastRedState = LOW;
int lastGreenState = LOW;
int ledState = LOW;

const float GAMMA = 0.7;
const float RL10 = 50;

DHTesp DHT22;
float tempVal = 0;
float humidVal = 0;
float lightVal = 500;
int soundVal = 0;

LiquidCrystal_I2C lcd(0x27, 16, 2);
TM1637 tm1637;
RTC_DS1307 rtc;

bool PomodoroTechnique = false;
bool TimeBlocking = false;
bool TimeBoxing = false;

unsigned int step = 0;

unsigned int nowHour = 0;
unsigned int nowMinute = 0;

unsigned int hour = 0;
unsigned int minute = 0;
unsigned long second = 0;

unsigned int startHour = 0;
unsigned int startMinute = 0;
unsigned int endHour = 0;
unsigned int endMinute = 0;

//===============================================================================
// Các hàm của Lê Thành Lợi
void ledSV22() {
  unsigned long currentTime = millis();
  if(currentTime >= lastBlinkTime + 1000) {
    ledState = !ledState;
    digitalWrite(pinGreenLed, ledState ? HIGH : LOW);
    lastBlinkTime = currentTime;
  }
}


void getEnvData() {
  TempAndHumidity data = DHT22.getTempAndHumidity();
  tempVal = data.temperature;
  humidVal = data.humidity;

  float analogValue = analogRead(pinPhotoSensor);
  float voltage = analogValue / 4096.0 * 5.0;
  float resistance = 2000.0 * voltage / (1.0 - voltage / 5.0);
  lightVal = pow(RL10 * 1e3 * pow(10.0, GAMMA) / resistance, (1.0 / GAMMA));

  soundVal = analogRead(pinSoundSensor);
}


void publishEnvData() {
  unsigned long currentTime = millis();
  if(currentTime >= lastPubEnvDataTime + 5000) {
    StaticJsonDocument<200> jsonDoc;
  
    jsonDoc["temperature"] = tempVal;
    jsonDoc["humidity"] = humidVal;
    jsonDoc["light"] = lightVal;
    jsonDoc["sound"] = soundVal;
    
    char buffer[200];
    serializeJson(jsonDoc, buffer);
    client.publish("/SV22/in/EnvData", buffer);

    lastPubEnvDataTime = currentTime;
  }
}


void storeEnvData() {
  unsigned long currentTime = millis();
  if(currentTime >= lastStoreEnvDataTime + 30000) {
    float ran1 = random(-4000, 8001) / 100.0;
    float ran2 = random(0, 10001) / 100.0;
    float ran3 = random(1, 100001) / 100.0;
    int ran4 = random(0, 181);
    ThingSpeak.setField(1, ran1);
    ThingSpeak.setField(2, ran2);
    ThingSpeak.setField(3, ran3);
    ThingSpeak.setField(4, ran4);
    ThingSpeak.writeFields(myChannelId, myWriteAPIKey);
    lastStoreEnvDataTime = currentTime;
  }
}


long getDistance() {
  digitalWrite(pinTRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(pinTRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(pinTRIG, LOW);

  long duration = pulseIn(pinECHO, HIGH);
  long distance = duration * 0.034 / 2.0;
  return distance;
}


void checkPresence() {
  int PIRVal = digitalRead(pinPIR);
  if(PIRVal == 1) {
    lastDistance = getDistance();
    isPresent = true;
  }

  if(isPresent) {
    unsigned long currentDistance = getDistance();
    if(currentDistance > lastDistance + 50) {
      unsigned long currentTime = millis();
      if(currentTime > lastPresent + 10000) {
        digitalWrite(pinGreenLed, LOW);
        tone(pinBuzzer, 200, 560);
        if(currentTime > lastPresent + 20000) {
          client.publish("/SV22/in/Notification","");
          noTone(pinBuzzer);
          isPresent = false;
          stop = true;
        }
      }
    }
    else {
      lastPresent = millis();
      int buttonState = digitalRead(pinGreenButton);
      if(buttonState == HIGH) {
        noTone(pinBuzzer);
        lastDistance = getDistance();
      }
    }
  }
}


void wifiConnect() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println(" Connected to Wifi!");
}


void mqttConnect() {
  while(!client.connect("SV22-22127151-22127238"))
    delay(5000);
  Serial.println("Connected to MQTT Server!");

  // Subscribe topic ở đây (thiết bị nhận tín hiệu từ web)
  client.subscribe("/SV22/out/turnOn");
  client.subscribe("/SV22/out/turnOff");

  client.subscribe("/SV22/out/mode/led");
  client.subscribe("/SV22/out/mode/fan");

  client.subscribe("/SV22/out/mode/man/led");
  client.subscribe("/SV22/out/mode/man/fan");

  client.subscribe("/SV22/out/PomodoroTechnique");
  client.subscribe("/SV22/out/PomodoroTechnique/stop");

  client.subscribe("/SV22/out/TimeBlocking/setUp");
  client.subscribe("/SV22/out/TimeBlocking");
  client.subscribe("/SV22/out/TimeBlocking/stop");

}


// MQTT Receiver
void callback(char* topic, byte* message, unsigned int length) {
  Serial.println(topic);
  String payload;
  for(int i = 0; i < length; i++)
    payload += (char)message[i];
  Serial.println(payload);

  // Xử lý tín hiệu nhận ở đây
  if(strcmp(topic, "/SV22/out/turnOn") == 0)
    isWorking = true;

  if(strcmp(topic, "/SV22/out/turnOff") == 0)
    isWorking = false;

  if(strcmp(topic, "/SV22/out/mode/led") == 0) {
    if(payload == "AUTO")
      isLedAuto = true;
    else {
      isLedAuto = false;
      digitalWrite(pinRelayForLed, LOW);
    }
  }

  if(strcmp(topic, "/SV22/out/mode/fan") == 0) {
    if(payload == "AUTO")
      isFanAuto = true;
    else {
      isFanAuto = false;
      digitalWrite(pinRelayForFan, LOW);
    }
  }

  if(strcmp(topic, "/SV22/out/mode/man/led") == 0) {
    if(payload == "true")
      digitalWrite(pinRelayForLed, HIGH);
    else digitalWrite(pinRelayForLed, LOW);
  }

  if(strcmp(topic, "/SV22/out/mode/man/fan") == 0) {
    if(payload == "true")
      digitalWrite(pinRelayForFan, HIGH);
    else digitalWrite(pinRelayForFan, LOW);
  }
    
  if(strcmp(topic, "/SV22/out/PomodoroTechnique") == 0) {
    lastDistance = getDistance();
    isPresent = true;
    tm1637.displayClear();
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("POMODORO");
    lcd.setCursor(3, 1);
    lcd.print("TECHNIQUE!");
    tone(pinBuzzer, 2000, 200);
    PomodoroTechnique = true;
    digitalWrite(pinGreenLed, HIGH);
    client.publish("/SV22/in/PomodoroTechnique/prepare", "prepare");
    minute = 5;
    second = 0;
    step = 1;
  }

  if(strcmp(topic, "/SV22/out/PomodoroTechnique/stop") == 0)
    if(PomodoroTechnique)
      stop = true;

  if(strcmp(topic, "/SV22/out/TimeBlocking/setUp") == 0)
    sscanf(payload.c_str(), "%d:%d:%d:%d", &startHour, &startMinute, &endHour, &endMinute);

  if(strcmp(topic, "/SV22/out/TimeBlocking") == 0) {
    digitalWrite(pinGreenLed, LOW);
    lcd.clear();
    lcd.setCursor(6, 0);
    lcd.print("TIME");
    lcd.setCursor(4, 1);
    lcd.print("BLOCKING");
    tone(pinBuzzer, 2000, 200);
    TimeBlocking = true;
    step = 1;
  }

  if(strcmp(topic, "/SV22/out/TimeBlocking/stop") == 0)
    if(TimeBlocking)
      stop = true;
  
}


bool countdown() {
  unsigned long currentTime = millis();
  if(currentTime >= lastCountdownTime + 1000) {
    tm1637.displayTime(minute, second, colon);
    colon = !colon;
    second--;
    if(second == -1) {
      second = 59;
      minute--;
      if(minute == -1)
        return true;
    }
    lastCountdownTime = currentTime;
  }
  return false;
}


void checkStop() {
  int greenButtonState = digitalRead(pinGreenButton);
  if(greenButtonState != lastGreenState) {
    lastMillis = millis();
    lastGreenState = greenButtonState;
  }

  while(digitalRead(pinGreenButton) == HIGH) {
    if(millis() > lastMillis + 5000) {
      lastMillis = millis();
      stop = true;
      break;
    }
  }
}


bool confirm() {
  if(digitalRead(pinGreenButton) == HIGH) {
    digitalWrite(pinGreenLed, LOW);
    return true;
  }
  unsigned long currentTime = millis();
  if(currentTime > lastToneTime + 1000) {
    lastToneTime = currentTime;
    tone(pinBuzzer, 500, 500);
    ledState = !ledState;
    digitalWrite(pinGreenLed, ledState ? HIGH : LOW);
  }
  return false; 
}


void pomodoroTechnique() {
  if(!PomodoroTechnique)
    return;

  checkPresence();
  if(stop) {
    PomodoroTechnique = false;
    client.publish("/SV22/in/PomodoroTechnique/stop", "stop");
    tm1637.displayClear();
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("STOPPED!");
    isPresent = false;
    step = 0;
    stop = false;
    return;
  }
  
  if(step == 1) {
    // Prepare
    checkStop();
    if(stop)
      return;
    if(digitalRead(pinGreenButton) == HIGH || countdown()) {
      tone(pinBuzzer, 2000, 200);
      digitalWrite(pinGreenLed, LOW);
      client.publish("/SV22/in/PomodoroTechnique/start", "start1");
      minute = 0;
      second = 10;
      step = 2;
    }
  }
  else if(step == 2) {
    // Cycle 1
    checkStop();
    if(stop)
      return;
    if(countdown()) {
      tone(pinBuzzer, 2000, 200);
      delay(400);
      tone(pinBuzzer, 2000, 200);
      delay(400);
      tone(pinBuzzer, 2000, 200);
      client.publish("/SV22/in/PomodoroTechnique/break", "break");
      minute = 0;
      second = 10;
      step = 3;
    }
  }
  else if(step == 3) {
    // Break 1
    checkStop();
    if(stop)
      return;
    ledState = !ledState;
    digitalWrite(pinGreenLed, ledState ? HIGH : LOW);
    if(countdown())
      step = 4;
  }
  else if(step == 4 && confirm()) {
    client.publish("/SV22/in/PomodoroTechnique/start", "start2");
    minute = 0;
    second = 10;
    step = 5;
  }
  else if(step == 5) {
    // Cycle 2
    checkStop();
    if(stop)
      return;
    if(countdown()) {
      tone(pinBuzzer, 2000, 200);
      delay(400);
      tone(pinBuzzer, 2000, 200);
      delay(400);
      tone(pinBuzzer, 2000, 200);
      client.publish("/SV22/in/PomodoroTechnique/break", "break");
      minute = 0;
      second = 10;
      step = 6;
    }
  }
  else if(step == 6) {
    // Break 2
    checkStop();
    if(stop)
      return;
    ledState = !ledState;
    digitalWrite(pinGreenLed, ledState ? HIGH : LOW);
    if(countdown())
      step = 7;
  }
  else if(step == 7 && confirm()) {
    client.publish("/SV22/in/PomodoroTechnique/start", "start3");
    minute = 0;
    second = 10;
    step = 8;
  }
  else if(step == 8) {
    // Cycle 3
    checkStop();
    if(stop)
      return;
    if(countdown()) {
      tone(pinBuzzer, 2000, 200);
      delay(400);
      tone(pinBuzzer, 2000, 200);
      delay(400);
      tone(pinBuzzer, 2000, 200);
      client.publish("/SV22/in/PomodoroTechnique/break", "break");
      minute = 0;
      second = 10;
      step = 9;
    }
  }
  else if(step == 9) {
    // Break 3
    checkStop();
    if(stop)
      return;
    ledState = !ledState;
    digitalWrite(pinGreenLed, ledState ? HIGH : LOW);
    if(countdown())
    step = 10;
  }
  else if(step == 10 && confirm()) {
    client.publish("/SV22/in/PomodoroTechnique/start", "start4");
    minute = 0;
    second = 10;
    step = 11;
  }
  else if(step == 11) {
    // Cycle 4
    checkStop();
    if(stop)
      return;
    if(countdown()) {
      client.publish("/SV22/in/PomodoroTechnique/end", "end");
      tone(pinBuzzer, 500, 2000);
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("CONGRATULATIONS!");
      isPresent = false;
      step = 0;
      PomodoroTechnique = false;
    }
  }
}


//===============================================================================
// Các hàm của Lâm Tiến Huy
void timeSV22() {
  DateTime now = rtc.now();
  nowHour = now.hour();
  nowMinute = now.minute();
  tm1637.displayTime(nowHour, nowMinute, colon);
  colon = !colon;
}

void ledControl() {
  if(isLedAuto) {
    if(lightVal < 300) {
      digitalWrite(pinRelayForLed, HIGH);
      if(!isLedSend) {
        client.publish("/SV22/in/mode/auto/led", "on");
        isLedSend = true;
      }
    }
    else {
      digitalWrite(pinRelayForLed, LOW);
      if(isLedSend) {
        client.publish("/SV22/in/mode/auto/led", "off");
        isLedSend = false;
      }
    }
  }
}


void fanControl() {
  if(isFanAuto) {
    if(tempVal > 25 || humidVal > 40) {
      digitalWrite(pinRelayForFan, HIGH);
      if(!isFanSend) {
        client.publish("/SV22/in/mode/auto/fan", "on");
        isFanSend = true;
      }
    }
    else {
      digitalWrite(pinRelayForFan, LOW);
      if(isFanSend) {
        client.publish("/SV22/in/mode/auto/fan", "off");
        isFanSend = false;
      }
    }
  }
}


void timeBlocking() {
  if(!TimeBlocking)
    return;

  if(stop) {
    TimeBlocking = false;
    client.publish("/SV22/in/TimeBlocking/stop", "stop");
    tm1637.displayClear();
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("STOPPED!");
    isPresent = false;
    step = 0;
    stop = false;
    return;
  }

  timeSV22();
  
  if(step == 1) {
    if(startHour == endHour && startMinute == endMinute) {
      client.publish("/SV22/in/TimeBlocking/makeError", "error");
      TimeBlocking = false;
      lcd.clear();
      step = 0;
      return;
    }
    step = 2;
  }
  else if(step == 2) {
    checkStop();
    if(stop)
      return;
    hour = nowHour;
    minute = nowMinute + 1;
    if(minute == 60) {
      minute = 0;
      hour++;
      if(hour == 24)
        hour = 0;
    }
    if(hour == startHour && minute == startMinute) {
      client.publish("/SV22/in/TimeBlocking/remind", "remind");
      client.publish("/SV22/in/TimeBlocking/Notification", "");
      step = 3;
    }
  }
  else if(step == 3) {
    checkStop();
    if(stop)
      return;
    if(nowHour == startHour && nowMinute == startMinute) {
      client.publish("/SV22/in/TimeBlocking/confirmStart", "confirmStart");
      step = 4;
    }
  }
  else if(step == 4) {
    checkStop();
    if(stop)
      return;
    if(confirm()) {
      client.publish("/SV22/in/TimeBlocking/start", "start");
      isPresent = true;
      lastDistance = getDistance();
      step = 5;
    }
  }
  else if(step == 5) {
    checkPresence();
    checkStop();
    if(stop)
      return;
    if(nowHour == endHour && nowMinute == endMinute) {
      client.publish("/SV22/in/TimeBlocking/confirmEnd", "confirmEnd");
      step = 6;
    }
  }
  else if(step == 6) {
    checkStop();
    if(stop)
      return;
    if(confirm()) {
      client.publish("/SV22/in/TimeBlocking/end", "end");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("CONGRATULATIONS!");
      isPresent = false;
      step = 0;
      TimeBlocking = false;
    }
  }
}


//===============================================================================
void setup() {
  Serial.begin(9600);
  Serial.print("Connecting to WiFi: ");
  wifiConnect();
  ThingSpeak.begin(ThingSpeakClient);

  client.setServer(MQTTServer, port);
  client.setCallback(callback);
  client.setKeepAlive(90);

  pinMode(pinRedButton, INPUT);
  pinMode(pinGreenButton, INPUT);

  pinMode(pinBuzzer, OUTPUT);
  pinMode(pinGreenLed, OUTPUT);

  DHT22.setup(pinDHT22, DHTesp::DHT22);
  pinMode(pinPhotoSensor, INPUT);
  pinMode(pinSoundSensor, INPUT);

  pinMode(pinPIR, INPUT);
  pinMode(pinTRIG, OUTPUT);
  pinMode(pinECHO, INPUT);

  pinMode(pinRelayForLed, OUTPUT);
  pinMode(pinRelayForFan, OUTPUT);

  lcd.init();
  lcd.backlight();
  lcd.print("Welcome to SV22!");

  tm1637.begin(CLK, DIO, 4);
  tm1637.setBrightness(7);
  
  if (! rtc.begin())
    Serial.println("Couldn't find RTC");

  randomSeed(analogRead(0));
}


void loop() {
  if(!client.connected()) {
    Serial.print("Connecting to MQTT: ");
    mqttConnect();
  }
  client.loop();

  int redButtonState = digitalRead(pinRedButton);
  if(redButtonState != lastRedState) {
    if(redButtonState == HIGH) {
      isWorking = !isWorking;
      if(isWorking)
        client.publish("/SV22/in/turnOn", "on");
      else client.publish("/SV22/in/turnOff", "off");
    }
    lastRedState = redButtonState;
  }

  if(isWorking) {
    if(step == 0) {
      ledSV22();
      timeSV22();
    }

    getEnvData();
    storeEnvData();
    publishEnvData();

    ledControl();
    fanControl();

    pomodoroTechnique();
    timeBlocking();
    
    
  }
  else {
    lcd.clear();
    tm1637.displayClear();
    digitalWrite(pinGreenLed, LOW);
    digitalWrite(pinBuzzer, LOW);
    digitalWrite(pinRelayForLed, LOW);
    digitalWrite(pinRelayForFan, LOW);
    PomodoroTechnique = false;
    TimeBlocking = false;
    TimeBoxing = false;
    step = 0;
  }
  lastMillis = millis();
  delay(200);
}
Sound SensorBreakout
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
GND5VSDASCLSQWRTCDS1307+
4-Digit Display