/*
  Home Automation System

  This smart home automation system keeps an eye on your living space 
  through multiple sensors connected to an ESP32. It monitors temperature, 
  light levels, and noise, displaying real time sound readings on a colorful 
  LED strip that changes from green to red as volume increases. The system sends 
  Telegram alerts when conditions go above certain set thresholds, like when it's too hot 
  (oven been left on for too long) too bright (light in the room was left on), 
  or too noisy for too long (party too loud). Everything connects through MQTT to a Node-RED 
  dashboard where you can monitor all your home conditions at once. It's a complete 
  IoT solution that helps you maintain a comfortable environment while being mindful 
  of neighbors.

  The circuit:
  * DHT (temperature sensor) - Pin 33
  * Light sensor - Pin 34
  * Sound sensor - Pin 32
  * LED Strip - Pin 13

  Video link: https://www.youtube.com/watch?v=OmoFRWe68Xg&ab_channel=OronPaz
  
  Created By:
  Oron Paz - 326647914
  Natan Yudka - 808767
  Simon Abadi - 807633
*/ 


#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <FastLED.h>

// WiFi credentials
const char* ssid = YOUR_WIFI_NAME;
const char* password = YOUR_WIFI_PASSWORD;

// Local MQTT broker setup
const char* mqtt_server = YOUR_IP; // IP of your Node-RED server
const int mqtt_port = 1883;

// MQTT topics for temperature
const char* mqtt_temp_topic = "home/kitchen/oven/temperature";
const char* mqtt_temp_alert_topic = "home/kitchen/oven/alert";

// MQTT topics for light
const char* mqtt_light_topic = "home/room/light/level";
const char* mqtt_light_alert_topic = "home/room/light/alert";

// MQTT topics for sound
const char* mqtt_sound_topic = "home/room/sound/level";
const char* mqtt_sound_alert_topic = "home/room/sound/alert";

// DHT sensor setup
#define DHTPIN 33        // ESP32 pin connected to DHT sensor
#define DHTTYPE DHT22    // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE);

// Light sensor setup
#define LIGHT_SENSOR_PIN 34  // ESP32 analog pin for light sensor (LDR)

// Sound sensor setup
#define SOUND_SENSOR_PIN 32  // ESP32 analog pin for microphone/sound sensor

// LED strip setup
#define LED_PIN     13       // ESP32 pin connected to LED strip data line
#define NUM_LEDS    12       // Number of LEDs in your strip
#define LED_TYPE    WS2812B  // Type of LED strip
#define COLOR_ORDER GRB      // RGB order for your LED strip
CRGB leds[NUM_LEDS];         // Define the array of LEDs
#define MAX_BRIGHTNESS 150   // Maximum brightness (0-255)

// Create WiFi and MQTT clients
WiFiClient espClient;
PubSubClient client(espClient);

// Variables for temperature monitoring
unsigned long lastTempMsgTime = 0;
const long tempInterval = 2000;  // Publish every 2 seconds
unsigned long highTempStartTime = 0;
bool isHighTemp = false;
bool tempAlertSent = false;
float tempThreshold = 25.0;  // Alert if temperature exceeds 30°C

// Variables for light monitoring
unsigned long lastLightMsgTime = 0;
const long lightInterval = 2000;  // Publish every 2 seconds
unsigned long brightLightStartTime = 0;
bool isBrightLight = false;
bool brightAlertSent = false;
int brightThreshold = 1400;  // Alert if light level exceeds this value

// Variables for sound monitoring
unsigned long lastSoundMsgTime = 0;
const long soundInterval = 1000;  // Publish every 1 second
unsigned long loudSoundStartTime = 0;
bool isLoudSound = false;
bool soundAlertSent = false;
int soundThreshold = 2000;  // Alert if sound level exceeds this value (adjust based on your microphone)
const long soundAlertDuration = 10000;  // 10 seconds of loud noise before alerting

