/*
  ############################################################################
  # Codigo Criado por: MarioParente
  # Data: __/__/2023
  # Detalhes do projeto: v0.1 - Hitórico de temperatura das ultima 24h
  #                             
  # # Atualizações:
  # 20/08/23 - v0.1   - Introduzido suporte para diferentes tipos de sensores: DHT22 e BMP180
  # Set/2024 - v0.2   - Implementado relógio para inicialização da hora
  #          - v0.2.1 - Realizada limpeza e otimização do código para melhor desempenho
  #          - v0.3   - Adicionado loop de tempo para controle contínuo
  #          - v0.3.1 - Melhorada a organização das funções e otimização do código
  #          - v0.3.2 - Desenvolvido termômetro dinâmico para visualização de temperatura
  #          - v0.3.3 - Mais otimizações no código para eficiência
  # Out/2024 - v0.3.4 - Funções de exibição e menu reformuladas e otimizadas
  ############################################################################*/

  // DHT22  Testado em __/__/____
  // BMP180 Testado em 25/09/2024
  // Clock  Testado em 26/09/2024


#include <Adafruit_SSD1306.h> // Biblioteca para o display OLED
#include <DHTesp.h>           // Biblioteca para o sensor DHT22
#include <BMP180I2C.h>        // Biblioteca para o sensor BMP180
#include <RTClib.h>           // Biblioteca para o RTC
#define I2C_ADDRESS 0x77 // Endereço I2C do sensor BMP180

//Variáveis globais 
  const int DHT_PIN = 15; // Pino do sensor DHT22

  // Definição de variáveis de estado do sensor
  int Sensor = 0;

  // Definição de variáveis para temperatura e umidade
  int T = 0, H = 0;

  // Definição de variáveis para temperatura e umidade atuais, máximas e mínimas
  int Atual_T = 0, Max_T = 0, Min_T = 0; 
  int Atual_H = 0, Max_H = 0, Min_H = 0; 

  int Menu = 0; // Controle do menu

  // Ponto de tempo e coordenadas para gráfico
  int T_Ponto = -1; // Ponto de tempo para gráfico
  int Ponto_A = 0, Ponto_B = 0; // Coordenadas para o gráfico

  // Instância dos sensores e do display
  DHTesp dhtSensor;
  TempAndHumidity data; 
  BMP180I2C bmp180(I2C_ADDRESS); // Instância do sensor BMP180
  Adafruit_SSD1306 display(128, 64, &Wire); // Instância do display OLED

  // Instância do RTC
  RTC_DS1307 rtc; 
  //RTC_DS3231 rtc; 
  
  int falhasConsecutivas = 0;  // Contador de falhas consecutivas na leitura do RTC
  int Ano_ini = 0;

//

void ini_Sensor() {
  // Auto detecção do sensor BMP180
  if (!bmp180.begin()) {
    display.setCursor(0, 30);
    Serial.println(F("BMP180 Não Encontrado"));
    display.print(F("BMP180 Nao Encontrado"));
  } else {
   display.setCursor(15, 30); 
    Serial.println(F("BMP180 Encontrado"));
    display.print(F("BMP180 Encontrado")); 
    Sensor = 2; // Define o sensor BMP como ativo
    }

  if (Sensor == 0) { // Verifica se o BMP não foi encontrado

    // Auto deteção do sensotr DHT22
     // Obtém dados de temperatura e umidade do sensor DHT
    TempAndHumidity data = dhtSensor.getTempAndHumidity();
     
    if (String(data.temperature) == "nan") {
    
      display.setCursor(5, 20);
      Serial.println(F("DHT22 Não Encontrado"));
      display.print(F("DHT22 Nao Encontrado")); 
     } else {
      display.setCursor(20, 20);
      Serial.println(F("DHT22 Encontrado"));
      display.print(F("DHT22 Encontrado")); 
      Sensor = 1; // Define o sensor DHT como ativo
    }
  }
  
  // Verifica a inicialização do RTC
  if (!rtc.begin()) {
    display.setCursor(10, 40);      
    display.print(F("RTC Não Encontrado"));
    Serial.println("RTC Nao Encontrado");    
  } else {
    display.setCursor(25, 40);     
    display.print(F("RTC Encontrado"));
    Serial.println("RTC DS3231 Encontrado");   
  }

  // Exibe a hora atual no Serial Monitor
  DateTime now = rtc.now();
  Ano_ini = now.year();
  Serial.println(String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " " + String(now.hour()) + ":" + String(now.minute()));
  delay(100);
}

