//Imports
#include <LiquidCrystal.h>
#include <DHT.h>

#define DHTPIN 9 // Pino do DHT
#define DHTTYPE DHT22  // Tipo de sensor DHT

DHT dht(DHTPIN, DHTTYPE); // DHT

LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2); // Pinos do display LCD

float lastAvgTemp; // Armazena a última média de temperatura
float lastAvgHumd; // Armazena a última média de umidade

float tempReadings[10]; // Array para armazenar as últimas 10 leituras de temperatura
float humdReadings[10]; // Array para armazenar as últimas 10 leituras de umidade
int currentIndex = 0; // Índice para rastrear a posição atual no array de leituras

int lightLevel; //Nível de luz

const int pinLDR = A0;  // Pino do LED do LDR
const int ledRed = 8;   // Pino do LED vermelho
const int ledYel = 7;   // Pino do LED amarelo
const int ledGre = 6;   // Pino do LED verde
const int pinBUZ = 13;  // Pino do buzzer

void setup() {
  Serial.begin(115200); // Inicializa a comunicação serial
  lcd.begin(16, 2); // Inicializa o LCD com 16 colunas e 2 linhas
  dht.begin(); // Inicializa o sensor DHT

  analogWrite(pinBUZ, OUTPUT); // Inicializa buzzer

  // Inicializa LEDs
  pinMode(ledRed, OUTPUT);
  pinMode(ledYel, OUTPUT);
  pinMode(ledGre, OUTPUT);

  // Inicializa LDR
  pinMode(pinLDR, INPUT);

  booting(); // Exibe uma mensagem inicial no LCD
  delay(1000); // Aguarda 1 segundo
  lcd.clear(); // Limpa o LCD
  lcd.setCursor(4, 0);
  lcd.print("Loading"); // Exibe uma mensagem de loading para as primeiras 10 leituras
}

void loop() {
  int valorLDR = analogRead(pinLDR); 
  lightLevel = map(valorLDR, 500, 969, 0, 100); //Utiliza a função map para interpretar os valores do LDR

  float temp = dht.readTemperature(); // Lê a temperatura do sensor DHT
  float humid = dht.readHumidity();   // Lê a umidade do sensor DHT

  verifyLDR(); // Verifica o nível de luz


    //Verifica se há algum LED de 'atenção' ligado e se o ambiente está escuro
  if (digitalRead(ledYel) == HIGH || digitalRead(ledRed) == HIGH && valorLDR < 40) {

    // Verifica se o LED amarelo pode ser desligado
    if (10 < lastAvgTemp && lastAvgTemp < 15) {
      digitalWrite(ledYel, LOW);
    }
    // Verifica se o LED vermelho pode ser desligado
    if (50 < lastAvgHumd && lastAvgHumd < 70) {
      digitalWrite(ledRed, LOW);
    }
  }
  

  // Verifica se o buzzer pode ser desligado. Exije ambiente escuro
  if (digitalRead(ledYel) == LOW && digitalRead(ledRed) == LOW && valorLDR < 40) {
    noTone(pinBUZ);
  }

  tempReadings[currentIndex] = temp;  // Armazena a leitura atual da temperatura
  humdReadings[currentIndex] = humid;  // Armazena a leitura atual da umidade

  currentIndex = (currentIndex + 1) % 10; // Atualiza o índice para a próxima leitura

  if (currentIndex == 9) {
    tenthRead();
  }

  // Exibe informações no monitor serial
  serialLog(temp, humid, valorLDR);

  delay(500); // Aguarda 500 milissegundos
}

// Exibe uma mensagem de inicialização no LCD
void booting() {
  lcd.setCursor(3, 1);
  tone(pinBUZ, 400);
  lcd.print("NEXT FUTURE "); // Nome da empresa
  noTone(pinBUZ);
  delay(1000);
  move_pac(); // Exibe animação do pacman
  lcd.clear(); // Limpa o LCD
  delay(200);
  slogan(); // Exibe um slogan animado no LCD
}

// Aniamção do "Pac-Man" comendo a logo da empresa no LCD
void move_pac() {
  String line = "NEXT FUTURE";
  ghost(); // Exibe uma imagem de fantasma no LCD
  move_image(14, 1, 10); // Move e exibe uma imagem no LCD
  for (int pos = 0; pos < 15; pos++) {
    move(pos, line); // Move uma imagem no LCD
    line.remove(0, 1);
    tone(pinBUZ, 500);
    delay(150);
    noTone(pinBUZ);
    lcd.clear(); // Limpa o LCD
  }
}