// Variables for LED visualization
int maxSoundReading = 300;  // Maximum expected sound reading
int currentSoundLevel = 0;   // Current sound level for LED visualization

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32HomeMonitor-";
    clientId += String(random(0xffff), HEX);
    
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  
  // Initialize sensors
  dht.begin();
  
  // Set ADC resolution (ESP32 is 12-bit by default)
  analogReadResolution(12);  // 0-4095
  
  // Initialize LED strip
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.setBrightness(MAX_BRIGHTNESS);
  
  // Show startup sequence
  startupLEDSequence();
}

void monitorTemperature() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - lastTempMsgTime >= tempInterval) {
    lastTempMsgTime = currentMillis;
    
    // Read temperature
    float temperature = dht.readTemperature();
    
    // Check if reading is valid
    if (!isnan(temperature)) {
      // Temperature alert logic
      if (temperature > tempThreshold) {
        if (!isHighTemp) {
          // Start timing when temp first goes above threshold
          isHighTemp = true;
          highTempStartTime = currentMillis;
          Serial.println("High temperature detected, starting timer");
        } else {
          // Check if it's been hot for more than 5 seconds
          if (!tempAlertSent && (currentMillis - highTempStartTime > 5000)) {
            // Send alert
            String alertMsg = "ALERT: Oven has been hot for over 5 seconds!";
            client.publish(mqtt_temp_alert_topic, alertMsg.c_str());
            tempAlertSent = true;
            Serial.println("Temperature alert sent: " + alertMsg);
          }
        }
      } else {
        // Reset when temperature drops
        if (isHighTemp && temperature < (tempThreshold - 2.0)) {  // Add hysteresis
          isHighTemp = false;
          tempAlertSent = false;
          Serial.println("Temperature returned to normal");
        }
      }
      
      // Always publish the current temperature
      char tempString[8];
      dtostrf(temperature, 6, 2, tempString);
      
      Serial.print("Publishing temperature: ");
      Serial.println(tempString);
      
      client.publish(mqtt_temp_topic, tempString);
    } else {
      Serial.println("Failed to read from DHT sensor!");
    }
  }
}

void monitorLight() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - lastLightMsgTime >= lightInterval) {
    lastLightMsgTime = currentMillis;
    
    // Read light level and invert it
    int rawLightLevel = analogRead(LIGHT_SENSOR_PIN);
    int lightLevel = 4095 - rawLightLevel; // Invert the reading (0-4095 range)
    
    // Now higher value means brighter light
    if (lightLevel > brightThreshold) {
      if (!isBrightLight) {
        // Start timing when light first goes above threshold
        isBrightLight = true;
        brightLightStartTime = currentMillis;
        Serial.println("Bright light detected, starting timer");
      } else {
        // Check if it's been bright for more than 20 seconds
        if (!brightAlertSent && (currentMillis - brightLightStartTime > 20000)) {
          // Send alert
          String alertMsg = "ALERT: Room has been too bright for over 20 seconds!";
          client.publish(mqtt_light_alert_topic, alertMsg.c_str());
          brightAlertSent = true;
          Serial.println("Bright light alert sent: " + alertMsg);
        }
      }
    } else {
      // Reset when light level decreases (gets darker)
      if (isBrightLight && lightLevel < (brightThreshold - 200)) {  // Add hysteresis
        isBrightLight = false;
        brightAlertSent = false;
        Serial.println("Light returned to normal brightness");
      }
    }
    
    // Always publish the inverted light level
    char lightString[8];
    itoa(lightLevel, lightString, 10);
    
    Serial.print("Publishing light level (inverted): ");
    Serial.println(lightString);
    
    client.publish(mqtt_light_topic, lightString);
  }
}

void startupLEDSequence() {
  // Clear all LEDs
  fill_solid(leds, NUM_LEDS, CRGB::Black);
  FastLED.show();
  delay(500);
  
  // Blue wave startup sequence
  for(int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB::Blue;
    FastLED.show();
    delay(50);
  }
  
  for(int i = NUM_LEDS-1; i >= 0; i--) {
    leds[i] = CRGB::Black;
    FastLED.show();
    delay(50);
  }
}

