// ================== Plataforma ==================
#if defined(ESP8266)
  #include <ESP8266WiFi.h>
  #define ISR_ATTR ICACHE_RAM_ATTR
  #define USING_ESP8266 1
#else
  #include <WiFi.h>
  #define ISR_ATTR IRAM_ATTR
  #define USING_ESP32 1
#endif
#include <PubSubClient.h>
#include <time.h>
// ================== CONFIG WIFI/MQTT ==================
#define WIFI_SSID  "Wokwi-GUEST"//para o wokwi
#define WIFI_PASS  ""
#define MQTT_HOST  "broker.emqx.io"
#define MQTT_PORT  1883
#define MQTT_TOPIC "AQF"
#define MQTT_QOS   1
// ================== MAPEAMENTO DE PINOS ==================
#if defined(USING_ESP32)
// ---- ESP32 DevKit-v4 ----
  #define ledPin    0
  #define PIN_IN1   14   // FLUXOSTATO FRIO (ATIVO = LOW)
  #define PIN_IN2   27   // FLUXOSTATO QUENTE (ATIVO = LOW)
  #define PIN_JMP1  16   // J1: +5 s
  #define PIN_JMP2  17   // J2: +10 s
  #define PIN_JMP3  18   // J3: +20 s
  #define PIN_OUT1  25   // SFRIO (ativo LOW)
  #define PIN_OUT2  26   // SQUENTE (ativo LOW)
  #define LED_WIFI  2    // LED Wi-Fi conectado
  #define LED_MQTT  4    // LED MQTT conectado
  #define LED_PUB   5    // LED “publicação” (pisca 4 s)
#else
// ---- ESP8266 NodeMCU ----
  #define PIN_IN1   D5   // GPIO14; ATIVO=LOW
  #define PIN_IN2   D6   // GPIO12; ATIVO=LOW
  #define PIN_JMP1  D0   // GPIO16; +5 s
  #define PIN_JMP2  D3   // GPIO0;  +10 s (*strap*)
  #define PIN_JMP3  D4   // GPIO2;  +20 s (*strap*)
  #define PIN_OUT1  D1   // GPIO5  - SFRIO (ativo LOW)
  #define PIN_OUT2  D2   // GPIO4  - SQUENTE (ativo LOW)
  #define LED_WIFI  D0
  #define LED_MQTT  D7
  #define LED_PUB   D8