// Move a imagem de "Pac-Man" e alterna entre boca aberta e fechada
void move(int pos, String texto) {
  if (pos % 2 == 0) {
    fechado(); // Se a posição for par, a boca estará fechada

  } else {
    aberto();  // Se a posição for ímpar, a boca estará aberta
  }
  if (texto != "") { // Verifica se a string não está vazia
    lcd.setCursor(pos + 3, 1); // Define a posição do cursor no LCD
    lcd.print(texto); // Imprime texto no LCD
  }

  ghost_move(pos);

  move_image(pos + 0, 0, 4);
  move_image(pos + 1, 0, 5);
  move_image(pos + 2, 0, 6);
  move_image(pos + 0, 1, 7);
  move_image(pos + 1, 1, 8);
  move_image(pos + 2, 1, 9);

  delay(300); // Aguarda 300 milissegundos antes de continuar a animação
}

// Define a imagem de "Pac-Man" com a boca fechada
void fechado() {
  // Define a matriz de bits para a imagem de "Pac-Man" com a boca fechada
  byte i4[8] = {B00000, B00000, B00000, B00001, B00010, B00010, B00100, B00100};
  byte i5[8] = {B00000, B00000, B11111, B00000, B00000, B00110, B00110, B00000};
  byte i6[8] = {B00000, B00000, B00000, B10000, B01000, B01000, B00100, B00100};
  byte i7[8] = {B00100, B00100, B00010, B00010, B00001, B00000, B00000, B00000};
  byte i8[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B00000, B00000};
  byte i9[8] = {B00100, B00100, B01000, B01000, B10000, B00000, B00000, B00000};

  // Cria os caracteres personalizados para a boca fechada de "Pac-Man"
  lcd.createChar(4, i4);
  lcd.createChar(5, i5);
  lcd.createChar(6, i6);
  lcd.createChar(7, i7);
  lcd.createChar(10, i8);
  lcd.createChar(9, i9);
}

// Define a imagem de "Pac-Man" com a boca aberta
void aberto() {
  // Define a matriz de bits para a imagem de "Pac-Man" com a boca aberta
  byte i4[8] = {B00000, B00000, B00000, B00001, B00010, B00010, B00100, B00100};
  byte i5[8] = {B00000, B00000, B11111, B00000, B00000, B00110, B00110, B00000};
  byte i6[8] = {B00000, B00000, B00000, B10000, B01000, B00100, B01000, B10000};
  byte i7[8] = {B00100, B00100, B00010, B00010, B00001, B00000, B00000, B00000};
  byte i8[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B00000, B00000};
  byte i9[8] = {B10000, B01000, B00100, B01000, B10000, B00000, B00000, B00000};

  // Cria os caracteres personalizados para a boca aberta de "Pac-Man"
  lcd.createChar(4, i4);
  lcd.createChar(5, i5);
  lcd.createChar(6, i6);
  lcd.createChar(7, i7);
  lcd.createChar(8, i8);
  lcd.createChar(9, i9);
}

// Define a imagem de um fantasma
void ghost() {
  byte ghost[8] = {B01110, B11111, B10101, B11011, B11111, B11111, B10101, B00000};
  lcd.createChar(10, ghost);
  move_image(14, 1, 10);
}

// Move a imagem de um fantasma no LCD
void ghost_move(int pos) {
  byte ghost[8] = {B01110, B11111, B10101, B11011, B11111, B11111, B10101, B00000};
  lcd.createChar(10, ghost);
  if (pos % 2 != 0) {
    move_image(15, 1, 10);
  } else {
    move_image(14, 1, 10);
  }
}

// Exibe um slogan animado no LCD
void slogan() {
  String line = "FUTURE IS NOW!";
  for (int i = 0; i < line.length(); i++) {

    //Animação de letras caindo
    lcd.setCursor(i + 1, 0);
    lcd.print(line[i]);
    delay(150);
    lcd.setCursor(i + 1, 0);
    lcd.print(" ");
    lcd.setCursor(i + 1, 1);
    lcd.print(line[i]);

    // Caso o char não seja um blankspace, ativa o buzzer
    if (!(isWhitespace(line[i]))) {
      tone(pinBUZ, 250);
      delay(150);
      noTone(pinBUZ);
    }
  }
}

// Exibe informações no monitor serial
void serialLog(float temp, float humid, int valorLDR) {
  Serial.println("Leitura: " + String(currentIndex + 1));
  Serial.println("Temp: " + String(temp) + "°C");
  Serial.println("Last Avarage Temp: " + String(lastAvgTemp) + "°C");
  Serial.println("Humidity: " + String(humid) + " %");
  Serial.println("Last Avarage Humidity: " + String(lastAvgHumd) + " %");
  Serial.println("Luminosidade: " + String(lightLevel));
  Serial.println("ValorLDR: " + String(valorLDR));
  Serial.println("---");
}