void updateLEDStrip(int soundLevel) {
  // Calculate how many LEDs should be lit based on sound level
  int ledsToLight = map(soundLevel, 0, maxSoundReading, 0, NUM_LEDS);
  ledsToLight = constrain(ledsToLight, 0, NUM_LEDS);
  
  // Update LED display
  for(int i = 0; i < NUM_LEDS; i++) {
    if(i < ledsToLight) {
      // Set color based on level - green to yellow to red as sound increases
      if(i < NUM_LEDS * 0.5) {
        // First half: green gradient
        leds[i] = CRGB::Green;
      } else if(i < NUM_LEDS * 0.75) {
        // Next quarter: yellow gradient
        leds[i] = CRGB::Yellow;
      } else {
        // Last quarter: red gradient
        leds[i] = CRGB::Red;
      }
    } else {
      // Turn off LEDs that shouldn't be lit
      leds[i] = CRGB::Black;
    }
  }
  
  // Show the updated LED strip
  FastLED.show();
}

void monitorSound() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - lastSoundMsgTime >= soundInterval) {
    lastSoundMsgTime = currentMillis;
    
    // Read sound level from microphone
    // Take multiple samples to get a more stable reading
    int soundSum = 0;
    int samples = 10;
    
    for(int i = 0; i < samples; i++) {
      int soundValue = analogRead(SOUND_SENSOR_PIN);
      soundSum += soundValue;
      delay(1); // Short delay between readings
    }
    
    int soundLevel = soundSum / samples;
    
    // Save current sound level for LED visualization
    currentSoundLevel = soundLevel;
    
    // Update LED strip based on sound level
    updateLEDStrip(soundLevel);
    
    // Check if sound is above threshold
    if (soundLevel > soundThreshold) {
      if (!isLoudSound) {
        // Start timing when sound first goes above threshold
        isLoudSound = true;
        loudSoundStartTime = currentMillis;
        Serial.println("Loud sound detected, starting timer");
      } else {
        // Check if it's been loud for more than the alert duration threshold
        if (!soundAlertSent && (currentMillis - loudSoundStartTime > soundAlertDuration)) {
          // Send alert
          String alertMsg = "ALERT: Noise level has been too high for over 10 seconds! Current level: " + String(soundLevel);
          client.publish(mqtt_sound_alert_topic, alertMsg.c_str());
          soundAlertSent = true;
          Serial.println("Sound alert sent: " + alertMsg);
          
          // Flash all LEDs red to indicate alert sent
          for(int j = 0; j < 3; j++) {
            fill_solid(leds, NUM_LEDS, CRGB::Red);
            FastLED.show();
            delay(200);
            fill_solid(leds, NUM_LEDS, CRGB::Black);
            FastLED.show();
            delay(200);
          }
          // Restore LED display
          updateLEDStrip(soundLevel);
        }
      }
    } else {
      // Reset when sound level decreases
      if (isLoudSound && soundLevel < (soundThreshold - 300)) {  // Add hysteresis
        isLoudSound = false;
        if (soundAlertSent) {
          // Send recovery message
          String recoveryMsg = "Noise level has returned to normal: " + String(soundLevel);
          client.publish(mqtt_sound_alert_topic, recoveryMsg.c_str());
          Serial.println(recoveryMsg);
          
          // Flash all LEDs green to indicate recovery
          for(int j = 0; j < 3; j++) {
            fill_solid(leds, NUM_LEDS, CRGB::Green);
            FastLED.show();
            delay(200);
            fill_solid(leds, NUM_LEDS, CRGB::Black);
            FastLED.show();
            delay(200);
          }
          // Restore LED display
          updateLEDStrip(soundLevel);
        }
        soundAlertSent = false;
        Serial.println("Sound returned to normal level");
      }
    }
    
    // Always publish the current sound level
    char soundString[8];
    itoa(soundLevel, soundString, 10);
    
    Serial.print("Publishing sound level: ");
    Serial.println(soundString);
    
    client.publish(mqtt_sound_topic, soundString);
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  // Monitor all sensors
  monitorTemperature();
  monitorLight();
  monitorSound();
}
