/*
  This code aims to provide a smart clock alarm.
  
  By getting the relevant message from Adafruit IO, the alarm will start to ring.
  To turn off the alarm, light sensor should indicate that there is light in the area three touch sensors need to be pressed simultaneously.
  The alert status (playing or not) and the light status in the room are reflected in the Adafruit IO dashboard.

  In addition, the ESP32 uses a sensor to receive the temperature and humidity in the environment and additionally makes an
  API request to OpenWeatherMapto receive the same data.
  The four data are sent and updated in the Adafruit IO dashboard.
  
  On the dashboard it is required to mark an appropriate dressing temperature as well as when you have finished dressing.
  This data affects the blink light that is connected to ESP32 and will turn off only when the requested and correct parameters are marked.


  The circuit:
  * Input: Touch sensors, light sensor, temperature and humidity sensor, 
  * Output: Speaker, blink

  Created By:
  Guy Laor
*/


#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <DHT_U.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#define DHTPIN 15
#define DHTTYPE    DHT22

const char* ssid = "your_network_name";
const char* password = "12345678";

const char* mqtt_server = "io.adafruit.com";
const int mqtt_port = 1883;
const char* mqtt_username = "userName";
const char* mqtt_password = "aio_abcabcabc";

const char* alarmStatusTopic = "userName/feeds/AlarmStatus";
const char* lightStatusTopic = "userName/feeds/LightStatus";
const char* roomTempertureTopic = "userName/feeds/RoomTemperature";
const char* roomHumidityTopic = "userName/feeds/RoomHumidity";
const char* actualTempertureTopic = "userName/feeds/ActualTemperature";
const char* actualHumidityTopic = "userName/feeds/ActualHumidity";
const char* correctDegreesForDressingTopic = "userName/feeds/CorrectDegreesForDressing";
const char* dressStatusTopic = "userName/feeds/DressStatus";

const int BLINK_PIN = 17; // Define intput pin for light sensor
const int LIGHT_SENSOR_PIN = 32; // Define intput pin for light sensor
const int TONE_OUTPUT_PIN = 26; // Define the output pin for the speaker
const int TONE_PWM_CHANNEL = 0; // Define the PWM channel for tone generation

int touchSensorPins[] = {T4, T5, T6};
int touchThresholds[] = {10, 10, 10};
unsigned long lastTouchTime[3] = {0};
unsigned long debounceDelay = 200;

DHT_Unified dht(DHTPIN, DHTTYPE);

WiFiClient espClient;
PubSubClient mqttClient(espClient);

unsigned long lightSensorDarkLimit = 3000;
unsigned long lastWeatherMessage = 0;

bool isAlarmActive = false;
bool isLight = false;
bool isDressStatus = false;
bool isCorrectDegreesForDressing = false;

String tempStr;
float temperatureFromSensor, humidityFromSensor;
String actualTemperature, actualHumidity;
String jsonBuffer;

// ---------------Connetivity fields---------------

String openWeatherMapApiKey = "yourApiKey";

// Latitude&Longtitude of Lima, Peru:
String lat = "-12.046373";
String lon = "-77.042755";

// To present temperture in Celcius units:
String tempertureUnit = "metric";

String openWeatherUrl = "https://api.openweathermap.org/data/2.5/weather?units=" + tempertureUnit +"&lat=" + lat + "&lon=" + lon + "&appid=" + openWeatherMapApiKey;
// ---------------End Connetivity fields---------------

String getJsonContent(const char* url) {
  String payload = "{}";
  WiFiClientSecure client;
  client.setInsecure();
  HTTPClient https;
  https.begin(client, url);
  int rc = https.GET();
  if (rc > 0) {
    Serial.printf("HTTP Response code: %d\n", rc);
    payload = https.getString();
  }
  else {
    Serial.printf("Error code: %d\n", rc);
  }
  https.end();
  return payload;
}