void Sensor_DHT() {
  // Obtém dados de temperatura e umidade do sensor DHT
  TempAndHumidity data = dhtSensor.getTempAndHumidity();
  
  // Converte os valores para inteiros  
  if (String(data.temperature) == "nan") {    
    Advertencia(); 
  } else {
    T = constrain(data.temperature, 0, 60);
    H = constrain(data.humidity, 0, 99);
  }
}
// Add adv
void Sensor_BMP() {
  // Mede a temperatura com o BMP180
  bmp180.measureTemperature();
  delay(100);  // Aguarda a medição ser concluída

  // Verifica se a medição foi realizada com sucesso
  if (bmp180.hasValue()) {
    T = constrain(bmp180.getTemperature(), 0, 60);
  }

  // Como o BMP180 não mede umidade, define H como 0
  H = 0;
}

void Tempo() {
  // Obtém a hora e minutos do RTC
  DateTime now = rtc.now();
  
  // Verifica se o RTC está retornando uma data/hora válida
  if (now.year() < Ano_ini) {  // Se o ano lido for menor que o gravado na inicialização consideramos uma falha
    falhasConsecutivas++;
    if (falhasConsecutivas >= 5) {
      //rtcValido = false;
      Advertencia();  // Chama a função de advertência
    }

  } else {
    falhasConsecutivas = 0;
    // Atualiza o display apenas se o RTC estiver funcionando
    int horaAtual = now.hour();
    int minutoAtual = now.minute();
  
    // Calcula o tempo total em blocos de 15 minutos
    int MarcadorAtual = ((horaAtual * 60) + minutoAtual) / 15;

    // Verifica se um novo ponto deve ser gerado
    if (T_Ponto != MarcadorAtual) {
      T_Ponto = MarcadorAtual;
      Ponto(T_Ponto, T); // Chama a função para gerar o ponto
    }   
  }
}

void Alteracao() {
  Menu = (Menu + 1) % 3;  // Alterna o Menu entre 0, 1 e 2
}

void Iniciando() {
  // Inicializa o sensor 
  if (Sensor == 1) {
    Sensor_DHT();
  } else if (Sensor == 2) {
    Sensor_BMP();
  }
  // Define valores iniciais de temperatura e umidade
  Atual_T = Max_T = Min_T = T;
  Atual_H = Max_H = Min_H = H;

  // Obtém a hora e minutos do RTC
  DateTime now = rtc.now();
  int totalMinutos = now.hour() * 60 + now.minute(); // Cálculo total de minutos

  // Calcula Ponto_A e Ponto_B
  Ponto_A = (totalMinutos / 15) + 29; // Ponto_A ajustado
  Ponto_B = map(Atual_T, 60, 0, 15, 51); // Mapeia Atual_T para Ponto_B
}

void ExibirDados(int tipo, int x, int n) {
  display.fillRect(33, 0, 80, 8, BLACK);
  //display.drawRect(33, 0, 80, 8, 1);
  switch (tipo) {
    case 0:  // Exibe valores atuais
      display.setCursor(33, 0);
      display.print("Atual:" + String(x) + "C " + String(n) + "%");
      break;
    case 1:  // Exibe valores máximos
      Max_T = max(Max_T, x);  // Atualiza Max_T se x for maior
      Max_H = max(Max_H, n);  // Atualiza Max_H se n for maior
      display.setCursor(33, 0);
      display.print("Max  :" + String(Max_T) + "C " + String(Max_H) + "%");
      break;
    case 2:  // Exibe valores mínimos
      Min_T = min(Min_T, x);  // Atualiza Min_T se x for menor
      Min_H = min(Min_H, n);  // Atualiza Min_H se n for menor
      display.setCursor(33, 0);
      display.print("Min  :" + String(Min_T) + "C " + String(Min_H) + "%");
      break;
  }
}

void Grafico() {
  display.drawLine(27, 53, 126, 53, 1); // linha Horizontal
  display.drawLine(27, 13, 27, 53, 1); // linha Vertical
  // Pontos linha horizontal
  for (int i = 0; i <= 4; i++) {
    display.drawPixel(29 + i * 24, 54, 1);
    display.drawPixel(29 + i * 24, 55, 1);
  }
  // Pontos linha Vertical
  for (int i = 0; i <= 4; i++) {
    display.drawPixel(25, 15 + i * 9, 1);
    display.drawPixel(26, 15 + i * 9, 1);
  } 
}

void Texto_grafico() {
  // Texto horizontal hora
  const char* horas[] = {"0", "6", "12", "18"};
  int posX_horas[] = {27, 51, 72, 96};
  
  for (int i = 0; i < 4; i++) {
    display.setCursor(posX_horas[i], 57);
    display.print(horas[i]);
  }
  // Texto vertical temperatura
  const char* temperaturas[] = {"60", "45", "30", "15", " 0"};
  int posY_temperaturas[] = {12, 21, 30, 39, 48};

  for (int i = 0; i < 5; i++) {
    display.setCursor(13, posY_temperaturas[i]);
    display.print(temperaturas[i]);
  }
  // Legenda
  display.setCursor(0, 0);
  display.print("Temp");
  display.setCursor(0, 57);
  display.print("Hora");
}

