#include <WiFi.h>
#include <PubSubClient.h>
#include "DHTesp.h"
//---Oled---// 
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>

Adafruit_MPU6050 mpu;

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 disp(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//DHT
DHTesp dht;

#define dhtPin 15
#define rPin 2
#define gPin 4
#define bPin 5
#define spkPin 18

//Sleep Mode
#define sleeptime 120  //tempo definido para dormir em segundos
#define cs 1000000    //conversão para micro segundos

#define wakeuptime 20

//Definir credenciais do wifi, neste caso dados móveis
const char* ssid = "Wokwi-GUEST";
const char* password = "";
//Definir endereço do MQTT
const char* mqtt_server = "66.29.142.107";
//Iniciar o espClient
WiFiClient espClient;
PubSubClient client(espClient);
//Variáveis de tempo auxiliares
long now = millis();
long lastMeasure = 0;
char *MPU_ax;
char *MPU_ay;
char *MPU_az;

void setRGB(int r, int g, int b)
{
  digitalWrite(rPin, !r);
  digitalWrite(gPin, !g);
  digitalWrite(bPin, !b);
}

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

  //This part of the code configures the ESP32 pins to be used as inputs or outputs.
  pinMode(LED_BUILTIN, OUTPUT);//Definir Variáveis como Output/Input
  pinMode(rPin, OUTPUT);//Definir Variáveis como Output/Input
  pinMode(gPin, OUTPUT);//Definir Variáveis como Output/Input
  pinMode(bPin, OUTPUT);//Definir Variáveis como Output/Input
  pinMode(spkPin, OUTPUT);//Definir Variáveis como Output/Input
  setRGB(0,0,1);

  //This part of the code initializes the MPU6050 sensor, displaying an error message if it does not connect.
  while (!mpu.begin()) {
    Serial.println("MPU6050 not connected!");
    setRGB(1,0,0);
    for(;;){
      delay(500);
      setRGB(0,0,0);
      tone(spkPin, 500, 100);
      delay(500);
      setRGB(1,0,0);
    }
  }
  mpu.setGyroRange(MPU6050_RANGE_500_DEG);
  Serial.println("MPU6050 ready!");

  //This part of the code initializes the DHT22 sensor to be used to measure temperature and humidity.
  dht.setup(dhtPin, DHTesp::DHT22);

  //This part of the code initializes the OLED to be used to display information.
  disp.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  disp.println("A");
  disp.display();

  //This part of the code initializes the Wi-Fi network connection so the ESP8266 can connect to it.
  setup_wifi();

  //This part of the code initializes the MQTT client, setting the MQTT server address.
  client.setServer(mqtt_server, 1883);  // uses the string defined with the server address: const char* mqtt_server = "jpsantos.ddns.net"; and the server communication port
  client.setCallback(callback);          // uses the function with prototype: void callback(String topic, byte* message, unsigned int length);
}

// Don't change the function below. This functions connects your ESP8266 to your router
void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // blinks the builtin LED when connected to a WiFi network
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.println("");
  Serial.print("WiFi connected - IP address: ");
  Serial.println(WiFi.localIP());
  delay(1000);
}

//This part of the code implements the callback function, which detects when a device publishes a message to a topic to which the ESP32 is subscribed.

void callback(String topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;

  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
}
// This functions reconnects your ESP8266 to your MQTT broker
// Change the function below if you want to subscribe to more topics with your ESP32
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (client.connect("ESP32Client")) {
      //Pusca duas vezes o led do ESP caso se tenha conetado ao MQTT broker
      digitalWrite(LED_BUILTIN, HIGH);
      delay(100);
      digitalWrite(LED_BUILTIN, LOW);
      delay(100);
      digitalWrite(LED_BUILTIN, HIGH);
      delay(100);
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("connected");
      //É aqui onde é subscrito o tópico do botão que defini no node-red
      client.subscribe("dg/lamp");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // espera 0.5 segundos antes de se tentar conectar
      delay(500);
    }
    client.subscribe("esp32/output");
  }
}

sensors_event_t event;

//This part of the code implements the loop function, which maintains the connection between the ESP32 and the MQTT broker. It also publishes the values of the data obtained by the sensors and displays this data on the OLED display
void loop()
{
  mpu.getGyroSensor()->getEvent(&event);
  Serial.print("[");
  Serial.print(millis());
  Serial.print("] X: ");
  Serial.print(event.gyro.x);
  Serial.print(", Y: ");
  Serial.print(event.gyro.y);
  Serial.print(", Z: ");
  Serial.print(event.gyro.z);
  Serial.println(" m/s^2");
  delay(500);
  //tone(spkPin, 1000, 10);
  const size_t BUFFER_SIZE = 100;
  const char *BUFFER_FORMAT = "%1.3f";
  char buffer[BUFFER_SIZE + 1] = "\0";  // Extra one for NULL terminator.

  snprintf(buffer, BUFFER_SIZE, BUFFER_FORMAT, event.gyro.x);
  client.publish("MPU_ax", buffer);

  snprintf(buffer, BUFFER_SIZE, BUFFER_FORMAT, event.gyro.y);
  client.publish("MPU_ay", buffer);

  snprintf(buffer, BUFFER_SIZE, BUFFER_FORMAT, event.gyro.z);
  client.publish("MPU_az", buffer);


  TempAndHumidity newValues = dht.getTempAndHumidity();
  Serial.println(" T:" + String(newValues.temperature) + " C" + " H:" + String(newValues.humidity) + " %");


  //Humidade e Temperatura
  int value = newValues.temperature;
  static char temp[7];
  dtostrf(value, 3, 2, temp);
  //---///
  int Hmd = newValues.humidity;
  static char hmdchar[7];
  dtostrf(Hmd, 3, 2, hmdchar);

  if (!client.connected()) {
    reconnect();
  }
  if (!client.loop())
    client.connect("ESP32Client");

  now = millis();
  //Temperatura
  client.publish("Temp", temp);
  //Humidade 
  client.publish("Hum", hmdchar);
  //Temperatura
  disp.clearDisplay();                  // clear the internal memory
  disp.setTextColor(SSD1306_WHITE);        // Draw white text
  disp.setCursor(0,0);

  char tmp[1024];
  sprintf(tmp,
   "Temp:%1.2fC\nHumid:%1.2f\nX:%1.2frad/s\nY:%1.2frad/s\nZ:%1.2frad/s\n",
   newValues.temperature,
   newValues.humidity,
   event.acceleration.x,
   event.acceleration.y,
   event.acceleration.z);

  disp.print(tmp);  // write something to the internal memory
  disp.display();

}
void sleep(){
  Serial.print("Modo de economia Ligado");
  esp_sleep_enable_timer_wakeup(sleeptime * cs);//Define O temporizador do ESP32 para Acordar
  esp_deep_sleep_start();

  delay(100);//Atrasa o Ciclo
}