#endif
// ================== ESTADO GLOBAL ==================
WiFiClient      g_net;
PubSubClient    g_mqtt(g_net);
volatile bool   g_in1Active = false;     // estado lógico (LOW=ativo)
volatile bool   g_in2Active = false;
volatile bool   g_irqIn1 = false;        // flags “houve mudança”
volatile bool   g_irqIn2 = false;
uint32_t        g_lastDeb1 = 0, g_lastDeb2 = 0;
const uint32_t  DEBOUNCE_MS = 40;
bool            g_outputsOn = false;
uint32_t        g_holdUntil = 0;
uint32_t        g_pubBlinkUntil = 0;
unsigned long   g_countDia = 0;
int             g_diaYday  = -1;
// ================== HELPERS ==================
// Ajuste: saídas ativas em LOW
inline void setOutput(int pin, bool on) {
  digitalWrite(pin, on ? LOW : HIGH);
}
inline bool inputActiveRaw(int pin) {
  // pull-up interno: LOW = ativo/aterrado
  return digitalRead(pin) == LOW;
}
uint32_t calcDelayMs() {
  uint32_t s = 0;
  if (digitalRead(PIN_JMP1) == LOW) s += 5;
  if (digitalRead(PIN_JMP2) == LOW) s += 10;
  if (digitalRead(PIN_JMP3) == LOW) s += 20;
  if (s == 0) s = 10; // padrão
  return s * 1000UL;
}
void logSerialAcionamento(time_t t) {
  struct tm tmLocal;
#if defined(ESP8266)
  tmLocal = *localtime(&t);
#else
  localtime_r(&t, &tmLocal);
#endif
  char buf[16];
  strftime(buf, sizeof(buf), "%H:%M:%S", &tmLocal);
  Serial.printf("[%s] acionamento #%lu\n", buf, g_countDia);
}
void publishJSON(time_t t) {
  struct tm tmLocal;
#if defined(ESP8266)
  tmLocal = *localtime(&t);
#else
  localtime_r(&t, &tmLocal);
#endif
  char buf[16];
  strftime(buf, sizeof(buf), "%H:%M:%S", &tmLocal);
  String payload;
  payload.reserve(64);
  payload += "{\"timestamp\":\"";
  payload += buf;          // exemplo: "14:22:05"
  payload += "\",\"count\":";
  payload += g_countDia;
  payload += "}";
  g_mqtt.publish(MQTT_TOPIC, payload.c_str());
  g_pubBlinkUntil = millis() + 4000UL;
}
// ================== ISRs (mínimas, IRAM-safe) ==================
// NÃO usar millis()/digitalRead() aqui.
void ISR_ATTR isrIn1() { g_irqIn1 = true; }
void ISR_ATTR isrIn2() { g_irqIn2 = true; }
// ================== CONEXÃO ==================
void wifiConnect() {
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print('.');
  }
  Serial.println("\nWi-Fi conectado");
}
void ensureMqtt() {
  while (!g_mqtt.connected()) {
    if (g_mqtt.connect("ESP-AQF")) {
      Serial.println("MQTT conectado");
    } else {
      delay(1000);
    }
  }
}
// ================== Tarefas (ESP32/FreeRTOS) ==================
#if defined(USING_ESP32)
void logicTask(void*){
// Estados anteriores para detectar bordas e debouncing
bool lastRaw1 = inputActiveRaw(PIN_IN1);
bool lastRaw2 = inputActiveRaw(PIN_IN2);
g_in1Active = lastRaw1;
g_in2Active = lastRaw2;
g_lastDeb1 = g_lastDeb2 = millis();
for(;;){
  const uint32_t now = millis();
  // Leitura “rápida” quando houver IRQ, mas com debounce no contexto normal
  bool raw1 = lastRaw1, raw2 = lastRaw2;
  if (g_irqIn1 || (now - g_lastDeb1 >= DEBOUNCE_MS)) {
    raw1 = inputActiveRaw(PIN_IN1);
    if (raw1 != lastRaw1) { g_lastDeb1 = now; lastRaw1 = raw1; }
    g_irqIn1 = false;
  }
  if (g_irqIn2 || (now - g_lastDeb2 >= DEBOUNCE_MS)) {
    raw2 = inputActiveRaw(PIN_IN2);
    if (raw2 != lastRaw2) { g_lastDeb2 = now; lastRaw2 = raw2; }
    g_irqIn2 = false;
  }
  // Estados válidos (pós-debounce)
  g_in1Active = lastRaw1;
  g_in2Active = lastRaw2;
  const bool any = g_in1Active || g_in2Active;
  if (any) {
    if (!g_outputsOn) {
      g_countDia++;
      time_t t; time(&t);
      logSerialAcionamento(t);
      if (g_mqtt.connected()) publishJSON(t);
      Serial.println(g_in1Active && g_in2Active ? "[TRIGGER] IN1+IN2"
                  : g_in1Active ? "[TRIGGER] IN1" : "[TRIGGER] IN2");
    }
    g_outputsOn = true;
    setOutput(PIN_OUT1, true);
    setOutput(PIN_OUT2, true);
    g_holdUntil = now + calcDelayMs(); // renova hold enquanto ativo
  } else {
    if ((int32_t)(now - g_holdUntil) >= 0) {
      if (g_outputsOn) Serial.println("[SAIDA] desligando (hold expirado)");
      g_outputsOn = false;
      setOutput(PIN_OUT1, false);
      setOutput(PIN_OUT2, false);
    }
  }
  vTaskDelay(pdMS_TO_TICKS(10)); // 100 Hz
}
}
void netTask(void*){
  for(;;){
    digitalWrite(LED_WIFI, (WiFi.status()==WL_CONNECTED) ? HIGH : LOW);
    digitalWrite(LED_MQTT, g_mqtt.connected() ? HIGH : LOW);
    digitalWrite(LED_PUB,  (millis() < g_pubBlinkUntil) ? HIGH : LOW);
    if (!g_mqtt.connected()) ensureMqtt();
    g_mqtt.loop();
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}
void dailyTask(void*){
  for(;;){
    time_t now; time(&now);
    struct tm tmUtc;
  #if defined(ESP8266)
    tmUtc = *gmtime(&now);
  #else
    gmtime_r(&now, &tmUtc);
  #endif
    if (g_diaYday < 0) g_diaYday = tmUtc.tm_yday;
    if (tmUtc.tm_yday != g_diaYday) {
      g_diaYday = tmUtc.tm_yday;
      g_countDia = 0;
      Serial.println("[reset diário] contador zerado");
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}
#endif
// ================== setup/loop ==================
void setup() {
  Serial.begin(9600);
  Serial.println("Iniciando");
  // Pinos
  pinMode(ledPin, OUTPUT);
  pinMode(PIN_IN1,  INPUT_PULLUP);
  pinMode(PIN_IN2,  INPUT_PULLUP);
  pinMode(PIN_JMP1, INPUT_PULLUP);
  pinMode(PIN_JMP2, INPUT_PULLUP);
  pinMode(PIN_JMP3, INPUT_PULLUP);
  pinMode(PIN_OUT1, OUTPUT); setOutput(PIN_OUT1, false);
  pinMode(PIN_OUT2, OUTPUT); setOutput(PIN_OUT2, false);
  pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, LOW);
  pinMode(LED_MQTT, OUTPUT); digitalWrite(LED_MQTT, LOW);
  pinMode(LED_PUB,  OUTPUT); digitalWrite(LED_PUB,  LOW);
  // Interrupções: apenas sinalizam “houve mudança”
  attachInterrupt(digitalPinToInterrupt(PIN_IN1), isrIn1, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_IN2), isrIn2, CHANGE);
  // Wi-Fi + MQTT + NTP
  wifiConnect();
  g_mqtt.setServer(MQTT_HOST, MQTT_PORT);
  configTime(-3 * 3600, 0, "pool.ntp.org", "time.nist.gov");// -3 horas em segundos = UTC-3 (São Paulo)
  // Estado inicial
  g_in1Active = inputActiveRaw(PIN_IN1);
  g_in2Active = inputActiveRaw(PIN_IN2);
  g_holdUntil = millis();
#if defined(USING_ESP32)
  // Tarefas (lógica=core1, redes=core0, diário=core1)
  xTaskCreatePinnedToCore(logicTask, "LogicTask", 4096, nullptr, 2, nullptr, 1);
  xTaskCreatePinnedToCore(netTask,   "NetTask",   4096, nullptr, 1, nullptr, 0);
  xTaskCreatePinnedToCore(dailyTask, "DailyTask", 2048, nullptr, 1, nullptr, 1);
#endif
}
void loop() {
#if defined(USING_ESP32)
  vTaskDelay(portMAX_DELAY); // FreeRTOS cuida
#else
  // ESP8266: laço cooperativo com as mesmas correções de debounce
  const uint32_t now = millis();
  static bool lastRaw1 = inputActiveRaw(PIN_IN1);
  static bool lastRaw2 = inputActiveRaw(PIN_IN2);
  static uint32_t lastDeb1 = now, lastDeb2 = now;
  // Debounce
  bool raw1 = inputActiveRaw(PIN_IN1);
  if (raw1 != lastRaw1 && (now - lastDeb1) >= DEBOUNCE_MS) { lastDeb1 = now; lastRaw1 = raw1; }
  bool raw2 = inputActiveRaw(PIN_IN2);
  if (raw2 != lastRaw2 && (now - lastDeb2) >= DEBOUNCE_MS) { lastDeb2 = now; lastRaw2 = raw2; }
  g_in1Active = lastRaw1; g_in2Active = lastRaw2;
  const bool any = g_in1Active || g_in2Active;
  if (any) {
    if (!g_outputsOn) {
      g_countDia++;
      time_t t; time(&t);
      logSerialAcionamento(t);
      if (g_mqtt.connected()) publishJSON(t);
    }
    g_outputsOn = true;
    setOutput(PIN_OUT1, true);
    setOutput(PIN_OUT2, true);
    g_holdUntil = now + calcDelayMs();
  } else {
    if ((int32_t)(now - g_holdUntil) >= 0) {
      g_outputsOn = false;
      setOutput(PIN_OUT1, false);
      setOutput(PIN_OUT2, false);
    }
  }
  if (!g_mqtt.connected()) ensureMqtt();
  g_mqtt.loop();
  digitalWrite(LED_WIFI, (WiFi.status()==WL_CONNECTED)?HIGH:LOW);
  digitalWrite(LED_MQTT, g_mqtt.connected()?HIGH:LOW);
  digitalWrite(LED_PUB,  (millis()<g_pubBlinkUntil)?HIGH:LOW);
  // Reset diário simples
  static unsigned long tLast = 0;
  if (millis() - tLast >= 1000) {
    tLast = millis();
    time_t t; time(&t);
    struct tm* pt = gmtime(&t);
    if (g_diaYday < 0) g_diaYday = pt->tm_yday;
    if (pt->tm_yday != g_diaYday) {
      g_diaYday = pt->tm_yday;
      g_countDia = 0;
      Serial.println("[reset diário] contador zerado");
    }
  }
  delay(10);
#endif
digitalWrite(ledPin, HIGH);
VtaskDelay(500);
digitalWrite(ledPin, LOW);
VtaskDelay(500);
}
Eng. Renato Maziero
Bomba_Fria
Bomba_Quente
5S
10S
20S
O USO DOS INVERSORES SÃO PARA SIMULAR MÓDULOS REAIS QUE
 SÃO ACIONADOS COM NÍVEL LOW, ENQUANTO ESSE MODELO DE
 RELAY NO WOKWI É ACIONADO EM NÍVEL HIGH.