#include <WiFi.h>
#include <PubSubClient.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>

#include <TimeLib.h>
#define BLYNK_PRINT Serial       
#include <BlynkSimpleEsp32.h>
#include <WiFiUdp.h>
#include <DHT.h>

// WiFi
char auth[] = "iot2esp32";//58rwI44x1cz4CEF0i-8JRo1nanBif0Ji
const char *ssid = "Wokwi-GUEST"; // Nama SSID WiFi
const char *password = "";  // WiFi password
static const char ntpServerName[] = "0.id.pool.ntp.org";
const int timeZone = 7;

// MQTT Broker
const char *mqtt_broker = "xxxx.sn.mynetname.net";//BROKER PRIVATE TO PUBLIC
const char *topic = "iot/main";
const char *topicDash = "iot/dash";
const char *mqtt_username = "mqtt";
const char *mqtt_password = "support";
const int mqtt_port = 1883;

/*
Author          : Ravenusa Arjuna K. 
Full SourceCode : https://github.com/ravenusa/IoT-SmartHome
*/

/* HC-SR501 Motion Detector */
#define pirPin 39
#define pinKamarMandi 1
#define DHTPIN 36          
#define DHTTYPE DHT22     // DHT 22
#define RFanC 18
#define RFanW 19
#define Buzz 34
#define relay 21

DHT dht(DHTPIN, DHTTYPE);
BlynkTimer timer;

int pirValue;// Place to store read PIR Value
int pinValue;//Variable to read virtual pin
int pirKamarMandi;
int pirValKM = 0;
int pirState = LOW;
int h;
float t;
String statusPir;
String statusPirKm;
String statusDHT;
String statusDHTBly;

BLYNK_WRITE(V0)
{
 pinValue = param.asInt();    
} 

WiFiClient espClient;
PubSubClient client(espClient);

void sendSensor()
{
  h = dht.readHumidity();
  t = dht.readTemperature();
  if (isnan(h) || isnan(t)) {
    Serial.println("Sensor DHT Tidak Terdeteksi !!");
    client.publish(topic, "Sensor DHT Tidak Terdeteksi !!", true);
    return;
  }
 
  Blynk.virtualWrite(V5, t); //suhu virtual 5
  Blynk.virtualWrite(V6, h); //kelembaban virtual 6
}

WiFiUDP Udp;
unsigned int localPort = 8888;

time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  // Set software serial baud to 9600;
  Serial.begin(9600);
  delay(10);
  Blynk.begin(auth, ssid, password, "o.tcp.ap.ngrok.io",13312);//Port bisa berubah-ubah ketika ngrok dijalankan
  dht.begin();
  timer.setInterval(1000L, sendSensor);
  pinMode(pirPin, INPUT);
  pinMode(DHTPIN, INPUT);
  pinMode(RFanC, OUTPUT);
  pinMode(RFanW, OUTPUT);
  pinMode(Buzz, OUTPUT);
  pinMode(relay, OUTPUT);

  Serial.print(" Menghubungan ke Jaringan");
  WiFi.begin("Wokwi-GUEST", "", 6);
  while (WiFi.status() != WL_CONNECTED) {
  delay(100);
  Serial.print("...");

  Wire.begin(2, 3);
  lcd.init();
  lcd.backlight();
  }
  delay(1000);
  Serial.println("\nBerhasil Terhubung ke Jaringan!!");

  //connecting to a mqtt broker
  client.setServer(mqtt_broker, mqtt_port);
  client.setCallback(terimaPesan);

  while (!client.connected()) {
    String client_id = "tujuh-client-MAC-";
    client_id += String(WiFi.macAddress());
    Serial.printf("\nMenghubungkan %s ke Broker\n", client_id.c_str());
      if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
        delay(1000);
        Serial.println("Broker Terhubung!!");
        Serial.println("\nHi, Selamat Datang dibroker Kelompok TUJUH\n");
      } else {
        Serial.print("Gagal Terhubung, Ulangi!! ");
        Serial.print(client.state());
        delay(2000);
      }
  }
  // publish dan subscribe
  client.publish(topic, " Hi, Selamat Datang dibroker Kelompok TUJUH");
  client.subscribe(topic);

  delay(3000);
  Serial.print("IP Addr : ");
  Serial.println(WiFi.localIP());
  setSyncProvider(getNtpTime);
  setSyncInterval(300);
}