// Calcula a média das últimas 10 leituras e exibe as telas de cada campo
void tenthRead() {
  float sumTemp = 0;
  float sumHumd = 0;

  for (int i = 0; i < 10; i++) {
    sumTemp += tempReadings[i];
    sumHumd += humdReadings[i];
  }

  lastAvgTemp = sumTemp / 10;
  lastAvgHumd = sumHumd / 10;

  show_data();
}

// Exibe dados no LCD
void show_data() {
  show_temp();
  delay(5000);
  show_humid();
  delay(5000);
  show_light();
}

// Exibe a temperatura no LCD
void show_temp() {
  if ((lastAvgTemp < 10 || lastAvgTemp > 15) && lastAvgTemp != 0) {
    digitalWrite(ledYel, HIGH);
    digitalWrite(ledGre, LOW);
    tone(pinBUZ, 500);
  }
  lcd.clear(); // Limpa o LCD
  lcd.setCursor(0, 0);
  lcd.print("Temp: " + String(lastAvgTemp, 1) + " C");
  term_image(lastAvgTemp); // Exibe uma imagem correspondente à temperatura no LCD
}

// Exibe a umidade no LCD
void show_humid() {
  if ((lastAvgHumd < 50 || lastAvgHumd > 70) && lastAvgHumd != 0) {
    digitalWrite(ledRed, HIGH);
    digitalWrite(ledGre, LOW);
    tone(pinBUZ, 400);
  }
  lcd.clear(); // Limpa o LCD
  lcd.setCursor(0, 0);
  lcd.print("Humid: " + String(lastAvgHumd, 1) + " %");
  humid_image(lastAvgHumd);
}

// Exibe a luminosidade no LCD
void show_light() {
  lcd.clear(); // Limpa o LCD
  lcd.setCursor(0, 0);
  lcd.print("Light Lvl: " + String(lightLevel));
  light_image();
}

// Define e exibe uma imagem correspondente à temperatura no LCD
void term_image(float temp) {
  byte termLow2[8] = {B01010, B01010, B01010, B01010, B10001, B10001, B10001, B01110};
  byte termLow1[8] = {B00100, B01010, B01010, B01010, B01010, B01010, B01010, B01010};

  byte termMid2[8] = {B01010, B01010, B01110, B01110, B11111, B11111, B11111, B01110};
  byte termMid1[8] = {B00100, B01010, B01010, B01010, B01010, B01010, B01010, B01010};

  byte termHig2[8] = {B01110, B01110, B01110, B01110, B11111, B11111, B11111, B01110};
  byte termHig1[8] = {B00100, B01110, B01110, B01110, B01110, B01110, B01110, B01110};

  // Cria os caracteres personalizados e os exibe no LCD, dependendo da temperatura
  if (temp < 10) {
    lcd.createChar(9, termLow1);
    lcd.createChar(10, termLow2);
    messageTemplate("Temp. Baixa");

  } else if (temp > 15) {
    lcd.createChar(9, termHig1);
    lcd.createChar(10, termHig2);
    messageTemplate("Temp. Alta");

  } else { // Temperatura ideal
    lcd.createChar(9, termMid1);
    lcd.createChar(10, termMid2);
    messageTemplate("Temp. OK");
  }
  move_image(14, 0, 9); // Move e exibe a primeira imagem no LCD
  move_image(14, 1, 10); // Move e exibe a segunda imagem no LCD
}

