/*
 Autores: Alba Sánchez Ibáñez, Lucas Colbert Eastgate
 Refatorado e corrigido por Gemini
 
 Este código foi refeito para usar a biblioteca PID_v1.h do Arduino.
 A lógica de controle agora é delegada para a biblioteca, tornando o
 código mais robusto e fácil de entender.
*/
#include <math.h>
#include <PID_v1.h>
#include "DHT.h"
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
// PINOS
const byte LEDROJO = 3;
const byte LEDVERDE = 5;
const byte THERMISTOR_PIN = 0;
const byte TRANSISTOR_PIN = 6; // PWM Pin para controle de potência
const byte DHT_PIN = 7;
const byte HUMIDITY_RELAY_PIN = 9;
const byte LCD_SDA_PIN = A4;
const byte LCD_SCL_PIN = A5;
// PERIODOS em milissegundos
const unsigned long PERIODO_TMP_MS = 1000;
const unsigned long PERIODO_MONITOR_MS = 1000;
const unsigned long PERIODO_DHT_MS = 2000; // DHT requires a 2-second delay between reads
const unsigned long PERIODO_DAY_MS = 8640; // 24 horas em milissegundos (24 * 60 * 60 * 1000)
// VALORES DO TERMISTOR (NTC 10kOhm)
const float THERMISTOR_T0 = 25.0; // Temperatura de referência
const float THERMISTOR_R0 = 10000.0; // Resistência na temperatura de referência
const float THERMISTOR_B = 3950.0; // Constante Beta
const int RESISTOR_DIVISOR = 10000; // Resistor em série com o termistor
// VALORES DE TEMPERATURA
double TMP_REF = 38.0; // Setpoint para a incubadora
const double TMP_MIN = 37.0;
const double TMP_MAX = 39.0;
// VALORES DE UMIDADE
double HUM_REF = 60.0; // Setpoint para a umidade
const double HUM_MIN = 58.0;
const double HUM_MAX = 62.0;
// VALORES DO CONTROLADOR PID
const float KP = 0.4044;
const float KI = 0.0100;
const float KD = 3.3718;
// VARIÁVEIS DE CONTROLE DO PID
double pid_input;
double pid_output;
// Cria a instância do objeto PID
PID myPID(&pid_input, &pid_output, &TMP_REF, KP, KI, KD, DIRECT);
// ESTRUTURAS DE ESTADO
struct Tmp {
  unsigned long last_ms;
  float val;
};
struct Monitor {
  unsigned long last_ms;
};
struct Humidificador {
  unsigned long last_ms;
  float val;
};
struct Incubadora {
  unsigned long last_ms;
  byte dia=0;
};
// Instâncias
Tmp tmp;
Monitor mtr;
Humidificador hum;
Incubadora inc;
// Objetos para os componentes
DHT dht(DHT_PIN, DHT22);
LiquidCrystal_I2C lcd(0x27, 16, 2); // Ajuste o endereço se necessário (0x3F ou 0x27)
// --- FUNÇÕES DE CÁLCULO ---
float val2tmp(int analog_value) {
  float resistance = RESISTOR_DIVISOR / ((1023.0 / analog_value) - 1.0);
  float celsius = 1.0 / (log(resistance / THERMISTOR_R0) / THERMISTOR_B + 1.0 / (THERMISTOR_T0 + 273.15)) - 273.15;
  return celsius;
}
float val2hum() {
  float h = dht.readHumidity();
  if (isnan(h)) {
    return -1.0; // Retorna um valor inválido em caso de erro de leitura
  }
  return h;
}
// --- SETUP DAS TAREFAS ---
void setup_tmp(unsigned long curr_ms, struct Tmp& tmp) {
  pinMode(LEDVERDE, OUTPUT);
  tmp.last_ms = curr_ms - PERIODO_TMP_MS;
  tmp.val = 0;
}
void setup_monitor(unsigned long curr_ms, struct Monitor& mtr) {
  Serial.begin(9600);
  while (!Serial);
  Serial.println(" # > Arduino Control de Temperatura e Umidade PID");
  Serial.println(" # $ - y20 :45 - w4 - l37.0 - l38.0 - l39.0 - tTemperatura - tCalefactor - tUmidade - tUmidificador");
  mtr.last_ms = curr_ms - PERIODO_MONITOR_MS;
}
void setup_pid() {
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0, 256);
  myPID.SetSampleTime(1000); 
}
void setup_dht(unsigned long curr_ms, struct Humidificador& hum) {
  pinMode(HUMIDITY_RELAY_PIN, OUTPUT);
  dht.begin();
  hum.last_ms = curr_ms - PERIODO_DHT_MS;
  hum.val = 0;
}
void setup_lcd(unsigned long curr_ms, struct Incubadora& inc) {
  lcd.init();
  lcd.backlight();
  inc.last_ms = curr_ms - PERIODO_DAY_MS;
  inc.dia;
}
// --- TAREFAS DE LOOP ---
void tarea_tmp(unsigned long curr_ms, struct Tmp& tmp) {
  if (curr_ms - tmp.last_ms >= PERIODO_TMP_MS) {
    tmp.last_ms += PERIODO_TMP_MS;
    int val = analogRead(THERMISTOR_PIN);
    tmp.val = val2tmp(val);
    byte est_tmp = (TMP_MIN <= tmp.val && tmp.val <= TMP_MAX) ? HIGH : LOW;
    digitalWrite(LEDVERDE, est_tmp);
  }
}
void tarea_dht(unsigned long curr_ms, struct Humidificador& hum) {
  if (curr_ms - hum.last_ms >= PERIODO_DHT_MS) {
    hum.last_ms += PERIODO_DHT_MS;
    hum.val = val2hum();
    // Lógica simples de controle ON/OFF para umidade
    if (hum.val != -1.0) { // Verifica se a leitura é válida
      if (hum.val < HUM_REF) {
        digitalWrite(HUMIDITY_RELAY_PIN, HIGH); // Ativa o umidificador
      } else {
        digitalWrite(HUMIDITY_RELAY_PIN, LOW); // Desativa o umidificador
      }
    }
  }
}
void tarea_monitor(unsigned long curr_ms, struct Monitor& mtr, const struct Tmp& tmp, const double& rele_estado, const struct Humidificador& hum_data) {
  if (curr_ms - mtr.last_ms >= PERIODO_MONITOR_MS) {
    mtr.last_ms += PERIODO_MONITOR_MS;
    if (Serial) {
      Serial.print(tmp.val);
      Serial.print(" ");
      Serial.print(rele_estado);
      Serial.print(" ");
      Serial.print(hum_data.val);
      Serial.print(" ");
      Serial.print(digitalRead(HUMIDITY_RELAY_PIN));
      Serial.print(" ");
      Serial.println(analogRead(TRANSISTOR_PIN));
    }
  }
}
void tarea_lcd(unsigned long curr_ms, struct Tmp& tmp, struct Humidificador& hum, struct Incubadora& inc) {
  if (curr_ms - inc.last_ms >= PERIODO_DAY_MS && inc.dia <= 21) {
    inc.last_ms += PERIODO_DAY_MS;
    inc.dia++;
  }
  
  lcd.setCursor(0, 0);
  lcd.print("Dia: ");
  if (inc.dia < 10) lcd.print("0");
  lcd.print(inc.dia);
  lcd.print(" de 21");
  
  lcd.setCursor(0, 1);
  lcd.print("T:");
  lcd.print(tmp.val, 1);
  lcd.print("C H:");
  lcd.print(hum.val, 1);
  lcd.print("%");
}
// --- MAIN ---
void setup() {
  unsigned long curr_ms = millis();
  pinMode(TRANSISTOR_PIN, OUTPUT);
  pinMode(LEDROJO, OUTPUT);
  
  setup_tmp(curr_ms, tmp);
  setup_monitor(curr_ms, mtr);
  setup_pid();
  setup_dht(curr_ms, hum);
  setup_lcd(curr_ms, inc);
}
void loop() {
  unsigned long curr_ms = millis();
  
  // Tarefas de leitura e controle
  tarea_tmp(curr_ms, tmp);
  tarea_dht(curr_ms, hum);
  
  // Atualiza a entrada do PID e computa o novo valor de saída
  pid_input = tmp.val;
  myPID.Compute();
  
  // Aplica a saída do PID para o aquecedor e o LED vermelho (PWM)
  analogWrite(TRANSISTOR_PIN, pid_output);
  analogWrite(LEDROJO, pid_output);  
  // Tarefa de monitoramento serial e LCD
  tarea_monitor(curr_ms, mtr, tmp, pid_output, hum);
  tarea_lcd(curr_ms, tmp, hum, inc);
}