void playAlarm() {
  Serial.println("Alarm is ON");
  isAlarmActive = true;
  tone(TONE_OUTPUT_PIN, 1000);
  delay(10);
}

void turnOffAlarm() {
  // Logic to turn off the alarm sound
  Serial.println("Alarm is OFF");
  isAlarmActive = false;
  noTone(TONE_OUTPUT_PIN);
}

void updateAlarmStatus(const char* value) {
  // Update AlarmStatus Feed with the specified value
  mqttClient.publish(alarmStatusTopic, value);
}

void updateLightStatus(unsigned long lightSensorValue) {
  if (checkIfLight(lightSensorValue) and !isLight) {
    isLight = true;
    const char* value = "LIGHT";
    mqttClient.publish(lightStatusTopic, value);
  }
  else if (!checkIfLight(lightSensorValue) and isLight) {
    isLight = false;
    const char* value = "DARK";
    mqttClient.publish(lightStatusTopic, value);
  }
}

void setupMqtt() {
  mqttClient.setServer(mqtt_server, mqtt_port);
  mqttClient.setCallback(alarmStatusCallback);
}

void checkMqtt() {
  if (!mqttClient.connected()) {
    reconnectMqtt();
  }
  mqttClient.loop();
}

void reconnectMqtt() {
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    if (mqttClient.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
      Serial.println("connected");
      mqttClient.subscribe(alarmStatusTopic);
      mqttClient.subscribe(correctDegreesForDressingTopic);
      mqttClient.subscribe(dressStatusTopic);
    }
    else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void connectToWifi() {
  Serial.println("Connecting to WiFi...");
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void alarmStatusCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  Serial.write(payload, length);
  Serial.println();

  // Handle incoming messages
  if (strcmp(topic, alarmStatusTopic) == 0) {
    handleAlarmStatusMessage(payload, length);
  } else if (strcmp(topic, correctDegreesForDressingTopic) == 0) {
    handleCorrectDegreesForDressingMessage(payload, length);
  } else if (strcmp(topic, dressStatusTopic) == 0) {
    handleDressStatusMessage(payload, length);
  }
}

void handleAlarmStatusMessage(byte* payload, unsigned int length) {
    if (length == 2 && strncmp((char*)payload, "ON", 2) == 0) {
      Serial.println("ON message received");
      // Activate the alarm
      playAlarm();
      // Reset correctDegreesForDressingTopic and dressStatusTopic
      tempStr = String(0);
      mqttClient.publish(correctDegreesForDressingTopic, tempStr.c_str());
      tempStr = "DOIT";
      mqttClient.publish(dressStatusTopic, tempStr.c_str());
      // Turn on blink
      updateBlinkIfNecessary();
    } else {
      Serial.println("OFF message received");
      // Deactivate the alarm
      turnOffAlarm();
    }
}

void handleCorrectDegreesForDressingMessage(byte* payload, unsigned int length) {
  if (length == 2 && strncmp((char*)payload, actualTemperature.c_str(), 2) == 0) {
    Serial.println("Correct degrees message received");
    isCorrectDegreesForDressing = true;
  }
  else {
    Serial.println("Incorrect degrees message received");
    isCorrectDegreesForDressing = false;
  }
}

void handleDressStatusMessage(byte* payload, unsigned int length) {
  if (length == 4 && strncmp((char*)payload, "DONE", 4) == 0) {
    Serial.println("Done message received");
    isDressStatus = true;
  } else {
    Serial.println("DO It message received");
    isDressStatus = false;
  }
}


bool checkIfLight(unsigned long lightSensorValue) {
  return lightSensorValue < lightSensorDarkLimit;
}

void readDHT22() {
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  Serial.println("Room weather details:");
  if (isnan(event.temperature)) {
    Serial.println(F("Error reading temperature sensor!"));
  }
  else {
    Serial.print(F("Temperature from sensor: "));
    Serial.print(event.temperature);
    Serial.println(F("°C"));
    temperatureFromSensor = event.temperature;
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    Serial.println(F("Error reading humidity sensor!"));
  }
  else {
    Serial.print(F("Humidity from sensor: "));
    Serial.print(event.relative_humidity);
    Serial.println(F("%"));
    humidityFromSensor = event.relative_humidity;
  }
}

void readWeatherDetails() {
  jsonBuffer = getJsonContent(openWeatherUrl.c_str());
  const size_t capacity = JSON_OBJECT_SIZE(3) + 1000;
  DynamicJsonDocument myObject(capacity);
  DeserializationError error = deserializeJson(myObject, jsonBuffer);
  if (error) {
    Serial.print("Failed to parse JSON: ");
    Serial.println(error.c_str());
    return;
  }

  Serial.println("Actual weather details:");
  actualTemperature = myObject["main"]["temp"].as<String>();
  actualHumidity = myObject["main"]["humidity"].as<String>();
  
  Serial.print(F("Actual Temperature: "));
  Serial.print(actualTemperature);
  Serial.println(F("°C"));

  Serial.print(F("Actual Humidity: "));
  Serial.print(actualHumidity);
  Serial.println(F("%"));
}

void publishSensorWeatherDetails() {
  Serial.println("publishing sensor's weather details");
    tempStr = String(temperatureFromSensor);
    mqttClient.publish(roomTempertureTopic, tempStr.c_str());
    tempStr = String(humidityFromSensor);
    mqttClient.publish(roomHumidityTopic, tempStr.c_str());
}
void publishActualWeather() {
  Serial.println("publishing actual weather details");
  tempStr = String(actualTemperature);
  mqttClient.publish(actualTempertureTopic, tempStr.c_str());
  tempStr = String(actualHumidity);
  mqttClient.publish(actualHumidityTopic, tempStr.c_str());
}

void updateBlinkIfNecessary() {
  if (isDressStatus && isCorrectDegreesForDressing) {
    digitalWrite(BLINK_PIN, LOW);    // turn the LED off
  } else {
    digitalWrite(BLINK_PIN, HIGH);   // turn the LED on
  }
}

void setup() {
  Serial.begin(115200);
  connectToWifi();
  setupMqtt();
  ledcAttachPin(TONE_OUTPUT_PIN, TONE_PWM_CHANNEL);
  pinMode(LIGHT_SENSOR_PIN, INPUT_PULLUP);
  pinMode(BLINK_PIN, OUTPUT);
  dht.begin();
}

void loop() {
  // Check MQTT messages and handle alarm status
  checkMqtt();

  // Check touch sensors
  int touchedCount = 0;

  for (int i = 0; i < 3; i++) {
    int touchValue = touchRead(touchSensorPins[i]);
    unsigned long currentTime = millis();

    // If the touch sensor value is below the threshold and debounce time has passed
    if (touchValue < touchThresholds[i] && (currentTime - lastTouchTime[i] > debounceDelay)) {
      touchedCount++;
      lastTouchTime[i] = currentTime;  // Update last touch time for debounce
      delay(200);
    }
  }

  // Check light sensor
  int lightSensorValue = analogRead(LIGHT_SENSOR_PIN);
  
  if (isAlarmActive and touchedCount == 3 and checkIfLight(lightSensorValue)) {
    // Turn off the alarm and update AlarmStatus Feed
    turnOffAlarm();
    updateAlarmStatus("OFF");
  }

  updateLightStatus(lightSensorValue);

  
  unsigned long now = millis();
  if (now - lastWeatherMessage > 60000 || actualTemperature.isEmpty()) {
    lastWeatherMessage = now;
    // Check temperature & humidity sensor
    readDHT22();
    delay(50);
    publishSensorWeatherDetails();
    delay(50);

    // Check Actual temperature & humidity details
    readWeatherDetails();
    delay(50);
    publishActualWeather(); 
  }

  // update blink
  updateBlinkIfNecessary();

  delay(100); // Adjust as needed for the desired loop speed
}