void criarTermometro() {
  // Marcação
  display.drawLine(9, 15, 10, 15, WHITE);  // Marcação na altura 14
  display.drawLine(9, 24, 10, 24, WHITE);  // Marcação na altura 23
  display.drawLine(9, 33, 10, 33, WHITE);  // Marcação na altura 32
  display.drawLine(9, 42, 10, 42, WHITE);  // Marcação na altura 41
  display.drawPixel(10, 51, WHITE);  // Marcação na altura 50

  // Termômetro
  display.drawPixel(5, 13, WHITE);  // Topo do tubo
  display.drawPixel(4, 14, WHITE);  // Canto superior esquerdo
  display.drawPixel(6, 14, WHITE);  // Canto superior direito
  display.drawLine(3, 15, 3, 54, WHITE);  // Linha esquerda do tubo
  display.drawLine(7, 15, 7, 54, WHITE);  // Linha direita do tubo
  display.drawLine(4, 49, 6, 49, WHITE);  // Divisão entre tubo e bulbo
  display.drawLine(2, 51, 2, 53, WHITE);  // Linha esquerda do bulbo
  display.drawLine(8, 51, 8, 53, WHITE);  // Linha direita do bulbo    
  display.drawLine(4, 55, 6, 55, WHITE);  // Base do bulbo
  display.drawRect(3, 51, 5, 3, BLACK);  // Espaço no meio do bulbo
  display.drawPixel(5, 49, BLACK);  // Espaço no meio    
  display.fillCircle(5, 52, 1, WHITE);  // Bulbo
}

void Ponto(int x, int y) {
  // Ajuste para o eixo x
  x += 29;

  // Limita o valor de y entre 0 e 60
  y = constrain(y, 0, 60);
  
  // Mapeia o valor de y para as coordenadas da tela
  y = map(y, 60, 0, 15, 51);

  // Desenha uma linha a partir do último ponto até o ponto atual
  if (Ponto_A == 124) {  //Verifica se estamos no final do gráfico (23:45) para desenhar até 24:00 (x = 125)
    display.drawLine(Ponto_A, Ponto_B, 125, y, 1); // Desenha até 24:00
   
    // Reinicia o gráfico no novo dia
    Ponto_A = 29;
    Ponto_B = y;
    display.drawLine(Ponto_A, Ponto_B, Ponto_A, Ponto_B, 1); // Desenha também o novo 00:00

  } else {
    // Desenha o ponto normalmente
    display.drawLine(Ponto_A, Ponto_B, x, y, 1);

    // Atualiza Ponto_A e Ponto_B para o próximo ponto
    Ponto_A = x;
    Ponto_B = y;
  }
}

void Mercurio(int a){
  a = constrain(a, 0, 60);
  a = map(a, 0, 60, 51, 15);
  a = constrain(a, 15, 47);
  display.drawLine(5, 47, 5, 14, BLACK); // Apaga o antigo  
  display.drawLine(5, 47, 5, a, WHITE); // Desenha o novo nível do mercúrio
}

void Advertencia() {
  // Desenhar linha vertical
  display.drawLine(122, 3, 122, 6, WHITE);  // Ponto inicial (118, 3) até (118, 6)
  
  // Desenhar pixel
  display.drawPixel(122, 8, WHITE);         // Pixel em (118, 8)

  // Desenhar triângulo
  display.drawTriangle(122, 0, 117, 9, 127, 9, WHITE);  // Vértices (118, 0), (113, 9), (123, 9)
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.begin(115200);
  Serial.println("###  Iniciando  ###");

  // Inicializa o display
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 
  display.clearDisplay();
  
  // Configura o sensor DHT
  dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
  
  // Ajusta o contraste do display
  display.ssd1306_command(SSD1306_SETCONTRAST);
  display.ssd1306_command(25); // Define o nível de brilho para 10%
  
  // Exibe a mensagem inicial
  display.setTextColor(WHITE, BLACK);
  display.setTextSize(1);
  display.setCursor(20, 0);
  display.print(F("Auto-Dedetecao"));

  // Inicializa os sensores e outros componentes
  ini_Sensor();
  display.display();

  // Pausa para a visualização da mensagem
  delay(2000);
  display.clearDisplay();
  
  // Chama funções para configurar o gráfico e iniciar a exibição
  Texto_grafico();
  Grafico();
  criarTermometro();  
  Iniciando();
  display.display();
}

void loop() {
  analogWrite(LED_BUILTIN, Menu == 0 ? 0 : 250);
  // Seleção do sensor e obtenção dos dados
  if (Sensor == 1) {
    Sensor_DHT(); // Lê os dados do sensor DHT
  } else if (Sensor == 2) {
    Sensor_BMP(); // Lê os dados do sensor BMP
  }

  // Exibe as informações no display de acordo com o menu atual
  ExibirDados(Menu, T, H);
  Tempo(); 
  Alteracao();
  Mercurio(T);
  display.display();
  delay(1000);
}
GND5VSDASCLSQWRTCDS1307+