void terimaPesan(char *topic, byte *payload,unsigned int length) {
  Serial.print("Pesan dari Topic : ");
  Serial.println(topic);
  Serial.print("Pesan : ");
  for (int i = 0; i < length; i++) {
    Serial.print((char) payload[i]);
  }
  Serial.println();
  Serial.println("=====================================");
  Serial.print("Temperature : ");
  Serial.print(t);
  Serial.print(" | Humidity : ");
  Serial.println(h);
  Serial.println("=====================================");
}

time_t prevDisplay = 0;

void loop() {
  client.loop();

  h = dht.readHumidity();
  t = dht.readTemperature();
  pirState = digitalRead(pinKamarMandi);

  if (pinValue == HIGH)    
    {
      getPirValue();
    }

  if (pirState == 0) {
    lcd.clear();
    lcd.setCursor(1, 0);
    lcd.print("Kamar Mandi");
    lcd.setCursor(1, 1);
    lcd.print("Kosong");
    digitalWrite(relay , LOW); 
    delay(1000);
    pirState = 0;

  }else{
      kamarMandi();
    }

  if (t <=28.5 ){
    digitalWrite(RFanW , HIGH);
    digitalWrite(RFanC , LOW);
    statusDHT = "Pemanas Ruangan Hidup";
    statusDHTBly = " >> Hidup";
  }
  else if (t >=33){
    digitalWrite(RFanW , LOW);  
    digitalWrite(RFanC , HIGH);
    statusDHT = "Kipas Menyala"; 
    statusDHTBly = " ";
  }
  else if (t >=28.6 && t <= 33.1){
    digitalWrite(RFanW , LOW);  
    digitalWrite(RFanC , LOW); 
    statusDHT = "Temp & Humid Normal";
    statusDHTBly = " ";
  }
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) {
      prevDisplay = now();
      digitalClockDisplay();
    }
  }
  if (hour() == 17 && minute() == 15){
    digitalWrite(relay, LOW);
  }
  else if (hour() == 6 && minute() == 0){
    digitalWrite(relay, HIGH);
  }

  String completeValDHT = String(t) + "°C - " + String(h) + "%";
  client.publish(topic, completeValDHT.c_str(), true);

  Blynk.virtualWrite(V7, statusDHT);
  Blynk.virtualWrite(V8, statusDHTBly); 

  StaticJsonDocument<256> doc;
  doc["temp"] = String(t);
  doc["humi"] = String(h);
  doc["rFanW"] = statusDHT;
  doc["PIR"] = statusPir;

  String out;
  serializeJson(doc, out);
  char json[out.length() + 1];
  out.toCharArray(json, out.length() + 1);

  if (client.publish(topicDash, json)) {
    Serial.println("Data berhasil dikirim ke topic " + String(topicDash));
  }else{
    Serial.println("Gagal mengirim data ke topic " + String(topicDash));
  }
  Blynk.run();
  timer.run();
}

void getPirValue(void){
  pirValue = digitalRead(pirPin);
  if (pirValue){ 
    Serial.println("Ada Gerakan !!");
    Blynk.notify("Ada Gerakan !!");  
    client.publish(topic, "Ada Gerakan !!", true);
    statusPir = "Ada Gerakan !!";
    Blynk.virtualWrite(V9, statusPir);
    tone(Buzz, 400);
    delay(300);
    noTone(9);
    delay(300);
    tone(Buzz, 400);
    delay(300);
  }else{
    statusPir = "Tidak Ada Gerakan !!";
    Blynk.virtualWrite(V9, statusPir);
    noTone(Buzz);
  }
}

void kamarMandi(void){
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("Ada Orang");
  lcd.setCursor(1, 1);
  lcd.print("Tunggu Sebentar"); 
  digitalWrite(relay , HIGH); 
  delay(1000);
  pirState = 0;
}

void digitalClockDisplay(){
  // Serial.print("=====================================");
  // Serial.println();
  // Serial.print(hour());
  // printDigits(minute());
  // printDigits(second());
  // Serial.print(" ");
  // Serial.print(day());
  // Serial.print(".");
  // Serial.print(month());
  // Serial.print(".");
  // Serial.print(year());
  // Serial.println();
  String completeTime = String(hour())+":"+String(minute())+":"+String(second());
  client.publish(topic, completeTime.c_str(), true);
}

void printDigits(int digits){
  Serial.print(":");
  if (digits < 10)
  Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime(){
  IPAddress ntpServerIP; // NTP server's ip address
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  // Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  // Serial.print(ntpServerName);
  // Serial.print(": ");
  // Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      //Serial.println("\nReceive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  // Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}
$abcdeabcde151015202530fghijfghij
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module