// Define e exibe uma imagem correspondente à umidade no LCD
void humid_image(float humid) {
  byte humidLow_1[8] = {B00111, B00011, B00001, B00000, B00000, B00000, B00000, B00000};
  byte humidLow_2[8] = {B00000, B00000, B00000, B00000, B00001, B00001, B00011, B00111};
  byte humidLow_3[8] = {B00000, B00000, B00000, B00000, B10000, B10000, B01000, B11100};
  byte humidLow_4[8] = {B11100, B11000, B10000, B00000, B00000, B00000, B00000, B00000};

  byte humidMid_1[8] = {B01111, B01111, B00111, B00011, B00000, B00000, B00000, B00000};
  byte humidMid_2[8] = {B00000, B00000, B00001, B00001, B00011, B00111, B00111, B01111};
  byte humidMid_3[8] = {B00000, B00000, B10000, B10000, B01000, B00100, B10100, B11110};
  byte humidMid_4[8] = {B11110, B11110, B11100, B11000, B00000, B00000, B00000, B00000};


  byte humidHigh_1[8] = {B11111, B11111, B11111, B01111, B00111, B00011, B00000, B00000};
  byte humidHigh_2[8] = {B00011, B00111, B01111, B01111, B11111, B11111, B11111, B11111};
  byte humidHigh_3[8] = {B11000, B10100, B10010, B10001, B10001, B11001, B11111, B11111};
  byte humidHigh_4[8] = {B11111, B11111, B11111, B11110, B11100, B11000, B00000, B00000};

  if (humid < 50) {
    lcd.createChar(11, humidLow_1);
    lcd.createChar(12, humidLow_2);
    lcd.createChar(13, humidLow_3);
    lcd.createChar(14, humidLow_4);

    messageTemplate("Humid. Baixa");

  } else if (humid > 70) {
    lcd.createChar(11, humidHigh_1);
    lcd.createChar(12, humidHigh_2);
    lcd.createChar(13, humidHigh_3);
    lcd.createChar(14, humidHigh_4);

    messageTemplate("Humid. Alta");

  } else {
    lcd.createChar(11, humidMid_1);
    lcd.createChar(12, humidMid_2);
    lcd.createChar(13, humidMid_3);
    lcd.createChar(14, humidMid_4);

    messageTemplate("Humid. OK");
  }

  move_image(14, 1, 11);
  move_image(14, 0, 12);
  move_image(15, 0, 13);
  move_image(15, 1, 14);
}

//Define e exibe uma imagem correspondente ao nível de luz no LDR
void light_image() {
  byte lampLow1[8] = {B00100, B00100, B00100, B00100, B01110, B01110, B11111, B10001};
  byte lampLow2[8] = {B10001, B10001, B10001, B01110, B00000, B00000, B00000, B00000};

  byte lampMid1[8] = {B00100, B00100, B00100, B00100, B01110, B01110, B11111, B10001};
  byte lampMid2[8] = {B10001, B11111, B11111, B01110, B00000, B00000, B00000, B00000};

  byte lampHigh1[8] = {B00100, B00100, B00100, B00100, B01110, B01110, B11111, B11111};
  byte lampHigh2[8] = {B11111, B11111, B11111, B01110, B00000, B00000, B00000, B00000};

  if (lightLevel >= 40 && lightLevel < 90) {
    lcd.createChar(10, lampLow1);
    lcd.createChar(11, lampLow2);
    messageTemplate("Ambiente Meia-Luz");

  } else if (lightLevel >= 90) {
    lcd.createChar(10, lampHigh1);
    lcd.createChar(11, lampHigh2);
    messageTemplate("Ambiente Muito Claro");

  } else {
    lcd.createChar(10, lampMid1);
    lcd.createChar(11, lampMid2);
     messageTemplate("Ambiente Escuro");
  }

  move_image(15, 0, 10);
  move_image(15, 1, 11);
}

// Verifica o nível de luz do ambiente
void verifyLDR() {
  // Em um ambiente muito claro, liga a luz vermelha
  if (lightLevel >= 80) {
    digitalWrite(ledRed, HIGH);
    digitalWrite(ledGre, LOW);

    //Caso a temperatura tenha sido normalizada, desliga a luz de alerta da temperatura e seu som no buzzer
    if (lastAvgTemp >= 10 && lastAvgTemp <= 15){
      digitalWrite(ledYel, LOW);
      noTone(pinBUZ);
    }
    tone(pinBUZ, 600);

  // Em um ambiente um pouco claro, liga a luz amarela
  } else if (lightLevel < 80 && lightLevel >= 40) {
    digitalWrite(ledYel, HIGH);
    digitalWrite(ledGre, LOW);

    //Caso a umidade tenha sido normalizada, desliga a luz de alerta da umidade e seu som no buzzer
    if (lastAvgHumd >= 50 && lastAvgHumd <= 70 ){
      digitalWrite(ledRed, LOW);
      noTone(pinBUZ);
    }
    tone(pinBUZ, 400);
  } else {
    // Caso todos os dados estejam normalizados e o ambiente esteja escuro, desliga todos os LEDs acesos, desliga o buzzer e liga o LED verde
    if (lastAvgHumd >= 50 && lastAvgHumd <= 70 && lastAvgTemp >= 10 && lastAvgTemp <= 30){
      digitalWrite(ledGre, HIGH);
      digitalWrite(ledRed, LOW);
      digitalWrite(ledYel, LOW);
      noTone(pinBUZ);
    }
    
  }
}

// Move uma imagem no LCD
void move_image(int x, int y, int byt) {
  lcd.setCursor(x, y); // Define a posição do cursor no LCD
  lcd.write(byte(byt)); // Exibe o caractere personalizado no LCD
}

void messageTemplate(String msg){
  lcd.setCursor(0, 1);
  lcd.print(msg);
}

$abcdeabcde151015202530354045505560fghijfghij