#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <WiFi.h>
#include <PubSubClient.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define LIGHT_SENSOR_PIN 36
#define TRIGGER_PIN 18
#define ECHO_PIN 5
#define LED_PIN_1 19
#define LED_PIN_2 4

float duration_us, distance_cm;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
#define DHT_PIN 2
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

// WiFi
const char *ssid = "Wokwi-GUEST"; // Enter your Wi-Fi name
const char *password = "";  // Enter Wi-Fi password

// MQTT Broker
const char *mqtt_broker = "broker.emqx.io";
const char *topic_temp = "emqx/esp32/temperature";
const char *topic_hum = "emqx/esp32/humidity";
const char *topic_light = "emqx/esp32/light";
const char *topic_distance = "emqx/esp32/distance";
const char *mqtt_username = "emqx";
const char *mqtt_password = "public";
const int mqtt_port = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {
    Serial.begin(115200);
    dht.begin();

    // SSD1306 initialization
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        Serial.println(F("SSD1306 allocation failed"));
        for (;;) ;
    }
    delay(500);
    display.clearDisplay();
    display.setTextColor(WHITE);

    // Sensor pins initialization
    pinMode(TRIGGER_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    pinMode(LED_PIN_1, OUTPUT);
    pinMode(LED_PIN_2, OUTPUT);

    // Connecting to WiFi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.println("Connecting to WiFi..");
    }
    Serial.println("Connected to the Wi-Fi network");

    // Connecting to the MQTT broker
    client.setServer(mqtt_broker, mqtt_port);
    client.setCallback(callback);
    while (!client.connected()) {
        String client_id = "esp32-client-";
        client_id += String(WiFi.macAddress());
        Serial.printf("The client %s connects to the public MQTT broker\n", client_id.c_str());
        if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
            Serial.println("Public EMQX MQTT broker connected");
        } else {
            Serial.print("failed with state ");
            Serial.print(client.state());
            delay(2000);
        }
    }
    // Publish a message to notify the connection
    client.publish(topic_temp, "ESP32 connected");
}

void loop() {
    delay(1000);

    // Reading temperature and humidity
    float temperature = dht.readTemperature();
    float humidity = dht.readHumidity();
    if (isnan(humidity) || isnan(temperature)) {
        Serial.println("Failed to read from DHT sensor");
        return;
    }

    // Display temperature and humidity on the OLED
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("Temperature: ");
    display.setTextSize(2);
    display.setCursor(0, 10);
    display.print(temperature);
    display.print(" ");
    display.setTextSize(1);
    display.cp437(true);
    display.write(167);
    display.setTextSize(2);
    display.print("C");
    display.setTextSize(1);
    display.setCursor(0, 35);
    display.print("Humidity: ");
    display.setTextSize(2);
    display.setCursor(0, 45);
    display.print(humidity);
    display.print(" %");
    display.display();

    // Publish temperature and humidity to MQTT
    char tempString[8];
    dtostrf(temperature, 1, 2, tempString);
    client.publish(topic_temp, tempString);

    char humString[8];
    dtostrf(humidity, 1, 2, humString);
    client.publish(topic_hum, humString);

    // Light sensor reading
    int lightValue = analogRead(LIGHT_SENSOR_PIN);
    Serial.print("Light sensor: ");
    Serial.println(lightValue);
    // Publish light sensor value to MQTT
    char lightString[8];
    dtostrf(lightValue, 1, 0, lightString); // No decimal places for light sensor value
    client.publish(topic_light, lightString);

    // Ultrasonic sensor reading
    digitalWrite(TRIGGER_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIGGER_PIN, LOW);
    duration_us = pulseIn(ECHO_PIN, HIGH);
    distance_cm = 0.017 * duration_us;

    if (distance_cm < 30) {
        digitalWrite(LED_PIN_1, HIGH);
    } else {
        digitalWrite(LED_PIN_1, LOW);
    }

    Serial.print("Distance: ");
    Serial.print(distance_cm);
    Serial.println(" cm");
    // Publish distance sensor value to MQTT
    char distanceString[8];
    dtostrf(distance_cm, 1, 2, distanceString); // 2 decimal places for distance
    client.publish(topic_distance, distanceString);

    Blink(LED_PIN_2);
    client.loop();
    delay(500);
}

void Blink(int pin) {
    digitalWrite(pin, HIGH);
    delay(10);
    digitalWrite(pin, LOW);
}

void callback(char *topic, byte *payload, unsigned int length) {
    Serial.print("Message arrived in topic: ");
    Serial.println(topic);
    Serial.print("Message: ");
    for (int i = 0; i < length; i++) {
        Serial.print((char) payload[i]);
    }
    Serial.println();
    Serial.println("-----------------------");
}